Move: Implement inspection which reports mismatch between package directive and containing directory

This commit is contained in:
Alexey Sedunov
2015-04-29 11:49:18 +03:00
parent fe96a8563a
commit d096e74c10
66 changed files with 612 additions and 20 deletions
@@ -41,6 +41,7 @@ import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.PsiComment
import org.jetbrains.kotlin.resolve.calls.CallTransformer.CallForImplicitInvoke
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.name.FqName
public fun JetCallElement.getCallNameExpression(): JetSimpleNameExpression? {
val calleeExpression = getCalleeExpression()
@@ -534,3 +535,10 @@ inline fun <reified T : JetElement, R> flatMapDescendantsOfTypeVisitor(
inline fun <reified T : JetElement> PsiElement.forEachDescendantsOfType(noinline block: (T) -> Unit) =
accept(forEachDescendantOfTypeVisitor(block))
public fun PsiFile.getFqNameByDirectory(): FqName {
val qualifiedNameByDirectory = getParent()?.getPackage()?.getQualifiedName()
return qualifiedNameByDirectory?.let { FqName(it) } ?: FqName.ROOT
}
public fun JetFile.packageMatchesDirectory(): Boolean = getPackageFqName() == getFqNameByDirectory()
@@ -25,38 +25,42 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingOffsetIndependentIntention
import org.jetbrains.kotlin.psi.JetElement
import java.util.*
public abstract class IntentionBasedInspection<T: JetElement>(
protected val intention: JetSelfTargetingOffsetIndependentIntention<T>
protected val intentions: List<JetSelfTargetingOffsetIndependentIntention<T>>,
protected val elementType: Class<T>
) : AbstractKotlinInspection() {
constructor(intention: JetSelfTargetingOffsetIndependentIntention<T>): this(listOf(intention), intention.elementType)
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
return object: PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
if (!intention.elementType.isInstance(element)) return
if (!elementType.isInstance(element) || element.getTextLength() == 0) return
[suppress("UNCHECKED_CAST")]
val targetElement = element as T
if (!intention.isApplicableTo(targetElement)) return
for (intention in intentions) {
if (!intention.isApplicableTo(targetElement)) continue
val fix = object: LocalQuickFix {
override fun getFamilyName(): String {
return getName()
}
val fix = object: LocalQuickFix {
private val text = intention.getText()
override fun getName(): String {
return intention.getText()
}
override fun getFamilyName() = getName()
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
targetElement.getOrCreateEditor()?.let { editor ->
editor.getCaretModel().moveToOffset(targetElement.getTextOffset())
intention.applyTo(targetElement, editor)
override fun getName() = text
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
targetElement.getOrCreateEditor()?.let { editor ->
editor.getCaretModel().moveToOffset(targetElement.getTextOffset())
intention.applyTo(targetElement, editor)
}
}
}
}
holder.registerProblem(targetElement, intention.getText(), problemHighlightType, fix)
holder.registerProblem(targetElement, intention.getText(), problemHighlightType, fix)
}
}
}
}
@@ -0,0 +1,5 @@
<html>
<body>
Reports file's package directive not matching location of the file
</body>
</html>
@@ -0,0 +1,8 @@
// File: bar/foo/test.kt
<spot>package bar.foo</spot>
class MyClass: OtherClass {
fun myFun() {
}
}
@@ -0,0 +1,10 @@
// File: bar/foo/test.kt
<spot>package foo.bar</spot>
import bar.foo.OtherClass
class MyClass: OtherClass {
fun myFun() {
}
}
@@ -0,0 +1,6 @@
<html>
<body>
This intention changes file's package directive so that it matches containing directory.
All import directives and usages are updated accordingly
</body>
</html>
@@ -0,0 +1,10 @@
// File: foo/bar/test.kt
<spot>package foo.bar</spot>
import bar.foo.OtherClass
class MyClass: OtherClass {
fun myFun() {
}
}
@@ -0,0 +1,10 @@
// File: bar/foo/test.kt
<spot>package foo.bar</spot>
import bar.foo.OtherClass
class MyClass: OtherClass {
fun myFun() {
}
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention moves file to the directory matching its package directive.
</body>
</html>
+18
View File
@@ -853,6 +853,16 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.refactoring.move.changePackage.MoveFileToPackageMatchingDirectoryIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.refactoring.move.changePackage.ChangePackageToMatchDirectoryIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ExplicitGetInspection"
displayName="Explicit 'get'"
groupName="Kotlin"
@@ -915,6 +925,14 @@
enabledByDefault="true"
level="WARNING"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.refactoring.move.changePackage.PackageDirectoryMismatchInspection"
displayName="Package name does not match containing directory"
groupName="Kotlin"
enabledByDefault="true"
level="WARNING"
/>
<project.converterProvider implementation="org.jetbrains.kotlin.idea.converters.JetRunConfigurationSettingsFormatConverterProvider"/>
</extensions>
@@ -0,0 +1,40 @@
/*
* 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.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.psi.psiUtil.getFqNameByDirectory
import org.jetbrains.kotlin.psi.psiUtil.packageMatchesDirectory
public class ChangePackageToMatchDirectoryIntention : JetSelfTargetingOffsetIndependentIntention<JetPackageDirective>(
javaClass(), "", "Change file's package to match directory"
) {
override fun isApplicableTo(element: JetPackageDirective): Boolean {
val file = element.getContainingJetFile()
if (file.packageMatchesDirectory()) return false
setText("Change file's package to '${file.getFqNameByDirectory().asString()}'")
return true
}
override fun applyTo(element: JetPackageDirective, editor: Editor) {
KotlinChangePackageRefactoring(element.getContainingJetFile()).run(element.getContainingFile().getFqNameByDirectory())
}
}
@@ -0,0 +1,60 @@
/*
* 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.refactoring.move.changePackage
import com.intellij.CommonBundle
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.ui.Messages
import com.intellij.psi.JavaPsiFacade
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
import com.intellij.refactoring.util.RefactoringMessageUtil
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.psi.psiUtil.packageMatchesDirectory
public class MoveFileToPackageMatchingDirectoryIntention : JetSelfTargetingOffsetIndependentIntention<JetPackageDirective>(
javaClass(), "", "Move file to package-matching directory"
) {
override fun isApplicableTo(element: JetPackageDirective): Boolean {
if (element.getContainingJetFile().packageMatchesDirectory()) return false
val qualifiedName = element.getQualifiedName()
val dirName = if (qualifiedName.isEmpty()) "source root" else "'${qualifiedName.replace('.', '/')}'"
setText("Move file to $dirName")
return true
}
override fun applyTo(element: JetPackageDirective, editor: Editor) {
val file = element.getContainingJetFile()
val project = file.getProject()
val targetDirectory = MoveClassesOrPackagesUtil.chooseDestinationPackage(
project,
element.getQualifiedName(),
file.getContainingDirectory()
) ?: return
RefactoringMessageUtil.checkCanCreateFile(targetDirectory, file.getName())?.let {
Messages.showMessageDialog(project, it, CommonBundle.getErrorTitle(), Messages.getErrorIcon())
return
}
MoveFilesOrDirectoriesUtil.doMoveFile(file, targetDirectory)
}
}
@@ -0,0 +1,25 @@
/*
* 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.refactoring.move.changePackage
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
import org.jetbrains.kotlin.psi.JetPackageDirective
public class PackageDirectoryMismatchInspection: IntentionBasedInspection<JetPackageDirective>(
listOf(MoveFileToPackageMatchingDirectoryIntention(), ChangePackageToMatchDirectoryIntention()),
javaClass()
)
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getPackage
import org.jetbrains.kotlin.psi.psiUtil.packageMatchesDirectory
public class MoveKotlinFileHandler : MoveFileHandler() {
private var packageNameInfo: PackageNameInfo? = null
@@ -46,10 +47,6 @@ public class MoveKotlinFileHandler : MoveFileHandler() {
declarationMoveProcessor = null
}
private fun JetFile.packageMatchesDirectory(): Boolean {
return getPackageFqName().asString() == getParent()?.getPackage()?.getQualifiedName()
}
override fun canProcessElement(element: PsiFile?): Boolean {
if (element is PsiCompiledElement || element !is JetFile) return false
return !JavaProjectRootsUtil.isOutsideJavaSourceRoot(element)
@@ -0,0 +1,5 @@
package bar
class Foo {
}
@@ -0,0 +1,5 @@
package foo
class Foo {
}
@@ -0,0 +1,19 @@
<problems>
<problem>
<file>barPackageMismatched.kt</file>
<line>1</line>
<module>testPackageDirectoryMismatch_PackageDirectoryMismatch_0</module>
<entry_point TYPE="file" FQNAME="barPackageMismatched.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Package name does not match containing directory</problem_class>
<description>Move file to 'bar'</description>
</problem>
<problem>
<file>barPackageMismatched.kt</file>
<line>1</line>
<module>testPackageDirectoryMismatch_PackageDirectoryMismatch_0</module>
<entry_point TYPE="file" FQNAME="barPackageMismatched.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Package name does not match containing directory</problem_class>
<description>Change file's package to 'foo'</description>
</problem>
</problems>
@@ -0,0 +1,3 @@
{
"inspectionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.PackageDirectoryMismatchInspection"
}
@@ -0,0 +1,7 @@
package
class Foo
fun foo() {
}
@@ -0,0 +1,7 @@
<caret>package
class Foo
fun foo() {
}
@@ -0,0 +1,5 @@
{
"mainFile": "source/test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.MoveFileToPackageMatchingDirectoryIntention",
"intentionText": "Move file to source root"
}
@@ -0,0 +1,7 @@
package target
class Foo
fun foo() {
}
@@ -0,0 +1,7 @@
<caret>package target
class Foo
fun foo() {
}
@@ -0,0 +1,5 @@
{
"mainFile": "source/test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.MoveFileToPackageMatchingDirectoryIntention",
"intentionText": "Move file to 'target'"
}
@@ -0,0 +1,9 @@
package foo.bar
class Foo {
}
fun foo() {
}
@@ -0,0 +1,9 @@
pack<caret>age foo.bar
class Foo {
}
fun foo() {
}
@@ -0,0 +1,5 @@
{
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.MoveFileToPackageMatchingDirectoryIntention",
"isApplicable": "false",
"mainFile": "foo/bar/test.kt"
}
@@ -0,0 +1,5 @@
class Foo
fun foo() {
}
@@ -0,0 +1,6 @@
class Test {
static void test() {
new Foo();
_DefaultPackage.foo();
}
}
@@ -0,0 +1,8 @@
import static _DefaultPackage.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,7 @@
import Foo
import foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,7 @@
<caret>package source
class Foo
fun foo() {
}
@@ -0,0 +1,6 @@
class Test {
static void test() {
new source.Foo();
source.SourcePackage.foo();
}
}
@@ -0,0 +1,4 @@
fun test() {
source.Foo()
source.foo()
}
@@ -0,0 +1,9 @@
import source.Foo;
import static source.SourcePackage.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,7 @@
import source.Foo
import source.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,5 @@
{
"mainFile": "test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.ChangePackageToMatchDirectoryIntention",
"intentionText": "Change file's package to ''"
}
@@ -0,0 +1,7 @@
package target
class Foo
fun foo() {
}
@@ -0,0 +1,8 @@
package target;
class Test {
static void test() {
new Foo();
TargetPackage.foo();
}
}
@@ -0,0 +1,6 @@
package target
fun test() {
Foo()
foo()
}
@@ -0,0 +1,10 @@
package target;
import static target.TargetPackage.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package target
import target.Foo
import target.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,11 @@
package usages;
import target.Foo;
import target.TargetPackage;
class Test {
static void test() {
new Foo();
TargetPackage.foo();
}
}
@@ -0,0 +1,9 @@
package usages
import target.Foo
import target.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,11 @@
package usages;
import target.Foo;
import static target.TargetPackage.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package usages
import target.Foo
import target.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,7 @@
<caret>package source
class Foo
fun foo() {
}
@@ -0,0 +1,8 @@
package target;
class Test {
static void test() {
new source.Foo();
source.SourcePackage.foo();
}
}
@@ -0,0 +1,6 @@
package target
fun test() {
source.Foo()
source.foo()
}
@@ -0,0 +1,11 @@
package target;
import source.Foo;
import static source.SourcePackage.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package target
import source.Foo
import source.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,8 @@
package usages;
class Test {
static void test() {
new source.Foo();
source.SourcePackage.foo();
}
}
@@ -0,0 +1,6 @@
package usages
fun test() {
source.Foo()
source.foo()
}
@@ -0,0 +1,11 @@
package usages;
import source.Foo;
import static source.SourcePackage.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package usages
import source.Foo
import source.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,5 @@
{
"mainFile": "target/test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.ChangePackageToMatchDirectoryIntention",
"intentionText": "Change file's package to 'target'"
}
@@ -0,0 +1,9 @@
package foo.bar
class Foo {
}
fun foo() {
}
@@ -0,0 +1,9 @@
pack<caret>age foo.bar
class Foo {
}
fun foo() {
}
@@ -0,0 +1,5 @@
{
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.ChangePackageToMatchDirectoryIntention",
"isApplicable": "false",
"mainFile": "foo/bar/test.kt"
}
@@ -34,4 +34,10 @@ public class JetMultiFileInspectionTestGenerated extends AbstractJetMultiFileIns
public void testAllFilesPresentInMultiFileInspections() throws Exception {
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileInspections"), Pattern.compile("^(.+)\\.test$"));
}
@TestMetadata("mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test")
public void testMismatchedProjectAndDirectory_MismatchedProjectAndDirectory() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileInspections/mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test");
doTest(fileName);
}
}
@@ -34,4 +34,40 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT
public void testAllFilesPresentInMultiFileIntentions() throws Exception {
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileIntentions"), Pattern.compile("^(.+)\\.test$"));
}
@TestMetadata("moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test")
public void testMoveFileToPackageMatchingDirectory_moveToDefaultDirectory_MoveToDefaultDirectory() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test");
doTest(fileName);
}
@TestMetadata("moveFileToPackageMatchingDirectory/moveToNonDefaultDirectory/moveToNonDefaultDirectory.test")
public void testMoveFileToPackageMatchingDirectory_moveToNonDefaultDirectory_MoveToNonDefaultDirectory() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveFileToPackageMatchingDirectory/moveToNonDefaultDirectory/moveToNonDefaultDirectory.test");
doTest(fileName);
}
@TestMetadata("moveFileToPackageMatchingDirectory/packageMatchesDirectory/packageMatchesDirectory.test")
public void testMoveFileToPackageMatchingDirectory_packageMatchesDirectory_PackageMatchesDirectory() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveFileToPackageMatchingDirectory/packageMatchesDirectory/packageMatchesDirectory.test");
doTest(fileName);
}
@TestMetadata("reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test")
public void testReconcilePackageWithDirectory_changeToDefaultPackage_ChangeToDefaultPackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test");
doTest(fileName);
}
@TestMetadata("reconcilePackageWithDirectory/changeToNonDefaultPackage/changeToNonDefaultPackage.test")
public void testReconcilePackageWithDirectory_changeToNonDefaultPackage_ChangeToNonDefaultPackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/changeToNonDefaultPackage/changeToNonDefaultPackage.test");
doTest(fileName);
}
@TestMetadata("reconcilePackageWithDirectory/packageMatchesDirectory/packageMatchesDirectory.test")
public void testReconcilePackageWithDirectory_packageMatchesDirectory_PackageMatchesDirectory() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/packageMatchesDirectory/packageMatchesDirectory.test");
doTest(fileName);
}
}