Compare commits

...

1 Commits

Author SHA1 Message Date
Evgeny Gerashchenko
3d84f225ab Simplest inline function handler. 2015-07-08 17:55:00 +03:00
15 changed files with 304 additions and 70 deletions

View File

@@ -288,6 +288,7 @@
<refactoring.introduceParameterMethodUsagesProcessor
implementation="org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterMethodUsageProcessor"/>
<inlineActionHandler implementation="org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlineValHandler"/>
<inlineActionHandler implementation="org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlineFunctionHandler"/>
<treeStructureProvider implementation="org.jetbrains.kotlin.idea.projectView.JetProjectViewProvider"/>
<colorSettingsPage implementation="org.jetbrains.kotlin.idea.highlighter.KotlinColorSettingsPage"/>

View File

@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
@@ -58,8 +59,6 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
private val text: String
) : DeprecatedSymbolUsageFixBase(element, replaceWith) {
private val LOG = Logger.getInstance(javaClass<DeprecatedSymbolUsageInWholeProjectFix>());
override fun getFamilyName() = "Replace deprecated symbol usage in whole project"
override fun getText() = text
@@ -81,73 +80,12 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
) {
val psiElement = element.getReference()!!.resolve()!!
ProgressManager.getInstance().run(
object : Task.Modal(project, "Applying '$text'", true) {
override fun run(indicator: ProgressIndicator) {
val usages = runReadAction {
val searchScope = JetSourceFilterScope.kotlinSources(GlobalSearchScope.projectScope(project), project)
val findUsagesHandler = KotlinFindUsagesHandlerFactory(project).createFindUsagesHandler(psiElement, false)!!
val processor = CommonProcessors.CollectProcessor<UsageInfo>()
val options = createFindUsagesOptions(psiElement, searchScope, project)
findUsagesHandler.processElementUsages(psiElement, processor, options)
processor.getResults().map { it.getElement() }.filterIsInstance<JetSimpleNameExpression>()
}
replaceUsages(project, usages, replacement)
}
})
}
private fun createFindUsagesOptions(element: PsiElement, searchScope: GlobalSearchScope, project: Project): FindUsagesOptions {
val options: FindUsagesOptions = when (element) {
is JetNamedFunction -> {
with(KotlinFunctionFindUsagesOptions(project)) {
isSkipImportStatements = true
isOverridingMethods = false
isImplementingMethods = false
isIncludeInherited = false
isIncludeOverloadUsages = false
this
}
}
is JetProperty -> {
with(KotlinPropertyFindUsagesOptions(project)) {
isSkipImportStatements = true
this
}
}
else -> throw IllegalArgumentException(element.toString()) //TODO?
}
options.searchScope = searchScope
options.isSearchForTextOccurrences = false
return options
}
private fun replaceUsages(project: Project, usages: Collection<JetSimpleNameExpression>, replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression) {
UIUtil.invokeLaterIfNeeded {
project.executeCommand(getText()) {
runWriteAction {
for (usage in usages) {
try {
if (!usage.isValid()) continue // TODO: nested calls
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = usage.getResolvedCall(bindingContext) ?: continue
if (!resolvedCall.getStatus().isSuccess()) continue
// copy replacement expression because it is modified by performReplacement
DeprecatedSymbolUsageFixBase.performReplacement(usage, bindingContext, resolvedCall, replacement.copy())
}
catch (e: Throwable) {
LOG.error(e)
}
}
}
}
}
findAndReplaceUsages(project, psiElement as JetNamedDeclaration, replacement, text, false)
}
companion object : JetSingleIntentionActionFactory() {
private val LOG = Logger.getInstance(javaClass<DeprecatedSymbolUsageInWholeProjectFix>());
//TODO: better rendering needed
private val RENDERER = DescriptorRenderer.withOptions {
modifiers = emptySet()
@@ -168,5 +106,85 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
return DeprecatedSymbolUsageInWholeProjectFix(nameExpression, replacement, "Replace usages of '$descriptorName' in whole project")
}
// TODO extract the following code
public fun findAndReplaceUsages(
project: Project,
declaration: JetNamedDeclaration,
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
text: String,
deleteDeclaration: Boolean
) {
ProgressManager.getInstance().run(
object : Task.Modal(project, "Applying '$text'", true) {
override fun run(indicator: ProgressIndicator) {
val usages = runReadAction {
val searchScope = JetSourceFilterScope.kotlinSources(GlobalSearchScope.projectScope(project), project) // TODO maybe use scope instead of project scope?
val findUsagesHandler = KotlinFindUsagesHandlerFactory(project).createFindUsagesHandler(declaration, false)!!
val processor = CommonProcessors.CollectProcessor<UsageInfo>()
val options = createFindUsagesOptions(declaration, searchScope, project)
findUsagesHandler.processElementUsages(declaration, processor, options)
processor.getResults().map { it.getElement() }.filterIsInstance<JetSimpleNameExpression>()
}
replaceUsages(project, usages, replacement, text, if (deleteDeclaration) declaration else null)
}
})
}
private fun createFindUsagesOptions(element: PsiElement, searchScope: GlobalSearchScope, project: Project): FindUsagesOptions {
val options: FindUsagesOptions = when (element) {
is JetNamedFunction -> {
with(KotlinFunctionFindUsagesOptions(project)) {
isSkipImportStatements = true
isOverridingMethods = false
isImplementingMethods = false
isIncludeInherited = false
isIncludeOverloadUsages = false
this
}
}
is JetProperty -> {
with(KotlinPropertyFindUsagesOptions(project)) {
isSkipImportStatements = true
this
}
}
else -> throw IllegalArgumentException(element.toString()) //TODO?
}
options.searchScope = searchScope
options.isSearchForTextOccurrences = false
return options
}
private fun replaceUsages(
project: Project,
usages: Collection<JetSimpleNameExpression>,
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
commandText: String,
declarationToDelete: JetNamedDeclaration?
) {
UIUtil.invokeLaterIfNeeded {
project.executeCommand(commandText) {
runWriteAction {
for (usage in usages) {
try {
if (!usage.isValid()) continue // TODO: nested calls
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = usage.getResolvedCall(bindingContext) ?: continue
if (!resolvedCall.getStatus().isSuccess()) continue
// copy replacement expression because it is modified by performReplacement
DeprecatedSymbolUsageFixBase.performReplacement(usage, bindingContext, resolvedCall, replacement.copy())
}
catch (e: Throwable) {
LOG.error(e)
}
}
declarationToDelete?.delete()
}
}
}
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010-2015 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.kotlin.idea.refactoring.inline
import com.google.common.collect.Lists
import com.intellij.lang.Language
import com.intellij.lang.refactoring.InlineActionHandler
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.RefactoringBundle
import com.intellij.usageView.UsageInfo
import com.intellij.util.CommonProcessors
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
import org.jetbrains.kotlin.idea.quickfix.DeprecatedSymbolUsageInWholeProjectFix
import org.jetbrains.kotlin.idea.quickfix.ReplaceWith
import org.jetbrains.kotlin.idea.quickfix.ReplaceWithAnnotationAnalyzer
import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
public class KotlinInlineFunctionHandler: InlineActionHandler() {
override fun isEnabledForLanguage(language: Language) = JetLanguage.INSTANCE == language
override fun canInlineElement(element: PsiElement): Boolean {
return element is JetNamedFunction
&& element.hasBody() && !element.hasBlockBody() // TODO support multiline functions
&& element.getUseScope() is GlobalSearchScope // TODO support local functions
}
override fun inlineElement(project: Project, editor: Editor?, element: PsiElement) {
element as JetNamedFunction
val descriptor = element.resolveToDescriptor() as SimpleFunctionDescriptor
val replacement = ReplaceWithAnnotationAnalyzer.analyze(
ReplaceWith(element.getBodyExpression()!!.getText()),
descriptor,
element.getResolutionFacade(),
project
)
DeprecatedSymbolUsageInWholeProjectFix.findAndReplaceUsages(
project,
element,
replacement,
RefactoringBundle.message("inline.command", element.getName()),
true
)
}
}

View File

@@ -0,0 +1,11 @@
fun <caret>f(p: Int) = g()
fun g() {
}
fun complexFun(): Int {
}
fun main(args: Array<String>) {
f(complexFun())
}

View File

@@ -0,0 +1,10 @@
fun g() {
}
fun complexFun(): Int {
}
fun main(args: Array<String>) {
complexFun()
g()
}

View File

@@ -0,0 +1,8 @@
fun <caret>f(p: Int) = p + p
fun complexFun(): Int {
}
fun main(args: Array<String>) {
f(complexFun())
}

View File

@@ -0,0 +1,7 @@
fun complexFun(): Int {
}
fun main(args: Array<String>) {
val p = complexFun()
p + p
}

View File

@@ -0,0 +1,5 @@
fun <caret>f() = "foo"
fun main(args: Array<String>) {
println(f() + f())
}

View File

@@ -0,0 +1,3 @@
fun main(args: Array<String>) {
println("foo" + "foo")
}

View File

@@ -0,0 +1,10 @@
fun <caret>foo(p1: String, p2: () -> Boolean) = bar(p1, null, p2)
fun bar(p1: String, p2: String?, p3: () -> Boolean)
fun foo(i: I) {
foo("foo") {
println("bar")
println("baz")
}
}

View File

@@ -0,0 +1,8 @@
fun bar(p1: String, p2: String?, p3: () -> Boolean)
fun foo(i: I) {
bar("foo", null) {
println("bar")
println("baz")
}
}

View File

@@ -0,0 +1,5 @@
fun <caret>f(p1: Int, p2: Int) = p1 + p2
fun main(args: Array<String>) {
println(f(3, 5))
}

View File

@@ -0,0 +1,3 @@
fun main(args: Array<String>) {
println(3 + 5)
}

View File

@@ -16,13 +16,16 @@
package org.jetbrains.kotlin.idea.refactoring.inline;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.lang.refactoring.InlineActionHandler;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase;
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
@@ -35,6 +38,17 @@ import static com.intellij.codeInsight.TargetElementUtilBase.ELEMENT_NAME_ACCEPT
import static com.intellij.codeInsight.TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED;
public abstract class AbstractInlineTest extends JetLightCodeInsightFixtureTestCase {
@Nullable
private static InlineActionHandler findSuitableHandler(@NotNull PsiElement psiElement) {
InlineActionHandler[] inlineActionHandlers = Extensions.getExtensions(InlineActionHandler.EP_NAME);
for (InlineActionHandler handler : inlineActionHandlers) {
if (handler.canInlineElement(psiElement)) {
return handler;
}
}
return null;
}
protected void doTest(@NotNull String path) throws IOException {
File afterFile = new File(path + ".after");
@@ -43,11 +57,14 @@ public abstract class AbstractInlineTest extends JetLightCodeInsightFixtureTestC
boolean afterFileExists = afterFile.exists();
final PsiElement targetElement =
TargetElementUtilBase.findTargetElement(myFixture.getEditor(), ELEMENT_NAME_ACCEPTED | REFERENCED_ELEMENT_ACCEPTED);
final KotlinInlineValHandler handler = new KotlinInlineValHandler();
TargetElementUtil.findTargetElement(myFixture.getEditor(), ELEMENT_NAME_ACCEPTED | REFERENCED_ELEMENT_ACCEPTED);
assertNotNull(targetElement);
final InlineActionHandler handler = findSuitableHandler(targetElement);
List<String> expectedErrors = InTextDirectivesUtils.findLinesWithPrefixesRemoved(myFixture.getFile().getText(), "// ERROR: ");
if (handler.canInlineElement(targetElement)) {
if (handler != null) {
try {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override

View File

@@ -416,6 +416,54 @@ public class InlineTestGenerated extends AbstractInlineTest {
}
}
@TestMetadata("idea/testData/refactoring/inline/function")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Function extends AbstractInlineTest {
public void testAllFilesPresentInFunction() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/inline/function"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("idea/testData/refactoring/inline/function/expressionBody")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ExpressionBody extends AbstractInlineTest {
public void testAllFilesPresentInExpressionBody() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/inline/function/expressionBody"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ComplexArgumentNotUsed.kt")
public void testComplexArgumentNotUsed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/ComplexArgumentNotUsed.kt");
doTest(fileName);
}
@TestMetadata("ComplexArgumentUsedTwice.kt")
public void testComplexArgumentUsedTwice() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/ComplexArgumentUsedTwice.kt");
doTest(fileName);
}
@TestMetadata("Constant.kt")
public void testConstant() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/Constant.kt");
doTest(fileName);
}
@TestMetadata("FunctionalParameterPassed.kt")
public void testFunctionalParameterPassed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/FunctionalParameterPassed.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/Simple.kt");
doTest(fileName);
}
}
}
@TestMetadata("idea/testData/refactoring/inline/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)