[groovy] IDEA-220527 Rewrite 'Create method' using JvmElementActionsFactory

GitOrigin-RevId: 257144d55c9b7d57d281e1a9d5514ef167e04109
This commit is contained in:
alexey.afanasiev
2019-10-10 10:34:02 +00:00
committed by intellij-monorepo-bot
parent fe78876b14
commit 3339a4762c
91 changed files with 746 additions and 12 deletions
@@ -1,11 +1,11 @@
// 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 com.intellij.lang.jvm.actions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ApplicationManager
internal fun List<IntentionAction>.groupActionsByType(): List<IntentionAction> {
fun List<IntentionAction>.groupActionsByType(): List<IntentionAction> {
if (ApplicationManager.getApplication().isUnitTestMode) {
return this
}
@@ -9,13 +9,14 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.parentOfType
import org.jetbrains.plugins.groovy.annotator.intentions.QuickfixUtil
import org.jetbrains.plugins.groovy.codeInspection.GroovyQuickFixFactory
import org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.requests.generateCreateMethodActions
import org.jetbrains.plugins.groovy.lang.GrCreateClassKind
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrExtendsClause
@@ -115,15 +116,17 @@ fun generateReferenceExpressionFixes(ref: GrReferenceExpression): Collection<Int
generateAddDynamicMemberAction(ref)?.let(actions::add)
if (targetClass !is SyntheticElement || targetClass is GroovyScriptClass) {
actions += factory.createCreateFieldFromUsageFix(ref)
if (PsiUtil.isAccessedForReading(ref)) {
actions += factory.createCreateGetterFromUsageFix(ref, targetClass)
}
if (PsiUtil.isLValue(ref)) {
actions += factory.createCreateSetterFromUsageFix(ref)
}
if (ref.parent is GrCall && ref.parent is GrExpression) {
actions += factory.createCreateMethodFromUsageFix(ref)
val parent = ref.parent
if (parent is GrMethodCall) {
actions += generateCreateMethodActions(parent)
} else {
actions += factory.createCreateFieldFromUsageFix(ref)
if (PsiUtil.isAccessedForReading(ref)) {
actions += factory.createCreateGetterFromUsageFix(ref, targetClass)
}
if (PsiUtil.isLValue(ref)) {
actions += factory.createCreateSetterFromUsageFix(ref)
}
}
}
@@ -0,0 +1,92 @@
// 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.codeInspection.untypedUnresolvedAccess.requests
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix.getTargetSubstitutor
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.*
import com.intellij.openapi.components.service
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiJvmSubstitutor
import com.intellij.psi.PsiType
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
import com.intellij.psi.util.createSmartPointer
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrSpreadArgument
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrCallExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition
import org.jetbrains.plugins.groovy.lang.psi.impl.GrMapType
import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil
internal abstract class CreateExecutableFromGroovyUsageRequest<out T : GrCallExpression>(
call: T,
private val modifiers: Collection<JvmModifier>
) : CreateExecutableRequest {
private val psiManager = call.manager
private val project = psiManager.project
private val callPointer: SmartPsiElementPointer<T> = call.createSmartPointer(project)
protected val call: T get() = callPointer.element ?: error("dead pointer")
override fun isValid() = callPointer.element != null
override fun getAnnotations() = emptyList<AnnotationRequest>()
override fun getModifiers() = modifiers
override fun getTargetSubstitutor() = PsiJvmSubstitutor(project, getTargetSubstitutor(call))
override fun getExpectedParameters(): List<ExpectedParameter> {
val argumentTypes = getArgumentTypes() ?: return emptyList()
val codeStyleManager: JavaCodeStyleManager = project.service()
return argumentTypes.map {(type, _) ->
//if (expression != null) codeStyleManager.suggestSemanticNames(expression) //TODO add semantic names based on expression
val names = codeStyleManager.suggestNames(emptyList(), VariableKind.PARAMETER, type).names
expectedParameter(expectedTypes(type, ExpectedType.Kind.SUPERTYPE), names.toList())
}
}
fun getArgumentTypes(): List<Pair<PsiType, GrExpression?>>? {
val result = mutableListOf<Pair<PsiType, GrExpression?>>()
val namedArguments = call.namedArguments
if (namedArguments.isNotEmpty()) {
result.add(GrMapType.createFromNamedArgs(call, namedArguments) to null)
}
val expressionArguments = call.expressionArguments
for (expression in expressionArguments) {
val type = anonymousClassesToBase(expression.type)
if (expression is GrSpreadArgument) {
if (type is GrTupleType) {
type.componentTypes.forEach { result.add(it to null) }
}
else {
return null
}
}
else {
val expectedType = type ?: TypesUtil.getJavaLangObject(expression)
result.add(expectedType to expression)
}
}
val closureArguments = call.closureArguments
for (closure in closureArguments) {
val expectedType = closure.type ?: TypesUtil.getJavaLangObject(closure)
result.add(expectedType to null)
}
return result
}
private fun anonymousClassesToBase(type: PsiType?): PsiType? {
if (type !is PsiClassType) return type
val resolved = type.resolve()
return if (resolved is GrAnonymousClassDefinition) resolved.baseClassType else type
}
override fun getParameters() = getParameters(expectedParameters, project)
}
@@ -0,0 +1,45 @@
// 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.codeInspection.untypedUnresolvedAccess.requests
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.lang.jvm.actions.ExpectedType
import com.intellij.lang.jvm.actions.expectedType
import com.intellij.lang.jvm.actions.expectedTypes
import com.intellij.psi.*
import com.intellij.psi.util.parentOfType
import com.intellij.psi.util.parents
import com.intellij.util.containers.withPrevious
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider
internal class CreateMethodFromGroovyUsageRequest(
methodCall: GrMethodCall,
modifiers: Collection<JvmModifier>
) : CreateExecutableFromGroovyUsageRequest<GrMethodCall>(methodCall, modifiers), CreateMethodRequest {
override fun isValid() = super.isValid() && call.let {
getRefExpression()?.referenceName != null
}
private fun getRefExpression() = call.invokedExpression as? GrReferenceExpression
override fun getMethodName() = getRefExpression()?.referenceName!!
override fun getReturnType() : List<ExpectedType> {
val expected = GroovyExpectedTypesProvider.getDefaultExpectedTypes(call)
if (expected.isEmpty()) {
return expectedTypes(PsiType.VOID)
}
return expected.map { expectedType(it, ExpectedType.Kind.EXACT) }
}
fun getAnchor(targetClass: PsiClass): PsiElement? {
val enclosingMember = call.parentOfType(PsiMethod::class, PsiField::class, PsiClassInitializer::class) ?: return null
for ((parent, lastParent) in enclosingMember.parents().withPrevious()) {
if (parent == targetClass) return lastParent
}
return null
}
}
@@ -0,0 +1,39 @@
// 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.codeInspection.untypedUnresolvedAccess.requests
import com.intellij.lang.jvm.JvmClass
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.psi.PsiClass
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.util.GrStaticChecker
class MethodRequestsBuilder(private val myCall: GrMethodCall) {
private val myRequests = LinkedHashMap<JvmClass, CreateMethodRequest>()
fun buildRequests(): Map<JvmClass, CreateMethodRequest> {
build()
return myRequests
}
private fun build() {
val invokedExpression = myCall.invokedExpression as? GrReferenceExpression ?: return
val targetClasses = getTargetClasses(invokedExpression) {
psiClass -> psiClass.manager.isInProject(psiClass)
}
targetClasses.forEach {
processClass(it, invokedExpression)
}
}
private fun processClass(clazz: PsiClass, invokedExpression: GrReferenceExpression) {
//if (CreateMethodFromUsageFix.isMethodSignatureExists(myCall, clazz)) return // TODO add check
val modifiers = mutableSetOf<JvmModifier>()
if (GrStaticChecker.isInStaticContext(invokedExpression, clazz)) modifiers += JvmModifier.STATIC
myRequests[clazz] = CreateMethodFromGroovyUsageRequest(myCall, modifiers)
}
}
@@ -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.codeInspection.untypedUnresolvedAccess.requests
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.lang.jvm.actions.EP_NAME
import com.intellij.lang.jvm.actions.groupActionsByType
import com.intellij.psi.PsiClass
import org.jetbrains.plugins.groovy.annotator.intentions.QuickfixUtil
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
fun generateCreateMethodActions(call: GrMethodCall): List<IntentionAction> {
val methodRequests = MethodRequestsBuilder(call).buildRequests()
val extensions = EP_NAME.extensions
return methodRequests.flatMap { (clazz, request) ->
extensions.flatMap { ext ->
ext.createAddMethodActions(clazz, request)
}
}.groupActionsByType()
}
internal fun getTargetClasses(ref: GrReferenceExpression, predicate: (ref: PsiClass) -> Boolean): List<PsiClass> {
val targetClass = QuickfixUtil.findTargetClass(ref)
if (targetClass == null || !predicate(targetClass)) return emptyList()
val classes = mutableListOf<PsiClass>()
collectSupers(targetClass, classes, predicate)
return classes
}
private fun collectSupers(psiClass: PsiClass, classes: MutableList<PsiClass>, predicate: (ref: PsiClass) -> Boolean) {
classes.add(psiClass)
val supers = psiClass.supers
for (aSuper in supers) {
if (classes.contains(aSuper)) continue
if (predicate(aSuper)) {
collectSupers(aSuper, classes, predicate)
}
}
}
@@ -0,0 +1,163 @@
// 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.fixes
import com.intellij.psi.impl.source.PostprocessReformattingAspect
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection
import org.jetbrains.plugins.groovy.lang.highlighting.GrHighlightingTestBase
import org.jetbrains.plugins.groovy.util.TestUtils
@CompileStatic
class CreateMethodFromUsageTest extends GrHighlightingTestBase {
private static final String BEFORE = "Before.groovy"
private static final String AFTER = "After.groovy"
private static final String USAGE = "script.groovy"
public static final String CREATE_METHOD = 'Create method'
public static final String CREATE_ABSTRACT_METHOD = 'Create abstract method'
public static final String CREATE_CONSTRUCTOR = 'Create constructor'
final String getBasePath() {
return TestUtils.testDataPath + 'fixes/createMethodFromUsage/' + getTestName(true) + '/'
}
@Override
void setUp() {
super.setUp()
fixture.configureByFiles(USAGE, BEFORE)
fixture.enableInspections(GrUnresolvedAccessInspection)
}
private void doTest(String action = CREATE_METHOD, int actionCount = 1) {
fixture.with {
def fixes = filterAvailableIntentions(action)
assert fixes.size() == actionCount
if (actionCount == 0) return
launchAction fixes.first()
PostprocessReformattingAspect.getInstance(project).doPostponedFormatting()
checkResultByFile(BEFORE, AFTER, true)
}
}
void testSimple1() {
doTest()
}
void _testSimple2() {
doTest()
}
void _testSimple3() {
doTest()
}
void _testSimple4() {
doTest()
}
void testAbstract() {
doTest(CREATE_ABSTRACT_METHOD)
}
void testAbstractStatic() {
doTest(CREATE_ABSTRACT_METHOD, 0)
}
void testAbstractInNonAbstract() {
doTest(CREATE_ABSTRACT_METHOD, 0)
}
void testAbstractInInterface() {
doTest()
}
void testArrayParam() {
doTest()
}
void _testAssertDescription() {
doTest()
}
void testGeneric() {
doTest()
}
void testMultiMap() {
doTest()
}
void testClosureArgument() {
doTest()
}
void testMethodReference() {
doTest()
}
void testSeveralReturnTypes() {
doTest()
}
void testCapturedWildcard() {
doTest()
}
void _testParameterNameSuggestion() {
doTest()
}
void _testPolyadicExpression() {
doTest()
}
void testNestedExpression() {
doTest()
}
void testInAnonymousClass() {
doTest()
}
void testTypeParameterFromWildcard() {
doTest()
}
void _testUnresolvedArg() {
doTest(CREATE_METHOD, 0)
}
void _testIntegerCast() {
doTest()
}
void testSeveralArguments() {
doTest()
}
void _testConstructor1() {
doTest(CREATE_CONSTRUCTOR)
}
void _testConstructor2() {
doTest(CREATE_CONSTRUCTOR)
}
void _testConstructorAnon() {
doTest(CREATE_CONSTRUCTOR)
}
void testConstructorInterface() {
doTest(CREATE_CONSTRUCTOR, 0)
}
void testConstructorTrait() {
doTest(CREATE_CONSTRUCTOR, 0)
}
void testConstructorEnum() {
doTest(CREATE_CONSTRUCTOR, 0)
}
}
@@ -0,0 +1,3 @@
abstract class A {
abstract void test()
}
@@ -0,0 +1,2 @@
abstract class A {
}
@@ -0,0 +1,5 @@
class Test {
Test(A a) {
a.te<caret>st()
}
}
@@ -0,0 +1,3 @@
interface A {
void test()
}
@@ -0,0 +1,2 @@
interface A {
}
@@ -0,0 +1,5 @@
class Test {
Test(A a) {
a.te<caret>st()
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
Test(A a) {
a.te<caret>st()
}
}
@@ -0,0 +1,2 @@
abstract class A {
}
@@ -0,0 +1,5 @@
class Test {
Test() {
A.TE<caret>ST()
}
}
@@ -0,0 +1,5 @@
class A {
void test(int[] ints) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
Test() {
new A().te<caret>st(new int[0])
}
}
@@ -0,0 +1,5 @@
class A {
String test() {
null
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,2 @@
A a = new A()
assert false, a.te<caret>st()
@@ -0,0 +1,5 @@
class A {
void foo(Test.List<? super CharSequence> list) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,11 @@
class Test {
private List<? extends CharSequence> sequences = null;
{
new A().f<caret>oo(sequences.map())
}
interface List<T> {
List<? super T> map()
}
}
@@ -0,0 +1,5 @@
class A {
void f(Closure<Void> voidClosure) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
void foo() {
new A().f<caret>{}
}
}
@@ -0,0 +1,9 @@
class A {
A(){
}
A(int i) {
}
}
@@ -0,0 +1,5 @@
class A {
A(){
}
}
@@ -0,0 +1 @@
new A<caret>(10)
@@ -0,0 +1,9 @@
class A {
A(){
}
A(Test test) {
}
}
@@ -0,0 +1,5 @@
class A {
A(){
}
}
@@ -0,0 +1,5 @@
class Test {
void main2() {
new A<caret>(this)
}
}
@@ -0,0 +1,9 @@
class A {
A(int i){
}
A(Test test) {
}
}
@@ -0,0 +1,5 @@
class A {
A(int i){
}
}
@@ -0,0 +1,5 @@
class Test {
void main2() {
new A(<caret>this){}
}
}
@@ -0,0 +1,2 @@
enum A {
}
@@ -0,0 +1,5 @@
class Test {
void main2() {
new A(<caret>this){};
}
}
@@ -0,0 +1,2 @@
interface A {
}
@@ -0,0 +1,5 @@
class Test {
void main2() {
new A(<caret>this){}
}
}
@@ -0,0 +1,2 @@
trait A {
}
@@ -0,0 +1,5 @@
class Test {
void main2() {
new A(<caret>this){}
}
}
@@ -0,0 +1,5 @@
class A {
def <T extends String> void bar(T t, T t) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
def <T extends String> void foo (T t1, T t2) {
new A().<caret>bar (t1, t2)
}
}
@@ -0,0 +1,5 @@
class A {
static void f(Runnable runnable) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,9 @@
class Test {
{
new Runnable() {
void run() {
A.<caret>f(this)
}
}
}
}
@@ -0,0 +1,5 @@
class A {
static Object test() {
null
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
{
int i = (Integer)A.te<caret>st()
}
}
@@ -0,0 +1,5 @@
class A {
void f(Closure<Void> voidClosure) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
void foo() {
new A().f<caret>(Test::foo)
}
}
@@ -0,0 +1,5 @@
class A {
Map<String, String[]> toMulti(Map<String, String> stringStringMap) {
null
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,6 @@
class BrokenCreateMethod {
void foo(Map<String, String> bar) {
Map<String, String[]> multiBar = new A().<caret>toMulti(bar)
}
}
@@ -0,0 +1,5 @@
class A {
static int f(int integer) {
0
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
{
double d = -A.<caret>f(2)
}
}
@@ -0,0 +1,5 @@
class A {
static void test2(String fooBar) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,4 @@
class Foo {
static String FOO_BAR = "Bar";
void test1() { A.tes<caret>t2(FOO_BAR) }
}
@@ -0,0 +1,5 @@
class A {
static long f(int i) {
0
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
{
long l = A.f(1) + A.<caret>f(2);
}
}
@@ -0,0 +1,5 @@
class A {
void test(int integer, Thread thread, Runnable runnable, List<String> strings) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,10 @@
class Test {
Test(java.util.List<java.lang.String> l) {
new A().te<caret>st(1, new java.lang.Thread(), new java.lang.Runnable(){
@Override
void run() {
}
}, l)
}
}
@@ -0,0 +1,5 @@
class A {
Object bar() {
null
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,6 @@
class Test {
void foo(A a) {
Object x = a.<caret>bar()
String s = a.bar()
}
}
@@ -0,0 +1,5 @@
class A {
void test() {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1 @@
new A().te<caret>st()
@@ -0,0 +1,5 @@
class A {
void test() {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
Test() {
new A().test(<caret>)
}
}
@@ -0,0 +1,5 @@
class A {
void test() {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
Test() {
new A().test()<caret>
}
}
@@ -0,0 +1,7 @@
class A {
void test(int i) {}
void test() {
}
}
@@ -0,0 +1,3 @@
class A {
void test(int i) {}
}
@@ -0,0 +1 @@
new A().te<caret>st()
@@ -0,0 +1,5 @@
class A {
def <T> void test(List<? extends T> ts) {
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
def <T> void f(List<? extends T> l) {
new A().te<caret>st(l)
}
}
@@ -0,0 +1,2 @@
class A {
}
@@ -0,0 +1,5 @@
class Test {
{
new A().fo<caret>o(bar)
}
}