diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt index 1dd49aa3ffd..2ee99ea3f13 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt @@ -87,7 +87,7 @@ public abstract class JetSelfTargetingIntention( return !isIntentionBaseInspectionEnabled(project, target) } - private fun isIntentionBaseInspectionEnabled(project: Project, target: TElement): Boolean { + protected fun isIntentionBaseInspectionEnabled(project: Project, target: TElement): Boolean { val inspection = findInspection(javaClass) ?: return false val key = HighlightDisplayKey.find(inspection.shortName) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index 4c57b39d2cb..e0061d53ab7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -73,6 +73,7 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.string.collapseSpaces import org.jetbrains.kotlin.j2k.ConverterSettings import org.jetbrains.kotlin.j2k.JavaToKotlinConverter +import org.jetbrains.kotlin.name.FqNameBase import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.psi.psiUtil.* @@ -664,4 +665,6 @@ public fun invokeOnceOnCommandFinish(action: () -> Unit) { commandProcessor.addCommandListener(listener) } -public fun String.quoteIfNeeded(): String = if (KotlinNameSuggester.isIdentifier(this)) this else "`$this`" \ No newline at end of file +public fun String.quoteIfNeeded(): String = if (KotlinNameSuggester.isIdentifier(this)) this else "`$this`" + +public fun FqNameBase.hasIdentifiersOnly(): Boolean = pathSegments().all { KotlinNameSuggester.isIdentifier(it.asString()) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageIntention.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageIntention.kt index f074ac956eb..6e4056ea5c9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageIntention.kt @@ -16,22 +16,26 @@ package org.jetbrains.kotlin.idea.refactoring.move.changePackage; -import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.template.* -import com.intellij.codeInsight.template.impl.TemplateManagerImpl import com.intellij.codeInsight.template.impl.TemplateState import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor -import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils -import org.jetbrains.kotlin.idea.core.refactoring.canRefactor +import org.jetbrains.kotlin.idea.core.refactoring.hasIdentifiersOnly import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingOffsetIndependentIntention +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.psi.JetPackageDirective public class ChangePackageIntention: JetSelfTargetingOffsetIndependentIntention(javaClass(), "Change package") { + companion object { + private val PACKAGE_NAME_VAR = "PACKAGE_NAME" + } + override fun isApplicableTo(element: JetPackageDirective) = element.getPackageNameExpression() != null override fun applyTo(element: JetPackageDirective, editor: Editor) { @@ -48,25 +52,47 @@ public class ChangePackageIntention: JetSelfTargetingOffsetIndependentIntention< val builder = TemplateBuilderImpl(file) builder.replaceElement( nameExpression, + PACKAGE_NAME_VAR, object: Expression() { override fun calculateQuickResult(context: ExpressionContext?) = TextResult(currentName) override fun calculateResult(context: ExpressionContext?) = TextResult(currentName) override fun calculateLookupItems(context: ExpressionContext?) = arrayOf(LookupElementBuilder.create(currentName)) - } + }, + true ) + + var enteredName: String? = null + var affectedRange: TextRange? = null + editor.getCaretModel().moveToOffset(0) TemplateManager.getInstance(project).startTemplate( editor, builder.buildInlineTemplate(), object: TemplateEditingAdapter() { + override fun beforeTemplateFinished(state: TemplateState?, template: Template?) { + if (state == null) return + enteredName = state.getVariableValue(PACKAGE_NAME_VAR)!!.text + affectedRange = state.getSegmentRange(0) + } + override fun templateFinished(template: Template?, brokenOff: Boolean) { - if (!brokenOff) { - // Restore original name and run refactoring - val packageDirective = file.getPackageDirective()!! - val newFqName = packageDirective.getFqName() - packageDirective.setFqName(FqName(currentName)) - KotlinChangePackageRefactoring(file).run(newFqName) + if (brokenOff || enteredName == null || affectedRange == null) return + + // Restore original name and run refactoring + + val document = editor.document + project.executeWriteCommand(text) { + document.replaceString(affectedRange!!.startOffset, affectedRange!!.endOffset, currentName) } + PsiDocumentManager.getInstance(project).commitDocument(document) + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document) + + if (!FqNameUnsafe(enteredName!!).hasIdentifiersOnly()) { + CodeInsightUtils.showErrorHint(project, editor, "$enteredName is not a valid package name", "Change package", null) + return + } + + KotlinChangePackageRefactoring(file).run(FqName(enteredName!!)) } } ) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageToMatchDirectoryIntention.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageToMatchDirectoryIntention.kt index 2857512202e..40615bbe67e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageToMatchDirectoryIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/ChangePackageToMatchDirectoryIntention.kt @@ -17,11 +17,11 @@ package org.jetbrains.kotlin.idea.refactoring.move.changePackage import com.intellij.openapi.editor.Editor -import org.jetbrains.kotlin.idea.core.refactoring.canRefactor -import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingOffsetIndependentIntention -import org.jetbrains.kotlin.psi.JetPackageDirective import org.jetbrains.kotlin.idea.core.getFqNameByDirectory import org.jetbrains.kotlin.idea.core.packageMatchesDirectory +import org.jetbrains.kotlin.idea.core.refactoring.hasIdentifiersOnly +import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingOffsetIndependentIntention +import org.jetbrains.kotlin.psi.JetPackageDirective public class ChangePackageToMatchDirectoryIntention : JetSelfTargetingOffsetIndependentIntention( javaClass(), "", "Change file's package to match directory" @@ -30,11 +30,23 @@ public class ChangePackageToMatchDirectoryIntention : JetSelfTargetingOffsetInde val file = element.getContainingJetFile() if (file.packageMatchesDirectory()) return false - setText("Change file's package to '${file.getFqNameByDirectory().asString()}'") + val fqNameByDirectory = file.getFqNameByDirectory() + if (!fqNameByDirectory.hasIdentifiersOnly()) { + if (isIntentionBaseInspectionEnabled(file.project, element)) { + text = "File package doesn't match directory" + return true + } + return false + } + + text = "Change file's package to '${fqNameByDirectory.asString()}'" return true } override fun applyTo(element: JetPackageDirective, editor: Editor) { - KotlinChangePackageRefactoring(element.getContainingJetFile()).run(element.getContainingFile().getFqNameByDirectory()) + val file = element.getContainingJetFile() + val newFqName = file.getFqNameByDirectory() + if (!newFqName.hasIdentifiersOnly()) return + KotlinChangePackageRefactoring(file).run(newFqName) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt index 94e6b20a086..455312e1673 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt @@ -24,8 +24,10 @@ import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectori import com.intellij.usageView.UsageInfo import org.jetbrains.kotlin.idea.JetLanguage import org.jetbrains.kotlin.idea.codeInsight.shorten.runWithElementsToShortenIsEmptyIgnored +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.packageMatchesDirectory +import org.jetbrains.kotlin.idea.core.refactoring.hasIdentifiersOnly import org.jetbrains.kotlin.idea.refactoring.move.* import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.* import org.jetbrains.kotlin.name.FqNameUnsafe @@ -57,6 +59,7 @@ public class MoveKotlinFileHandler : MoveFileHandler() { val newPackageName = FqNameUnsafe(newPackage.qualifiedName) if (oldPackageName.asString() == newPackageName.asString()) return null + if (newPackageName != UNKNOWN_PACKAGE_FQ_NAME && !newPackageName.hasIdentifiersOnly()) return null return PackageNameInfo(oldPackageName, newPackageName) } diff --git a/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/after/p/123/main.kt b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/after/p/123/main.kt new file mode 100644 index 00000000000..08a269a0755 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/after/p/123/main.kt @@ -0,0 +1,5 @@ +package p + +fun foo() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/after/q/usage.kt b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/after/q/usage.kt new file mode 100644 index 00000000000..0396b8271b8 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/after/q/usage.kt @@ -0,0 +1,7 @@ +package q + +import p.foo + +fun bar() { + foo() +} \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/before/p/main.kt b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/before/p/main.kt new file mode 100644 index 00000000000..08a269a0755 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/before/p/main.kt @@ -0,0 +1,5 @@ +package p + +fun foo() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/before/q/usage.kt b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/before/q/usage.kt new file mode 100644 index 00000000000..0396b8271b8 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/before/q/usage.kt @@ -0,0 +1,7 @@ +package q + +import p.foo + +fun bar() { + foo() +} \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/moveFileToInvalidPackage.test b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/moveFileToInvalidPackage.test new file mode 100644 index 00000000000..d72aab8e065 --- /dev/null +++ b/idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/moveFileToInvalidPackage.test @@ -0,0 +1,5 @@ +{ + "mainFile": "p/main.kt", + "type": "MOVE_FILES", + "targetPackage": "p.123" +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/JetMoveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/JetMoveTestGenerated.java index 14e005c3043..0966e43cf47 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/JetMoveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/JetMoveTestGenerated.java @@ -245,6 +245,12 @@ public class JetMoveTestGenerated extends AbstractJetMoveTest { doTest(fileName); } + @TestMetadata("kotlin/moveFile/invalidTargetPackage/moveFileToInvalidPackage.test") + public void testKotlin_moveFile_invalidTargetPackage_MoveFileToInvalidPackage() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/moveFileToInvalidPackage.test"); + doTest(fileName); + } + @TestMetadata("kotlin/moveFile/moveFileToFile/moveFileToFile.test") public void testKotlin_moveFile_moveFileToFile_MoveFileToFile() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveFile/moveFileToFile/moveFileToFile.test");