updating run configuration on class rename (KT-6731)

This commit is contained in:
Dmitry Jemerov
2015-07-02 19:17:24 +02:00
committed by Nikolay Krasko
parent 4fbd982059
commit 247ffdccbe
4 changed files with 124 additions and 43 deletions
@@ -36,7 +36,10 @@ import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethod;
import com.intellij.refactoring.listeners.RefactoringElementAdapter;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import kotlin.KotlinPackage; import kotlin.KotlinPackage;
import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function1;
import org.jdom.Element; import org.jdom.Element;
@@ -51,6 +54,7 @@ import org.jetbrains.kotlin.idea.stubindex.JetTopLevelFunctionFqnNameIndex;
import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.JetDeclaration; import org.jetbrains.kotlin.psi.JetDeclaration;
import org.jetbrains.kotlin.psi.JetDeclarationContainer;
import org.jetbrains.kotlin.psi.JetNamedFunction; import org.jetbrains.kotlin.psi.JetNamedFunction;
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode; import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
@@ -58,7 +62,7 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import java.util.*; import java.util.*;
public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurationModule> public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurationModule>
implements CommonJavaRunConfigurationParameters { implements CommonJavaRunConfigurationParameters, RefactoringListenerProvider {
public String MAIN_CLASS_NAME; public String MAIN_CLASS_NAME;
public String VM_PARAMETERS; public String VM_PARAMETERS;
@@ -218,6 +222,36 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
return state; return state;
} }
@Nullable
@Override
public RefactoringElementListener getRefactoringElementListener(PsiElement element) {
if (element instanceof JetDeclarationContainer) {
FqName name = KotlinRunConfigurationProducer.Companion.getStartClassFqName((JetDeclarationContainer) element);
if (name != null && name.asString().equals(MAIN_CLASS_NAME)) {
return new RefactoringElementAdapter() {
@Override
public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) {
updateMainClassName(newElement);
}
@Override
protected void elementRenamedOrMoved(@NotNull PsiElement newElement) {
updateMainClassName(newElement);
}
};
}
}
return null;
}
private void updateMainClassName(PsiElement element) {
JetDeclarationContainer container = KotlinRunConfigurationProducer.Companion.getEntryPointContainer(element);
FqName name = KotlinRunConfigurationProducer.Companion.getStartClassFqName(container);
if (name != null) {
MAIN_CLASS_NAME = name.asString();
}
}
private static class MyJavaCommandLineState extends JavaCommandLineState { private static class MyJavaCommandLineState extends JavaCommandLineState {
private final JetRunConfiguration myConfiguration; private final JetRunConfiguration myConfiguration;
@@ -53,20 +53,6 @@ public class KotlinRunConfigurationProducer : RunConfigurationProducer<JetRunCon
return true return true
} }
private fun getStartClassFqName(container: JetDeclarationContainer?): FqName? = when(container) {
null -> null
is JetFile -> PackageClassUtils.getPackageClassFqName(container.getPackageFqName())
is JetClassOrObject -> {
if (container is JetObjectDeclaration && container.isCompanion()) {
val containerClass = container.getParentOfType<JetClass>(true)
containerClass?.getFqName()
} else {
container.getFqName()
}
}
else -> throw IllegalArgumentException("Invalid entry-point container: " + (container as PsiElement).getText())
}
private fun getEntryPointContainer(location: Location<*>?): JetDeclarationContainer? { private fun getEntryPointContainer(location: Location<*>?): JetDeclarationContainer? {
if (location == null) return null if (location == null) return null
if (DumbService.getInstance(location.getProject()).isDumb()) return null if (DumbService.getInstance(location.getProject()).isDumb()) return null
@@ -77,31 +63,7 @@ public class KotlinRunConfigurationProducer : RunConfigurationProducer<JetRunCon
val locationElement = location.getPsiElement() val locationElement = location.getPsiElement()
val psiFile = locationElement.getContainingFile() return getEntryPointContainer(locationElement)
if (!(psiFile is JetFile && ProjectRootsUtil.isInProjectOrLibSource(psiFile))) return null
val resolutionFacade = psiFile.getResolutionFacade()
val mainFunctionDetector = MainFunctionDetector { resolutionFacade.resolveToDescriptor(it) as FunctionDescriptor }
var currentElement = locationElement.declarationContainer(false)
while (currentElement != null) {
var entryPointContainer = currentElement
if (entryPointContainer is JetClass) {
entryPointContainer = entryPointContainer.getCompanionObjects().singleOrNull()
}
if (entryPointContainer != null && mainFunctionDetector.hasMain(entryPointContainer.getDeclarations())) return entryPointContainer
currentElement = (currentElement as PsiElement).declarationContainer(true)
}
return null
}
private fun PsiElement.declarationContainer(strict: Boolean): JetDeclarationContainer? {
val element = if (strict)
PsiTreeUtil.getParentOfType(this, javaClass<JetClassOrObject>(), javaClass<JetFile>())
else
PsiTreeUtil.getNonStrictParentOfType(this, javaClass<JetClassOrObject>(), javaClass<JetFile>())
return element as JetDeclarationContainer?
} }
private fun setupConfigurationByQName(module: Module, private fun setupConfigurationByQName(module: Module,
@@ -118,4 +80,49 @@ public class KotlinRunConfigurationProducer : RunConfigurationProducer<JetRunCon
return configuration.getRunClass() == startClassFQName.asString() && return configuration.getRunClass() == startClassFQName.asString() &&
context.getModule() == configuration.getConfigurationModule().getModule() context.getModule() == configuration.getConfigurationModule().getModule()
} }
companion object {
public fun getEntryPointContainer(locationElement: PsiElement): JetDeclarationContainer? {
val psiFile = locationElement.getContainingFile()
if (!(psiFile is JetFile && ProjectRootsUtil.isInProjectOrLibSource(psiFile))) return null
val resolutionFacade = psiFile.getResolutionFacade()
val mainFunctionDetector = MainFunctionDetector { resolutionFacade.resolveToDescriptor(it) as FunctionDescriptor }
var currentElement = locationElement.declarationContainer(false)
while (currentElement != null) {
var entryPointContainer = currentElement
if (entryPointContainer is JetClass) {
entryPointContainer = entryPointContainer.getCompanionObjects().singleOrNull()
}
if (entryPointContainer != null && mainFunctionDetector.hasMain(entryPointContainer.getDeclarations())) return entryPointContainer
currentElement = (currentElement as PsiElement).declarationContainer(true)
}
return null
}
public fun getStartClassFqName(container: JetDeclarationContainer?): FqName? = when(container) {
null -> null
is JetFile -> PackageClassUtils.getPackageClassFqName(container.getPackageFqName())
is JetClassOrObject -> {
if (container is JetObjectDeclaration && container.isCompanion()) {
val containerClass = container.getParentOfType<JetClass>(true)
containerClass?.getFqName()
} else {
container.getFqName()
}
}
else -> throw IllegalArgumentException("Invalid entry-point container: " + (container as PsiElement).getText())
}
private fun PsiElement.declarationContainer(strict: Boolean): JetDeclarationContainer? {
val element = if (strict)
PsiTreeUtil.getParentOfType(this, javaClass<JetClassOrObject>(), javaClass<JetFile>())
else
PsiTreeUtil.getNonStrictParentOfType(this, javaClass<JetClassOrObject>(), javaClass<JetFile>())
return element as JetDeclarationContainer?
}
}
} }
@@ -0,0 +1,8 @@
package renameTest
import kotlin.platform.platformStatic
object Foo {
platformStatic fun main(args: Array<String>) {
}
}
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.CodeInsightTestCase
import com.intellij.execution.Executor import com.intellij.execution.Executor
import com.intellij.execution.Location import com.intellij.execution.Location
import com.intellij.execution.PsiLocation import com.intellij.execution.PsiLocation
import com.intellij.execution.RunManagerEx
import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.configurations.JavaCommandLine import com.intellij.execution.configurations.JavaCommandLine
import com.intellij.execution.configurations.JavaParameters import com.intellij.execution.configurations.JavaParameters
@@ -34,17 +35,21 @@ import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiComment import com.intellij.psi.PsiComment
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.refactoring.RefactoringFactory
import com.intellij.testFramework.MapDataContext import com.intellij.testFramework.MapDataContext
import com.intellij.testFramework.PlatformTestCase import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.PsiTestUtil import com.intellij.testFramework.PsiTestUtil
import org.jetbrains.kotlin.idea.search.allScope import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.stubindex.JetFullClassNameIndex
import org.jetbrains.kotlin.idea.stubindex.JetTopLevelFunctionFqnNameIndex import org.jetbrains.kotlin.idea.stubindex.JetTopLevelFunctionFqnNameIndex
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinJsRuntimeAndSdk import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinJsRuntimeAndSdk
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinRuntimeAndSdk import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinRuntimeAndSdk
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetFunction
import org.jetbrains.kotlin.psi.JetNamedDeclaration import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.JetTreeVisitorVoid import org.jetbrains.kotlin.psi.JetTreeVisitorVoid
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
@@ -98,6 +103,19 @@ class RunConfigurationTest: CodeInsightTestCase() {
doTest(ConfigLibraryUtil::configureKotlinJsRuntimeAndSdk) doTest(ConfigLibraryUtil::configureKotlinJsRuntimeAndSdk)
} }
fun testUpdateOnClassRename() {
val createModuleResult = configureModule(moduleDirPath("module"), getTestProject().getBaseDir()!!)
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(createModuleResult.module, PluginTestCaseBase.mockJdk())
val runConfiguration = createConfigurationFromObject("renameTest.Foo", save = true)
val obj = JetFullClassNameIndex.getInstance().get("renameTest.Foo", getTestProject(), getTestProject().allScope()).single()
val rename = RefactoringFactory.getInstance(getTestProject()).createRename(obj, "Bar")
rename.run()
Assert.assertEquals("renameTest.Bar", runConfiguration.MAIN_CLASS_NAME)
}
private fun doTest(configureRuntime: (Module, Sdk) -> Unit) { private fun doTest(configureRuntime: (Module, Sdk) -> Unit) {
val baseDir = getTestProject().getBaseDir()!! val baseDir = getTestProject().getBaseDir()!!
val createModuleResult = configureModule(moduleDirPath("module"), baseDir) val createModuleResult = configureModule(moduleDirPath("module"), baseDir)
@@ -140,10 +158,24 @@ class RunConfigurationTest: CodeInsightTestCase() {
private fun createConfigurationFromMain(mainFqn: String): JetRunConfiguration { private fun createConfigurationFromMain(mainFqn: String): JetRunConfiguration {
val mainFunction = JetTopLevelFunctionFqnNameIndex.getInstance().get(mainFqn, getTestProject(), getTestProject().allScope()).first() val mainFunction = JetTopLevelFunctionFqnNameIndex.getInstance().get(mainFqn, getTestProject(), getTestProject().allScope()).first()
val dataContext = MapDataContext() return createConfigurationFromElement(mainFunction)
dataContext.put(Location.DATA_KEY, PsiLocation(getTestProject(), mainFunction)) }
return ConfigurationContext.getFromContext(dataContext)!!.getConfiguration()!!.getConfiguration() as JetRunConfiguration private fun createConfigurationFromObject(objectFqn: String, save: Boolean = false): JetRunConfiguration {
val obj = JetFullClassNameIndex.getInstance().get(objectFqn, getTestProject(), getTestProject().allScope()).single()
val mainFunction = obj.getDeclarations().single { it is JetFunction && it.getName() == "main" }
return createConfigurationFromElement(mainFunction, save)
}
private fun createConfigurationFromElement(element: PsiElement?, save: Boolean = false): JetRunConfiguration {
val dataContext = MapDataContext()
dataContext.put(Location.DATA_KEY, PsiLocation(getTestProject(), element))
val runnerAndConfigurationSettings = ConfigurationContext.getFromContext(dataContext)!!.getConfiguration()
if (save) {
RunManagerEx.getInstanceEx(myProject).setTemporaryConfiguration(runnerAndConfigurationSettings)
}
return runnerAndConfigurationSettings!!.getConfiguration() as JetRunConfiguration
} }
private fun configureModule(moduleDir: String, outputParentDir: VirtualFile, configModule: Module = getModule()): CreateModuleResult { private fun configureModule(moduleDir: String, outputParentDir: VirtualFile, configModule: Module = getModule()): CreateModuleResult {