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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/
* Completion of `target` and `source` properties in `@ValueMapping` annotation
* Completion of `componentModel` in `@Mapper` and `@MapperConfig` annotations
* Completion of `qualifiedByName` in `@Mapping` annotation
* Completion of `name` in `@InheritConfiguration` and `@InheritInverseConfiguration` annotations
* Go To Declaration for properties in `target` and `source` to setters / getters
* Go To Declaration for `Mapping#qualifiedByName`
* Go To Declaration for `name` in `@InheritConfiguration` and `@InheritInverseConfiguration` annotations
* Find usages of properties in `target` and `source` and find usages of setters / getters in `@Mapping` annotations
* Highlighting properties in `target` and `source`
* Errors and Quick fixes:
Expand All @@ -41,6 +43,7 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/
* `*` 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 `qualifiedByName` in `@Mapping` annotation
* Unknown reference inspection for `name` in `@InheritConfiguration` and `@InheritInverseConfiguration` annotations

## Requirements

Expand Down
3 changes: 3 additions & 0 deletions description.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
<li>Completion of <code>target</code> and <code>source</code> properties in <code>@ValueMapping</code> annotation</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>
<li>Completion of <code>name</code> in <code>@InheritConfiguration</code> and <code>@InheritInverseConfiguration</code> annotations</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>Mapping#qualifiedByName</code></li>
<li>Go To Declaration for <code>name</code> in <code>@InheritConfiguration</code> and <code>@InheritInverseConfiguration</code> annotations</li>
<li>Highlighting properties in <code>target</code> and <code>source</code></li>
<li>Refactoring support for properties and methods renaming</li>
<li>Errors and Quick Fixes:</li>
Expand All @@ -45,6 +47,7 @@
<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>qualifiedByName</code> in <code>@Mapping</code> annotation. </li>
<li>Unknown reference inspection for <code>name</code> in <code>@InheritConfiguration</code> and <code>@InheritInverseConfiguration</code> annotations. </li>
</ul>
</li>
</ul>
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.Arrays;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;

import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mapstruct.intellij.MapStructBundle;
import org.mapstruct.intellij.inspection.inheritance.SourceMethod;

import static org.mapstruct.intellij.inspection.inheritance.InheritConfigurationUtils.findMappingMethodsFromInheritScope;
import static org.mapstruct.intellij.util.MapstructUtil.MAPPER_ANNOTATION_FQN;
import static org.mapstruct.intellij.util.MapstructUtil.asLookupWithRepresentableText;
import static org.mapstruct.intellij.util.TargetUtils.getRelevantType;

