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.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion.TEST_IS_PRE_RELEASE_SYSTEM_PROPERTY
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.incremental.withIC
|
||||
import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTestBase.LibraryDependency.*
|
||||
@@ -464,6 +463,29 @@ open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() {
|
||||
buildAllModules().assertSuccessful()
|
||||
}
|
||||
|
||||
fun testPureJavaProject() {
|
||||
initProject(JVM_FULL_RUNTIME)
|
||||
|
||||
fun build() {
|
||||
var someFilesCompiled = false
|
||||
|
||||
buildCustom(CanceledStatus.NULL, TestProjectBuilderLogger(), BuildResult()) {
|
||||
project.setTestingContext(TestingContext(LookupTracker.DO_NOTHING, object : TestingBuildLogger {
|
||||
override fun compilingFiles(files: Collection<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() {
|
||||
doTestWithRuntime()
|
||||
}
|
||||
@@ -626,40 +648,6 @@ open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() {
|
||||
result.assertSuccessful()
|
||||
}
|
||||
|
||||
/*
|
||||
* Here we're checking that enabling inference in IDE doesn't affect compilation via JPS
|
||||
*
|
||||
* the following two tests are connected:
|
||||
* - testKotlinProjectWithEnabledNewInferenceInIDE checks that project is compiled when new inference is enabled only in IDE
|
||||
* - this is done via project component
|
||||
* - testKotlinProjectWithErrorsBecauseOfNewInference checks that project isn't compiled when new inference is enabled in the compiler
|
||||
*
|
||||
* So, if the former will fail => option affects JPS compilation, it's bad. Also, if the latter test fails => test is useless as it's
|
||||
* compiled with new and old inference.
|
||||
*
|
||||
*/
|
||||
fun testKotlinProjectWithEnabledNewInferenceInIDE() {
|
||||
initProject(JVM_MOCK_RUNTIME)
|
||||
val module = myProject.modules.single()
|
||||
val args = module.kotlinCompilerArguments
|
||||
args.languageVersion = LanguageVersion.KOTLIN_1_3.versionString
|
||||
myProject.kotlinCommonCompilerArguments = args
|
||||
|
||||
buildAllModules().assertSuccessful()
|
||||
}
|
||||
|
||||
fun testKotlinProjectWithErrorsBecauseOfNewInference() {
|
||||
initProject(JVM_MOCK_RUNTIME)
|
||||
val module = myProject.modules.single()
|
||||
val args = module.kotlinCompilerArguments
|
||||
args.newInference = true
|
||||
myProject.kotlinCommonCompilerArguments = args
|
||||
|
||||
val result = buildAllModules()
|
||||
result.assertFailed()
|
||||
result.checkErrors()
|
||||
}
|
||||
|
||||
private fun createKotlinJavaScriptLibraryArchive() {
|
||||
val jarFile = File(workDir, KOTLIN_JS_LIBRARY_JAR)
|
||||
try {
|
||||
|
||||
+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