Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/
* Code completions
* Completion of `target` and `source` properties in `@Mapping` annotation (nested properties also work)
* Completion of `target` and `source` properties in `@ValueMapping` annotation
* Completion of `targets` in `@Ignored` annotation with `prefix` support
* Completion of `componentModel` in `@Mapper` and `@MapperConfig` annotations
* Completion of `qualifiedByName` in `@Mapping` annotation
* Go To Declaration for properties in `target` and `source` to setters / getters
* Go To Declaration for `targets` in `@Ignored` annotation
* Go To Declaration for `Mapping#qualifiedByName`
* Find usages of properties in `target` and `source` and find usages of setters / getters in `@Mapping` annotations
* Find usages of properties in `target` and `source` and find usages of setters / getters in `@Mapping` and `@Ignored` annotations
* Highlighting properties in `target` and `source`
* Errors and Quick fixes:
* `@Mapper` or `@MapperConfig` annotation missing
Expand All @@ -37,9 +39,10 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/
* No `source` defined in `@Mapping` annotation
* 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`.
* 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.
* `target` mapped more than once by `@Mapping` and/or `@Ignored` 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 `source` and `target` in `@Mapping` and `@ValueMapping` annotation.
* Unknown reference inspection for `targets` and `prefix` in `@Ignored` annotation.
* Unknown reference inspection for `qualifiedByName` in `@Mapping` annotation

## Requirements
Expand Down
8 changes: 5 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ dependencies {
testFramework( TestFrameworkType.Platform.INSTANCE )
testFramework( TestFrameworkType.Bundled.INSTANCE )
}
implementation('org.mapstruct:mapstruct:1.5.3.Final')
implementation('org.mapstruct:mapstruct:1.7.0.Beta1')
testImplementation(platform('org.junit:junit-bom:5.11.0'))
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('org.junit.jupiter:junit-jupiter-api')
testImplementation('org.junit.jupiter:junit-jupiter-engine')
testRuntimeOnly('org.junit.vintage:junit-vintage-engine')
testImplementation('org.assertj:assertj-core:3.26.3')
testImplementation('org.assertj:assertj-core:3.27.7')
testImplementation('org.apache.commons:commons-text:1.15.0')
testImplementation( 'junit:junit:4.13.2' )
testRuntimeOnly('org.immutables:value:2.10.1')
Expand All @@ -130,7 +130,9 @@ tasks.register('libs', Sync) {
include('mapstruct-intellij-*.jar')
include('MapStruct-Intellij-*.jar')
}
rename('mapstruct-1.5.3.Final.jar', 'mapstruct.jar')
rename { String fileName ->
return fileName.startsWith('mapstruct-') ? 'mapstruct.jar' : fileName
}
}

tasks.register('testLibs', Sync) {
Expand Down
12 changes: 12 additions & 0 deletions change-notes.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
<html lang="en">
<h2>1.10.0</h2>
<ul>
<li>Support for <code>@Ignored</code> annotation (MapStruct 1.7):
<ul>
<li>Code completion for <code>targets</code> in <code>@Ignored</code> annotation with <code>prefix</code> support</li>
<li>Go to declaration and find usages for <code>@Ignored</code> target properties</li>
<li>Unknown reference inspection for <code>targets</code> and <code>prefix</code> in <code>@Ignored</code> annotation</li>
<li>Unmapped target properties inspection considers <code>@Ignored</code> targets</li>
<li>Target mapped more than once inspection detects conflicts between <code>@Mapping</code> and <code>@Ignored</code></li>
</ul>
</li>
</ul>
<h2>1.9.1</h2>
<ul>
<li>Improve error messages for reference inspection</li>
Expand Down
9 changes: 6 additions & 3 deletions description.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
<ul>
<li>Completion of <code>target</code> and <code>source</code> properties in <code>@Mapping</code> annotation (nested properties also work)</li>
<li>Completion of <code>target</code> and <code>source</code> properties in <code>@ValueMapping</code> annotation</li>
<li>Completion of <code>targets</code> in <code>@Ignored</code> annotation with <code>prefix</code> support</li>
<li>Completion of <code>componentModel</code> in <code>@Mapper</code> and <code>@MapperConfig</code> annotations</li>
<li>Completion of <code>qualifiedByName</code> in <code>@Mapping</code> annotation</li>
</ul>
</li>
<li>Go To Declaration for properties in <code>target</code> and <code>source</code> to setters / getters</li>
<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>
<li>Go To Declaration for <code>targets</code> in <code>@Ignored</code> annotation</li>
<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>
<li>Go To Declaration for <code>Mapping#qualifiedByName</code></li>
<li>Highlighting properties in <code>target</code> and <code>source</code></li>
<li>Refactoring support for properties and methods renaming</li>
Expand All @@ -41,9 +43,10 @@
<li>No <code>source</code> defined in <code>@Mapping</code> annotation</li>
<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>
<li>More than one default source in <code>@Mapping</code> annotation defined with quick fixes: Remove <code>defaultValue</code>. Remove <code>defaultExpression</code>.</li>
<li><code>target</code> mapped more than once by <code>@Mapping</code> annotations with quick fixes: Remove annotation and change target property.</li>
<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>
<li><code>*</code> used as a source in <code>@Mapping</code> annotations with quick fixes: Replace <code>*</code> with method parameter name.</li>
<li>Unknown reference inspection for <code>source</code> and <code>target</code> in <code>@Mapping</code> and <code>@ValueMapping</code> annotation. </li>
<li>Unknown reference inspection for <code>source</code> and <code>target</code> in <code>@Mapping</code> and <code>@ValueMapping</code> annotation.</li>
<li>Unknown reference inspection for <code>targets</code> and <code>prefix</code> in <code>@Ignored</code> annotation.</li>
<li>Unknown reference inspection for <code>qualifiedByName</code> in <code>@Mapping</code> annotation. </li>
</ul>
</li>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* 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.codeinsight.references;

import java.util.Map;
import java.util.stream.Stream;

import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiRecordComponent;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mapstruct.intellij.util.MapStructVersion;
import org.mapstruct.intellij.util.MapstructUtil;
import org.mapstruct.intellij.util.TargetType;
import org.mapstruct.intellij.util.TargetUtils;

import static org.mapstruct.intellij.util.MapstructUtil.asLookup;
import static org.mapstruct.intellij.util.MapstructUtil.findRecordComponent;
import static org.mapstruct.intellij.util.MapstructUtil.isPublicModifiable;
import static org.mapstruct.intellij.util.MapstructUtil.isPublicNonStatic;
import static org.mapstruct.intellij.util.TargetUtils.findAllDefinedMappingTargets;
import static org.mapstruct.intellij.util.TargetUtils.findAllIgnoredTargets;
import static org.mapstruct.intellij.util.TargetUtils.isBuilderEnabled;
import static org.mapstruct.intellij.util.TargetUtils.publicWriteAccessors;
import static org.mapstruct.intellij.util.TargetUtils.resolveBuilderOrSelfClass;
import static org.mapstruct.intellij.util.TypeUtils.firstParameterPsiType;

/**
* Base class for target references ({@link MapstructTargetReference} and {@link MapstructIgnoredTargetReference}).
* Provides shared implementations for resolving the type of target elements.
*
* @author Filip Hrisafov
*/
abstract class BaseTargetReference extends BaseMappingReference {

protected final MapStructVersion mapStructVersion;

BaseTargetReference(@NotNull PsiElement element, @Nullable MapstructBaseReference previousReference,
TextRange rangeInElement, String value) {
super( element, previousReference, rangeInElement, value );
mapStructVersion = MapstructUtil.resolveMapStructProjectVersion( element.getContainingFile()
.getOriginalFile() );
}

@Override
PsiElement resolveInternal(@NotNull String value, @NotNull PsiType psiType) {
return resolveTargetElement( value, psiType, getMappingMethod() );
}

PsiElement resolveTargetElement(@NotNull String value, @NotNull PsiType psiType,
@Nullable PsiMethod mappingMethod) {
boolean builderSupportPresent = mapStructVersion.isBuilderSupported();
Pair<PsiClass, TargetType> pair = resolveBuilderOrSelfClass(
psiType,
builderSupportPresent && isBuilderEnabled( mappingMethod )
);
if ( pair == null ) {
return null;
}

PsiClass psiClass = pair.getFirst();
TargetType targetType = pair.getSecond();
PsiType typeToUse = targetType.type();

PsiRecordComponent recordComponent = findRecordComponent( value, psiClass );
if ( recordComponent != null ) {
return recordComponent;
}

if ( mapStructVersion.isConstructorSupported() && !targetType.builder() ) {
PsiMethod constructor = TargetUtils.resolveMappingConstructor( psiClass );
if ( constructor != null && constructor.hasParameters() ) {
for ( PsiParameter parameter : constructor.getParameterList().getParameters() ) {
if ( value.equals( parameter.getName() ) ) {
return parameter;
}
}
}
}

String capitalizedName = MapstructUtil.capitalize( value );
PsiMethod[] methods = psiClass.findMethodsByName( "set" + capitalizedName, true );
if ( methods.length != 0 && isPublicNonStatic( methods[0] ) ) {
return methods[0];
}

// If there is no such setter we need to check if there is a collection getter
methods = psiClass.findMethodsByName( "get" + capitalizedName, true );
if ( methods.length != 0 && isCollectionGetterWriteAccessor( methods[0] ) ) {
return methods[0];
}

if ( builderSupportPresent ) {
for ( Pair<PsiMethod, PsiSubstitutor> builderPair : psiClass.findMethodsAndTheirSubstitutorsByName(
value,
true
) ) {
PsiMethod method = builderPair.getFirst();
if ( method.getParameterList().getParametersCount() == 1 &&
mapstructUtil.isFluentSetter( method, typeToUse, builderPair.getSecond() ) ) {
return method;
}
}
}

PsiClass selfClass = PsiUtil.resolveClassInType( psiType );
if ( selfClass != null ) {
PsiField field = selfClass.findFieldByName( value, true );
if ( field != null && isPublicModifiable( field ) ) {
return field;
}
}

return null;
}

@NotNull
@Override
Object[] getVariantsInternal(@NotNull PsiType psiType) {

PsiMethod mappingMethod = getMappingMethod();

Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors = publicWriteAccessors(
psiType,
mapStructVersion,
mapstructUtil,
mappingMethod
);

if ( mappingMethod != null ) {
findAllDefinedTargets( mappingMethod ).forEach( accessors::remove );
}

return asLookup(
accessors,
BaseTargetReference::memberPsiType
);
}

protected Stream<String> findAllDefinedTargets(PsiMethod mappingMethod) {
return Stream.concat(
findAllDefinedMappingTargets( mappingMethod, mapStructVersion ),
findAllIgnoredTargets( mappingMethod )
);
}

@Nullable
@Override
PsiType resolvedType() {
PsiElement element = resolve();
if ( element instanceof PsiMethod psiMethod ) {
return firstParameterPsiType( psiMethod );
}
else if ( element instanceof PsiParameter psiParameter ) {
return psiParameter.getType();
}
else if ( element instanceof PsiRecordComponent psiRecordComponent ) {
return psiRecordComponent.getType();
}
else if ( element instanceof PsiField psiField ) {
return psiField.getType();
}

return null;
}

static PsiType memberPsiType(PsiElement psiMember) {
if ( psiMember instanceof PsiMethod psiMemberMethod ) {
return resolveMethodType( psiMemberMethod );
}
else if ( psiMember instanceof PsiVariable psiMemberVariable ) {
return psiMemberVariable.getType();
}
return null;
}

static PsiType resolveMethodType(PsiMethod psiMethod) {
PsiParameter[] psiParameters = psiMethod.getParameterList().getParameters();
if ( psiParameters.length == 0 ) {
return psiMethod.getReturnType();
}
return psiParameters[0].getType();
}

private static boolean isCollectionGetterWriteAccessor(@NotNull PsiMethod method) {
if ( !isPublicNonStatic( method ) ) {
return false;
}
PsiParameterList parameterList = method.getParameterList();
if ( parameterList.getParametersCount() > 0 ) {
return false;
}
return TargetUtils.isMethodReturnTypeAssignableToCollectionOrMap( method );
}
}
Loading
Loading