PackageDirectoryMismatchInspection should report when a package statement is missing

#KT-13549 Fixed
This commit is contained in:
Dmitry Gridin
2019-04-24 15:46:54 +07:00
parent 2ae30de980
commit 0198df4935
18 changed files with 136 additions and 40 deletions
@@ -6,10 +6,14 @@
package org.jetbrains.kotlin.idea.refactoring.move.changePackage package org.jetbrains.kotlin.idea.refactoring.move.changePackage
import com.intellij.CommonBundle import com.intellij.CommonBundle
import com.intellij.codeInspection.* import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.openapi.roots.JavaProjectRootsUtil
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.refactoring.PackageWrapper import com.intellij.refactoring.PackageWrapper
import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination
@@ -24,44 +28,38 @@ import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly
import org.jetbrains.kotlin.idea.refactoring.isInjectedFragment import org.jetbrains.kotlin.idea.refactoring.isInjectedFragment
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPackageDirective import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.KtVisitorVoid import org.jetbrains.kotlin.psi.packageDirectiveVisitor
class PackageDirectoryMismatchInspection : AbstractKotlinInspection() { class PackageDirectoryMismatchInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = packageDirectiveVisitor(fun(directive: KtPackageDirective) {
val file = directive.containingKtFile
if (file.isInjectedFragment || file.packageMatchesDirectoryOrImplicit()) return
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = val fixes = mutableListOf<LocalQuickFix>()
object : KtVisitorVoid() { val qualifiedName = directive.qualifiedName
override fun visitPackageDirective(directive: KtPackageDirective) { val dirName = if (qualifiedName.isEmpty()) "source root" else "'${qualifiedName.replace('.', '/')}'"
super.visitPackageDirective(directive) fixes += MoveFileToPackageFix(dirName)
if (directive.text.isEmpty()) return val fqNameByDirectory = file.getFqNameByDirectory()
when {
val file = directive.containingKtFile fqNameByDirectory.isRoot ->
if (file.isInjectedFragment || file.packageMatchesDirectoryOrImplicit()) return fixes += ChangePackageFix("source root", fqNameByDirectory)
fqNameByDirectory.hasIdentifiersOnly() ->
val fixes = mutableListOf<LocalQuickFix>() fixes += ChangePackageFix("'${fqNameByDirectory.asString()}'", fqNameByDirectory)
val qualifiedName = directive.qualifiedName
val dirName = if (qualifiedName.isEmpty()) "source root" else "'${qualifiedName.replace('.', '/')}'"
fixes += MoveFileToPackageFix(dirName)
val fqNameByDirectory = file.getFqNameByDirectory()
when {
fqNameByDirectory.isRoot ->
fixes += ChangePackageFix("source root", fqNameByDirectory)
fqNameByDirectory.hasIdentifiersOnly() ->
fixes += ChangePackageFix("'${fqNameByDirectory.asString()}'", fqNameByDirectory)
}
val fqNameWithImplicitPrefix = file.parent?.getFqNameWithImplicitPrefix()
if (fqNameWithImplicitPrefix != null && fqNameWithImplicitPrefix != fqNameByDirectory) {
fixes += ChangePackageFix("'${fqNameWithImplicitPrefix.asString()}'", fqNameWithImplicitPrefix)
}
holder.registerProblem(
directive,
"Package directive doesn't match file location",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
*fixes.toTypedArray()
)
}
} }
val fqNameWithImplicitPrefix = file.parent?.getFqNameWithImplicitPrefix()
if (fqNameWithImplicitPrefix != null && fqNameWithImplicitPrefix != fqNameByDirectory) {
fixes += ChangePackageFix("'${fqNameWithImplicitPrefix.asString()}'", fqNameWithImplicitPrefix)
}
holder.registerProblem(
file,
directive.textRange,
"Package directive doesn't match file location",
*fixes.toTypedArray()
)
})
private class MoveFileToPackageFix(val dirName: String) : LocalQuickFix { private class MoveFileToPackageFix(val dirName: String) : LocalQuickFix {
override fun getFamilyName() = "Move file to package-matching directory" override fun getFamilyName() = "Move file to package-matching directory"
@@ -71,16 +69,16 @@ class PackageDirectoryMismatchInspection : AbstractKotlinInspection() {
override fun startInWriteAction() = false override fun startInWriteAction() = false
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val directive = descriptor.psiElement as? KtPackageDirective ?: return val file = descriptor.psiElement as? KtFile ?: return
val file = directive.containingKtFile val directive = file.packageDirective ?: return
val sourceRoots = JavaProjectRootsUtil.getSuitableDestinationSourceRoots(project) val sourceRoots = JavaProjectRootsUtil.getSuitableDestinationSourceRoots(project)
val packageWrapper = PackageWrapper(PsiManager.getInstance(project), directive.qualifiedName) val packageWrapper = PackageWrapper(PsiManager.getInstance(project), directive.qualifiedName)
val fileToMove = directive.containingFile val fileToMove = directive.containingFile
val chosenRoot = val chosenRoot =
sourceRoots.singleOrNull() sourceRoots.singleOrNull()
?: MoveClassesOrPackagesUtil.chooseSourceRoot(packageWrapper, sourceRoots, fileToMove.containingDirectory) ?: MoveClassesOrPackagesUtil.chooseSourceRoot(packageWrapper, sourceRoots, fileToMove.containingDirectory)
?: return ?: return
val targetDirFactory = AutocreatingSingleSourceRootMoveDestination(packageWrapper, chosenRoot) val targetDirFactory = AutocreatingSingleSourceRootMoveDestination(packageWrapper, chosenRoot)
targetDirFactory.verify(fileToMove)?.let { targetDirFactory.verify(fileToMove)?.let {
Messages.showMessageDialog(project, it, CommonBundle.getErrorTitle(), Messages.getErrorIcon()) Messages.showMessageDialog(project, it, CommonBundle.getErrorTitle(), Messages.getErrorIcon())
@@ -107,8 +105,7 @@ class PackageDirectoryMismatchInspection : AbstractKotlinInspection() {
override fun getName() = "Change file's package to $packageName" override fun getName() = "Change file's package to $packageName"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val directive = descriptor.psiElement as? KtPackageDirective ?: return val file = descriptor.psiElement as? KtFile ?: return
val file = directive.containingKtFile
KotlinChangePackageRefactoring(file).run(packageFqName) KotlinChangePackageRefactoring(file).run(packageFqName)
} }
} }
@@ -0,0 +1,10 @@
<problems>
<problem>
<file>rootPackageMatched.kt</file>
<line>1</line>
<module>testMismatchedProjectAndDirectoryRoot_MismatchedProjectAndDirectoryRoot</module>
<entry_point TYPE="file" FQNAME="rootPackageMatched.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Package name does not match containing directory</problem_class>
<description>Package directive doesn't match file location</description>
</problem>
</problems>
@@ -0,0 +1,3 @@
{
"inspectionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.PackageDirectoryMismatchInspection"
}
@@ -0,0 +1,5 @@
{
"mainFile": "source/test.kt",
"inspectionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.PackageDirectoryMismatchInspection",
"fix": "Move file to source root"
}
@@ -0,0 +1,7 @@
package target
class Foo
fun foo() {
}
@@ -0,0 +1,6 @@
package target
fun test() {
Foo()
foo()
}
@@ -0,0 +1,9 @@
package usages
import target.foo
import target.Foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,9 @@
package target
import foo
import Foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,9 @@
package usages
import foo
import Foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,5 @@
{
"mainFile": "target/test.kt",
"inspectionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.PackageDirectoryMismatchInspection",
"fix": "Change file's package to 'target'"
}
@@ -54,6 +54,11 @@ public class MultiFileInspectionTestGenerated extends AbstractMultiFileInspectio
runTest("idea/testData/multiFileInspections/mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test"); runTest("idea/testData/multiFileInspections/mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test");
} }
@TestMetadata("mismatchedProjectAndDirectoryRoot/mismatchedProjectAndDirectoryRoot.test")
public void testMismatchedProjectAndDirectoryRoot_MismatchedProjectAndDirectoryRoot() throws Exception {
runTest("idea/testData/multiFileInspections/mismatchedProjectAndDirectoryRoot/mismatchedProjectAndDirectoryRoot.test");
}
@TestMetadata("platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test") @TestMetadata("platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test")
public void testPlatformExtensionReceiverOfInline_PlatformExtensionReceiverOfInline() throws Exception { public void testPlatformExtensionReceiverOfInline_PlatformExtensionReceiverOfInline() throws Exception {
runTest("idea/testData/multiFileInspections/platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test"); runTest("idea/testData/multiFileInspections/platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test");
@@ -44,6 +44,11 @@ public class MultiFileLocalInspectionTestGenerated extends AbstractMultiFileLoca
runTest("idea/testData/multiFileLocalInspections/moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test"); runTest("idea/testData/multiFileLocalInspections/moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test");
} }
@TestMetadata("moveFileToPackageMatchingDirectory/moveToDefaultDirectoryWithoutPackageKeyword/moveToDefaultDirectoryWithoutPackageKeyword.test")
public void testMoveFileToPackageMatchingDirectory_moveToDefaultDirectoryWithoutPackageKeyword_MoveToDefaultDirectoryWithoutPackageKeyword() throws Exception {
runTest("idea/testData/multiFileLocalInspections/moveFileToPackageMatchingDirectory/moveToDefaultDirectoryWithoutPackageKeyword/moveToDefaultDirectoryWithoutPackageKeyword.test");
}
@TestMetadata("moveFileToPackageMatchingDirectory/moveToNonDefaultDirectory/moveToNonDefaultDirectory.test") @TestMetadata("moveFileToPackageMatchingDirectory/moveToNonDefaultDirectory/moveToNonDefaultDirectory.test")
public void testMoveFileToPackageMatchingDirectory_moveToNonDefaultDirectory_MoveToNonDefaultDirectory() throws Exception { public void testMoveFileToPackageMatchingDirectory_moveToNonDefaultDirectory_MoveToNonDefaultDirectory() throws Exception {
runTest("idea/testData/multiFileLocalInspections/moveFileToPackageMatchingDirectory/moveToNonDefaultDirectory/moveToNonDefaultDirectory.test"); runTest("idea/testData/multiFileLocalInspections/moveFileToPackageMatchingDirectory/moveToNonDefaultDirectory/moveToNonDefaultDirectory.test");
@@ -69,6 +74,11 @@ public class MultiFileLocalInspectionTestGenerated extends AbstractMultiFileLoca
runTest("idea/testData/multiFileLocalInspections/reconcilePackageWithDirectory/changeToNonDefaultPackage/changeToNonDefaultPackage.test"); runTest("idea/testData/multiFileLocalInspections/reconcilePackageWithDirectory/changeToNonDefaultPackage/changeToNonDefaultPackage.test");
} }
@TestMetadata("reconcilePackageWithDirectory/changeToNonDefaultPackageFromRoot/changeToNonDefaultPackageFromRoot.test")
public void testReconcilePackageWithDirectory_changeToNonDefaultPackageFromRoot_ChangeToNonDefaultPackageFromRoot() throws Exception {
runTest("idea/testData/multiFileLocalInspections/reconcilePackageWithDirectory/changeToNonDefaultPackageFromRoot/changeToNonDefaultPackageFromRoot.test");
}
@TestMetadata("reconcilePackageWithDirectory/innerClass/innerClass.test") @TestMetadata("reconcilePackageWithDirectory/innerClass/innerClass.test")
public void testReconcilePackageWithDirectory_innerClass_InnerClass() throws Exception { public void testReconcilePackageWithDirectory_innerClass_InnerClass() throws Exception {
runTest("idea/testData/multiFileLocalInspections/reconcilePackageWithDirectory/innerClass/innerClass.test"); runTest("idea/testData/multiFileLocalInspections/reconcilePackageWithDirectory/innerClass/innerClass.test");