提交 ff7576f8 编写于 作者: N Nikita Bobko 提交者: Nikolay Krasko

202: Fix compilation

上级 eb67c451
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.actions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.ide.actions.RevealFileAction
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.MessageType
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.wm.WindowManager
import com.intellij.psi.PsiFile
import com.intellij.ui.BrowserHyperlinkListener
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import java.io.File
class ShowKotlinGradleDslLogs : IntentionAction, AnAction(), DumbAware {
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
openLogsDirIfPresent(project)
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
openLogsDirIfPresent(project)
}
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?) = RevealFileAction.isSupported()
override fun update(e: AnActionEvent) {
val presentation = e.presentation
presentation.isEnabledAndVisible = e.project != null && RevealFileAction.isSupported()
presentation.text = NAME
}
private fun openLogsDirIfPresent(project: Project) {
val logsDir = findLogsDir()
if (logsDir != null) {
RevealFileAction.openDirectory(logsDir)
} else {
val parent = WindowManager.getInstance().getStatusBar(project)?.component
?: WindowManager.getInstance().findVisibleFrame()?.rootPane
JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(
KotlinIdeaGradleBundle.message(
"text.gradle.dsl.logs.cannot.be.found.automatically.see.how.to.find.logs",
gradleTroubleshootingLink
),
MessageType.ERROR,
BrowserHyperlinkListener.INSTANCE
)
.setFadeoutTime(5000)
.createBalloon()
.showInCenterOf(parent)
}
}
/** The way how to find Gradle logs is described here
* @see org.jetbrains.kotlin.idea.actions.ShowKotlinGradleDslLogs.gradleTroubleshootingLink
*/
private fun findLogsDir(): File? {
val userHome = System.getProperty("user.home")
return when {
SystemInfo.isMac -> File("$userHome/Library/Logs/gradle-kotlin-dsl")
SystemInfo.isLinux -> File("$userHome/.gradle-kotlin-dsl/logs")
SystemInfo.isWindows -> File("$userHome/AppData/Local/gradle-kotlin-dsl/log")
else -> null
}.takeIf { it?.exists() == true }
}
override fun startInWriteAction() = false
override fun getText() = NAME
override fun getFamilyName() = NAME
companion object {
private const val gradleTroubleshootingLink = "https://docs.gradle.org/current/userguide/kotlin_dsl.html#troubleshooting"
val NAME = KotlinIdeaGradleBundle.message("action.text.show.kotlin.gradle.dsl.logs.in", RevealFileAction.getFileManagerName())
}
}
\ No newline at end of file
/*
* 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);
}
}));
}
}
/*
* 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) {
}
/*
* 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()
)
}
}
/*
* 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)
}
}
\ No newline at end of file
/*
* 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)
}
}
\ No newline at end of file
/*
* 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
}
}
\ No newline at end of file
/*
* 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
}
}
\ No newline at end of file
/*
* 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
}
}
/*
* 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))
}
}
}
\ No newline at end of file
/*
* 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
}
}
}
/*
* 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()
}
}
\ No newline at end of file
/*
* 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
}
}
/*
* 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)")
}
}
}
}
/*
* 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
}
}
}
\ No newline at end of file
/*
* 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)
}
\ No newline at end of file
/*
* 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
}
}
\ No newline at end of file
......@@ -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 {
......
/*
* 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 }
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册