diff --git a/build.gradle b/build.gradle
index 8469be34..999c271f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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 {
diff --git a/change-notes.html b/change-notes.html
index 93513de1..ae75d18c 100644
--- a/change-notes.html
+++ b/change-notes.html
@@ -1,6 +1,7 @@
1.2.3
+ - Kotlin: Code completion for
target
and source
in @Mapping
and @ValueMapping
- Bug fix: False positive unmapped target property for multi source methods
1.2.2
diff --git a/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java b/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java
index 73f4c4ce..7c5eceb4 100644
--- a/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java
+++ b/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java
@@ -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;
@@ -40,6 +42,15 @@ abstract class BaseReference extends PsiReferenceBase {
*/
@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;
}
}
diff --git a/src/main/java/org/mapstruct/intellij/codeinsight/references/KtMapstructReferenceContributor.kt b/src/main/java/org/mapstruct/intellij/codeinsight/references/KtMapstructReferenceContributor.kt
index 3fdb6e14..bb27e89d 100644
--- a/src/main/java/org/mapstruct/intellij/codeinsight/references/KtMapstructReferenceContributor.kt
+++ b/src/main/java/org/mapstruct/intellij/codeinsight/references/KtMapstructReferenceContributor.kt
@@ -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
@@ -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)
)
}
diff --git a/src/main/java/org/mapstruct/intellij/util/KtMapstructElementUtil.kt b/src/main/java/org/mapstruct/intellij/util/KtMapstructElementUtil.kt
deleted file mode 100644
index 25f9919d..00000000
--- a/src/main/java/org/mapstruct/intellij/util/KtMapstructElementUtil.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 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.ElementPattern
-import com.intellij.patterns.PatternCondition
-import com.intellij.patterns.PlatformPatterns
-import com.intellij.patterns.PsiElementPattern
-import com.intellij.psi.PsiElement
-import com.intellij.util.ProcessingContext
-import org.jetbrains.kotlin.psi.KtAnnotationEntry
-import org.jetbrains.kotlin.psi.KtStringTemplateExpression
-import org.jetbrains.kotlin.psi.KtValueArgument
-import kotlin.reflect.KClass
-
-/**
- * @author Frank Wang
- */
-fun String.toValueMappingPattern(): ElementPattern {
- return this.toElementPattern(MapstructUtil.VALUE_MAPPING_ANNOTATION_FQN, 2)
-}
-
-fun String.toMappingElementPattern(): ElementPattern {
- return this.toElementPattern(MapstructUtil.MAPPING_ANNOTATION_FQN, 2)
-}
-
-fun String.toValueMappingsPattern(): ElementPattern {
- return this.toElementPattern(MapstructUtil.VALUE_MAPPINGS_ANNOTATION_FQN, 5)
-}
-
-fun String.toMappingsElementPattern(): ElementPattern {
- return this.toElementPattern(MapstructUtil.MAPPINGS_ANNOTATION_FQN, 5)
-}
-
-private fun String.toElementPattern(annotationFqName: String, level: Int): ElementPattern {
- val paramName = this
- return PlatformPatterns.psiElement(KtStringTemplateExpression::class.java).withParent(
- KtValueArgument::class.psiPattern { ktValueArgument, _ ->
- ktValueArgument.getArgumentName()?.text == paramName
- }.withSuperParent(
- level,
- KtAnnotationEntry::class.psiPattern { ktAnnotationEntry, _ ->
- ktAnnotationEntry.getFqName()?.asString() == annotationFqName
- }
- )
- )
-}
-
-private inline fun KClass.psiPattern(
- crossinline acceptsInvoker: (T, ProcessingContext?) -> Boolean
-): PsiElementPattern.Capture {
- return PlatformPatterns.psiElement(T::class.java).with(
- object : PatternCondition("KtValueArgument") {
- override fun accepts(t: T, context: ProcessingContext?): Boolean {
- return acceptsInvoker(t, context)
- }
- }
- )
-}
diff --git a/src/main/java/org/mapstruct/intellij/util/MapstructKotlinElementUtils.java b/src/main/java/org/mapstruct/intellij/util/MapstructKotlinElementUtils.java
new file mode 100644
index 00000000..c33cd1e1
--- /dev/null
+++ b/src/main/java/org/mapstruct/intellij/util/MapstructKotlinElementUtils.java
@@ -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
+ );
+ }
+}
diff --git a/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java b/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java
index f1af0bd6..38f6613c 100644
--- a/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java
+++ b/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java
@@ -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;
@@ -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 );
+ }
+
}
diff --git a/src/main/java/org/mapstruct/intellij/util/MapstructUtil.kt b/src/main/java/org/mapstruct/intellij/util/MapstructUtil.kt
index 117220bb..78b98301 100644
--- a/src/main/java/org/mapstruct/intellij/util/MapstructUtil.kt
+++ b/src/main/java/org/mapstruct/intellij/util/MapstructUtil.kt
@@ -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
@@ -27,10 +23,6 @@ fun PsiElement.getPsiMethod(): PsiMethod? {
return this.getNonStrictParentOfType() ?: this.getNonStrictParentOfType()?.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
diff --git a/src/main/java/org/mapstruct/intellij/util/patterns/KotlinElementPattern.java b/src/main/java/org/mapstruct/intellij/util/patterns/KotlinElementPattern.java
new file mode 100644
index 00000000..9afdd7cb
--- /dev/null
+++ b/src/main/java/org/mapstruct/intellij/util/patterns/KotlinElementPattern.java
@@ -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>
+ extends PsiElementPattern {
+
+ public KotlinElementPattern(final Class aClass) {
+ super( aClass );
+ }
+
+ public Self insideRepeatableAnnotationParam(
+ ElementPattern annotationQualifiedName,
+ ElementPattern 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 extends KotlinElementPattern> {
+ public Capture(Class aClass) {
+ super( aClass );
+ }
+
+ }
+}
diff --git a/src/main/java/org/mapstruct/intellij/util/patterns/KtAnnotationEntryPattern.java b/src/main/java/org/mapstruct/intellij/util/patterns/KtAnnotationEntryPattern.java
new file mode 100644
index 00000000..42285f42
--- /dev/null
+++ b/src/main/java/org/mapstruct/intellij/util/patterns/KtAnnotationEntryPattern.java
@@ -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 {
+
+ static final KtAnnotationEntryPattern KT_ANNOTATION_ENTRY_PATTERN = new KtAnnotationEntryPattern();
+
+ private KtAnnotationEntryPattern() {
+ super( KtAnnotationEntry.class );
+ }
+
+ public KtAnnotationEntryPattern qName(ElementPattern pattern) {
+ return with( new PatternCondition( "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 );
+ }
+ } );
+ }
+
+}
diff --git a/src/main/java/org/mapstruct/intellij/util/patterns/KtValueArgumentNamePattern.java b/src/main/java/org/mapstruct/intellij/util/patterns/KtValueArgumentNamePattern.java
new file mode 100644
index 00000000..bd170db6
--- /dev/null
+++ b/src/main/java/org/mapstruct/intellij/util/patterns/KtValueArgumentNamePattern.java
@@ -0,0 +1,40 @@
+/*
+ * 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.PropertyPatternCondition;
+import com.intellij.patterns.PsiElementPattern;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.kotlin.psi.KtValueArgumentName;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class KtValueArgumentNamePattern extends PsiElementPattern {
+
+ static final KtValueArgumentNamePattern KT_VALUE_ARGUMENT_PATTERN = new KtValueArgumentNamePattern();
+
+ private KtValueArgumentNamePattern() {
+ super( KtValueArgumentName.class );
+ }
+
+ @NotNull
+ @Override
+ public KtValueArgumentNamePattern withName(@NotNull ElementPattern name) {
+ return with( new PropertyPatternCondition( "withKtValueName", name ) {
+ @Nullable
+ @Override
+ public String getPropertyValue(@NotNull Object o) {
+ if ( o instanceof KtValueArgumentName ) {
+ return ( (KtValueArgumentName) o ).getAsName().getIdentifier();
+ }
+ return null;
+ }
+ } );
+ }
+}
diff --git a/src/main/java/org/mapstruct/intellij/util/patterns/KtValueArgumentPattern.java b/src/main/java/org/mapstruct/intellij/util/patterns/KtValueArgumentPattern.java
new file mode 100644
index 00000000..dabd4fc8
--- /dev/null
+++ b/src/main/java/org/mapstruct/intellij/util/patterns/KtValueArgumentPattern.java
@@ -0,0 +1,29 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.kotlin.psi.KtValueArgument;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class KtValueArgumentPattern extends PsiElementPattern {
+
+ static final KtValueArgumentPattern KT_VALUE_ARGUMENT_PATTERN = new KtValueArgumentPattern();
+
+ private KtValueArgumentPattern() {
+ super( KtValueArgument.class );
+ }
+
+ @NotNull
+ @Override
+ public KtValueArgumentPattern withName(@NotNull ElementPattern name) {
+ return withChild( MapStructKotlinPatterns.ktValueArgumentName().withName( name ) );
+ }
+}
diff --git a/src/main/java/org/mapstruct/intellij/util/patterns/MapStructKotlinPatterns.java b/src/main/java/org/mapstruct/intellij/util/patterns/MapStructKotlinPatterns.java
new file mode 100644
index 00000000..b1d6f295
--- /dev/null
+++ b/src/main/java/org/mapstruct/intellij/util/patterns/MapStructKotlinPatterns.java
@@ -0,0 +1,31 @@
+/*
+ * 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.StandardPatterns;
+import com.intellij.psi.PsiElement;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class MapStructKotlinPatterns extends StandardPatterns {
+
+ public static KotlinElementPattern.Capture psiElement() {
+ return new KotlinElementPattern.Capture<>( PsiElement.class );
+ }
+
+ public static KtValueArgumentPattern ktValueArgument() {
+ return KtValueArgumentPattern.KT_VALUE_ARGUMENT_PATTERN;
+ }
+
+ public static KtValueArgumentNamePattern ktValueArgumentName() {
+ return KtValueArgumentNamePattern.KT_VALUE_ARGUMENT_PATTERN;
+ }
+
+ public static KtAnnotationEntryPattern ktAnnotation() {
+ return KtAnnotationEntryPattern.KT_ANNOTATION_ENTRY_PATTERN;
+ }
+}