From af40b9d7a73ad45b16e28bdeb2c6a9e231184c7c Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Fri, 30 Dec 2022 12:17:35 -0600 Subject: [PATCH] Fix for #1308: add `delegate` or `owner` qualifier within closure/lambda see #1288 --- .../ConvertToPropertyActionTests.groovy | 30 ++++++++++++- .../actions/ConvertToPropertyAction.java | 42 ++++++++++++------- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/actions/ConvertToPropertyActionTests.groovy b/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/actions/ConvertToPropertyActionTests.groovy index 53f36a3ce9..ca36ad4a90 100644 --- a/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/actions/ConvertToPropertyActionTests.groovy +++ b/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/actions/ConvertToPropertyActionTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2009-2021 the original author or authors. + * Copyright 2009-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -149,6 +149,20 @@ final class ConvertToPropertyActionTests extends GroovyEditorTestSuite { assertEditorContents 'def getX(){0}\n[x:1].with { this.x }' } + @Test // https://github.com/groovy/groovy-eclipse/issues/1308 + void testImplicitGetterToProperty3() { + def pogo = 'class C {\n def getX(){0}\n}\n' + convertToProperty pogo + "new C().with { get${CARET}X() }" + assertEditorContents pogo + 'new C().with { x }' // no qualifier + + convertToProperty pogo + "def x=1\nnew C().with { get${CARET}X() }" + assertEditorContents pogo + 'def x=1\nnew C().with { delegate.x }' + + def stc = { '@groovy.transform.TypeChecked void test(){\n' + it + '\n}' } + convertToProperty pogo + stc("def x=1\nnew C().with { get${CARET}X() }") + assertEditorContents pogo + stc('def x=1\nnew C().with { delegate.x }') + } + @Test void testImplicitIsserToProperty() { addGroovySource 'class Foo { static void isSomething() {} }', 'Foo' @@ -168,6 +182,20 @@ final class ConvertToPropertyActionTests extends GroovyEditorTestSuite { assertEditorContents 'void setX(x){}\n[x:1].with { this.x = 0 }' } + @Test // https://github.com/groovy/groovy-eclipse/issues/1308 + void testImplicitSetterToProperty3() { + def pogo = 'class C {\n void setX(x){}\n}\n' + convertToProperty pogo + "new C().with { set${CARET}X(0) }" + assertEditorContents pogo + 'new C().with { x = 0 }' // no qualifier + + convertToProperty pogo + "def x=1\nnew C().with { set${CARET}X(0) }" + assertEditorContents pogo + 'def x=1\nnew C().with { delegate.x = 0 }' + + def stc = { '@groovy.transform.TypeChecked void test(){\n' + it + '\n}' } + convertToProperty pogo + stc("def x=1\nnew C().with { set${CARET}X(0) }") + assertEditorContents pogo + stc('def x=1\nnew C().with { delegate.x = 0 }') + } + @Test void testStaticGetterToProperty() { convertToProperty "import java.lang.management.*; ManagementFactory.getRun${CARET}timeMXBean()" diff --git a/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/refactoring/actions/ConvertToPropertyAction.java b/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/refactoring/actions/ConvertToPropertyAction.java index 78fbaba8b6..2b5d723f21 100644 --- a/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/refactoring/actions/ConvertToPropertyAction.java +++ b/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/refactoring/actions/ConvertToPropertyAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2021 the original author or authors. + * Copyright 2009-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import static java.beans.Introspector.decapitalize; import static java.util.regex.Pattern.compile; +import static org.codehaus.groovy.transform.stc.StaticTypesMarker.IMPLICIT_RECEIVER; import static org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_ASSIGNMENT_OPERATOR; import static org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_ASSIGNMENT_OPERATOR; @@ -84,12 +85,12 @@ public void run() { } } - public static TextEdit createEdit(final GroovyCompilationUnit gcu, final int pos, final int len) { + public static TextEdit createEdit(final GroovyCompilationUnit gcu, final int idx, final int len) { ModuleNodeInfo info = gcu.getModuleInfo(true); if (!info.isEmpty()) { MethodCall call = null; - ASTNode node = new ASTNodeFinder(new Region(pos, len)).doVisit(info.module); + ASTNode node = new ASTNodeFinder(new Region(idx, len)).doVisit(info.module); if (node instanceof ConstantExpression) { IASTFragment fragment = new FindSurroundingNode(new Region(node)).doVisitSurroundingNode(info.module); if (fragment.kind() == ASTFragmentKind.METHOD_CALL) { @@ -114,8 +115,8 @@ public static TextEdit createEdit(final GroovyCompilationUnit gcu, final int pos TextEdit edit = new ReplaceEdit(offset, length, decapitalize(propertyName)); // implicit-this call may require qualifier to retain its semantics - if (call.getReceiver().getEnd() < 1 && isTypeChange(gcu, edit, node)) { - edit = new ReplaceEdit(offset, length, call.getReceiver().getText() + "." + decapitalize(propertyName)); + if (call.getReceiver().getEnd() < 1 && isTypeChange(edit, node, gcu)) { + edit = new ReplaceEdit(offset, length, getReceiver(call) + "." + decapitalize(propertyName)); } return edit; @@ -149,9 +150,10 @@ public static TextEdit createEdit(final GroovyCompilationUnit gcu, final int pos } // implicit-this call may require qualifier to retain its semantics - if (call.getReceiver().getEnd() < 1 && isTypeChange(gcu, edit, node)) { - edit.removeChild(0); // add qualifier to the property name - replacement.insert(0, call.getReceiver().getText() + "."); + if (call.getReceiver().getEnd() < 1 && isTypeChange(edit, node, gcu)) { + edit.removeChild(0); + // add qualifier to the property name + replacement.insert(0, getReceiver(call) + "."); edit.addChild(new ReplaceEdit(offset, length, replacement.toString())); } @@ -164,14 +166,26 @@ public static TextEdit createEdit(final GroovyCompilationUnit gcu, final int pos //-------------------------------------------------------------------------- - private static boolean isTypeChange(final GroovyCompilationUnit gcu, final TextEdit edit, final ASTNode node) { + private static String getReceiver(final MethodCall call) { + String receiver = ((ASTNode) call).getNodeMetaData(IMPLICIT_RECEIVER); + if (receiver == null) { + receiver = call.getReceiver().getText(); + } + return receiver; + } + + private static boolean isTypeChange(final TextEdit edit, final ASTNode node, final GroovyCompilationUnit unit) { try { - TypeLookupResult before = inferNodeType(node, gcu); - TextEdit undo = gcu.applyTextEdit(edit.copy(), null); - TypeLookupResult after = inferNodeType(new ASTNodeFinder(new Region(node.getStart(), 0)).doVisit(gcu.getModuleNode()), gcu); - gcu.applyTextEdit(undo, null); + TypeLookupResult before = lookupNodeType(node, unit); + TextEdit undo = unit.applyTextEdit(edit.copy(), null); + TypeLookupResult after = lookupNodeType(new ASTNodeFinder(new Region(node.getStart(), 0)).doVisit(unit.getModuleNode()), unit); + unit.applyTextEdit(undo, null); if (!before.declaringType.equals(after.declaringType)) { + if (!before.declaringType.equals(before.scope.getThis())) { + ASTNode call = (node instanceof MethodCall ? node : before.scope.getEnclosingNode()); // TODO: refactor side-effect solution! + call.getNodeMetaData(IMPLICIT_RECEIVER, x -> before.declaringType.equals(before.scope.getDelegate()) ? "delegate" : "owner"); + } return true; } } catch (Exception e) { @@ -180,7 +194,7 @@ private static boolean isTypeChange(final GroovyCompilationUnit gcu, final TextE return false; } - private static TypeLookupResult inferNodeType(final ASTNode node, final GroovyCompilationUnit unit) { + private static TypeLookupResult lookupNodeType(/* */ final ASTNode node, final GroovyCompilationUnit unit) { TypeLookupResult[] result = new TypeLookupResult[1]; new TypeInferencingVisitorFactory().createVisitor(unit).visitCompilationUnit((n, r, x) -> { if (n == node) {