From d7b882e87e90ba87eaa0c2231affcd43a9be33b0 Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Wed, 17 Feb 2016 12:23:02 +0300 Subject: [PATCH] Fix launch configuration for main in companion and top-level objects #KT-10233 Fixed --- .../kotlin/core/utils/JetMainDetector.java | 76 ---------------- .../kotlin/core/utils/ProjectUtils.java | 16 ---- .../core/tests/launch/KotlinLaunchTest.java | 22 +++-- .../core/tests/launch/KotlinLaunchTestCase.kt | 4 +- .../kotlin/ui/launch/KotlinLaunchShortcut.kt | 70 +++++++++------ .../ui/launch/KotlinLaunchableTester.kt | 88 ++++++++++++++++++- .../ui/launch/KotlinMainLauncherTab.java | 17 ++-- 7 files changed, 153 insertions(+), 140 deletions(-) delete mode 100644 kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/JetMainDetector.java diff --git a/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/JetMainDetector.java b/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/JetMainDetector.java deleted file mode 100644 index 9253b2d14..000000000 --- a/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/JetMainDetector.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright 2000-2014 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.core.utils; - -import java.util.Collection; -import java.util.List; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.psi.KtDeclaration; -import org.jetbrains.kotlin.psi.KtFile; -import org.jetbrains.kotlin.psi.KtNamedFunction; -import org.jetbrains.kotlin.psi.KtParameter; -import org.jetbrains.kotlin.psi.KtTypeReference; - -public class JetMainDetector { - private JetMainDetector() { - } - - public static boolean hasMain(@NotNull List declarations) { - return findMainFunction(declarations) != null; - } - - public static boolean isMain(@NotNull KtNamedFunction function) { - if ("main".equals(function.getName())) { - List parameters = function.getValueParameters(); - if (parameters.size() == 1) { - KtTypeReference reference = parameters.get(0).getTypeReference(); - if (reference != null && reference.getText().equals("Array")) { // TODO correct check - return true; - } - } - } - - return false; - } - - @Nullable - public static KtNamedFunction getMainFunction(@NotNull Collection files) { - for (KtFile file : files) { - KtNamedFunction mainFunction = findMainFunction(file.getDeclarations()); - if (mainFunction != null) { - return mainFunction; - } - } - - return null; - } - - @Nullable - private static KtNamedFunction findMainFunction(@NotNull List declarations) { - for (KtDeclaration declaration : declarations) { - if (declaration instanceof KtNamedFunction) { - KtNamedFunction candidateFunction = (KtNamedFunction) declaration; - if (isMain(candidateFunction)) { - return candidateFunction; - } - } - } - return null; - } -} \ No newline at end of file diff --git a/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/ProjectUtils.java b/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/ProjectUtils.java index 16bed25aa..aeefa934e 100644 --- a/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/ProjectUtils.java +++ b/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/utils/ProjectUtils.java @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; @@ -66,17 +65,6 @@ public class ProjectUtils { public static final String KT_HOME = getKtHome(); - public static IFile findFilesWithMain(Collection files) { - for (IFile file : files) { - KtFile jetFile = KotlinPsiManager.INSTANCE.getParsedFile(file); - if (JetMainDetector.hasMain(jetFile.getDeclarations())) { - return file; - } - } - - return null; - } - public static IJavaProject getJavaProjectFromCollection(Collection files) { IJavaProject javaProject = null; for (IFile file : files) { @@ -87,10 +75,6 @@ public static IJavaProject getJavaProjectFromCollection(Collection files) return javaProject; } - public static boolean hasMain(IFile file) { - return findFilesWithMain(Arrays.asList(file)) != null; - } - @Nullable public static String getPackageByFile(IFile file) { KtFile jetFile = KotlinPsiManager.INSTANCE.getParsedFile(file); diff --git a/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTest.java b/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTest.java index c9154a802..1996b6198 100644 --- a/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTest.java +++ b/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTest.java @@ -20,30 +20,40 @@ public class KotlinLaunchTest extends KotlinLaunchTestCase { - private static final String SOURCE_CODE = "fun main(args : Array) { print(\"ok\") }"; + private static final String MAIN_FUNCTION = "fun main(args : Array) { print(\"ok\") }"; @Test public void launchSimpleProject() { - doTest(SOURCE_CODE, "test_project", "org.jet.pckg", null); + doTest(MAIN_FUNCTION, "test_project", "org.jet.pckg", null); } @Test public void launchWhenProjectNameHaveSpace() { - doTest(SOURCE_CODE, "test project", "pckg", null); + doTest(MAIN_FUNCTION, "test project", "pckg", null); } @Test public void launchWithTwoSourceFolders() { - doTest(SOURCE_CODE, "testProject", "pckg", "src2"); + doTest(MAIN_FUNCTION, "testProject", "pckg", "src2"); } @Test public void launchWhenSourceFolderHaveSpace() { - doTest(SOURCE_CODE, "testProject", "pckg", "src directory"); + doTest(MAIN_FUNCTION, "testProject", "pckg", "src directory"); } @Test public void launchFileWithJvmNameAnnotation() { - doTest("@file:JvmName(\"some\") " + SOURCE_CODE, "testProject", "some.pckg", null); + doTest("@file:JvmName(\"some\") " + MAIN_FUNCTION, "testProject", "some.pckg", null); + } + + @Test + public void launchMainFromObject() { + doTest("object SomeObj { @JvmStatic " + MAIN_FUNCTION + " }", "testProject", "pckg", null); + } + + @Test + public void launchMainFromCompanionObject() { + doTest("class SomeClass { companion object { @JvmStatic " + MAIN_FUNCTION + " } }", "testProject", "pckg", null); } } diff --git a/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTestCase.kt b/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTestCase.kt index 04b1c85dd..4728a3c7b 100644 --- a/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTestCase.kt +++ b/kotlin-eclipse-ui-test/src/org/jetbrains/kotlin/core/tests/launch/KotlinLaunchTestCase.kt @@ -31,6 +31,7 @@ import org.eclipse.ui.console.IConsoleManager import org.jetbrains.kotlin.core.log.KotlinLogger import org.jetbrains.kotlin.testframework.editor.KotlinEditorTestCase import org.jetbrains.kotlin.ui.launch.KotlinLaunchShortcut +import org.jetbrains.kotlin.ui.launch.getEntryPoint import org.junit.Assert import org.eclipse.core.runtime.NullProgressMonitor import org.jetbrains.kotlin.testframework.utils.KotlinTestUtils @@ -74,7 +75,8 @@ abstract class KotlinLaunchTestCase : KotlinEditorTestCase() { var launch: ILaunch? = null try { - val launchConfiguration = KotlinLaunchShortcut.createConfiguration(testEditor.getEditingFile()) + val entryPoint = getEntryPoint(getEditor().parsedFile!!, getEditor().javaProject!!) + val launchConfiguration = KotlinLaunchShortcut.createConfiguration(entryPoint!!, testEditor.getEclipseProject()) launch = DebugUIPlugin.buildAndLaunch(launchConfiguration, "run", NullProgressMonitor()) synchronized (launch) { diff --git a/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchShortcut.kt b/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchShortcut.kt index 7a855b2c3..b7578bcfe 100644 --- a/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchShortcut.kt +++ b/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchShortcut.kt @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright 2000-2014 JetBrains s.r.o. +* Copyright 2000-2016 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. @@ -43,13 +43,18 @@ import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.ui.editors.KotlinFileEditor +import org.eclipse.jdt.core.JavaCore +import org.jetbrains.kotlin.psi.KtDeclaration class KotlinLaunchShortcut : ILaunchShortcut { companion object { - fun createConfiguration(file: IFile): ILaunchConfiguration { - val configWC = getLaunchConfigurationType().newInstance(null, "Config - " + file.getName()) - configWC.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, getFileClassName(file).asString()) - configWC.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, file.getProject().getName()) + fun createConfiguration(entryPoint: KtDeclaration, project: IProject): ILaunchConfiguration? { + val classFqName = getStartClassFqName(entryPoint) + if (classFqName == null) return null + + val configWC = getLaunchConfigurationType().newInstance(null, "Config - " + entryPoint.getContainingKtFile().getName()) + configWC.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, classFqName.asString()) + configWC.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, project.getName()) return configWC.doSave() } @@ -75,50 +80,59 @@ class KotlinLaunchShortcut : ILaunchShortcut { } } - val fileWithMain = ProjectUtils.findFilesWithMain(files) - if (fileWithMain != null) { - launchWithMainClass(fileWithMain, mode) - return - } + if (files.isEmpty()) return - launchProject(files[0].getProject(), mode) + val mainFile = files.first() + val javaProject = JavaCore.create(mainFile.getProject()) + val ktFile = KotlinPsiManager.INSTANCE.getParsedFile(mainFile) + val entryPoint = getEntryPoint(ktFile, javaProject) + if (entryPoint != null) { + launchWithMainClass(entryPoint, javaProject.project, mode) + } } override fun launch(editor: IEditorPart, mode: String) { if (editor !is KotlinFileEditor) return - val file = EditorUtil.getFile(editor) + val file = editor.getFile() if (file == null) { KotlinLogger.logError("Failed to retrieve IFile from editor " + editor, null) return } - if (ProjectUtils.hasMain(file)) { - launchWithMainClass(file, mode) + val parsedFile = editor.parsedFile + if (parsedFile == null) return + + val javaProject = editor.javaProject + if (javaProject == null) return + + val entryPoint = getEntryPoint(parsedFile, javaProject) + if (entryPoint != null) { + launchWithMainClass(entryPoint, javaProject.project, mode) return } - - launchProject(file.getProject(), mode) } - private fun launchProject(project: IProject, mode: String) { - val fileWithMain = ProjectUtils.findFilesWithMain(KotlinPsiManager.INSTANCE.getFilesByProject(project)) - if (fileWithMain != null) { - launchWithMainClass(fileWithMain, mode) + private fun launchWithMainClass(entryPoint: KtDeclaration, project: IProject, mode: String) { + val configuration = findLaunchConfiguration(getLaunchConfigurationType(), entryPoint, project) ?: + createConfiguration(entryPoint, project) + + if (configuration != null) { + DebugUITools.launch(configuration, mode) } } - private fun launchWithMainClass(fileWithMain: IFile, mode: String) { - val configuration = findLaunchConfiguration(getLaunchConfigurationType(), fileWithMain) ?: createConfiguration(fileWithMain) - DebugUITools.launch(configuration, mode) - } - - private fun findLaunchConfiguration(configurationType: ILaunchConfigurationType, mainClass: IFile): ILaunchConfiguration? { + private fun findLaunchConfiguration( + configurationType: ILaunchConfigurationType, + entryPoint: KtDeclaration, + project: IProject): ILaunchConfiguration? { val configs = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations(configurationType) - val mainClassName = getFileClassName(mainClass).asString() + val mainClassName = getStartClassFqName(entryPoint)?.asString() + if (mainClassName == null) return null + for (config in configs) { if (config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, null as String?) == mainClassName && - config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, null as String?) == mainClass.project.name) { + config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, null as String?) == project.name) { return config } } diff --git a/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchableTester.kt b/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchableTester.kt index 2c0b8b190..7953685d4 100644 --- a/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchableTester.kt +++ b/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinLaunchableTester.kt @@ -1,3 +1,19 @@ +/******************************************************************************* +* Copyright 2000-2016 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.ui.launch import org.eclipse.core.expressions.PropertyTester @@ -10,6 +26,17 @@ import org.jetbrains.kotlin.core.model.KotlinAnalysisFileCache import org.jetbrains.kotlin.idea.MainFunctionDetector import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.BindingContext +import java.util.ArrayList +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.eclipse.jdt.internal.core.JavaProject +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtDeclarationContainer +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil +import org.jetbrains.kotlin.name.FqName class KotlinLaunchableTester : PropertyTester() { override fun test(receiver: Any?, property: String?, args: Array?, expectedValue: Any?): Boolean { @@ -18,11 +45,64 @@ class KotlinLaunchableTester : PropertyTester() { val file = receiver.getAdapter(IFile::class.java) if (file == null) return false - val jetFile = KotlinPsiManager.getKotlinParsedFile(file) - if (jetFile == null) return false + val ktFile = KotlinPsiManager.getKotlinParsedFile(file) + if (ktFile == null) return false val javaProject = JavaCore.create(file.getProject()) - val bindingContext = KotlinAnalysisFileCache.getAnalysisResult(jetFile, javaProject).analysisResult.bindingContext - return MainFunctionDetector(bindingContext).hasMain(jetFile.getDeclarations()) + return checkFileHashMain(ktFile, javaProject) } +} + +fun checkFileHashMain(ktFile: KtFile, javaProject: IJavaProject): Boolean { + return getEntryPoint(ktFile, javaProject) != null +} + +fun getStartClassFqName(mainFunctionDeclaration: KtDeclaration): FqName? { + val container = mainFunctionDeclaration.declarationContainer() + return when (container) { + is KtFile -> JvmFileClassUtil.getFileClassInfoNoResolve(container).facadeClassFqName + + is KtClassOrObject -> { + if (container is KtObjectDeclaration && container.isCompanion()) { + val containerClass = PsiTreeUtil.getParentOfType(container, KtClass::class.java) + containerClass?.fqName + } else { + container.fqName + } + } + + else -> null + } +} + +fun getEntryPoint(ktFile: KtFile, javaProject: IJavaProject): KtDeclaration? { + val bindingContext = KotlinAnalysisFileCache.getAnalysisResult(ktFile, javaProject).analysisResult.bindingContext + val mainFunctionDetector = MainFunctionDetector(bindingContext) + + val topLevelDeclarations = ktFile.getDeclarations() + for (declaration in topLevelDeclarations) { + val mainFunction = when (declaration) { + is KtNamedFunction -> if (mainFunctionDetector.isMain(declaration)) declaration else null + + is KtClass -> { + mainFunctionDetector.findMainFunction(declaration.getCompanionObjects().flatMap { it.declarations }) + } + + is KtObjectDeclaration -> mainFunctionDetector.findMainFunction(declaration.declarations) + + else -> null + } + + if (mainFunction != null) return mainFunction + } + + return null +} + +private fun KtDeclaration.declarationContainer(): KtDeclarationContainer? { + return PsiTreeUtil.getParentOfType(this, KtClassOrObject::class.java, KtFile::class.java) as KtDeclarationContainer? +} + +private fun MainFunctionDetector.findMainFunction(declarations: List): KtNamedFunction? { + return declarations.filterIsInstance(KtNamedFunction::class.java).find { isMain(it) } } \ No newline at end of file diff --git a/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinMainLauncherTab.java b/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinMainLauncherTab.java index 0db8c6042..095502a07 100644 --- a/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinMainLauncherTab.java +++ b/kotlin-eclipse-ui/src/org/jetbrains/kotlin/ui/launch/KotlinMainLauncherTab.java @@ -28,7 +28,6 @@ import org.eclipse.jface.window.Window; import org.eclipse.ui.dialogs.ListDialog; import org.jetbrains.kotlin.core.builder.KotlinPsiManager; -import org.jetbrains.kotlin.core.utils.ProjectUtils; public class KotlinMainLauncherTab extends JavaMainTab implements ILaunchConfigurationTab { @@ -45,11 +44,11 @@ public void handleSearchButtonSelected() { projectFiles = KotlinPsiManager.INSTANCE.getFilesByProject(fProjText.getText()); List mainFiles = new ArrayList(); - for (IFile file : projectFiles) { - if (ProjectUtils.hasMain(file)) { - mainFiles.add(file); - } - } +// for (IFile file : projectFiles) { +// if (ProjectUtils.hasMain(file)) { +// mainFiles.add(file); +// } +// } dialog.setInput(mainFiles); @@ -62,8 +61,8 @@ public void handleSearchButtonSelected() { return; } - if (results[0] instanceof IFile) { - fMainText.setText(KotlinLaunchShortcut.getFileClassName((IFile) results[0]).asString()); - } +// if (results[0] instanceof IFile) { +// fMainText.setText(KotlinLaunchShortcut.getFileClassName((IFile) results[0]).asString()); +// } } } \ No newline at end of file