updating run configuration on class rename (KT-6731)
This commit is contained in:
committed by
Nikolay Krasko
parent
4fbd982059
commit
247ffdccbe
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user