Fix copy-paste from KTS files

#KT-37517 Fixed
This commit is contained in:
Vladimir Dolzhenko
2020-06-30 12:24:31 +00:00
parent 0a230905ae
commit 48a9226e1c
10 changed files with 297 additions and 17 deletions
@@ -130,10 +130,7 @@ import org.jetbrains.kotlin.idea.repl.AbstractIdeReplCompletionTest
import org.jetbrains.kotlin.idea.resolve.*
import org.jetbrains.kotlin.idea.scratch.AbstractScratchLineMarkersTest
import org.jetbrains.kotlin.idea.scratch.AbstractScratchRunActionTest
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationCompletionTest
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationHighlightingTest
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationNavigationTest
import org.jetbrains.kotlin.idea.script.AbstractScriptDefinitionsOrderTest
import org.jetbrains.kotlin.idea.script.*
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerLeafGroupingTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerMultiplatformTest
import org.jetbrains.kotlin.idea.slicer.AbstractSlicerNullnessGroupingTest
@@ -891,6 +888,23 @@ fun main(args: Array<String>) {
model("script/definition/completion", extension = null, recursive = false)
}
testClass<AbstractScriptConfigurationInsertImportOnPasteTest> {
model(
"script/definition/imports",
testMethod = "doTestCopy",
testClassName = "Copy",
extension = null,
recursive = false
)
model(
"script/definition/imports",
testMethod = "doTestCut",
testClassName = "Cut",
extension = null,
recursive = false
)
}
testClass<AbstractScriptDefinitionsOrderTest> {
model("script/definition/order", extension = null, recursive = false)
}
@@ -27,11 +27,13 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.allowResolveInDispatchThread
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.codeInsight.ReviewAddedImports.reviewAddedImports
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
import org.jetbrains.kotlin.idea.core.util.end
import org.jetbrains.kotlin.idea.core.util.range
import org.jetbrains.kotlin.idea.core.util.start
@@ -48,6 +50,7 @@ import org.jetbrains.kotlin.idea.util.getSourceRoot
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.kdoc.psi.api.KDocElement
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parsing.KotlinParserDefinition.Companion.STD_SCRIPT_EXT
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
@@ -428,15 +431,32 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
""".trimIndent()
val sourceFileUrl = transferableData.sourceFileUrl
val script = !sourceFileUrl.endsWith(KotlinFileType.EXTENSION)
val extension = run {
if (!script) return@run KotlinFileType.EXTENSION
ScriptDefinitionsManager.getInstance(project).getKnownFilenameExtensions().filter {
sourceFileUrl.endsWith(it)
}.sortedByDescending { it.length }.firstOrNull() ?: KotlinFileType.EXTENSION
}
val dummyOriginalFile = runReadAction {
KtPsiFactory(project)
.createAnalyzableFile(
"dummy-original.kt",
"dummy-original.$extension",
"$dummyOrigFileProlog${transferableData.sourceText}",
ctxFile
)
}
if (script) {
val originalFile = runReadAction {
val virtualFile = VirtualFileManager.getInstance().findFileByUrl(sourceFileUrl) ?: return@runReadAction null
PsiManager.getInstance(project).findFile(virtualFile)
} ?: return emptyList()
dummyOriginalFile.originalFile = originalFile
}
val offsetDelta = dummyOrigFileProlog.length - transferableData.sourceTextOffset
val dummyOriginalFileTextRanges =
@@ -476,7 +496,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
// - those source package imports those are not present in a fake package
// - all rest imports
val sourceImportPrefix = "import $sourcePkgName"
val sourceImportPrefix = "import ${if (sourcePkgName.isEmpty()) fakePkgName else sourcePkgName}"
val fakeImportPrefix = "import $fakePkgName"
val affectedSourcePkgImports = imports.filter { it.startsWith(sourceImportPrefix) }
@@ -511,7 +531,7 @@ class KotlinCopyPasteReferenceProcessor : CopyPastePostProcessor<BasicKotlinRefe
return """
${joinLines(dummyFileImports)}
import ${sourcePkgName}.*
${if (sourcePkgName.isNotEmpty()) "import ${sourcePkgName}.*" else ""}
${joinLines(filteredImports)}
"""
}
@@ -0,0 +1,7 @@
package to
val relocatedJarContents = configurations.creating {
attributes {
attribute(USAGE_ATTRIBUTE, JAVA_RUNTIME)
}
}
@@ -0,0 +1 @@
@@ -0,0 +1,8 @@
import custom.scriptDefinition.KotlinPlatformType.Companion.attribute
<selection>
val relocatedJarContents = configurations.creating {
attributes {
attribute(USAGE_ATTRIBUTE, JAVA_RUNTIME)
}
}</selection>
@@ -0,0 +1,2 @@
package to
<caret>
@@ -0,0 +1,58 @@
package custom.scriptDefinition
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.jvm.dependenciesFromClassContext
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.api.*
@KotlinScript(
displayName = "Definition for tests",
fileExtension = "kts",
compilationConfiguration = TemplateDefinition::class
)
open class Template(val args: Array<String>)
@Suppress("UNCHECKED_CAST")
object TemplateDefinition : ScriptCompilationConfiguration(
{
baseClass(Base::class)
jvm {
dependenciesFromClassContext(TemplateDefinition::class)
}
ide {
acceptedLocations(ScriptAcceptedLocation.Everywhere)
}
}
)
data class Attr(val name: String, val clazz: Class<*>)
class KotlinPlatformType {
companion object {
val attribute = Attr("org.gradle.usage", Attr::class.java)
}
}
class AttributesBuilder {
fun attribute(attribute: Attr, name: String) {}
}
class ConfigurationsBuilder {
fun attributes(configuration: AttributesBuilder.() -> Unit) {
AttributesBuilder().apply(configuration)
}
}
class Configurations {
fun creating(initializer: ConfigurationsBuilder.() -> Unit) {
ConfigurationsBuilder().apply(initializer)
}
}
open class Base {
val configurations = Configurations()
val USAGE_ATTRIBUTE = Attr("org.gradle.usage", Attr::class.java)
val JAVA_RUNTIME = "java-runtime"
}
@@ -0,0 +1,97 @@
/*
* 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.script
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.codeInsight.KotlinCopyPasteReferenceProcessor
import org.jetbrains.kotlin.idea.codeInsight.ReviewAddedImports
import org.jetbrains.kotlin.idea.test.dumpTextWithErrors
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractScriptConfigurationInsertImportOnPasteTest : AbstractScriptConfigurationTest() {
private val NO_ERRORS_DUMP_DIRECTIVE = "// NO_ERRORS_DUMP"
private val DELETE_DEPENDENCIES_BEFORE_PASTE_DIRECTIVE = "// DELETE_DEPENDENCIES_BEFORE_PASTE"
protected fun doTestCut(path: String) {
doTestAction(IdeActions.ACTION_CUT, path)
}
protected fun doTestCopy(path: String) {
doTestAction(IdeActions.ACTION_COPY, path)
}
private fun doTestAction(cutOrCopy: String, unused: String) {
val testDataFile = testDataFile()
val testFile = File(testDataFile, "script.kts")
val testFileText = FileUtil.loadFile(testFile, true)
val noErrorsDump = InTextDirectivesUtils.isDirectiveDefined(testFileText, NO_ERRORS_DUMP_DIRECTIVE)
val scriptFile = configureScriptFile(testDataFile.path)
if (!noErrorsDump) {
val sourceFile = scriptFile.toKtFile()
val dumpTextWithErrors = sourceFile.dumpTextWithErrors()
assertEquals(sourceFile.text, dumpTextWithErrors)
}
performEditorAction(cutOrCopy)
KotlinCopyPasteReferenceProcessor.declarationsToImportSuggested = emptyList()
ReviewAddedImports.importsToBeReviewed = emptyList()
val importsToBeDeletedFile = File(testDataFile, "imports_to_delete")
ReviewAddedImports.importsToBeDeleted = if (importsToBeDeletedFile.exists()) {
importsToBeDeletedFile.readLines()
} else {
emptyList()
}
val targetScript = createFileAndSyncDependencies(File(testDataFile, "target.kts"))
performEditorAction(IdeActions.ACTION_PASTE)
UIUtil.dispatchAllInvocationEvents()
val namesToImportDump = KotlinCopyPasteReferenceProcessor.declarationsToImportSuggested.joinToString("\n")
KotlinTestUtils.assertEqualsToFile(File(testDataFile, "expected.names"), namesToImportDump)
assertEquals(namesToImportDump, ReviewAddedImports.importsToBeReviewed.joinToString("\n"))
val sourceFile = targetScript.toKtFile()
val sourceText = if (noErrorsDump)
sourceFile.text
else
sourceFile.dumpTextWithErrors()
KotlinTestUtils.assertEqualsToFile(File(testDataFile, "expected.kts"), sourceText)
}
private fun VirtualFile.toKtFile() = PsiManager.getInstance(project).findFile(this) as KtFile
private fun performEditorAction(actionId: String): Boolean {
val dataContext = getEditorDataContext()
val managerEx = ActionManagerEx.getInstanceEx()
val action = managerEx.getAction(actionId)
val event = AnActionEvent(null, dataContext, ActionPlaces.UNKNOWN, Presentation(), managerEx, 0)
action.beforeActionPerformedUpdate(event)
if (!event.presentation.isEnabled) {
return false
}
ActionUtil.performActionDumbAwareWithCallbacks(action, event, dataContext)
return true
}
private fun getEditorDataContext() = (editor as EditorEx).dataContext
}
@@ -27,14 +27,15 @@ import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingUtil
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationTest.Companion.useDefaultTemplate
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.getProjectJdkTableSafe
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.MockLibraryUtil
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.test.TestMetadata
import org.jetbrains.kotlin.test.util.addDependency
import org.jetbrains.kotlin.test.util.projectLibrary
import org.jetbrains.kotlin.utils.PathUtil
@@ -43,13 +44,10 @@ import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_COMMON_JAR
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_JVM_JAR
import java.io.File
import java.util.regex.Pattern
import kotlin.reflect.full.findAnnotation
import kotlin.script.dependencies.Environment
import kotlin.script.experimental.api.ScriptDiagnostic
private val validKeys = setOf("javaHome", "sources", "classpath", "imports", "template-classes-names")
private const val useDefaultTemplate = "// DEPENDENCIES:"
private const val templatesSettings = "// TEMPLATES: "
// some bugs can only be reproduced when some module and script have intersecting library dependencies
private const val configureConflictingModule = "// CONFLICTING_MODULE"
@@ -62,6 +60,24 @@ internal val switches = listOf(
abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
companion object {
private const val SCRIPT_NAME = "script.kts"
val validKeys = setOf("javaHome", "sources", "classpath", "imports", "template-classes-names")
const val useDefaultTemplate = "// DEPENDENCIES:"
const val templatesSettings = "// TEMPLATES: "
}
protected fun testDataFile(fileName: String): File = File(testDataPath, fileName)
protected fun testDataFile(): File = testDataFile(fileName())
protected fun testPath(fileName: String = fileName()): String = testDataFile(fileName).toString()
protected fun testPath(): String = testPath(fileName())
protected open fun fileName(): String = KotlinTestUtils.getTestDataFileName(this::class.java, this.name) ?: (getTestName(false) + ".kt")
override fun getTestDataPath(): String {
return this::class.findAnnotation<TestMetadata>()?.value ?: super.getTestDataPath()
}
override fun setUpModule() {
@@ -84,12 +100,12 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
}
}
protected fun configureScriptFile(path: String) {
protected fun configureScriptFile(path: String): VirtualFile {
val mainScriptFile = findMainScript(path)
configureScriptFile(path, mainScriptFile)
return configureScriptFile(path, mainScriptFile)
}
protected fun configureScriptFile(path: String, mainScriptFile: File) {
protected fun configureScriptFile(path: String, mainScriptFile: File): VirtualFile {
val environment = createScriptEnvironment(mainScriptFile)
registerScriptTemplateProvider(environment)
@@ -136,7 +152,7 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
}
}
createFileAndSyncDependencies(mainScriptFile)
return createFileAndSyncDependencies(mainScriptFile)
}
private val oldScripClasspath: String? = System.getProperty("kotlin.script.classpath")
@@ -268,7 +284,7 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
}
}
protected fun createFileAndSyncDependencies(scriptFile: File) {
protected fun createFileAndSyncDependencies(scriptFile: File): VirtualFile {
var script: VirtualFile? = null
if (module != null) {
script = module.moduleFile?.parent?.findChild(scriptFile.name)
@@ -284,6 +300,7 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
configureByExistingFile(script)
loadScriptConfigurationSynchronously(script)
return script!!
}
protected open fun loadScriptConfigurationSynchronously(script: VirtualFile) {
@@ -0,0 +1,56 @@
/*
* 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.script;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@RunWith(JUnit3RunnerWithInners.class)
public class ScriptConfigurationInsertImportOnPasteTestGenerated extends AbstractScriptConfigurationInsertImportOnPasteTest {
@TestMetadata("idea/testData/script/definition/imports")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Copy extends AbstractScriptConfigurationInsertImportOnPasteTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestCopy, this, testDataFilePath);
}
public void testAllFilesPresentInCopy() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/script/definition/imports"), Pattern.compile("^([^\\.]+)$"), null, false);
}
@TestMetadata("implicitImport")
public void testImplicitImport() throws Exception {
runTest("idea/testData/script/definition/imports/implicitImport/");
}
}
@TestMetadata("idea/testData/script/definition/imports")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Cut extends AbstractScriptConfigurationInsertImportOnPasteTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestCut, this, testDataFilePath);
}
public void testAllFilesPresentInCut() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/script/definition/imports"), Pattern.compile("^([^\\.]+)$"), null, false);
}
@TestMetadata("implicitImport")
public void testImplicitImport() throws Exception {
runTest("idea/testData/script/definition/imports/implicitImport/");
}
}
}