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

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 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 mappingElementPattern(String parameterName) { + return elementPattern( + parameterName, + MapstructUtil.MAPPING_ANNOTATION_FQN, + MapstructUtil.MAPPINGS_ANNOTATION_FQN + ); + } + + private static KotlinElementPattern.Capture 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; + } +}