Skip to content

Commit

Permalink
#20 Polish Kotlin Support
Browse files Browse the repository at this point in the history
Make sure that plugin works with a disabled Kotlin plugin
  • Loading branch information
filiphr committed Oct 17, 2020
1 parent 41dd190 commit 94710c0
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 98 deletions.
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ repositories {
}

intellij {
version System.getenv().getOrDefault('IDEA_VERSION', '2020.1.4')
version System.getenv().getOrDefault('IDEA_VERSION', ideaVersion)
type ideaType
downloadSources Boolean.valueOf(sources)
sameSinceUntilBuild Boolean.valueOf(isEAP)
alternativeIdePath idePath
updateSinceUntilBuild false
pluginName 'MapStruct-Intellij-Plugin'
// The properties plugin is needed because Kotlin uses it
// and for some reason plugins does not transitively pull itx
if ( !(version.startsWith('2018') || version.startsWith('2019.1'))) {
plugins = ['java', 'Kotlin', 'properties']
} else {
Expand Down
1 change: 1 addition & 0 deletions change-notes.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<html>
<h2>1.2.3</h2>
<ul>
<li>Kotlin: Code completion for <code>target</code> and <code>source</code> in <code>@Mapping</code> and <code>@ValueMapping</code></li>
<li>Bug fix: False positive unmapped target property for multi source methods</li>
</ul>
<h2>1.2.2</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiLiteral;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mapstruct.intellij.util.MapstructUtilKt;
Expand Down Expand Up @@ -40,6 +42,15 @@ abstract class BaseReference extends PsiReferenceBase<PsiElement> {
*/
@Nullable
PsiMethod getMappingMethod() {
return MapstructUtilKt.getPsiMethod( getElement() );
PsiElement element = getElement();
if ( element instanceof PsiLiteral ) {
return PsiTreeUtil.getParentOfType( element, PsiMethod.class );
}
else if ( "KtStringTemplateExpression".equals( element.getClass().getSimpleName() ) ) {
// We cannot do an instanceOf check here because the kotlin class is optional
return MapstructUtilKt.getPsiMethod( element );
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ package org.mapstruct.intellij.codeinsight.references

import com.intellij.psi.PsiReferenceContributor
import com.intellij.psi.PsiReferenceRegistrar
import org.mapstruct.intellij.util.toMappingElementPattern
import org.mapstruct.intellij.util.toMappingsElementPattern
import org.mapstruct.intellij.util.toValueMappingPattern
import org.mapstruct.intellij.util.toValueMappingsPattern

import org.mapstruct.intellij.util.MapstructKotlinElementUtils.mappingElementPattern
import org.mapstruct.intellij.util.MapstructKotlinElementUtils.valueMappingElementPattern

/**
* @author Frank Wang
Expand All @@ -20,36 +17,19 @@ class KtMapstructReferenceContributor : PsiReferenceContributor() {

override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
registrar.registerReferenceProvider(
"target".toMappingElementPattern(),
MappingTargetReferenceProvider(MapstructTargetReference::create)
)
registrar.registerReferenceProvider(
"source".toMappingElementPattern(),
MappingTargetReferenceProvider(MapstructSourceReference::create)
)
registrar.registerReferenceProvider(
"target".toMappingsElementPattern(),
mappingElementPattern("target"),
MappingTargetReferenceProvider(MapstructTargetReference::create)
)
registrar.registerReferenceProvider(
"source".toMappingsElementPattern(),
mappingElementPattern("source"),
MappingTargetReferenceProvider(MapstructSourceReference::create)
)

registrar.registerReferenceProvider(
"target".toValueMappingPattern(),
MappingTargetReferenceProvider(ValueMappingSourceReference::create)
)
registrar.registerReferenceProvider(
"source".toValueMappingPattern(),
MappingTargetReferenceProvider(ValueMappingTargetReference::create)
)
registrar.registerReferenceProvider(
"target".toValueMappingsPattern(),
valueMappingElementPattern("target"),
MappingTargetReferenceProvider(ValueMappingSourceReference::create)
)
registrar.registerReferenceProvider(
"source".toValueMappingsPattern(),
valueMappingElementPattern("source"),
MappingTargetReferenceProvider(ValueMappingTargetReference::create)
)
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.intellij.util;

import com.intellij.patterns.StandardPatterns;
import com.intellij.psi.PsiElement;
import org.mapstruct.intellij.util.patterns.KotlinElementPattern;

import static org.mapstruct.intellij.util.patterns.MapStructKotlinPatterns.psiElement;

/**
* Utils for working with MapStruct kotlin elements.
*
* @author Filip Hrisafov
*/
public final class MapstructKotlinElementUtils {

/**
* Hide default constructor.
*/
private MapstructKotlinElementUtils() {
}

/**
* @param parameterName the name of the parameter in the {@code @ValueMapping} annotation
*
* @return an element pattern for a parameter in the {@code @ValueMapping} annotation
*/
public static KotlinElementPattern.Capture<? extends PsiElement> valueMappingElementPattern(String parameterName) {
return elementPattern(
parameterName,
MapstructUtil.VALUE_MAPPING_ANNOTATION_FQN,
MapstructUtil.VALUE_MAPPINGS_ANNOTATION_FQN
);
}

/**
* @param parameterName the name of the parameter in the {@code @Mapping} annotation
*
* @return an element pattern for a parameter in the {@code @Mapping} annotation
*/
public static KotlinElementPattern.Capture<? extends PsiElement> mappingElementPattern(String parameterName) {
return elementPattern(
parameterName,
MapstructUtil.MAPPING_ANNOTATION_FQN,
MapstructUtil.MAPPINGS_ANNOTATION_FQN
);
}

private static KotlinElementPattern.Capture<? extends PsiElement> elementPattern(String parameterName,
String annotationFQN,
String annotationHolderFQN
) {
return psiElement()
.insideRepeatableAnnotationParam(
StandardPatterns.string().equalTo( annotationFQN ),
StandardPatterns.string().equalTo( annotationHolderFQN ),
parameterName
);
}
}
6 changes: 6 additions & 0 deletions src/main/java/org/mapstruct/intellij/util/MapstructUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiFormatUtilBase;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NonNls;
Expand Down Expand Up @@ -529,4 +530,9 @@ public static boolean isInheritInverseConfiguration(PsiMethod method) {
return isAnnotated( method, INHERIT_INVERSE_CONFIGURATION, false );
}

@Nullable
public static PsiMethod getPsiMethod(PsiElement element) {
return PsiTreeUtil.getParentOfType( element, PsiMethod.class );
}

}
8 changes: 0 additions & 8 deletions src/main/java/org/mapstruct/intellij/util/MapstructUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.asJava.classes.createGeneratedMethodFromDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.IDELightClassGenerationSupport
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode


Expand All @@ -27,10 +23,6 @@ fun PsiElement.getPsiMethod(): PsiMethod? {
return this.getNonStrictParentOfType() ?: this.getNonStrictParentOfType<KtNamedFunction>()?.toPsiMethod()
}

fun KtAnnotationEntry.getFqName(): FqName? {
return this.analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION).get(BindingContext.ANNOTATION, this)?.fqName
}

private fun KtNamedFunction.toPsiMethod(): PsiMethod? {
// dealing with kotlin class
val ktClass = this.getNonStrictParentOfType(KtClass::class.java) ?: return null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.intellij.util.patterns;

import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.psi.PsiElement;
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes;

import static org.mapstruct.intellij.util.patterns.MapStructKotlinPatterns.ktAnnotation;
import static org.mapstruct.intellij.util.patterns.MapStructKotlinPatterns.ktValueArgument;

/**
* @author Filip Hrisafov
*/
public class KotlinElementPattern<T extends PsiElement, Self extends KotlinElementPattern<T, Self>>
extends PsiElementPattern<T, Self> {

public KotlinElementPattern(final Class<T> aClass) {
super( aClass );
}

public Self insideRepeatableAnnotationParam(
ElementPattern<String> annotationQualifiedName,
ElementPattern<String> annotationHolderQualifiedName,
String parameterName) {
// A repeatable annotation in kotlin has 2 possible ways of PSI structure:
// 1. Part of the repeatable holder
// @Mappings(
// Mapping(target = "name")
// )
// 2. Just the annotation
// @Mapping(target = "name")

KtValueArgumentPattern ktValueArgumentPattern = ktValueArgument().withName( parameterName );
return withElementType( KtStubElementTypes.STRING_TEMPLATE ).andOr(
withParent(
ktValueArgumentPattern
.withAncestor( 5, ktAnnotation().qName( annotationHolderQualifiedName ) )
),

withParent(
ktValueArgumentPattern
.withSuperParent( 2, ktAnnotation().qName( annotationQualifiedName ) )
)
);
}

public static class Capture<T extends PsiElement> extends KotlinElementPattern<T, KotlinElementPattern.Capture<T>> {
public Capture(Class<T> aClass) {
super( aClass );
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.intellij.util.patterns;

import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtAnnotationEntry;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;

/**
* @author Filip Hrisafov
*/
public class KtAnnotationEntryPattern extends PsiElementPattern<KtAnnotationEntry, KtAnnotationEntryPattern> {

static final KtAnnotationEntryPattern KT_ANNOTATION_ENTRY_PATTERN = new KtAnnotationEntryPattern();

private KtAnnotationEntryPattern() {
super( KtAnnotationEntry.class );
}

public KtAnnotationEntryPattern qName(ElementPattern<String> pattern) {
return with( new PatternCondition<KtAnnotationEntry>( "qName" ) {
@Override
public boolean accepts(@NotNull KtAnnotationEntry ktAnnotation, ProcessingContext context) {
AnnotationDescriptor descriptor = ResolutionUtils.analyze(
ktAnnotation,
BodyResolveMode.PARTIAL_FOR_COMPLETION
).get( BindingContext.ANNOTATION, ktAnnotation );

if ( descriptor == null ) {
return false;
}

FqName fqName = descriptor.getFqName();
if ( fqName == null ) {
return false;
}
return pattern.accepts( fqName.asString(), context );
}
} );
}

}
Loading

0 comments on commit 94710c0

Please sign in to comment.