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.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.refactoring.listeners.RefactoringElementAdapter;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import kotlin.KotlinPackage;
import kotlin.jvm.functions.Function1;
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.Name;
import org.jetbrains.kotlin.psi.JetDeclaration;
import org.jetbrains.kotlin.psi.JetDeclarationContainer;
import org.jetbrains.kotlin.psi.JetNamedFunction;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
@@ -58,7 +62,7 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import java.util.*;
public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurationModule>
implements CommonJavaRunConfigurationParameters {
implements CommonJavaRunConfigurationParameters, RefactoringListenerProvider {
public String MAIN_CLASS_NAME;
public String VM_PARAMETERS;
@@ -218,6 +222,36 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
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 final JetRunConfiguration myConfiguration;
@@ -53,20 +53,6 @@ public class KotlinRunConfigurationProducer : RunConfigurationProducer<JetRunCon
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? {
if (location == null) return null
if (DumbService.getInstance(location.getProject()).isDumb()) return null
@@ -77,31 +63,7 @@ public class KotlinRunConfigurationProducer : RunConfigurationProducer<JetRunCon
val locationElement = location.getPsiElement()
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
}
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?
return getEntryPointContainer(locationElement)
}
private fun setupConfigurationByQName(module: Module,
@@ -118,4 +80,49 @@ public class KotlinRunConfigurationProducer : RunConfigurationProducer<JetRunCon
return configuration.getRunClass() == startClassFQName.asString() &&
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.Location
import com.intellij.execution.PsiLocation
import com.intellij.execution.RunManagerEx
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.configurations.JavaCommandLine
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.psi.PsiComment
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.refactoring.RefactoringFactory
import com.intellij.testFramework.MapDataContext
import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.PsiTestUtil
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.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinJsRuntimeAndSdk
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinRuntimeAndSdk
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
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.JetTreeVisitorVoid
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
@@ -98,6 +103,19 @@ class RunConfigurationTest: CodeInsightTestCase() {
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) {
val baseDir = getTestProject().getBaseDir()!!
val createModuleResult = configureModule(moduleDirPath("module"), baseDir)
@@ -140,10 +158,24 @@ class RunConfigurationTest: CodeInsightTestCase() {
private fun createConfigurationFromMain(mainFqn: String): JetRunConfiguration {
val mainFunction = JetTopLevelFunctionFqnNameIndex.getInstance().get(mainFqn, getTestProject(), getTestProject().allScope()).first()
val dataContext = MapDataContext()
dataContext.put(Location.DATA_KEY, PsiLocation(getTestProject(), mainFunction))
return createConfigurationFromElement(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 {