202: Fix compilation
This commit is contained in:
committed by
Nikolay Krasko
parent
eb67c4519d
commit
ff7576f8e4
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
+137
@@ -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<Sdk> 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);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
}
|
||||||
+83
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+120
@@ -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<LanguageExtensionPoint<Annotator>>(LanguageAnnotators.EP_NAME.name)
|
||||||
|
val extensionPoint = pointName.getPoint(null)
|
||||||
|
|
||||||
|
val point = LanguageExtensionPoint<Annotator>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
@@ -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<LineMarkerInfo<*>> {
|
||||||
|
myFixture.doHighlighting()
|
||||||
|
|
||||||
|
return AbstractLineMarkersTest.checkHighlighting(myFixture.file, documentToAnalyze, expectedHighlighting, expectedFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Boolean>()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -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<KotlinParameterInfo> = 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
|
||||||
|
}
|
||||||
|
}
|
||||||
+111
@@ -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<KtCallElement> { 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<KtPrimaryConstructor>()?.textOffset == offset
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<SliceUsage>? {
|
||||||
|
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<KtElement>()
|
||||||
|
.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<KtTypeReference>() == 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LineMarkerInfo<*>> {
|
||||||
|
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<LineMarkerInfo<*>>) {
|
||||||
|
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<LineMarkerInfo<*>> {
|
||||||
|
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("<lineMarker") && markers.isEmpty()) {
|
||||||
|
throw AssertionError("Some line markers are expected, but nothing is present at all")
|
||||||
|
}
|
||||||
|
} catch (error: AssertionError) {
|
||||||
|
try {
|
||||||
|
val actualTextWithTestData = TagsTestDataUtil.insertInfoTags(markers, true, documentToAnalyze.text)
|
||||||
|
KotlinTestUtils.assertEqualsToFile(expectedFile, actualTextWithTestData)
|
||||||
|
} catch (failure: FileComparisonFailure) {
|
||||||
|
throw FileComparisonFailure(
|
||||||
|
error.message + "\n" + failure.message,
|
||||||
|
failure.expected,
|
||||||
|
failure.actual,
|
||||||
|
failure.filePath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return markers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.openapi.module.Module
|
||||||
|
import com.intellij.openapi.roots.ModifiableRootModel
|
||||||
|
import com.intellij.openapi.roots.OrderRootType
|
||||||
|
import com.intellij.openapi.vfs.LocalFileSystem
|
||||||
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.testFramework.ExpectedHighlightingData
|
||||||
|
import com.intellij.util.io.createFile
|
||||||
|
import com.intellij.util.io.write
|
||||||
|
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||||
|
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||||
|
import org.jetbrains.kotlin.idea.test.SdkAndMockLibraryProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
abstract class AbstractLineMarkersTestInLibrarySources : AbstractLineMarkersTest() {
|
||||||
|
|
||||||
|
private var libraryCleanPath: String? = null
|
||||||
|
|
||||||
|
private var libraryClean: File? = null
|
||||||
|
|
||||||
|
private fun getLibraryCleanPath(): String = libraryCleanPath!!
|
||||||
|
|
||||||
|
private fun getLibraryOriginalPath(): String = PluginTestCaseBase.getTestDataPathBase() + "/codeInsightInLibrary/_library"
|
||||||
|
|
||||||
|
override fun getProjectDescriptor(): SdkAndMockLibraryProjectDescriptor {
|
||||||
|
if (libraryCleanPath == null) {
|
||||||
|
val libraryClean = Files.createTempDirectory("lineMarkers_library")
|
||||||
|
val libraryOriginal = File(getLibraryOriginalPath())
|
||||||
|
libraryCleanPath = libraryClean.toString()
|
||||||
|
|
||||||
|
for (file in libraryOriginal.walkTopDown().filter { !it.isDirectory }) {
|
||||||
|
val text = file.readText().replace("</?lineMarker.*?>".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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <caret> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, InspectionToolWrapper<InspectionProfileEntry, InspectionEP>>()
|
||||||
|
|
||||||
|
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<InspectionToolWrapper<InspectionProfileEntry, InspectionEP>> {
|
||||||
|
return InspectionToolRegistrar.getInstance().createTools().filter {
|
||||||
|
it.extension.pluginDescriptor.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID
|
||||||
|
} as List<InspectionToolWrapper<InspectionProfileEntry, InspectionEP>>
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadKotlinInspectionExtensions() =
|
||||||
|
LocalInspectionEP.LOCAL_INSPECTION.extensions.filter {
|
||||||
|
it.pluginDescriptor.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun desc(tool: InspectionToolWrapper<InspectionProfileEntry, InspectionEP>): String {
|
||||||
|
return tool.toString() + " ('" + tool.descriptionContextClass + "') " +
|
||||||
|
"in " + if (tool.extension == null) null else tool.extension.pluginDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testExtensionPoints() {
|
||||||
|
val shortNames = THashMap<String, LocalInspectionEP>()
|
||||||
|
|
||||||
|
@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<String>()
|
||||||
|
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<String>()
|
||||||
|
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<String>,
|
||||||
|
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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<IntentionActionBean> {
|
||||||
|
val extensionPoint = Extensions.getRootArea().getExtensionPoint(IntentionManagerImpl.EP_INTENTION_ACTIONS)
|
||||||
|
return extensionPoint.extensions.toList().filter {
|
||||||
|
it.pluginDescriptor.pluginId == KotlinPluginUtil.KOTLIN_PLUGIN_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TreeStructureProvider>()
|
||||||
|
|
||||||
|
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 = "<bold>$text</bold>"
|
||||||
|
}
|
||||||
|
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("</bold><bold>"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return process(rootNode, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val SliceNode.sortedChildren: List<SliceNode>
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -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<HighlightInfo> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-35
@@ -58,7 +58,6 @@ import org.jetbrains.kotlin.codegen.AsmUtil
|
|||||||
import org.jetbrains.kotlin.codegen.JvmCodegenUtil
|
import org.jetbrains.kotlin.codegen.JvmCodegenUtil
|
||||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion.TEST_IS_PRE_RELEASE_SYSTEM_PROPERTY
|
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.components.LookupTracker
|
||||||
import org.jetbrains.kotlin.incremental.withIC
|
import org.jetbrains.kotlin.incremental.withIC
|
||||||
import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTestBase.LibraryDependency.*
|
import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTestBase.LibraryDependency.*
|
||||||
@@ -464,6 +463,29 @@ open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() {
|
|||||||
buildAllModules().assertSuccessful()
|
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<File>, allRemovedFilesFiles: Collection<File>) {
|
||||||
|
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() {
|
fun testKotlinJavaProject() {
|
||||||
doTestWithRuntime()
|
doTestWithRuntime()
|
||||||
}
|
}
|
||||||
@@ -626,40 +648,6 @@ open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() {
|
|||||||
result.assertSuccessful()
|
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() {
|
private fun createKotlinJavaScriptLibraryArchive() {
|
||||||
val jarFile = File(workDir, KOTLIN_JS_LIBRARY_JAR)
|
val jarFile = File(workDir, KOTLIN_JS_LIBRARY_JAR)
|
||||||
try {
|
try {
|
||||||
|
|||||||
+429
@@ -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 <T : UElement> replace(oldElement: UElement, newElement: T, elementType: Class<T>): 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<KtElement>()?.let { ShortenReferences.DEFAULT.process(it) }) {
|
||||||
|
is KtCallExpression, is KtQualifiedExpression -> replaced.cast<KtExpression>().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<KotlinUastElementFactory>().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<UExpression>,
|
||||||
|
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<KtDotQualifiedExpression>()?.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<ULiteralExpression>()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
/*override*/ fun createIntLiteral(value: Int, context: PsiElement?): ULiteralExpression {
|
||||||
|
return psiFactory.createExpression(value.toString()).toUElementOfType<ULiteralExpression>()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
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<KotlinUastElementFactory>().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<KotlinUastElementFactory>().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<KotlinUastElementFactory>().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<KotlinUastElementFactory>().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<KotlinUastElementFactory>().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<KtLambdaExpression>(false) ?: return null
|
||||||
|
lambdaExpression.parent.safeAs<KtLabeledExpression>()?.let { return it.getLabelName() }
|
||||||
|
val callExpression = lambdaExpression.getStrictParentOfType<KtCallExpression>() ?: 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<KotlinUastElementFactory>().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<KotlinUastElementFactory>().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<UExpression>): UBlockExpression? {
|
||||||
|
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
|
||||||
|
return createBlockExpression(expressions, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createBlockExpression(expressions: List<UExpression>, 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<UDeclaration>): UDeclarationsExpression? {
|
||||||
|
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
|
||||||
|
return createDeclarationExpression(declarations, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createDeclarationExpression(declarations: List<UDeclaration>, context: PsiElement?): UDeclarationsExpression? {
|
||||||
|
return object : KotlinUDeclarationsExpression(null), KotlinFakeUElement {
|
||||||
|
override var declarations: List<UDeclaration> = declarations
|
||||||
|
override fun unwrapToSourcePsi(): List<PsiElement> = declarations.flatMap { it.toSourcePsiFakeAware() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createLambdaExpression(
|
||||||
|
parameters: List<UParameterInfo>,
|
||||||
|
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<UParameterInfo>, body: UExpression): ULambdaExpression? {
|
||||||
|
logger<KotlinUastElementFactory>().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<UVariable>() as ULocalVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("use version with context parameter")
|
||||||
|
fun createLocalVariable(
|
||||||
|
suggestedName: String?,
|
||||||
|
type: PsiType?,
|
||||||
|
initializer: UExpression,
|
||||||
|
immutable: Boolean
|
||||||
|
): ULocalVariable? {
|
||||||
|
logger<KotlinUastElementFactory>().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 }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user