[groovy] IDEA-208346 Highlight illegal single argument lambda

This commit is contained in:
alexey.afanasiev
2019-03-04 15:37:49 +03:00
parent 6a754c36ac
commit bca122e2fe
11 changed files with 191 additions and 25 deletions
@@ -102,6 +102,7 @@ change.modifier.not=Make ''{0}'' not {1}
change.modifier.family.name=Change modifiers
parameter.cast.fix=Cast {0,number,ordinal} parameter to {1}
action.convert.lambda.to.closure=Convert lambda to closure
add.parenthesis.to.lambda.parameter.list=Add parenthesis to parameter list
# suppress inspection "UnusedProperty"
packageLocal.visibility.presentation=default visible
# suppress inspection "UnusedProperty"
@@ -193,6 +194,7 @@ unsupported.negated.instanceof=Negated 'instanceof' is not supported in current
unsupported.elvis.assignment=Elvis assignment is not supported in current version
unsupported.safe.index.access=Safe index access is not supported in current version
unsupported.lambda=Lambdas are not supported in current version
illegal.single.argument.lambda=Single argument form of lambda is available only as right part of assignment expression or as argument inside method call
#Override and implement
method.is.not.implemented=Method ''{0}'' is not implemented
@@ -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.annotator
@@ -7,9 +7,16 @@ import com.intellij.psi.PsiClass
import com.intellij.psi.PsiModifier
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.plugins.groovy.GroovyBundle
import org.jetbrains.plugins.groovy.annotator.intentions.AddParenthesisToLambdaParameterAction
import org.jetbrains.plugins.groovy.codeInspection.bugs.GrRemoveModifierFix
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor
import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrParenthesizedExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
@@ -31,5 +38,24 @@ class GroovyAnnotator30(private val holder: AnnotationHolder) : GroovyElementVis
registerFix(annotation, GrRemoveModifierFix(PsiModifier.DEFAULT, GroovyBundle.message("illegal.default.modifier.fix")), modifier)
}
}
override fun visitLambdaExpression(expression: GrLambdaExpression) {
checkSingleArgumentLambda(expression)
super.visitLambdaExpression(expression)
}
private fun checkSingleArgumentLambda(lambda: GrLambdaExpression) {
val parameterList = lambda.parameterList
parameterList.lParen?.let { return }
val parent = lambda.parent
when (parent) {
is GrAssignmentExpression, is GrVariable, is GrParenthesizedExpression -> return
is GrArgumentList -> if (parent.parent is GrMethodCallExpression) return
}
holder.createErrorAnnotation(parameterList, GroovyBundle.message("illegal.single.argument.lambda")).apply {
registerFix(AddParenthesisToLambdaParameterAction(lambda))
}
}
}
@@ -0,0 +1,47 @@
// 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.intentions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.util.createSmartPointer
import org.jetbrains.plugins.groovy.GroovyBundle.message
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory
import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression
class AddParenthesisToLambdaParameterAction(parameterList: GrLambdaExpression) : IntentionAction {
private val myLambda: SmartPsiElementPointer<GrLambdaExpression> = parameterList.createSmartPointer()
override fun getText(): String = familyName
override fun getFamilyName(): String = message("add.parenthesis.to.lambda.parameter.list")
override fun startInWriteAction(): Boolean = true
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
val parameterList = myLambda.element?.parameterList ?: return false
parameterList.lParen ?: return true
return false
}
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val lambda = myLambda.element ?: return
val closureText = closureText(lambda) ?: return
val closure = GroovyPsiElementFactory.getInstance(project).createLambdaFromText(closureText)
lambda.replaceWithExpression(closure, false)
}
private fun closureText(lambda: GrLambdaExpression): String? {
val closureText = StringBuilder()
closureText.append("(")
val parameterList = lambda.parameterList
appendTextBetween(closureText, parameterList.text, parameterList.lParen, parameterList.rParen)
closureText.append(")")
appendTextBetween(closureText, lambda.text, parameterList, null)
return closureText.toString()
}
}
@@ -4,7 +4,6 @@ package org.jetbrains.plugins.groovy.annotator.intentions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.util.createSmartPointer
@@ -51,25 +50,4 @@ class ConvertLambdaToClosureAction(lambda: GrLambdaExpression) : IntentionAction
closureText.append("}")
return closureText.toString()
}
/**
* Appends [text] to the [builder] cutting length of [left] text from the start and length of [right] text from the end.
*/
private fun appendTextBetween(builder: StringBuilder, text: String, left: PsiElement?, right: PsiElement?) {
val start = left?.textLength ?: 0
val end = text.length - (right?.textLength ?: 0)
builder.append(text, start, end)
}
/**
* Appends text of elements to the [builder] between [start] and [stop].
* If [stop] is `null` then all siblings of [start] are processed.
*/
private fun appendElements(builder: StringBuilder, start: PsiElement, stop: PsiElement) {
var current: PsiElement? = start.nextSibling
while (current !== null && current !== stop) {
builder.append(current.text)
current = current.nextSibling
}
}
}
@@ -0,0 +1,25 @@
// 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.intentions
import com.intellij.psi.PsiElement
/**
* Appends [text] to the [builder] cutting length of [left] text from the start and length of [right] text from the end.
*/
internal fun appendTextBetween(builder: StringBuilder, text: String, left: PsiElement?, right: PsiElement?) {
val start = left?.textLength ?: 0
val end = text.length - (right?.textLength ?: 0)
builder.append(text, start, end)
}
/**
* Appends text of elements to the [builder] between [start] and [stop].
* If [stop] is `null` then all siblings of [start] are processed.
*/
internal fun appendElements(builder: StringBuilder, start: PsiElement, stop: PsiElement) {
var current: PsiElement? = start.nextSibling
while (current !== null && current !== stop) {
builder.append(current.text)
current = current.nextSibling
}
}
@@ -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.lang.psi;
import com.intellij.openapi.components.ServiceManager;
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocMemberReference;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
@@ -187,6 +188,14 @@ public abstract class GroovyPsiElementFactory implements JVMElementFactory {
return createClosureFromText(s, null);
}
@NotNull
public abstract GrLambdaExpression createLambdaFromText(@NotNull String text, @Nullable PsiElement context);
@NotNull
public GrLambdaExpression createLambdaFromText(@NotNull String text) {
return createLambdaFromText(text, null);
}
@NotNull
public GrParameter createParameter(@NotNull String name, @Nullable String typeText, @Nullable GroovyPsiElement context) throws IncorrectOperationException {
return createParameter(name, typeText, null, context);
@@ -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.lang.psi.impl;
import com.intellij.lang.ASTNode;
@@ -26,6 +26,7 @@ import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocMemberReference;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocReferenceElement;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocTag;
import org.jetbrains.plugins.groovy.lang.psi.*;
import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
@@ -361,6 +362,17 @@ public class GroovyPsiElementFactoryImpl extends GroovyPsiElementFactory {
return ((GrClosableBlock)initializer);
}
@NotNull
@Override
public GrLambdaExpression createLambdaFromText(@NotNull String lambdaText, PsiElement context) throws IncorrectOperationException {
GroovyFile psiFile = createGroovyFileChecked("def __hdsjfghkl_sdhjfshglk_foo = " + lambdaText, false, context);
final GrStatement st = psiFile.getStatements()[0];
LOG.assertTrue(st instanceof GrVariableDeclaration, lambdaText);
final GrExpression initializer = ((GrVariableDeclaration)st).getVariables()[0].getInitializerGroovy();
LOG.assertTrue(initializer instanceof GrLambdaExpression, lambdaText);
return ((GrLambdaExpression)initializer);
}
private GroovyFileImpl createDummyFile(@NotNull CharSequence text, boolean physical) {
final String fileName = DUMMY_FILE_NAME + '.' + GroovyFileType.GROOVY_FILE_TYPE.getDefaultExtension();
final long stamp = System.currentTimeMillis();
@@ -0,0 +1,41 @@
// 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.lang.highlighting
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.GroovyBundle
import org.jetbrains.plugins.groovy.util.ActionTest
import org.jetbrains.plugins.groovy.util.Groovy30Test
import org.junit.Test
@CompileStatic
class AddParenthesisToLambdaParameterTest extends Groovy30Test implements ActionTest {
private void doTest(String before, String after) {
doActionTest(GroovyBundle.message("add.parenthesis.to.lambda.parameter.list"), before, after)
}
@Test
void 'statement expression'() {
doTest '<caret>a -> /*mark*/ b', '(a) -> /*mark*/ b'
}
@Test
void 'nested lambda'() {
doTest 'a -> <caret>b /*mark*/-> c', 'a -> (b) /*mark*/ -> c'
}
@Test
void 'multyline'() {
doTest '''
true ? a<caret>a -> {
//line comment
aa++
} : () -> {}
''', '''
true ? (aa) -> {
//line comment
aa++
} : () -> {}
'''
}
}
@@ -76,4 +76,8 @@ I i = {3}
void 'test reassigned var in lambda 2'() {
highlightingTest GrUnresolvedAccessInspection
}
void 'test illegal single argument lambda'() {
highlightingTest ()
}
}
@@ -0,0 +1,15 @@
// 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.util
import com.intellij.testFramework.LightProjectDescriptor
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.GroovyProjectDescriptors
@CompileStatic
abstract class Groovy30Test extends LightProjectTest {
@Override
final LightProjectDescriptor getProjectDescriptor() {
GroovyProjectDescriptors.GROOVY_3_0
}
}
@@ -0,0 +1,7 @@
<error descr="Single argument form of lambda is available only as right part of assignment expression or argument inside method call">a</error> -> a
def l = b-> <error descr="Single argument form of lambda is available only as right part of assignment expression or argument inside method call">c</error>-> d
def l2 = false ? <error descr="Single argument form of lambda is available only as right part of assignment expression or argument inside method call">e</error>->e : <error descr="Single argument form of lambda is available only as right part of assignment expression or argument inside method call">f</error>->f
if ((e->e) != null){}
m(g->g)
def l3 = h->h
l3 = j->j