From db3160961d8b5b61e1b795182f547e55cdcbdec7 Mon Sep 17 00:00:00 2001 From: "alexey.afanasiev" Date: Tue, 16 Jul 2019 11:35:28 +0300 Subject: [PATCH] IDEA-218377 Support required named params GitOrigin-RevId: 518cca90081b6567fabed432658c3f66c8b5a3d3 --- .../plugins/groovy/GroovyBundle.properties | 1 + .../groovy/annotator/GroovyAnnotator25.kt | 37 ++++++++++++++++++- .../documentation/GroovyPresentationUtil.java | 11 ++---- .../GroovyNamedVariantArgumentProvider.kt | 8 +++- .../impl/namedVariant/NamedParamData.kt | 13 +++++-- .../NamedVariantTransformationSupport.kt | 3 +- .../impl/namedVariant/namedParams.kt | 22 ++++++----- .../Groovy25HighlightingTest.groovy | 6 +++ .../v25/namedParamsRequired.groovy | 17 +++++++++ .../requiredNamedParamInNamedVariant.groovy | 15 ++++++++ .../severalAbsentRequiredNamedParams.groovy | 17 +++++++++ 11 files changed, 125 insertions(+), 25 deletions(-) create mode 100644 plugins/groovy/testdata/highlighting/v25/namedParamsRequired.groovy create mode 100644 plugins/groovy/testdata/highlighting/v25/requiredNamedParamInNamedVariant.groovy create mode 100644 plugins/groovy/testdata/highlighting/v25/severalAbsentRequiredNamedParams.groovy diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/GroovyBundle.properties b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/GroovyBundle.properties index 4b297b72f643..2f48583a5e90 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/GroovyBundle.properties +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/GroovyBundle.properties @@ -74,6 +74,7 @@ cannot.apply.constructor=Constructor ''{0}'' in ''{1}'' cannot be applied to ''{ cannot.apply.default.constructor=Cannot apply default constructor for class ''{0}'' cannot.instantiate.abstract.class=Cannot instantiate abstract class ''{0}'' cannot.instantiate.interface=Cannot instantiate interface ''{0}'' +missing.required.named.parameter=Missing required ''{0}'' named parameter loss.of.precision=Possible loss of precision from ''{0}'' to ''{1}'' cannot.assign=Cannot assign ''{0}'' to ''{1}'' cannot.cast=Cannot cast ''{0}'' to ''{1}'' diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator25.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator25.kt index 30b0581cf8ed..822b845dfe7e 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator25.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator25.kt @@ -1,15 +1,19 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.annotator import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier import org.jetbrains.plugins.groovy.GroovyBundle import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrCallExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod +import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil import org.jetbrains.plugins.groovy.transformations.immutable.hasImmutableAnnotation import org.jetbrains.plugins.groovy.transformations.immutable.isImmutable import org.jetbrains.plugins.groovy.transformations.impl.namedVariant.collectAllParamsFromNamedVariantMethod +import org.jetbrains.plugins.groovy.transformations.impl.namedVariant.collectNamedParams /** * Check features introduced in groovy 2.5 @@ -20,7 +24,10 @@ class GroovyAnnotator25(private val holder: AnnotationHolder) : GroovyElementVis collectAllParamsFromNamedVariantMethod(method).groupBy { it.first }.filter { it.value.size > 1 }.forEach { (name, parameters) -> val parametersList = parameters.joinToString { "'${it.second.name}'" } parameters.drop(1).map { (_, parameter) -> parameter }.forEach { - holder.createErrorAnnotation(it.nameIdentifierGroovy, GroovyBundle.message("duplicating.named.parameter", name, parametersList)) + val nameIdentifier = it.nameIdentifier + if (nameIdentifier != null) { + holder.createErrorAnnotation(nameIdentifier, GroovyBundle.message("duplicating.named.parameter", name, parametersList)) + } } } super.visitMethod(method) @@ -37,4 +44,30 @@ class GroovyAnnotator25(private val holder: AnnotationHolder) : GroovyElementVis holder.createErrorAnnotation(field.nameIdentifierGroovy, GroovyBundle.message("field.should.be.immutable", field.name)) } } + + override fun visitCallExpression(callExpression: GrCallExpression) { + checkRequiredNamedArguments(callExpression) + + super.visitCallExpression(callExpression) + } + + private fun checkRequiredNamedArguments(callExpression: GrCallExpression) { + val namedArguments = callExpression.namedArguments.mapNotNull { it.labelName }.toSet() + if (namedArguments.isEmpty()) return + + val resolveResult = callExpression.advancedResolve() + val method = resolveResult.element as? PsiMethod ?: return + + val parameters = method.parameterList.parameters + val mapParameter = (if (parameters.isNotEmpty()) parameters[0] else null) ?: return + + val requiredNamedParams = collectNamedParams(mapParameter).filter { it.required } + + requiredNamedParams.forEach { + if (!namedArguments.contains(it.name) && PsiUtil.isCompileStatic(callExpression)) { + val message = GroovyBundle.message("missing.required.named.parameter", it.name) + holder.createErrorAnnotation(callExpression, message) + } + } + } } \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyPresentationUtil.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyPresentationUtil.java index a5e66102984e..21ca2728dc4b 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyPresentationUtil.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyPresentationUtil.java @@ -9,19 +9,16 @@ import com.intellij.psi.PsiType; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.MethodSignature; import com.intellij.util.ArrayUtilRt; -import kotlin.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; +import org.jetbrains.plugins.groovy.transformations.impl.namedVariant.NamedParamData; import org.jetbrains.plugins.groovy.transformations.impl.namedVariant.NamedParamsUtil; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * @author ven @@ -105,8 +102,8 @@ public class GroovyPresentationUtil { } private static boolean presentNamedParameters(@NotNull StringBuilder buffer, @NotNull GrParameter parameter) { - List> pairs = NamedParamsUtil.collectNamedParams(parameter); - StringUtil.join(pairs, pair -> pair.getFirst() + ": " + pair.getSecond().getPresentableText(), ", ", buffer); + List pairs = NamedParamsUtil.collectNamedParams(parameter); + StringUtil.join(pairs, namedParam -> namedParam.getName() + ": " + namedParam.getType().getPresentableText(), ", ", buffer); return !pairs.isEmpty(); } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/GroovyNamedVariantArgumentProvider.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/GroovyNamedVariantArgumentProvider.kt index 601a88a814b8..4c7536e0cd6a 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/GroovyNamedVariantArgumentProvider.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/GroovyNamedVariantArgumentProvider.kt @@ -1,9 +1,10 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.transformations.impl.namedVariant import com.intellij.psi.PsiMethod import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor +import org.jetbrains.plugins.groovy.extensions.impl.NamedArgumentDescriptorImpl import org.jetbrains.plugins.groovy.extensions.impl.TypeCondition import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall @@ -19,6 +20,9 @@ class GroovyNamedVariantArgumentProvider : GroovyNamedArgumentProvider() { val parameters = method.parameterList.parameters val mapParameter = (if (parameters.isNotEmpty()) parameters[0] else null) ?: return - collectNamedParams(mapParameter).forEach { result[it.first] = TypeCondition(it.second) } + collectNamedParams(mapParameter).forEach { + val type = it.type + result[it.name] = if (type != null) TypeCondition(type, it.navigation) else NamedArgumentDescriptorImpl(it.navigation) + } } } \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedParamData.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedParamData.kt index f43aa16e7c42..3281b4721a65 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedParamData.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedParamData.kt @@ -1,7 +1,14 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.transformations.impl.namedVariant +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiParameter import com.intellij.psi.PsiType -import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter -internal data class NamedParamData(val name: String, val type: PsiType?, val origin: GrParameter) \ No newline at end of file +data class NamedParamData( + val name: String, + val type: PsiType?, + val origin: PsiParameter, + val navigation: PsiElement, + val required: Boolean = false +) \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedVariantTransformationSupport.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedVariantTransformationSupport.kt index 74f008776fe4..44ba7a6d4af9 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedVariantTransformationSupport.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/NamedVariantTransformationSupport.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.transformations.impl.namedVariant import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT @@ -34,6 +34,7 @@ class NamedVariantTransformationSupport : AstTransformationSupport { modifierList.addAnnotation(GROOVY_TRANSFORM_NAMED_PARAM).let { it.addAttribute("type", namedParam.type?.presentableText ?: JAVA_LANG_OBJECT) it.addAttribute("value", "\"${namedParam.name}\"") + it.addAttribute("required", "${namedParam.required}") } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/namedParams.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/namedParams.kt index 853f5d4f84ed..5c49315294ac 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/namedParams.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/namedVariant/namedParams.kt @@ -9,9 +9,9 @@ import com.intellij.psi.util.PropertyUtilBase import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral -import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod +import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil import org.jetbrains.plugins.groovy.lang.psi.impl.getArrayValue import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils @@ -25,7 +25,7 @@ const val GROOVY_TRANSFORM_NAMED_PARAMS = "groovy.transform.NamedParams" const val GROOVY_TRANSFORM_NAMED_DELEGATE = "groovy.transform.NamedDelegate" -fun collectNamedParams(mapParameter: PsiParameter): List> { +fun collectNamedParams(mapParameter: PsiParameter): List { if (!mapParameter.type.equalsToText(CommonClassNames.JAVA_UTIL_MAP)) return emptyList() val annotations = mapParameter @@ -34,25 +34,26 @@ fun collectNamedParams(mapParameter: PsiParameter): List> ?.getArrayValue { it as? GrAnnotation } if (annotations != null) { - return annotations.mapNotNull(::constructNamedParameter) + return annotations.mapNotNull { constructNamedParameter(it, mapParameter) } } - return mapParameter.annotations.mapNotNull(::constructNamedParameter) + return mapParameter.annotations.mapNotNull{ constructNamedParameter(it, mapParameter) } } -private fun constructNamedParameter(annotation: PsiAnnotation): Pair? { +private fun constructNamedParameter(annotation: PsiAnnotation, owner: PsiParameter): NamedParamData? { if(annotation.qualifiedName != GROOVY_TRANSFORM_NAMED_PARAM) return null val attributeLiteral = annotation.findAttributeValue("value") val name = (attributeLiteral as? GrLiteral)?.value as? String ?: return null val classValue = annotation.findAttributeValue("type") as? GrExpression ?: return null val type = ResolveUtil.getClassReferenceFromExpression(classValue) ?: return null - return name to type + val required = GrAnnotationUtil.inferBooleanAttribute(annotation, "required") ?: false + return NamedParamData(name, type, owner, annotation, required) } /** * The order of the parameters is preserved as it is in the code */ -fun collectAllParamsFromNamedVariantMethod(method: GrMethod): List> { +fun collectAllParamsFromNamedVariantMethod(method: GrMethod): List> { val namedParams = collectNamedParamsFromNamedVariantMethod(method).groupBy { it.origin } return method.parameterList.parameters.flatMap { parameter -> namedParams[parameter]?.let { @@ -72,14 +73,15 @@ internal fun collectNamedParamsFromNamedVariantMethod(method: GrMethod): List - result.add(NamedParamData(propertyName, propertyType, parameter)) + result.add(NamedParamData(propertyName, propertyType, parameter, psiAnnotation)) } } return result diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy25HighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy25HighlightingTest.groovy index 1f516e975169..73dc1d5a52dc 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy25HighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy25HighlightingTest.groovy @@ -29,6 +29,12 @@ class Groovy25HighlightingTest extends LightGroovyTestCase implements Highlighti highlightingTest() } + void 'test named params required'() { highlightingTest() } + + void 'test several absent required named params'() { highlightingTest() } + + void 'test required named param in named variant'() { highlightingTest() } + void 'test named delegate without properties'() { highlightingTest() } void 'test immutable fields'() { highlightingTest() } diff --git a/plugins/groovy/testdata/highlighting/v25/namedParamsRequired.groovy b/plugins/groovy/testdata/highlighting/v25/namedParamsRequired.groovy new file mode 100644 index 000000000000..2d5d0d502f67 --- /dev/null +++ b/plugins/groovy/testdata/highlighting/v25/namedParamsRequired.groovy @@ -0,0 +1,17 @@ +import groovy.transform.CompileStatic +import groovy.transform.NamedParam +import groovy.transform.NamedParams + +@CompileStatic +void namedParams(@NamedParams([ + @NamedParam(value = 'first', type = String, required = true), + @NamedParam(value = 'last', type = Integer) +]) Map args, int i) { + + print args.last +} + +@CompileStatic +def m() { + namedParams(1, last: 1) +} \ No newline at end of file diff --git a/plugins/groovy/testdata/highlighting/v25/requiredNamedParamInNamedVariant.groovy b/plugins/groovy/testdata/highlighting/v25/requiredNamedParamInNamedVariant.groovy new file mode 100644 index 000000000000..34215221fa31 --- /dev/null +++ b/plugins/groovy/testdata/highlighting/v25/requiredNamedParamInNamedVariant.groovy @@ -0,0 +1,15 @@ +import groovy.transform.CompileStatic +import groovy.transform.NamedParam +import groovy.transform.NamedVariant + +@CompileStatic +@NamedVariant +void namedParams(int i, @NamedParam(value = 'param', required = true) Integer n) { + print i + +} + +@CompileStatic +def m() { + namedParams(1, a :1) +} diff --git a/plugins/groovy/testdata/highlighting/v25/severalAbsentRequiredNamedParams.groovy b/plugins/groovy/testdata/highlighting/v25/severalAbsentRequiredNamedParams.groovy new file mode 100644 index 000000000000..7cd3af7b90d5 --- /dev/null +++ b/plugins/groovy/testdata/highlighting/v25/severalAbsentRequiredNamedParams.groovy @@ -0,0 +1,17 @@ +import groovy.transform.CompileStatic +import groovy.transform.NamedParam +import groovy.transform.NamedParams + +@CompileStatic +void namedParams(@NamedParams([ + @NamedParam(value = 'first', type = String, required = true), + @NamedParam(value = 'last', type = Integer, required = true) +]) Map args, int i) { + + print args.last +} + +@CompileStatic +def m() { + namedParams(1, param: 1) +} \ No newline at end of file