From ff7576f8e4137733dd11c8acbdec5e703d5fd2e2 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Tue, 9 Jun 2020 13:20:35 +0300 Subject: [PATCH] 202: Fix compilation --- .../actions/ShowKotlinGradleDslLogs.kt.202 | 88 ++++ .../idea/test/PluginTestCaseBase.java.202 | 137 ++++++ .../jetbrains/kotlin/idea/test/compat.kt.202 | 20 + .../idea/testFramework/gradleRoutines.kt.202 | 83 ++++ .../idea/testFramework/projectRoutines.kt.202 | 120 +++++ .../scratch/ScratchLineMarkersTest.kt.202 | 75 +++ .../TrailingCommaInspection.kt.202 | 192 ++++++++ .../KotlinMutableMethodDescriptor.kt.202 | 48 ++ .../KotlinTargetElementEvaluator.kt.202 | 111 +++++ .../idea/slicer/KotlinSliceProvider.kt.202 | 127 ++++++ .../AbstractLineMarkersTest.kt.202 | 185 ++++++++ ...ractLineMarkersTestInLibrarySources.kt.202 | 91 ++++ .../AbstractUsageHighlightingTest.kt.202 | 64 +++ .../InspectionDescriptionTest.kt.202 | 149 ++++++ .../IntentionDescriptionTest.kt.202 | 65 +++ .../kotlin/idea/slicer/SlicerTestUtil.kt.202 | 136 ++++++ .../AbstractMultiHighlightingTest.kt.202 | 50 ++ .../jps/build/KotlinJpsBuildTest.kt.202 | 58 +-- .../KotlinUastCodeGenerationPlugin.kt.202 | 429 ++++++++++++++++++ 19 files changed, 2193 insertions(+), 35 deletions(-) create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt.202 create mode 100644 idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java.202 create mode 100644 idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/compat.kt.202 create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.202 create mode 100644 idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/projectRoutines.kt.202 create mode 100644 idea/scripting-support/test/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt.202 create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/TrailingCommaInspection.kt.202 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt.202 create mode 100644 idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt.202 create mode 100644 idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt.202 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt.202 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTestInLibrarySources.kt.202 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractUsageHighlightingTest.kt.202 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/inspections/InspectionDescriptionTest.kt.202 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionDescriptionTest.kt.202 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestUtil.kt.202 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt.202 create mode 100644 plugins/uast-kotlin-idea/src/org/jetbrains/uast/kotlin/generate/KotlinUastCodeGenerationPlugin.kt.202 diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt.202 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt.202 new file mode 100644 index 00000000000..1451b364eb8 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/actions/ShowKotlinGradleDslLogs.kt.202 @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.actions + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.ide.actions.RevealFileAction +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.MessageType +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.wm.WindowManager +import com.intellij.psi.PsiFile +import com.intellij.ui.BrowserHyperlinkListener +import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle +import java.io.File + +class ShowKotlinGradleDslLogs : IntentionAction, AnAction(), DumbAware { + override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { + openLogsDirIfPresent(project) + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + openLogsDirIfPresent(project) + } + + override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?) = RevealFileAction.isSupported() + + override fun update(e: AnActionEvent) { + val presentation = e.presentation + presentation.isEnabledAndVisible = e.project != null && RevealFileAction.isSupported() + presentation.text = NAME + } + + private fun openLogsDirIfPresent(project: Project) { + val logsDir = findLogsDir() + if (logsDir != null) { + RevealFileAction.openDirectory(logsDir) + } else { + val parent = WindowManager.getInstance().getStatusBar(project)?.component + ?: WindowManager.getInstance().findVisibleFrame()?.rootPane + JBPopupFactory.getInstance() + .createHtmlTextBalloonBuilder( + KotlinIdeaGradleBundle.message( + "text.gradle.dsl.logs.cannot.be.found.automatically.see.how.to.find.logs", + gradleTroubleshootingLink + ), + MessageType.ERROR, + BrowserHyperlinkListener.INSTANCE + ) + .setFadeoutTime(5000) + .createBalloon() + .showInCenterOf(parent) + } + } + + /** The way how to find Gradle logs is described here + * @see org.jetbrains.kotlin.idea.actions.ShowKotlinGradleDslLogs.gradleTroubleshootingLink + */ + private fun findLogsDir(): File? { + val userHome = System.getProperty("user.home") + return when { + SystemInfo.isMac -> File("$userHome/Library/Logs/gradle-kotlin-dsl") + SystemInfo.isLinux -> File("$userHome/.gradle-kotlin-dsl/logs") + SystemInfo.isWindows -> File("$userHome/AppData/Local/gradle-kotlin-dsl/log") + else -> null + }.takeIf { it?.exists() == true } + } + + override fun startInWriteAction() = false + + override fun getText() = NAME + + override fun getFamilyName() = NAME + + companion object { + private const val gradleTroubleshootingLink = "https://docs.gradle.org/current/userguide/kotlin_dsl.html#troubleshooting" + + val NAME = KotlinIdeaGradleBundle.message("action.text.show.kotlin.gradle.dsl.logs.in", RevealFileAction.getFileManagerName()) + } +} \ No newline at end of file diff --git a/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java.202 b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java.202 new file mode 100644 index 00000000000..16a8d2e0235 --- /dev/null +++ b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java.202 @@ -0,0 +1,137 @@ +/* + * 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.test; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.projectRoots.JavaSdk; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; +import com.intellij.testFramework.IdeaTestUtil; +import kotlin.jvm.functions.Function0; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; +import org.jetbrains.kotlin.idea.util.IjPlatformUtil; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestJdkKind; + +import java.io.File; + +public class PluginTestCaseBase { + public static final String TEST_DATA_DIR = "idea/testData"; + public static final String TEST_DATA_PROJECT_RELATIVE = "/" + TEST_DATA_DIR; + + private PluginTestCaseBase() { + } + + @NotNull + public static String getTestDataPathBase() { + return KotlinTestUtils.getHomeDirectory() + TEST_DATA_PROJECT_RELATIVE; + } + + @NotNull + @TestOnly + private static Sdk createMockJdk(@NotNull String name, String path) { + return IdeaTestUtil.createMockJdk(name, path, false); + } + + @NotNull + private static Sdk getSdk(String sdkHome, String name) { + ProjectJdkTable table = IjPlatformUtil.getProjectJdkTableSafe(); + Sdk existing = table.findJdk(name); + if (existing != null) { + return existing; + } + return JavaSdk.getInstance().createJdk(name, sdkHome, true); + } + + @NotNull + public static Sdk mockJdk() { + return getSdk("compiler/testData/mockJDK/jre", "Mock JDK"); + } + + @NotNull + public static Sdk mockJdk6() { + return getSdk("compiler/testData/mockJDK/jre", "1.6"); + } + + @NotNull + public static Sdk mockJdk8() { + // Using JDK 6, but with version 1.8 + return getSdk("compiler/testData/mockJDK/jre", "1.8"); + } + + @TestOnly + @NotNull + public static Sdk mockJdk9() { + return getSdk("compiler/testData/mockJDK9/jre", "9"); + } + + @NotNull + public static Sdk fullJdk() { + String javaHome = System.getProperty("java.home"); + assert new File(javaHome).isDirectory(); + return getSdk(javaHome, "Full JDK"); + } + + @NotNull + public static Sdk addJdk(@NotNull Disposable disposable, @NotNull Function0 getJdk) { + Sdk jdk = getJdk.invoke(); + Sdk[] allJdks = IjPlatformUtil.getProjectJdkTableSafe().getAllJdks(); + for (Sdk existingJdk : allJdks) { + if (existingJdk == jdk) { + return existingJdk; + } + } + ApplicationManager.getApplication().runWriteAction(() -> IjPlatformUtil.getProjectJdkTableSafe().addJdk(jdk, disposable)); + return jdk; + } + + @NotNull + public static Sdk jdk(@NotNull TestJdkKind kind) { + switch (kind) { + case MOCK_JDK: + return mockJdk(); + case FULL_JDK_9: + String jre9 = KotlinTestUtils.getJdk9Home().getPath(); + VfsRootAccess.allowRootAccess(jre9); + return getSdk(jre9, "Full JDK 9"); + case FULL_JDK: + return fullJdk(); + default: + throw new UnsupportedOperationException(kind.toString()); + } + } + + public static boolean isAllFilesPresentTest(@NotNull String testName) { + return StringUtil.startsWithIgnoreCase(testName, "allFilesPresentIn"); + } + + @TestOnly + public static void clearSdkTable(@NotNull Disposable disposable) { + Disposer.register(disposable, () -> ApplicationManager.getApplication().runWriteAction(() -> { + ProjectJdkTable jdkTable = IjPlatformUtil.getProjectJdkTableSafe(); + for (Sdk sdk : jdkTable.getAllJdks()) { + jdkTable.removeJdk(sdk); + } + })); + } +} diff --git a/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/compat.kt.202 b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/compat.kt.202 new file mode 100644 index 00000000000..2e18b3276e8 --- /dev/null +++ b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/compat.kt.202 @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.test + +import com.intellij.codeInsight.daemon.impl.EditorTracker +import com.intellij.ide.startup.impl.StartupManagerImpl +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.StartupManager + +// FIX ME WHEN BUNCH 192 REMOVED +fun editorTrackerProjectOpened(project: Project) { + EditorTracker.getInstance(project) +} + +// FIX ME WHEN BUNCH 193 REMOVED +fun runPostStartupActivitiesOnce(project: Project) { +} diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.202 b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.202 new file mode 100644 index 00000000000..da3836f7aff --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/gradleRoutines.kt.202 @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.testFramework + +import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder +import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode +import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.externalSystem.util.ExternalSystemUtil +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import org.gradle.util.GradleVersion +import org.jetbrains.plugins.gradle.service.project.open.setupGradleSettings +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.settings.GradleSettings +import org.jetbrains.plugins.gradle.util.GradleConstants +import org.jetbrains.plugins.gradle.util.GradleLog +import org.jetbrains.plugins.gradle.util.suggestGradleVersion +import java.io.File +import kotlin.test.assertNotNull + +fun refreshGradleProject(projectPath: String, project: Project) { + _importProject(File(projectPath).absolutePath, project) + + dispatchAllInvocationEvents() +} + +const val GRADLE_JDK_NAME = "Gradle JDK" + +/** + * inspired by org.jetbrains.plugins.gradle.service.project.open.importProject(projectDirectory, project) + */ +private fun _importProject(projectPath: String, project: Project) { + GradleLog.LOG.info("Import project at $projectPath") + val gradleProjectSettings = GradleProjectSettings() + val gradleVersion = suggestGradleVersion(project) ?: GradleVersion.current() + + GradleSettings.getInstance(project).gradleVmOptions = + "-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${System.getProperty("user.dir")}" + + setupGradleSettings(project, gradleProjectSettings, projectPath, gradleVersion) + gradleProjectSettings.gradleJvm = GRADLE_JDK_NAME + + GradleSettings.getInstance(project).getLinkedProjectSettings(projectPath)?.let { linkedProjectSettings -> + linkedProjectSettings.gradleJvm = GRADLE_JDK_NAME + } + + _attachGradleProjectAndRefresh(gradleProjectSettings, project) +} + +/** + * inspired by org.jetbrains.plugins.gradle.service.project.open.attachGradleProjectAndRefresh(gradleProjectSettings, project) + * except everything is MODAL_SYNC + */ +private fun _attachGradleProjectAndRefresh( + gradleProjectSettings: GradleProjectSettings, + project: Project +) { + val externalProjectPath = gradleProjectSettings.externalProjectPath + ExternalProjectsManagerImpl.getInstance(project).runWhenInitialized { + DumbService.getInstance(project).runWhenSmart { + ExternalSystemUtil.ensureToolWindowInitialized(project, GradleConstants.SYSTEM_ID) + } + } + val settings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) + if (settings.getLinkedProjectSettings(externalProjectPath) == null) { + settings.linkProject(gradleProjectSettings) + } + + StatefulTestGradleProjectRefreshCallback(externalProjectPath, project).use { callback -> + ExternalSystemUtil.refreshProject( + externalProjectPath, + ImportSpecBuilder(project, GradleConstants.SYSTEM_ID) + .use(ProgressExecutionMode.MODAL_SYNC) + .callback(callback) + .build() + ) + } +} diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/projectRoutines.kt.202 b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/projectRoutines.kt.202 new file mode 100644 index 00000000000..d509f736420 --- /dev/null +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/projectRoutines.kt.202 @@ -0,0 +1,120 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.testFramework + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl +import com.intellij.ide.impl.OpenProjectTask +import com.intellij.ide.startup.impl.StartupManagerImpl +import com.intellij.lang.LanguageAnnotators +import com.intellij.lang.LanguageExtensionPoint +import com.intellij.lang.annotation.Annotator +import com.intellij.openapi.Disposable +import com.intellij.openapi.editor.Document +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ex.ProjectManagerEx +import com.intellij.openapi.startup.StartupManager +import com.intellij.platform.PlatformProjectOpenProcessor +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.impl.PsiDocumentManagerBase +import com.intellij.testFramework.ExtensionTestUtil +import com.intellij.testFramework.runInEdtAndWait +import com.intellij.util.ui.UIUtil +import org.jetbrains.kotlin.idea.parameterInfo.HintType +import org.jetbrains.kotlin.idea.perf.util.logMessage +import org.jetbrains.kotlin.idea.test.runPostStartupActivitiesOnce +import java.nio.file.Paths + +fun commitAllDocuments() { + val fileDocumentManager = FileDocumentManager.getInstance() + runInEdtAndWait { + fileDocumentManager.saveAllDocuments() + } + + ProjectManagerEx.getInstanceEx().openProjects.forEach { project -> + val psiDocumentManagerBase = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase + + runInEdtAndWait { + psiDocumentManagerBase.clearUncommittedDocuments() + psiDocumentManagerBase.commitAllDocuments() + } + } +} + +fun commitDocument(project: Project, document: Document) { + val psiDocumentManagerBase = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase + runInEdtAndWait { + psiDocumentManagerBase.commitDocument(document) + } +} + +fun saveDocument(document: Document) { + val fileDocumentManager = FileDocumentManager.getInstance() + + runInEdtAndWait { + fileDocumentManager.saveDocument(document) + } +} + +fun enableHints(enable: Boolean) = + HintType.values().forEach { it.option.set(enable) } + +fun dispatchAllInvocationEvents() { + runInEdtAndWait { + UIUtil.dispatchAllInvocationEvents() + } +} + +fun loadProjectWithName(path: String, name: String): Project? = + PlatformProjectOpenProcessor.openExistingProject(Paths.get(path), Paths.get(path), OpenProjectTask(projectName = name)) + +fun TestApplicationManager.closeProject(project: Project) { + val name = project.name + val startupManagerImpl = StartupManager.getInstance(project) as StartupManagerImpl + val daemonCodeAnalyzerSettings = DaemonCodeAnalyzerSettings.getInstance() + val daemonCodeAnalyzerImpl = DaemonCodeAnalyzer.getInstance(project) as DaemonCodeAnalyzerImpl + + setDataProvider(null) + daemonCodeAnalyzerSettings.isImportHintEnabled = true // return default value to avoid unnecessary save + startupManagerImpl.checkCleared() + daemonCodeAnalyzerImpl.cleanupAfterTest() + + logMessage { "project '$name' is about to be closed" } + dispatchAllInvocationEvents() + val projectManagerEx = ProjectManagerEx.getInstanceEx() + projectManagerEx.forceCloseProjectEx(project, true) + + logMessage { "project '$name' successfully closed" } +} + +fun runStartupActivities(project: Project) { + // obsolete +} + +fun waitForAllEditorsFinallyLoaded(project: Project) { + // routing is obsolete in 192 +} + +fun replaceWithCustomHighlighter(parentDisposable: Disposable, fromImplementationClass: String, toImplementationClass: String) { + val pointName = ExtensionPointName.create>(LanguageAnnotators.EP_NAME.name) + val extensionPoint = pointName.getPoint(null) + + val point = LanguageExtensionPoint() + point.language = "kotlin" + point.implementationClass = toImplementationClass + + val extensions = extensionPoint.extensions + val filteredExtensions = + extensions.filter { it.language != "kotlin" || it.implementationClass != fromImplementationClass } + .toList() + // custom highlighter is already registered if filteredExtensions has the same size as extensions + if (filteredExtensions.size < extensions.size) { + ExtensionTestUtil.maskExtensions(pointName, filteredExtensions + listOf(point), parentDisposable) + } +} \ No newline at end of file diff --git a/idea/scripting-support/test/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt.202 b/idea/scripting-support/test/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt.202 new file mode 100644 index 00000000000..96dc27720a9 --- /dev/null +++ b/idea/scripting-support/test/org/jetbrains/kotlin/idea/scratch/ScratchLineMarkersTest.kt.202 @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scratch + +import com.intellij.codeInsight.daemon.LineMarkerInfo +import com.intellij.ide.scratch.ScratchFileService +import com.intellij.ide.scratch.ScratchRootType +import com.intellij.openapi.editor.Document +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiDocumentManager +import com.intellij.testFramework.ExpectedHighlightingData +import com.intellij.testFramework.FileEditorManagerTestCase +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.codeInsight.AbstractLineMarkersTest +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager +import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest.Companion.configureOptions +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.KtFile +import java.io.File + +abstract class AbstractScratchLineMarkersTest : FileEditorManagerTestCase() { + fun doScratchTest(path: String) { + val fileText = FileUtil.loadFile(File(path)) + + val scratchVirtualFile = ScratchRootType.getInstance().createScratchFile( + project, + "scratch.kts", + KotlinLanguage.INSTANCE, + fileText, + ScratchFileService.Option.create_if_missing + ) ?: error("Couldn't create scratch file") + + myFixture.openFileInEditor(scratchVirtualFile) + + ScriptConfigurationManager.updateScriptDependenciesSynchronously(myFixture.file) + + val scratchFileEditor = getScratchEditorForSelectedFile(FileEditorManager.getInstance(project), myFixture.file.virtualFile) + ?: error("Couldn't find scratch panel") + + configureOptions(scratchFileEditor, fileText, null) + + val project = myFixture.project + val document = myFixture.editor.document + + val data = ExpectedHighlightingData(document, false, false, false) + data.init() + + PsiDocumentManager.getInstance(project).commitAllDocuments() + + val markers = doAndCheckHighlighting(document, data, File(path)) + + AbstractLineMarkersTest.assertNavigationElements(myFixture.project, myFixture.file as KtFile, markers) + } + + override fun tearDown() { + super.tearDown() + + ScratchFileService.getInstance().scratchesMapping.mappings.forEach { file, _ -> + runWriteAction { file.delete(this) } + } + } + + private fun doAndCheckHighlighting( + documentToAnalyze: Document, expectedHighlighting: ExpectedHighlightingData, expectedFile: File + ): List> { + myFixture.doHighlighting() + + return AbstractLineMarkersTest.checkHighlighting(myFixture.file, documentToAnalyze, expectedHighlighting, expectedFile) + } + +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/TrailingCommaInspection.kt.202 b/idea/src/org/jetbrains/kotlin/idea/inspections/TrailingCommaInspection.kt.202 new file mode 100644 index 00000000000..4a5bbce11e2 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/TrailingCommaInspection.kt.202 @@ -0,0 +1,192 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.application.options.CodeStyle +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.codeStyle.CodeStyleManager +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.formatter.TrailingCommaVisitor +import org.jetbrains.kotlin.idea.formatter.kotlinCustomSettings +import org.jetbrains.kotlin.idea.formatter.trailingComma.* +import org.jetbrains.kotlin.idea.formatter.trailingCommaAllowedInModule +import org.jetbrains.kotlin.idea.util.isComma +import org.jetbrains.kotlin.idea.util.isLineBreak +import org.jetbrains.kotlin.idea.util.leafIgnoringWhitespaceAndComments +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.psiUtil.* +import javax.swing.JComponent +import kotlin.properties.Delegates + +class TrailingCommaInspection( + @JvmField + var addCommaWarning: Boolean = false +) : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = object : TrailingCommaVisitor() { + override val recursively: Boolean = false + private var useTrailingComma by Delegates.notNull() + + override fun process(trailingCommaContext: TrailingCommaContext) { + val element = trailingCommaContext.ktElement + val kotlinCustomSettings = CodeStyle.getSettings(element.project).kotlinCustomSettings + useTrailingComma = kotlinCustomSettings.addTrailingCommaIsAllowedFor(element) + when (trailingCommaContext.state) { + TrailingCommaState.MISSING, TrailingCommaState.EXISTS -> { + checkCommaPosition(element) + checkLineBreaks(element) + } + else -> Unit + } + + checkTrailingComma(trailingCommaContext) + } + + private fun checkLineBreaks(commaOwner: KtElement) { + val first = TrailingCommaHelper.elementBeforeFirstElement(commaOwner) + if (first?.nextLeaf(true)?.isLineBreak() == false) { + first.nextSibling?.let { + registerProblemForLineBreak(commaOwner, it, ProblemHighlightType.INFORMATION) + } + } + + val last = TrailingCommaHelper.elementAfterLastElement(commaOwner) + if (last?.prevLeaf(true)?.isLineBreak() == false) { + registerProblemForLineBreak( + commaOwner, + last, + if (addCommaWarning) ProblemHighlightType.GENERIC_ERROR_OR_WARNING else ProblemHighlightType.INFORMATION, + ) + } + } + + private fun checkCommaPosition(commaOwner: KtElement) { + for (invalidComma in TrailingCommaHelper.findInvalidCommas(commaOwner)) { + reportProblem( + invalidComma, + KotlinBundle.message("inspection.trailing.comma.comma.loses.the.advantages.in.this.position"), + KotlinBundle.message("inspection.trailing.comma.fix.comma.position") + ) + } + } + + private fun checkTrailingComma(trailingCommaContext: TrailingCommaContext) { + val commaOwner = trailingCommaContext.ktElement + val trailingCommaOrLastElement = TrailingCommaHelper.trailingCommaOrLastElement(commaOwner) ?: return + when (trailingCommaContext.state) { + TrailingCommaState.MISSING -> { + if (!trailingCommaAllowedInModule(commaOwner)) return + reportProblem( + trailingCommaOrLastElement, + KotlinBundle.message("inspection.trailing.comma.missing.trailing.comma"), + KotlinBundle.message("inspection.trailing.comma.add.trailing.comma"), + if (addCommaWarning) ProblemHighlightType.GENERIC_ERROR_OR_WARNING else ProblemHighlightType.INFORMATION, + ) + } + TrailingCommaState.REDUNDANT -> { + reportProblem( + trailingCommaOrLastElement, + KotlinBundle.message("inspection.trailing.comma.useless.trailing.comma"), + KotlinBundle.message("inspection.trailing.comma.remove.trailing.comma"), + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + checkTrailingCommaSettings = false, + ) + } + else -> Unit + } + } + + private fun reportProblem( + commaOrElement: PsiElement, + message: String, + fixMessage: String, + highlightType: ProblemHighlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + checkTrailingCommaSettings: Boolean = true, + ) { + val commaOwner = commaOrElement.parent as KtElement + // case for KtFunctionLiteral, where PsiWhiteSpace after KtTypeParameterList isn't included in this list + val problemOwner = commaOwner.parent + holder.registerProblem( + problemOwner, + message, + highlightType.applyCondition(!checkTrailingCommaSettings || useTrailingComma), + commaOrElement.textRangeOfCommaOrSymbolAfter.shiftLeft(problemOwner.startOffset), + createQuickFix(fixMessage, commaOwner), + ) + } + + private fun registerProblemForLineBreak( + commaOwner: KtElement, + elementForTextRange: PsiElement, + highlightType: ProblemHighlightType, + ) { + val problemElement = commaOwner.parent + holder.registerProblem( + problemElement, + KotlinBundle.message("inspection.trailing.comma.missing.line.break"), + highlightType.applyCondition(useTrailingComma), + TextRange.from(elementForTextRange.startOffset, 1).shiftLeft(problemElement.startOffset), + createQuickFix(KotlinBundle.message("inspection.trailing.comma.add.line.break"), commaOwner), + ) + } + + private fun ProblemHighlightType.applyCondition(condition: Boolean): ProblemHighlightType = when { + ApplicationManager.getApplication().isUnitTestMode -> ProblemHighlightType.GENERIC_ERROR_OR_WARNING + condition -> this + else -> ProblemHighlightType.INFORMATION + } + + private fun createQuickFix( + fixMessage: String, + commaOwner: KtElement, + ): LocalQuickFix = object : LocalQuickFix { + val commaOwnerPointer = commaOwner.createSmartPointer() + + override fun getFamilyName(): String = fixMessage + + override fun applyFix(project: Project, problemDescriptor: ProblemDescriptor) { + val element = commaOwnerPointer.element ?: return + val range = createFormatterTextRange(element) + val settings = CodeStyle.getSettings(project).clone() + settings.kotlinCustomSettings.ALLOW_TRAILING_COMMA = true + settings.kotlinCustomSettings.ALLOW_TRAILING_COMMA_ON_CALL_SITE = true + CodeStyle.doWithTemporarySettings(project, settings, Runnable { + CodeStyleManager.getInstance(project).reformatRange(element, range.startOffset, range.endOffset) + }) + } + } + + private fun createFormatterTextRange(commaOwner: KtElement): TextRange { + val startElement = TrailingCommaHelper.elementBeforeFirstElement(commaOwner) ?: commaOwner + val endElement = TrailingCommaHelper.elementAfterLastElement(commaOwner) ?: commaOwner + return TextRange.create(startElement.startOffset, endElement.endOffset) + } + + private val PsiElement.textRangeOfCommaOrSymbolAfter: TextRange + get() { + val textRange = textRange + if (isComma) return textRange + + return nextLeaf()?.leafIgnoringWhitespaceAndComments(false)?.endOffset?.takeIf { it > 0 }?.let { + TextRange.create(it - 1, it).intersection(textRange) + } ?: TextRange.create(textRange.endOffset - 1, textRange.endOffset) + } + } + + override fun createOptionsPanel(): JComponent? { + val panel = MultipleCheckboxOptionsPanel(this) + panel.addCheckbox(KotlinBundle.message("inspection.trailing.comma.report.also.a.missing.comma"), "addCommaWarning") + return panel + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt.202 b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt.202 new file mode 100644 index 00000000000..1cfcb1d4d77 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt.202 @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.refactoring.changeSignature + +import org.jetbrains.kotlin.descriptors.Visibility + +class KotlinMutableMethodDescriptor(override val original: KotlinMethodDescriptor) : KotlinMethodDescriptor by original { + private val parameters: MutableList = original.parameters + + override var receiver: KotlinParameterInfo? = original.receiver + set(value: KotlinParameterInfo?) { + if (value != null && value !in parameters) { + parameters.add(value) + } + field = value + } + + fun addParameter(parameter: KotlinParameterInfo) { + parameters.add(parameter) + } + + fun addParameter(index: Int, parameter: KotlinParameterInfo) { + parameters.add(index, parameter) + } + + fun removeParameter(index: Int) { + val paramInfo = parameters.removeAt(index) + if (paramInfo == receiver) { + receiver = null + } + } + + fun renameParameter(index: Int, newName: String) { + parameters[index].name = newName + } + + fun clearNonReceiverParameters() { + parameters.clear() + receiver?.let { parameters.add(it) } + } + + override fun getVisibility(): Visibility { + return original.visibility + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt.202 b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt.202 new file mode 100644 index 00000000000..75b404ccc77 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt.202 @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.search.ideaExtensions + +import com.intellij.codeInsight.JavaTargetElementEvaluator +import com.intellij.codeInsight.TargetElementEvaluatorEx +import com.intellij.codeInsight.TargetElementUtil +import com.intellij.codeInsight.TargetElementUtilExtender +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiReference +import com.intellij.util.BitUtil +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.idea.intentions.isAutoCreatedItUsage +import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.resolve.source.getPsi + +class KotlinTargetElementEvaluator : TargetElementEvaluatorEx, TargetElementUtilExtender { + companion object { + const val DO_NOT_UNWRAP_LABELED_EXPRESSION = 0x100 + const val BYPASS_IMPORT_ALIAS = 0x200 + + // Place caret after the open curly brace in lambda for generated 'it' + fun findLambdaOpenLBraceForGeneratedIt(ref: PsiReference): PsiElement? { + val element: PsiElement = ref.element + if (element.text != "it") return null + + if (element !is KtNameReferenceExpression || !isAutoCreatedItUsage(element)) return null + + val itDescriptor = element.resolveMainReferenceToDescriptors().singleOrNull() ?: return null + val descriptorWithSource = itDescriptor.containingDeclaration as? DeclarationDescriptorWithSource ?: return null + val lambdaExpression = descriptorWithSource.source.getPsi()?.parent as? KtLambdaExpression ?: return null + return lambdaExpression.leftCurlyBrace.treeNext?.psi + } + + // Navigate to receiver element for this in extension declaration + fun findReceiverForThisInExtensionFunction(ref: PsiReference): PsiElement? { + val element: PsiElement = ref.element + if (element.text != "this") return null + + if (element !is KtNameReferenceExpression) return null + val callableDescriptor = element.resolveMainReferenceToDescriptors().singleOrNull() as? CallableDescriptor ?: return null + + if (!callableDescriptor.isExtension) return null + val callableDeclaration = callableDescriptor.source.getPsi() as? KtCallableDeclaration ?: return null + + return callableDeclaration.receiverTypeReference + } + } + + override fun getAdditionalDefinitionSearchFlags() = 0 + + override fun getAdditionalReferenceSearchFlags() = DO_NOT_UNWRAP_LABELED_EXPRESSION or BYPASS_IMPORT_ALIAS + + override fun getAllAdditionalFlags() = additionalDefinitionSearchFlags + additionalReferenceSearchFlags + + override fun includeSelfInGotoImplementation(element: PsiElement): Boolean = !(element is KtClass && element.isAbstract()) + + override fun getElementByReference(ref: PsiReference, flags: Int): PsiElement? { + if (ref is KtSimpleNameReference && ref.expression is KtLabelReferenceExpression) { + val refTarget = ref.resolve() as? KtExpression ?: return null + if (!BitUtil.isSet(flags, DO_NOT_UNWRAP_LABELED_EXPRESSION)) { + return refTarget.getLabeledParent(ref.expression.getReferencedName()) ?: refTarget + } + return refTarget + } + + if (!BitUtil.isSet(flags, BYPASS_IMPORT_ALIAS)) { + (ref.element as? KtSimpleNameExpression)?.mainReference?.getImportAlias()?.let { return it } + } + + // prefer destructing declaration entry to its target if element name is accepted + if (ref is KtDestructuringDeclarationReference && BitUtil.isSet(flags, TargetElementUtil.ELEMENT_NAME_ACCEPTED)) { + return ref.element + } + + val refExpression = ref.element as? KtSimpleNameExpression + val calleeExpression = refExpression?.getParentOfTypeAndBranch { calleeExpression } + if (calleeExpression != null) { + (ref.resolve() as? KtConstructor<*>)?.let { + return if (flags and JavaTargetElementEvaluator().additionalReferenceSearchFlags != 0) it else it.containingClassOrObject + } + } + + if (BitUtil.isSet(flags, TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED)) { + return findLambdaOpenLBraceForGeneratedIt(ref) + ?: findReceiverForThisInExtensionFunction(ref) + } + + return null + } + + override fun isIdentifierPart(file: PsiFile, text: CharSequence, offset: Int): Boolean { + val elementAtCaret = file.findElementAt(offset) + + if (elementAtCaret?.node?.elementType == KtTokens.IDENTIFIER) return true + // '(' is considered identifier part if it belongs to primary constructor without 'constructor' keyword + return elementAtCaret?.getNonStrictParentOfType()?.textOffset == offset + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt.202 b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt.202 new file mode 100644 index 00000000000..e4a87c4691d --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt.202 @@ -0,0 +1,127 @@ +/* + * Copyright 2010-2017 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.slicer + +import com.intellij.codeInsight.Nullability +import com.intellij.ide.util.treeView.AbstractTreeStructure +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType +import com.intellij.slicer.* +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.references.KtReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isPlainWithEscapes +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.psi2ir.deparenthesize +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.isError +import org.jetbrains.kotlin.types.isNullabilityFlexible + +class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer { + companion object { + val LEAF_ELEMENT_EQUALITY = object : SliceLeafEquality() { + override fun substituteElement(element: PsiElement) = (element as? KtReference)?.resolve() ?: element + } + } + + class KotlinGroupByNullnessAction(treeBuilder: SliceTreeBuilder) : GroupByNullnessActionBase(treeBuilder) { + override fun isAvailable() = true + } + + val leafAnalyzer by lazy { SliceLeafAnalyzer(LEAF_ELEMENT_EQUALITY, this) } + + val nullnessAnalyzer: HackedSliceNullnessAnalyzerBase by lazy { + object : HackedSliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) { + override fun checkNullability(element: PsiElement?): Nullability { + val types = when (element) { + is KtCallableDeclaration -> listOfNotNull((element.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType) + is KtDeclaration -> emptyList() + is KtExpression -> listOfNotNull(element.analyze(BodyResolveMode.PARTIAL).getType(element)) + else -> emptyList() + } + return when { + types.isEmpty() -> return Nullability.UNKNOWN + types.all { KotlinBuiltIns.isNullableNothing(it) } -> Nullability.NULLABLE + types.any { it.isError || TypeUtils.isNullableType(it) || it.isNullabilityFlexible() } -> Nullability.UNKNOWN + else -> Nullability.NOT_NULL + } + } + } + } + + override fun createRootUsage(element: PsiElement, params: SliceAnalysisParams) = KotlinSliceUsage(element, params) + + override fun transform(usage: SliceUsage): Collection? { + if (usage is KotlinSliceUsage) return null + return listOf(KotlinSliceUsage(usage.element ?: return null, usage.parent, KotlinSliceAnalysisMode.Default, false)) + } + + override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtElement? { + val element = atCaret.parentsWithSelf + .filterIsInstance() + .firstOrNull(::isSliceElement) + ?.deparenthesize() ?: return null + + if (dataFlowToThis) { + if (element is KtConstantExpression) return null + if (element is KtStringTemplateExpression && element.isPlainWithEscapes()) return null + if (element is KtClassLiteralExpression) return null + if (element is KtCallableReferenceExpression) return null + } + + return element + } + + private fun isSliceElement(element: KtElement): Boolean { + return when { + element is KtProperty -> true + element is KtParameter -> true + element is KtDeclarationWithBody -> true + element is KtClass && !element.hasExplicitPrimaryConstructor() -> true + element is KtExpression && element !is KtDeclaration && element.parentOfType() == null -> true + element is KtTypeReference && element == (element.parent as? KtCallableDeclaration)?.receiverTypeReference -> true + else -> false + } + } + + override fun getElementForDescription(element: PsiElement): PsiElement { + return (element as? KtSimpleNameExpression)?.mainReference?.resolve() ?: element + } + + override fun getRenderer() = KotlinSliceUsageCellRenderer + + override fun startAnalyzeLeafValues(structure: AbstractTreeStructure, finalRunnable: Runnable) { + leafAnalyzer.startAnalyzeValues(structure, finalRunnable) + } + + override fun startAnalyzeNullness(structure: AbstractTreeStructure, finalRunnable: Runnable) { + nullnessAnalyzer.startAnalyzeNullness(structure, finalRunnable) + } + + override fun registerExtraPanelActions(group: DefaultActionGroup, builder: SliceTreeBuilder) { + if (builder.dataFlowToThis) { + group.add(GroupByLeavesAction(builder)) + group.add(KotlinGroupByNullnessAction(builder)) + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt.202 b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt.202 new file mode 100644 index 00000000000..e766ab1bfea --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt.202 @@ -0,0 +1,185 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.codeInsight + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings +import com.intellij.codeInsight.daemon.LineMarkerInfo +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl +import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import com.intellij.rt.execution.junit.FileComparisonFailure +import com.intellij.testFramework.ExpectedHighlightingData +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.testFramework.UsefulTestCase +import junit.framework.TestCase +import org.jetbrains.kotlin.idea.highlighter.markers.TestableLineMarkerNavigator +import org.jetbrains.kotlin.idea.navigation.NavigationTestUtils +import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.TagsTestDataUtil +import org.jetbrains.kotlin.test.util.renderAsGotoImplementation +import org.junit.Assert +import java.io.File + +abstract class AbstractLineMarkersTest : KotlinLightCodeInsightFixtureTestCase() { + + override fun getProjectDescriptor(): LightProjectDescriptor { + return KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE + } + + fun doTest(path: String) = doTest(path) {} + + protected fun doAndCheckHighlighting( + psiFile: PsiFile, + documentToAnalyze: Document, + expectedHighlighting: ExpectedHighlightingData, + expectedFile: File + ): List> { + myFixture.doHighlighting() + + return checkHighlighting(psiFile, documentToAnalyze, expectedHighlighting, expectedFile) + } + + fun doTest(path: String, additionalCheck: () -> Unit) { + val fileText = FileUtil.loadFile(testDataFile()) + try { + ConfigLibraryUtil.configureLibrariesByDirective(myFixture.module, PlatformTestUtil.getCommunityPath(), fileText) + if (InTextDirectivesUtils.findStringWithPrefixes(fileText, "METHOD_SEPARATORS") != null) { + DaemonCodeAnalyzerSettings.getInstance().SHOW_METHOD_SEPARATORS = true + } + + myFixture.configureByFile(fileName()) + val project = myFixture.project + val document = myFixture.editor.document + + val data = ExpectedHighlightingData(document, false, false, false) + data.init() + + PsiDocumentManager.getInstance(project).commitAllDocuments() + + val markers = doAndCheckHighlighting(myFixture.file, document, data, testDataFile()) + + assertNavigationElements(myFixture.project, myFixture.file as KtFile, markers) + additionalCheck() + } catch (exc: Exception) { + throw RuntimeException(exc) + } finally { + ConfigLibraryUtil.unconfigureLibrariesByDirective(module, fileText) + DaemonCodeAnalyzerSettings.getInstance().SHOW_METHOD_SEPARATORS = false + } + + } + + companion object { + + @Suppress("SpellCheckingInspection") + private const val LINE_MARKER_PREFIX = "LINEMARKER:" + private const val TARGETS_PREFIX = "TARGETS" + + fun assertNavigationElements(project: Project, file: KtFile, markers: List>) { + val navigationDataComments = KotlinTestUtils.getLastCommentsInFile( + file, KotlinTestUtils.CommentType.BLOCK_COMMENT, false + ) + if (navigationDataComments.isEmpty()) return + + for ((navigationCommentIndex, navigationComment) in navigationDataComments.reversed().withIndex()) { + val description = getLineMarkerDescription(navigationComment) + val navigateMarkers = markers.filter { it.lineMarkerTooltip?.startsWith(description) == true } + val navigateMarker = navigateMarkers.singleOrNull() ?: navigateMarkers.getOrNull(navigationCommentIndex) + + TestCase.assertNotNull( + String.format("Can't find marker for navigation check with description \"%s\"", description), + navigateMarker + ) + + val handler = navigateMarker!!.navigationHandler + if (handler is TestableLineMarkerNavigator) { + val navigateElements = handler.getTargetsPopupDescriptor(navigateMarker.element)?.targets?.sortedBy { + it.renderAsGotoImplementation() + } + val actualNavigationData = NavigationTestUtils.getNavigateElementsText(project, navigateElements) + + UsefulTestCase.assertSameLines(getExpectedNavigationText(navigationComment), actualNavigationData) + } else { + Assert.fail("Only TestableLineMarkerNavigator are supported in navigate check") + } + } + } + + private fun getLineMarkerDescription(navigationComment: String): String { + val firstLineEnd = navigationComment.indexOf("\n") + TestCase.assertTrue( + "The first line in block comment must contain description of marker for navigation check", firstLineEnd != -1 + ) + + var navigationMarkerText = navigationComment.substring(0, firstLineEnd) + + TestCase.assertTrue( + String.format("Add %s directive in first line of comment", LINE_MARKER_PREFIX), + navigationMarkerText.startsWith(LINE_MARKER_PREFIX) + ) + + navigationMarkerText = navigationMarkerText.substring(LINE_MARKER_PREFIX.length) + + return navigationMarkerText.trim { it <= ' ' } + } + + private fun getExpectedNavigationText(navigationComment: String): String { + val firstLineEnd = navigationComment.indexOf("\n") + + var expectedNavigationText = navigationComment.substring(firstLineEnd + 1) + + TestCase.assertTrue( + String.format("Marker %s is expected before navigation data", TARGETS_PREFIX), + expectedNavigationText.startsWith(TARGETS_PREFIX) + ) + + expectedNavigationText = expectedNavigationText.substring(expectedNavigationText.indexOf("\n") + 1) + + return expectedNavigationText + } + + fun checkHighlighting( + psiFile: PsiFile, + documentToAnalyze: Document, + expectedHighlighting: ExpectedHighlightingData, + expectedFile: File + ): MutableList> { + val markers = DaemonCodeAnalyzerImpl.getLineMarkers(documentToAnalyze, psiFile.project) + + try { + expectedHighlighting.checkLineMarkers(psiFile, markers, documentToAnalyze.text) + + // This is a workaround for sad bug in ExpectedHighlightingData: + // the latter doesn't throw assertion error when some line markers are expected, but none are present. + if (FileUtil.loadFile(expectedFile).contains("".toRegex(), "") + val cleanFile = libraryClean.resolve(file.relativeTo(libraryOriginal).path) + cleanFile.createFile() + cleanFile.write(text) + } + this.libraryClean = File(libraryCleanPath) + } + return object : SdkAndMockLibraryProjectDescriptor(getLibraryCleanPath(), false) { + override fun configureModule(module: Module, model: ModifiableRootModel) { + super.configureModule(module, model) + + val library = model.moduleLibraryTable.getLibraryByName(LIBRARY_NAME)!! + val modifiableModel = library.modifiableModel + + modifiableModel.addRoot(LocalFileSystem.getInstance().findFileByIoFile(libraryClean!!)!!, OrderRootType.SOURCES) + modifiableModel.commit() + } + } + } + + fun doTestWithLibrary(path: String) { + doTest(path) { + val fileSystem = VirtualFileManager.getInstance().getFileSystem("file") + val libraryOriginal = File(getLibraryOriginalPath()) + val project = myFixture.project + for (file in libraryOriginal.walkTopDown().filter { !it.isDirectory }) { + myFixture.openFileInEditor(fileSystem.findFileByPath(file.absolutePath)!!) + val data = ExpectedHighlightingData(myFixture.editor.document, false, false, false) + data.init() + + val librarySourceFile = libraryClean!!.resolve(file.relativeTo(libraryOriginal).path) + myFixture.openFileInEditor(fileSystem.findFileByPath(librarySourceFile.absolutePath)!!) + val document = myFixture.editor.document + PsiDocumentManager.getInstance(project).commitAllDocuments() + + if (!ProjectRootsUtil.isLibrarySourceFile(project, myFixture.file.virtualFile)) { + throw AssertionError("File ${myFixture.file.virtualFile.path} should be in library sources!") + } + + doAndCheckHighlighting(myFixture.file, document, data, file) + } + } + } + + override fun tearDown() { + libraryClean?.deleteRecursively() + ConfigLibraryUtil.removeLibrary(module, SdkAndMockLibraryProjectDescriptor.LIBRARY_NAME) + + super.tearDown() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractUsageHighlightingTest.kt.202 b/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractUsageHighlightingTest.kt.202 new file mode 100644 index 00000000000..bebbeeb9fa9 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractUsageHighlightingTest.kt.202 @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.highlighter + +import com.intellij.codeInsight.daemon.impl.HighlightInfo +import com.intellij.codeInsight.daemon.impl.HighlightInfoType +import com.intellij.codeInsight.highlighting.HighlightUsagesHandler +import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.markup.RangeHighlighter +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.testFramework.ExpectedHighlightingData +import org.jetbrains.kotlin.idea.test.* + +abstract class AbstractUsageHighlightingTest : KotlinLightCodeInsightFixtureTestCase() { + companion object { + // Not standard to leave it in text after configureByFile and remove manually after collecting highlighting information + const val CARET_TAG = "~" + } + + protected fun doTest(unused: String) { + myFixture.configureByFile(fileName()) + val document = myFixture.editor.document + val data = ExpectedHighlightingData(document, false, false, true, false) + data.init() + + val caret = document.extractMarkerOffset(project, CARET_TAG) + assert(caret != -1) { "Caret marker '$CARET_TAG' expected" } + editor.caretModel.moveToOffset(caret) + + HighlightUsagesHandler.invoke(project, editor, myFixture.file) + val highlighters = myFixture.editor.markupModel.allHighlighters + + val infos = highlighters + .filter { isUsageHighlighting(it) } + .map { highlighter -> + var startOffset = highlighter.startOffset + var endOffset = highlighter.endOffset + + if (startOffset > caret) startOffset += CARET_TAG.length + if (endOffset > caret) endOffset += CARET_TAG.length + + HighlightInfo.newHighlightInfo(HighlightInfoType.INFORMATION) + .range(startOffset, endOffset) + .create() + } + + data.checkResult(myFixture.file, infos, StringBuilder(document.text).insert(caret, CARET_TAG).toString()) + } + + private fun isUsageHighlighting(info: RangeHighlighter): Boolean { + val globalScheme = EditorColorsManager.getInstance().globalScheme + + val readAttributes: TextAttributes = + globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES) + val writeAttributes: TextAttributes = + globalScheme.getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES) + + return info.textAttributes == readAttributes || info.textAttributes == writeAttributes + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/InspectionDescriptionTest.kt.202 b/idea/tests/org/jetbrains/kotlin/idea/inspections/InspectionDescriptionTest.kt.202 new file mode 100644 index 00000000000..9b854a3bbbc --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/InspectionDescriptionTest.kt.202 @@ -0,0 +1,149 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.codeInspection.InspectionEP +import com.intellij.codeInspection.InspectionProfileEntry +import com.intellij.codeInspection.LocalInspectionEP +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.codeInspection.ex.InspectionToolRegistrar +import com.intellij.codeInspection.ex.InspectionToolWrapper +import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.util.text.StringUtil +import com.intellij.testFramework.LightPlatformTestCase +import com.intellij.testFramework.UsefulTestCase +import gnu.trove.THashMap +import org.jetbrains.kotlin.idea.KotlinPluginUtil +import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner +import org.junit.runner.RunWith + +@RunWith(JUnit3WithIdeaConfigurationRunner::class) +class InspectionDescriptionTest : LightPlatformTestCase() { + + fun testDescriptionsAndShortNames() { + val shortNames = THashMap>() + + val inspectionTools = loadKotlinInspections() + val errors = StringBuilder() + for (toolWrapper in inspectionTools) { + val description = toolWrapper.loadDescription() + + if (description == null) { + errors.append("description is null for inspection '").append(desc(toolWrapper)) + } + + val shortName = toolWrapper.shortName + val tool = shortNames[shortName] + if (tool != null) { + errors.append( + "Short names must be unique: " + shortName + "\n" + + "inspection: '" + desc(tool) + "\n" + + " and '" + desc(toolWrapper) + ) + } + shortNames.put(shortName, toolWrapper) + } + + UsefulTestCase.assertEmpty(errors.toString()) + } + + private fun loadKotlinInspections(): List> { + return InspectionToolRegistrar.getInstance().createTools().filter { + it.extension.pluginDescriptor.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID + } as List> + } + + private fun loadKotlinInspectionExtensions() = + LocalInspectionEP.LOCAL_INSPECTION.extensions.filter { + it.pluginDescriptor.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID + } + + private fun desc(tool: InspectionToolWrapper): String { + return tool.toString() + " ('" + tool.descriptionContextClass + "') " + + "in " + if (tool.extension == null) null else tool.extension.pluginDescriptor + } + + fun testExtensionPoints() { + val shortNames = THashMap() + + @Suppress("DEPRECATION") + val inspectionEPs = Extensions.getExtensions(LocalInspectionEP.LOCAL_INSPECTION) + + val tools = inspectionEPs.size + val errors = StringBuilder() + for (ep in inspectionEPs) { + val shortName = ep.getShortName() + val ep1 = shortNames[shortName] + if (ep1 != null) { + errors.append( + "Short names must be unique: '" + shortName + "':\n" + + "inspection: '" + ep1.implementationClass + "' in '" + ep1.pluginDescriptor + "'\n" + + "; and '" + ep.implementationClass + "' in '" + ep.pluginDescriptor + "'" + ) + } + shortNames.put(shortName, ep) + } + println("$tools inspection tools total via EP") + + UsefulTestCase.assertEmpty(errors.toString()) + } + + fun testInspectionMappings() { + val toolWrappers = loadKotlinInspections() + val errors = StringBuilder() + toolWrappers.filter({ toolWrapper -> toolWrapper.extension == null }).forEach { toolWrapper -> + errors.append("Please add XML mapping for ").append(toolWrapper.tool::class.java) + } + + UsefulTestCase.assertEmpty(errors.toString()) + } + + fun testMismatchedIds() { + val failMessages = mutableListOf() + for (ep in loadKotlinInspectionExtensions()) { + val toolName = ep.implementationClass + val tool = ep.instantiateTool() + if (tool is LocalInspectionTool) { + checkValue(failMessages, toolName, "suppressId", ep.id, ep.getShortName(), tool.id) + checkValue(failMessages, toolName, "alternateId", ep.alternativeId, null, tool.alternativeID) + checkValue(failMessages, toolName, "shortName", ep.getShortName(), null, tool.shortName) + checkValue(failMessages, toolName, "runForWholeFile", null, "false", tool.runForWholeFile().toString()) + } + } + + UsefulTestCase.assertEmpty(StringUtil.join(failMessages, "\n"), failMessages) + } + + fun testNotEmptyToolNames() { + val failMessages = mutableListOf() + for (ep in LocalInspectionEP.LOCAL_INSPECTION.extensions) { + val toolName = ep.implementationClass + if (ep.getDisplayName().isNullOrEmpty()) { + failMessages.add(toolName + ": toolName is not set, tool won't be available in `run inspection` action") + } + } + UsefulTestCase.assertEmpty(failMessages.joinToString("\n"), failMessages) + } + + private fun checkValue( + failMessages: MutableCollection, + toolName: String, + attributeName: String, + xmlValue: String?, + defaultXmlValue: String?, + javaValue: String? + ) { + if (StringUtil.isNotEmpty(xmlValue)) { + if (javaValue != xmlValue) { + failMessages.add("$toolName: mismatched $attributeName. Xml: $xmlValue; Java: $javaValue") + } + } else if (StringUtil.isNotEmpty(javaValue)) { + if (javaValue != defaultXmlValue) { + failMessages.add("$toolName: $attributeName overridden in wrong way, will work in tests only. Please set appropriate $attributeName value in XML ($javaValue)") + } + } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionDescriptionTest.kt.202 b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionDescriptionTest.kt.202 new file mode 100644 index 00000000000..38b04d39767 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionDescriptionTest.kt.202 @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.intentions + +import com.intellij.codeInsight.intention.IntentionActionBean +import com.intellij.codeInsight.intention.IntentionManager +import com.intellij.codeInsight.intention.impl.config.IntentionManagerImpl +import com.intellij.openapi.extensions.Extensions +import com.intellij.testFramework.LightPlatformTestCase +import com.intellij.testFramework.UsefulTestCase +import org.jetbrains.kotlin.idea.KotlinPluginUtil +import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner +import org.junit.runner.RunWith +import java.io.File + +@RunWith(JUnit3WithIdeaConfigurationRunner::class) +class IntentionDescriptionTest : LightPlatformTestCase() { + + private val necessaryNormalNames = listOf("description.html", "before.kt.template", "after.kt.template") + private val necessaryXmlNames = listOf("description.html", "before.xml.template", "after.xml.template") + private val necessaryMavenNames = listOf("description.html") + + fun testDescriptionsAndShortNames() { + val intentionTools = loadKotlinIntentions() + val errors = StringBuilder() + for (tool in intentionTools) { + val className = tool.className + val shortName = className.substringAfterLast(".").replace("$", "") + val directory = File("idea/resources-en/intentionDescriptions/$shortName") + if (!directory.exists() || !directory.isDirectory) { + if (tool.categories != null) { + errors.append("No description directory for intention '").append(className).append("'\n") + } + } else { + val necessaryNames = when { + shortName.isMavenIntentionName() -> necessaryMavenNames + shortName.isXmlIntentionName() -> necessaryXmlNames + else -> necessaryNormalNames + } + for (fileName in necessaryNames) { + val file = directory.resolve(fileName) + if (!file.exists() || !file.isFile) { + errors.append("No description file $fileName for intention '").append(className).append("'\n") + } + } + } + } + + UsefulTestCase.assertEmpty(errors.toString()) + } + + private fun String.isMavenIntentionName() = startsWith("MavenPlugin") + + private fun String.isXmlIntentionName() = startsWith("Add") && endsWith("ToManifest") + + private fun loadKotlinIntentions(): List { + val extensionPoint = Extensions.getRootArea().getExtensionPoint(IntentionManagerImpl.EP_INTENTION_ACTIONS) + return extensionPoint.extensions.toList().filter { + it.pluginDescriptor.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestUtil.kt.202 b/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestUtil.kt.202 new file mode 100644 index 00000000000..5ab9362ec62 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/slicer/SlicerTestUtil.kt.202 @@ -0,0 +1,136 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.slicer + +import com.intellij.analysis.AnalysisScope +import com.intellij.ide.projectView.TreeStructureProvider +import com.intellij.ide.util.treeView.AbstractTreeStructureBase +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.PsiSearchScopeUtil +import com.intellij.slicer.DuplicateMap +import com.intellij.slicer.SliceAnalysisParams +import com.intellij.slicer.SliceNode +import com.intellij.slicer.SliceRootNode +import com.intellij.usages.TextChunk +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.contains +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import java.awt.Font + +internal class TestSliceTreeStructure(private val rootNode: SliceNode) : AbstractTreeStructureBase(rootNode.project) { + override fun getProviders() = emptyList() + + override fun getRootElement() = rootNode + + override fun commit() { + } + + override fun hasSomethingToCommit() = false +} + +internal fun buildTreeRepresentation(rootNode: SliceNode): String { + val project = rootNode.element!!.project!! + val projectScope = GlobalSearchScope.projectScope(project) + + fun TextChunk.render(): String { + var text = text + if (attributes.fontType == Font.BOLD) { + text = "$text" + } + return text + } + + fun SliceNode.isSliceLeafValueClassNode() = this is HackedSliceLeafValueClassNode + + fun process(node: SliceNode, indent: Int): String { + val usage = node.element!!.value + + node.calculateDupNode() + val isDuplicated = !node.isSliceLeafValueClassNode() && node.duplicate != null + + return buildString { + when { + node is SliceRootNode && usage.element is KtFile -> { + node.sortedChildren.forEach { append(process(it, indent)) } + return@buildString + } + + // SliceLeafValueClassNode is package-private + node.isSliceLeafValueClassNode() -> append("[${node.nodeText}]\n") + + else -> { + val chunks = usage.text + if (!PsiSearchScopeUtil.isInScope(projectScope, usage.element!!)) { + append("LIB ") + } else { + append(chunks.first().render() + " ") + } + + repeat(indent) { append('\t') } + + if (usage is KotlinSliceDereferenceUsage) { + append("DEREFERENCE: ") + } + + if (usage is KotlinSliceUsage) { + usage.mode.inlineCallStack.forEach { + append("(INLINE CALL ${it.function?.name}) ") + } + usage.mode.behaviourStack.reversed().joinTo(this, separator = "") { it.testPresentationPrefix } + } + + if (isDuplicated) { + append("DUPLICATE: ") + } + + chunks.slice(1 until chunks.size).joinTo(this, separator = "") { it.render() } + + KotlinSliceUsageCellRenderer.containerSuffix(usage)?.let { + append(" ($it)") + } + + append("\n") + } + } + + if (!isDuplicated) { + node.sortedChildren.forEach { append(process(it, indent + 1)) } + } + }.replace(Regex(""), "") + } + + return process(rootNode, 0) +} + +private val SliceNode.sortedChildren: List + get() = children.sortedBy { it.value.element?.startOffset ?: -1 } + +internal fun testSliceFromOffset( + file: KtFile, + offset: Int, + doTest: (sliceProvider: KotlinSliceProvider, rootNode: SliceRootNode) -> Unit +) { + val fileText = file.text + val flowKind = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// FLOW: ") + val withDereferences = InTextDirectivesUtils.isDirectiveDefined(fileText, "// WITH_DEREFERENCES") + val analysisParams = SliceAnalysisParams().apply { + dataFlowToThis = when (flowKind) { + "IN" -> true + "OUT" -> false + else -> throw AssertionError("Invalid flow kind: $flowKind") + } + showInstanceDereferences = withDereferences + scope = AnalysisScope(file.project) + } + + val elementAtCaret = file.findElementAt(offset)!! + val sliceProvider = KotlinSliceProvider() + val expression = sliceProvider.getExpressionAtCaret(elementAtCaret, analysisParams.dataFlowToThis)!! + val rootUsage = sliceProvider.createRootUsage(expression, analysisParams) + val rootNode = SliceRootNode(file.project, DuplicateMap(), rootUsage) + doTest(sliceProvider, rootNode) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt.202 b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt.202 new file mode 100644 index 00000000000..aa06dd5799b --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt.202 @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.stubs + +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl +import com.intellij.codeInsight.daemon.impl.HighlightInfo +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.DumbService +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.impl.cache.CacheManager +import com.intellij.psi.impl.source.tree.TreeElement +import com.intellij.psi.impl.source.tree.TreeUtil +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.UsageSearchContext +import com.intellij.testFramework.ExpectedHighlightingData + +abstract class AbstractMultiHighlightingTest : AbstractMultiModuleTest() { + + protected open val shouldCheckLineMarkers = false + + protected open val shouldCheckResult = true + + override fun checkHighlighting(data: ExpectedHighlightingData): Collection { + data.init() + PsiDocumentManager.getInstance(myProject).commitAllDocuments() + + //to load text + ApplicationManager.getApplication().runWriteAction { TreeUtil.clearCaches(myFile.node as TreeElement) } + + //to initialize caches + if (!DumbService.isDumb(project)) { + CacheManager.SERVICE.getInstance(myProject) + .getFilesWithWord("XXX", UsageSearchContext.IN_COMMENTS, GlobalSearchScope.allScope(myProject), true) + } + + val infos = doHighlighting() + + val text = myEditor.document.text + if (shouldCheckLineMarkers) { + data.checkLineMarkers(myFile, DaemonCodeAnalyzerImpl.getLineMarkers(getDocument(file), project), text) + } + if (shouldCheckResult) { + data.checkResult(myFile, infos, text) + } + return infos + } +} \ No newline at end of file diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt.202 b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt.202 index 125ffde238d..3ac350767d3 100644 --- a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt.202 +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt.202 @@ -58,7 +58,6 @@ import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.JvmCodegenUtil import org.jetbrains.kotlin.config.IncrementalCompilation import org.jetbrains.kotlin.config.KotlinCompilerVersion.TEST_IS_PRE_RELEASE_SYSTEM_PROPERTY -import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.withIC import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTestBase.LibraryDependency.* @@ -464,6 +463,29 @@ open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() { buildAllModules().assertSuccessful() } + fun testPureJavaProject() { + initProject(JVM_FULL_RUNTIME) + + fun build() { + var someFilesCompiled = false + + buildCustom(CanceledStatus.NULL, TestProjectBuilderLogger(), BuildResult()) { + project.setTestingContext(TestingContext(LookupTracker.DO_NOTHING, object : TestingBuildLogger { + override fun compilingFiles(files: Collection, allRemovedFilesFiles: Collection) { + someFilesCompiled = true + } + })) + } + + assertFalse("Kotlin builder should return early if there are no Kotlin files", someFilesCompiled) + } + + build() + + rename("${workDir}/src/Test.java", "Test1.java") + build() + } + fun testKotlinJavaProject() { doTestWithRuntime() } @@ -626,40 +648,6 @@ open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() { result.assertSuccessful() } - /* - * Here we're checking that enabling inference in IDE doesn't affect compilation via JPS - * - * the following two tests are connected: - * - testKotlinProjectWithEnabledNewInferenceInIDE checks that project is compiled when new inference is enabled only in IDE - * - this is done via project component - * - testKotlinProjectWithErrorsBecauseOfNewInference checks that project isn't compiled when new inference is enabled in the compiler - * - * So, if the former will fail => option affects JPS compilation, it's bad. Also, if the latter test fails => test is useless as it's - * compiled with new and old inference. - * - */ - fun testKotlinProjectWithEnabledNewInferenceInIDE() { - initProject(JVM_MOCK_RUNTIME) - val module = myProject.modules.single() - val args = module.kotlinCompilerArguments - args.languageVersion = LanguageVersion.KOTLIN_1_3.versionString - myProject.kotlinCommonCompilerArguments = args - - buildAllModules().assertSuccessful() - } - - fun testKotlinProjectWithErrorsBecauseOfNewInference() { - initProject(JVM_MOCK_RUNTIME) - val module = myProject.modules.single() - val args = module.kotlinCompilerArguments - args.newInference = true - myProject.kotlinCommonCompilerArguments = args - - val result = buildAllModules() - result.assertFailed() - result.checkErrors() - } - private fun createKotlinJavaScriptLibraryArchive() { val jarFile = File(workDir, KOTLIN_JS_LIBRARY_JAR) try { diff --git a/plugins/uast-kotlin-idea/src/org/jetbrains/uast/kotlin/generate/KotlinUastCodeGenerationPlugin.kt.202 b/plugins/uast-kotlin-idea/src/org/jetbrains/uast/kotlin/generate/KotlinUastCodeGenerationPlugin.kt.202 new file mode 100644 index 00000000000..9a3dbe575cb --- /dev/null +++ b/plugins/uast-kotlin-idea/src/org/jetbrains/uast/kotlin/generate/KotlinUastCodeGenerationPlugin.kt.202 @@ -0,0 +1,429 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.uast.kotlin.generate + +import com.intellij.lang.Language +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiType +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.refactoring.fqName.fqName +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.idea.util.resolveToKotlinType +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.uast.* +import org.jetbrains.uast.generate.UParameterInfo +import org.jetbrains.uast.generate.UastCodeGenerationPlugin +import org.jetbrains.uast.generate.UastElementFactory +import org.jetbrains.uast.kotlin.* +import org.jetbrains.uast.kotlin.internal.KotlinFakeUElement +import org.jetbrains.uast.kotlin.internal.toSourcePsiFakeAware + +class KotlinUastCodeGenerationPlugin : UastCodeGenerationPlugin { + override val language: Language + get() = KotlinLanguage.INSTANCE + + override fun getElementFactory(project: Project): UastElementFactory = + KotlinUastElementFactory(project) + + override fun replace(oldElement: UElement, newElement: T, elementType: Class): T? { + val oldPsi = oldElement.toSourcePsiFakeAware().singleOrNull() ?: return null + val newPsi = newElement.sourcePsi?.let { + when { + it is KtCallExpression && it.parent is KtQualifiedExpression -> it.parent + else -> it + } + } ?: return null + + val psiFactory = KtPsiFactory(oldPsi.project) + val oldParentPsi = oldPsi.parent + val (updOldPsi, updNewPsi) = when { + oldParentPsi is KtStringTemplateExpression && oldParentPsi.entries.size == 1 -> + oldParentPsi to newPsi + + oldPsi is KtStringTemplateEntry && newPsi !is KtStringTemplateEntry && newPsi is KtExpression -> + oldPsi to psiFactory.createBlockStringTemplateEntry(newPsi) + + oldPsi is KtBlockExpression && newPsi is KtBlockExpression -> { + if (!hasBraces(oldPsi) && hasBraces(newPsi)) { + oldPsi to psiFactory.createLambdaExpression("none", newPsi.statements.joinToString("\n") { "println()" }).bodyExpression!!.also { + it.statements.zip(newPsi.statements).forEach { it.first.replace(it.second) } + } + } else + oldPsi to newPsi + } + + else -> + oldPsi to newPsi + } + + return when (val replaced = updOldPsi.replace(updNewPsi)?.safeAs()?.let { ShortenReferences.DEFAULT.process(it) }) { + is KtCallExpression, is KtQualifiedExpression -> replaced.cast().getPossiblyQualifiedCallExpression() + else -> replaced + }?.toUElementOfExpectedTypes(elementType) + } +} + +private fun hasBraces(oldPsi: KtBlockExpression): Boolean = oldPsi.lBrace != null && oldPsi.rBrace != null + +class KotlinUastElementFactory(project: Project) : UastElementFactory { + private val psiFactory = KtPsiFactory(project) + + @Deprecated("use version with context parameter") + override fun createQualifiedReference(qualifiedName: String, context: UElement?): UQualifiedReferenceExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createQualifiedReference(qualifiedName, context?.sourcePsi) + } + + /*override*/ fun createQualifiedReference(qualifiedName: String, context: PsiElement?): UQualifiedReferenceExpression? { + return psiFactory.createExpression(qualifiedName).let { + when (it) { + is KtDotQualifiedExpression -> KotlinUQualifiedReferenceExpression(it, null) + is KtSafeQualifiedExpression -> KotlinUSafeQualifiedExpression(it, null) + else -> null + } + + } + } + + override fun createCallExpression( + receiver: UExpression?, + methodName: String, + parameters: List, + expectedReturnType: PsiType?, + kind: UastCallKind, + context: PsiElement? + ): UCallExpression? { + if (kind != UastCallKind.METHOD_CALL) return null + + val typeParams = (context as? KtElement)?.let { kontext -> + val resolutionFacade = kontext.getResolutionFacade() + (expectedReturnType as? PsiClassType)?.parameters?.map { it.resolveToKotlinType(resolutionFacade) } + } + + val name = methodName.quoteIfNeeded() + val methodCall = psiFactory.createExpression( + buildString { + if (receiver != null) + append("a.") + append(name) + if (typeParams != null) { + append(typeParams.joinToString(", ", "<", ">") { type -> + type.fqName?.asString() ?: "" + }) + } + append("()") + } + ).getPossiblyQualifiedCallExpression() ?: return null + + if (receiver != null) { + methodCall.parent.safeAs()?.receiverExpression?.replace(wrapULiteral(receiver).sourcePsi!!) + } + + val valueArgumentList = methodCall.valueArgumentList + for (parameter in parameters) { + valueArgumentList?.addArgument(psiFactory.createArgument(wrapULiteral(parameter).sourcePsi as KtExpression)) + } + + return KotlinUFunctionCallExpression(methodCall, null) + + } + + override fun createStringLiteralExpression(text: String, context: PsiElement?): ULiteralExpression? { + return KotlinStringULiteralExpression(psiFactory.createExpression(StringUtil.wrapWithDoubleQuote(text)), null) + } + + override fun createNullLiteral(context: PsiElement?): ULiteralExpression { + return psiFactory.createExpression("null").toUElementOfType()!! + } + + /*override*/ fun createIntLiteral(value: Int, context: PsiElement?): ULiteralExpression { + return psiFactory.createExpression(value.toString()).toUElementOfType()!! + } + + private fun KtExpression.ensureBlockExpressionBraces(): KtExpression { + if (this !is KtBlockExpression || hasBraces(this)) return this + val blockExpression = psiFactory.createBlock(this.statements.joinToString("\n") { "println()" }) + for ((placeholder, statement) in blockExpression.statements.zip(this.statements)) { + placeholder.replace(statement) + } + return blockExpression + } + + @Deprecated("use version with context parameter") + fun createIfExpression(condition: UExpression, thenBranch: UExpression, elseBranch: UExpression?): UIfExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createIfExpression(condition, thenBranch, elseBranch, null) + } + + override fun createIfExpression( + condition: UExpression, + thenBranch: UExpression, + elseBranch: UExpression?, + context: PsiElement? + ): UIfExpression? { + val conditionPsi = condition.sourcePsi as? KtExpression ?: return null + val thenBranchPsi = thenBranch.sourcePsi as? KtExpression ?: return null + val elseBranchPsi = elseBranch?.sourcePsi as? KtExpression + + return KotlinUIfExpression(psiFactory.createIf(conditionPsi, thenBranchPsi.ensureBlockExpressionBraces(), elseBranchPsi?.ensureBlockExpressionBraces()), null) + } + + @Deprecated("use version with context parameter") + fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createParenthesizedExpression(expression, null) + } + + override fun createParenthesizedExpression(expression: UExpression, context: PsiElement?): UParenthesizedExpression? { + val source = expression.sourcePsi ?: return null + val parenthesized = psiFactory.createExpression("(${source.text})") as? KtParenthesizedExpression ?: return null + return KotlinUParenthesizedExpression(parenthesized, null) + } + + @Deprecated("use version with context parameter") + fun createSimpleReference(name: String): USimpleNameReferenceExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createSimpleReference(name, null) + } + + override fun createSimpleReference(name: String, context: PsiElement?): USimpleNameReferenceExpression? { + return KotlinUSimpleReferenceExpression(psiFactory.createSimpleName(name), null) + } + + @Deprecated("use version with context parameter") + fun createSimpleReference(variable: UVariable): USimpleNameReferenceExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createSimpleReference(variable, null) + } + + override fun createSimpleReference(variable: UVariable, context: PsiElement?): USimpleNameReferenceExpression? { + return createSimpleReference(variable.name ?: return null, context) + } + + @Deprecated("use version with context parameter") + fun createReturnExpresion(expression: UExpression?, inLambda: Boolean): UReturnExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createReturnExpresion(expression, inLambda, null) + } + + override fun createReturnExpresion(expression: UExpression?, inLambda: Boolean, context: PsiElement?): UReturnExpression? { + val label = if (inLambda && context != null) getParentLambdaLabelName(context)?.let { "@$it" } ?: "" else "" + val returnExpression = psiFactory.createExpression("return$label 1") as KtReturnExpression + val sourcePsi = expression?.sourcePsi + if (sourcePsi != null) { + returnExpression.returnedExpression!!.replace(sourcePsi) + } else { + returnExpression.returnedExpression?.delete() + } + return KotlinUReturnExpression(returnExpression, null) + } + + private fun getParentLambdaLabelName(context: PsiElement): String? { + val lambdaExpression = context.getParentOfType(false) ?: return null + lambdaExpression.parent.safeAs()?.let { return it.getLabelName() } + val callExpression = lambdaExpression.getStrictParentOfType() ?: return null + callExpression.valueArguments.find { + it.getArgumentExpression()?.unpackFunctionLiteral(allowParentheses = false) === lambdaExpression + } ?: return null + return callExpression.getCallNameExpression()?.text + } + + @Deprecated("use version with context parameter") + fun createBinaryExpression( + leftOperand: UExpression, + rightOperand: UExpression, + operator: UastBinaryOperator + ): UBinaryExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createBinaryExpression(leftOperand, rightOperand, operator, null) + } + + override fun createBinaryExpression( + leftOperand: UExpression, + rightOperand: UExpression, + operator: UastBinaryOperator, + context: PsiElement? + ): UBinaryExpression? { + val binaryExpression = joinBinaryExpression(leftOperand, rightOperand, operator) ?: return null + return KotlinUBinaryExpression(binaryExpression, null) + } + + private fun joinBinaryExpression( + leftOperand: UExpression, + rightOperand: UExpression, + operator: UastBinaryOperator + ): KtBinaryExpression? { + val leftPsi = leftOperand.sourcePsi ?: return null + val rightPsi = rightOperand.sourcePsi ?: return null + + val binaryExpression = psiFactory.createExpression("a ${operator.text} b") as? KtBinaryExpression ?: return null + binaryExpression.left?.replace(leftPsi) + binaryExpression.right?.replace(rightPsi) + return binaryExpression + } + + @Deprecated("use version with context parameter") + fun createFlatBinaryExpression( + leftOperand: UExpression, + rightOperand: UExpression, + operator: UastBinaryOperator + ): UPolyadicExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createFlatBinaryExpression(leftOperand, rightOperand, operator, null) + } + + override fun createFlatBinaryExpression( + leftOperand: UExpression, + rightOperand: UExpression, + operator: UastBinaryOperator, + context: PsiElement? + ): UPolyadicExpression? { + + fun unwrapParentheses(exp: KtExpression?) { + if (exp !is KtParenthesizedExpression) return + if (!KtPsiUtil.areParenthesesUseless(exp)) return + exp.expression?.let { exp.replace(it) } + } + + val binaryExpression = joinBinaryExpression(leftOperand, rightOperand, operator) ?: return null + unwrapParentheses(binaryExpression.left) + unwrapParentheses(binaryExpression.right) + + return psiFactory.createExpression(binaryExpression.text).toUElementOfType()!! + } + + @Deprecated("use version with context parameter") + fun createBlockExpression(expressions: List): UBlockExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createBlockExpression(expressions, null) + } + + override fun createBlockExpression(expressions: List, context: PsiElement?): UBlockExpression? { + val sourceExpressions = expressions.flatMap { it.toSourcePsiFakeAware() } + val block = psiFactory.createBlock( + sourceExpressions.joinToString(separator = "\n") { "println()" } + ) + for ((placeholder, psiElement) in block.statements.zip(sourceExpressions)) { + placeholder.replace(psiElement) + } + return KotlinUBlockExpression(block, null) + } + + @Deprecated("use version with context parameter") + fun createDeclarationExpression(declarations: List): UDeclarationsExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createDeclarationExpression(declarations, null) + } + + override fun createDeclarationExpression(declarations: List, context: PsiElement?): UDeclarationsExpression? { + return object : KotlinUDeclarationsExpression(null), KotlinFakeUElement { + override var declarations: List = declarations + override fun unwrapToSourcePsi(): List = declarations.flatMap { it.toSourcePsiFakeAware() } + } + } + + override fun createLambdaExpression( + parameters: List, + body: UExpression, + context: PsiElement? + ): ULambdaExpression? { + val resolutionFacade = (context as? KtElement)?.getResolutionFacade() + val validator = (context as? KtElement)?.let { usedNamesFilter(it) } ?: { true } + + val newLambdaStatements = if (body is UBlockExpression) { + body.expressions.flatMap { member -> + when { + member is UReturnExpression -> member.returnExpression?.toSourcePsiFakeAware().orEmpty() + else -> member.toSourcePsiFakeAware() + } + } + } else + listOf(body.sourcePsi!!) + + val ktLambdaExpression = psiFactory.createLambdaExpression( + parameters.joinToString(", ") { p -> + val ktype = resolutionFacade?.let { p.type?.resolveToKotlinType(it) } + StringBuilder().apply { + append(p.suggestedName ?: ktype?.let { KotlinNameSuggester.suggestNamesByType(it, validator).firstOrNull() }) + ?: KotlinNameSuggester.suggestNameByName("v", validator) + ktype?.fqName?.toString()?.let { append(": ").append(it) } + } + }, + newLambdaStatements.joinToString("\n") { "placeholder" } + ) + + for ((old, new) in ktLambdaExpression.bodyExpression!!.statements.zip(newLambdaStatements)) { + old.replace(new) + } + + return ktLambdaExpression.toUElementOfType()!! + } + + @Deprecated("use version with context parameter") + fun createLambdaExpression(parameters: List, body: UExpression): ULambdaExpression? { + logger().error("Please switch caller to the version with a context parameter") + return createLambdaExpression(parameters, body, null) + } + + override fun createLocalVariable( + suggestedName: String?, + type: PsiType?, + initializer: UExpression, + immutable: Boolean, + context: PsiElement? + ): ULocalVariable? { + val resolutionFacade = (context as? KtElement)?.getResolutionFacade() + val validator = (context as? KtElement)?.let { usedNamesFilter(it) } ?: { true } + val ktype = resolutionFacade?.let { type?.resolveToKotlinType(it) } + + val function = psiFactory.createFunction( + buildString { + append("fun foo() { ") + append(if (immutable) "val" else "var") + append(" ") + append(suggestedName ?: ktype?.let { KotlinNameSuggester.suggestNamesByType(it, validator).firstOrNull() }) + ?: KotlinNameSuggester.suggestNameByName("v", validator) + ktype?.fqName?.toString()?.let { append(": ").append(it) } + append(" = null") + append("}") + } + ) + + val ktVariable = PsiTreeUtil.findChildOfType(function, KtVariableDeclaration::class.java)!! + val newVariable = ktVariable.initializer!!.replace(initializer.sourcePsi!!).parent + return newVariable.toUElementOfType() as ULocalVariable + } + + @Deprecated("use version with context parameter") + fun createLocalVariable( + suggestedName: String?, + type: PsiType?, + initializer: UExpression, + immutable: Boolean + ): ULocalVariable? { + logger().error("Please switch caller to the version with a context parameter") + return createLocalVariable(suggestedName, type, initializer, immutable, null) + } +} + +private fun usedNamesFilter(context: KtElement): (String) -> Boolean { + val scope = context.getResolutionScope() + return { name: String -> scope.findClassifier(Name.identifier(name), NoLookupLocation.FROM_IDE) == null } +} \ No newline at end of file