/**
* Reference for {@link org.mapstruct.InheritConfiguration#name()} and
* {@link org.mapstruct.InheritInverseConfiguration#name()}.
*
* @author hduelme
*/
public class InheritConfigurationNameReference extends MapstructBaseReference {

private final boolean inverse;

/**
* Create a new {@link InheritConfigurationNameReference} with the provided parameters
*
* @param element the element that the reference belongs to
* @param previousReference the previous reference if there is one (in nested properties for example)
* @param rangeInElement the range that the reference represent in the {@code element}
* @param value the matched value (useful when {@code rangeInElement} is empty)
* @param inverse whether this is the inverse variant ({@link org.mapstruct.InheritInverseConfiguration})
*/
private InheritConfigurationNameReference(PsiElement element,
InheritConfigurationNameReference previousReference,
TextRange rangeInElement,
String value,
boolean inverse) {
super( element, previousReference, rangeInElement, value );
this.inverse = inverse;
}

@Override
PsiElement resolveInternal(@NotNull String value, @NotNull PsiType psiType) {
return null; // not needed
}

@Override
PsiElement resolveInternal(@NotNull String value, @NotNull PsiMethod mappingMethod) {

java.util.List<PsiMethod> namedCandidates = candidatesInScope( mappingMethod )
.filter( candidate -> value.equals( candidate.getName() ) )
.distinct()
.toList();

// Match the disambiguation rule of InheritConfigurationUtils#getResultMethod:
// a name value is only valid if it identifies exactly one in-scope candidate.
if ( namedCandidates.size() != 1 ) {
return null;
}

return namedCandidates.getFirst().getNameIdentifier();
}

@NotNull
private Stream<PsiMethod> candidatesInScope(@NotNull PsiMethod mappingMethod) {
return inScopeMethods( mappingMethod )
.filter( candidateFilter( mappingMethod ) )
.filter( m -> !m.equals( mappingMethod ) )
.distinct();
}

@NotNull
@Override
Object[] getVariantsInternal(@NotNull PsiType psiType) {
return LookupElement.EMPTY_ARRAY; // not needed
}

@NotNull
@Override
Object[] getVariantsInternal(@NotNull PsiMethod mappingMethod) {
return candidatesInScope( mappingMethod )
.map( this::methodAsLookup )
.filter( Objects::nonNull )
.toArray();
}

@Nullable
private LookupElement methodAsLookup(@NotNull PsiMethod method) {
String lookupString = method.getName();
if ( lookupString.isEmpty() ) {
return null;
}

PsiClass containingClass = method.getContainingClass();
String classSimpleName = containingClass != null ? containingClass.getName() : "?";

return asLookupWithRepresentableText(
method,
lookupString,
lookupString,
String.format(
" %s#%s(%s)",
classSimpleName,
method.getName(),
formatParameters( method )
)
);
}

@NotNull
private Stream<PsiMethod> inScopeMethods(@NotNull PsiMethod mappingMethod) {
PsiClass containingClass = mappingMethod.getContainingClass();
if ( containingClass == null ) {
return Stream.empty();
}

PsiAnnotation mapperAnnotation = containingClass.getAnnotation( MAPPER_ANNOTATION_FQN );
if ( mapperAnnotation == null ) {
return Stream.empty();
}

return findMappingMethodsFromInheritScope( containingClass, mapperAnnotation );
}

@NotNull
private Predicate<PsiMethod> candidateFilter(@NotNull PsiMethod mappingMethod) {
PsiType targetType = getRelevantType( mappingMethod );
SourceMethod sourceMethod = new SourceMethod( mappingMethod );
if ( inverse ) {
return candidate -> candidate.getBody() == null && sourceMethod.inverses( candidate, targetType );
}
return candidate -> candidate.getBody() == null && sourceMethod.canInheritFrom( candidate, targetType );
}

@NotNull
private static String formatParameters(@NotNull PsiMethod method) {
return Arrays.stream( method.getParameterList().getParameters() )
.map( PsiParameter::getType )
.map( PsiType::getPresentableText )
.collect( java.util.stream.Collectors.joining( ", " ) );
}

@Nullable
@Override
PsiType resolvedType() {
return null;
}

@NotNull
@Override
public String getUnresolvedMessagePattern() {
return MapStructBundle.message(
inverse ? "unknown.inheritInverseConfiguration.reference" : "unknown.inheritConfiguration.reference"
);
}

/**
* @param psiElement the literal for which references need to be created
* @return the references for the given {@code psiLiteral}
*/
static PsiReference[] createForward(PsiElement psiElement) {
return MapstructBaseReference.create( psiElement,
InheritConfigurationNameReference::newForward,
false );
}

/**
* @param psiElement the literal for which references need to be created
* @return the references for the given {@code psiLiteral}
*/
static PsiReference[] createInverse(PsiElement psiElement) {
return MapstructBaseReference.create( psiElement,
InheritConfigurationNameReference::newInverse,
false );
}

private static InheritConfigurationNameReference newForward(PsiElement element,
InheritConfigurationNameReference previous,
TextRange range,
String value) {
return new InheritConfigurationNameReference( element, previous, range, value, false );
}

private static InheritConfigurationNameReference newInverse(PsiElement element,
InheritConfigurationNameReference previous,
TextRange range,
String value) {
return new InheritConfigurationNameReference( element, previous, range, value, true );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.jetbrains.annotations.NotNull;

import static org.mapstruct.intellij.util.MapstructElementUtils.beanMappingElementPattern;
import static org.mapstruct.intellij.util.MapstructElementUtils.inheritConfigurationElementPattern;
import static org.mapstruct.intellij.util.MapstructElementUtils.inheritInverseConfigurationElementPattern;
import static org.mapstruct.intellij.util.MapstructElementUtils.mappingElementPattern;
import static org.mapstruct.intellij.util.MapstructElementUtils.valueMappingElementPattern;

Expand Down Expand Up @@ -48,6 +50,15 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar)
valueMappingElementPattern( "target" ),
new MappingTargetReferenceProvider( ValueMappingTargetReference::create )
);

