[groovy] parameter hints (IDEA-163484)

This commit is contained in:
Daniil Ovchinnikov
2016-11-03 17:01:08 +03:00
parent cf5531e2e6
commit 3ff626b2e7
3 changed files with 203 additions and 0 deletions
+3
View File
@@ -315,6 +315,9 @@
<codeInsight.createFieldFromUsageHelper language="Groovy"
implementationClass="org.jetbrains.plugins.groovy.annotator.intentions.GroovyCreateFieldFromUsageHelper"/>
<codeInsight.typeInfo language="Groovy" implementationClass="org.jetbrains.plugins.groovy.codeInsight.hint.GroovyExpressionTypeProvider"/>
<codeInsight.parameterNameHints
language="Groovy" implementationClass="org.jetbrains.plugins.groovy.codeInsight.hint.GroovyInlayParameterHintsProvider"
/>
<fileTypeFactory implementation="org.jetbrains.plugins.groovy.GroovyFileTypeLoader"/>
<fileTypeFactory implementation="org.jetbrains.plugins.groovy.dgm.DGMFileTypeFactory"/>
@@ -0,0 +1,81 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.codeInsight.hint
import com.intellij.codeInsight.hints.InlayInfo
import com.intellij.codeInsight.hints.InlayParameterHintsProvider
import com.intellij.codeInsight.hints.MethodInfo
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil
class GroovyInlayParameterHintsProvider : InlayParameterHintsProvider {
private companion object {
val blackList = setOf(
"org.codehaus.groovy.runtime.DefaultGroovyMethods.*"
)
}
override fun getParameterHints(element: PsiElement) = (element as? GrCall)?.doGetParameterHints() ?: emptyList()
private fun GrCall.doGetParameterHints(): List<InlayInfo>? {
val signature = GrClosureSignatureUtil.createSignature(this) ?: return null
val infos = GrClosureSignatureUtil.mapParametersToArguments(signature, this) ?: return null
val original = signature.parameters.zip(infos)
// leave only parameters with names
val map = original.mapNotNull {
it.first.name?.let { name -> name to it.second }
}.toMap()
// leave only regular arguments and varargs
val nonNamedArguments = map.filterValues {
!it.isMultiArg || it.args.none { it is GrNamedArgument }
}
return nonNamedArguments.mapNotNull {
val (name, info) = it
info.args.firstOrNull()?.let { arg ->
val inlayText = if (info.isMultiArg) "...$name" else name
InlayInfo(inlayText, arg.textRange.startOffset)
}
}
}
override fun getMethodInfo(element: PsiElement): MethodInfo? {
val call = element as? GrCall
val resolved = call?.resolveMethod()
val method = (resolved as? GrGdkMethod)?.staticMethod ?: resolved
return method?.getMethodInfo()
}
private fun PsiMethod.getMethodInfo(): MethodInfo? {
val clazzName = containingClass?.qualifiedName ?: return null
val fullMethodName = StringUtil.getQualifiedName(clazzName, name)
val paramNames: List<String> = parameterList.parameters.map { it.name ?: "" }
return MethodInfo(fullMethodName, paramNames)
}
override val defaultBlackList: Set<String> get() = blackList
override fun getBlackListDependencyLanguage() = JavaLanguage.INSTANCE
}
@@ -0,0 +1,119 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.codeInsight.hint
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager
import com.intellij.testFramework.LightProjectDescriptor
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor
import org.jetbrains.plugins.groovy.LightGroovyTestCase
@CompileStatic
class GroovyInlayParameterHintsProviderTest extends LightGroovyTestCase {
final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST
String prefix
@Override
void setUp() {
super.setUp()
fixture.addFileToProject 'Foo.groovy', '''\
class Foo {
Foo() {}
Foo(a, b, c) {}
void simple(a) {}
void defaultArgs(a, b = 1,c) {}
void mapArgs(Map m, a) {}
void varArgs(a, def... b) {}
void combo(Map m, a, b = 1, def ... c) {}
def getAt(a) {}
}
'''
}
private void testInlays(String text, Map<Integer, String> expectedParameterHints) {
fixture.configureByText '_.groovy', prefix ? prefix + text : text
fixture.doHighlighting()
def manager = ParameterHintsPresentationManager.instance
def inlays = editor.inlayModel.getInlineElementsInRange(0, editor.document.textLength)
def parameterHinInlays = inlays.findAll {
manager.isParameterHint(it)
}
int prefixLength = prefix?.length() ?: 0
def actualParameterHints = parameterHinInlays.collectEntries {
[(it.offset - prefixLength): manager.getHintText(it)]
}
assert actualParameterHints == expectedParameterHints
}
void 'test method call expressions'() {
prefix = '''\
def foo = new Foo()
foo.'''
testInlays 'simple(null)', [7: 'a']
testInlays 'defaultArgs(1, 2)', [12: 'a', 15: 'c']
testInlays 'defaultArgs(1, 2, 3)', [12: 'a', 15: 'b', 18: 'c']
testInlays 'mapArgs(foo: 1, null, bar: 2)', [16: 'a']
testInlays 'varArgs(1, 2, 3, 4)', [8: 'a', 11: '...b']
testInlays 'combo(1, foo: 10, 2, 3, 4, bar: 20, 5)', [6: 'a', 18: 'b', 21: '...c']
}
void 'test method call applications'() {
prefix = '''\
def foo = new Foo()
foo.'''
testInlays 'simple null', [7: 'a']
testInlays 'defaultArgs 1, 2', [12: 'a', 15: 'c']
testInlays 'defaultArgs 1, 2, 3', [12: 'a', 15: 'b', 18: 'c']
testInlays 'mapArgs foo: 1, null, bar: 2 ', [16: 'a']
testInlays 'varArgs 1, 2, 3, 4 ', [8: 'a', 11: '...b']
testInlays 'combo 1, foo: 10, 2, 3, 4, bar: 20, 5', [6: 'a', 18: 'b', 21: '...c']
}
void 'test index expression'() {
testInlays 'new Foo()[1]', [10: 'a']
}
void 'test new expression'() {
testInlays 'new Foo(1, 2, 4)', [8: 'a', 11: 'b', 14: 'c']
}
void 'test constructor invocation'() {
testInlays '''\
class Bar {
Bar() {
this(1, 4, 5)
}
Bar(a, b, c) {}
}
''', [31: 'a', 34: 'b', 37: 'c']
}
void 'test enum constants'() {
testInlays '''\
enum Baz {
ONE(1, 2),
TWO(4, 3)
Baz(a, b) {}
}
''', [17: 'a', 20: 'b', 30: 'a', 33: 'b']
}
void 'test no DGM inlays'() {
testInlays '[].each {}', [:]
}
}