Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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