registrar.registerReferenceProvider(
inheritConfigurationElementPattern( "name" ),
new MappingTargetReferenceProvider( InheritConfigurationNameReference::createForward )
);
registrar.registerReferenceProvider(
inheritInverseConfigurationElementPattern( "name" ),
new MappingTargetReferenceProvider( InheritConfigurationNameReference::createInverse )
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.jetbrains.annotations.Nullable;
import org.mapstruct.intellij.codeinsight.references.BaseReference;
import org.mapstruct.intellij.codeinsight.references.BaseValueMappingReference;
import org.mapstruct.intellij.codeinsight.references.InheritConfigurationNameReference;
import org.mapstruct.intellij.util.MapstructUtil;

/**
Expand Down Expand Up @@ -75,6 +76,11 @@ private boolean shouldRegisterProblem(BaseReference reference) {
return valueMappingReference.getEnumClass() != null;
}

if ( reference instanceof InheritConfigurationNameReference ) {
// The name attribute references a method name, not a property that could be a map key.
return true;
}

if ( hasSingleStringKeyMapSourceParameter( reference.getMappingMethod() ) ) {
// MapStruct allows source values as map keys, even if they are not resolvable as Java properties.
// Therefore, we don't report an unresolved reference problem here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ private static Set<SourceMethod> getApplicablePrototypeMethods(SourceMethod mapp
* @param mapperAnnotation the mapper annotation of the containing class
* @return possible mapping methods to be inherited from
*/
private static Stream<PsiMethod> findMappingMethodsFromInheritScope(@NotNull PsiClass containingClass,
public static Stream<PsiMethod> findMappingMethodsFromInheritScope(@NotNull PsiClass containingClass,
@NotNull PsiAnnotation mapperAnnotation) {

Stream<PsiMethod> localAndParentMethods = Arrays.stream( containingClass.getAllMethods() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ public static PsiJavaElementPattern.Capture<PsiElement> beanMappingElementPatter
return elementPattern( parameterName, MapstructUtil.BEAN_MAPPING_FQN );
}

/**
* @param parameterName the name of the parameter in the {@code @InheritConfiguration} annotation
*
* @return an element pattern for a parameter in the {@code @InheritConfiguration} annotation
*/
public static PsiJavaElementPattern.Capture<PsiElement> inheritConfigurationElementPattern(String parameterName) {
return elementPattern( parameterName, MapstructUtil.INHERIT_CONFIGURATION_FQN );
}

/**
* @param parameterName the name of the parameter in the {@code @InheritInverseConfiguration} annotation
*
* @return an element pattern for a parameter in the {@code @InheritInverseConfiguration} annotation
*/
public static PsiJavaElementPattern.Capture<PsiElement> inheritInverseConfigurationElementPattern(
String parameterName) {
return elementPattern( parameterName, MapstructUtil.INHERIT_INVERSE_CONFIGURATION_FQN );
}

private static PsiJavaElementPattern.Capture<PsiElement> elementPattern(String parameterName,
String annotationFQN) {
return psiElement()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ plugin.settings.quickFix.preferSourceBeforeTargetInMapping=Prefer source before
unknown.property=Unknown property ''{0}''
unknown.enum.constant=Unknown enum constant ''{0}''
unknown.qualifiedByName.reference=Unknown @Named reference ''{0}''
unknown.inheritConfiguration.reference=Unknown @InheritConfiguration method ''{0}''
unknown.inheritInverseConfiguration.reference=Unknown @InheritInverseConfiguration method ''{0}''
Loading
Loading