Make package name inspection global (KT-29549)

Report same package only once.

 #KT-29549 Fixed
This commit is contained in:
Nikolay Krasko
2019-01-25 19:57:26 +03:00
parent 6ee78fb629
commit 56d56ab842
5 changed files with 165 additions and 31 deletions
+1
View File
@@ -117,6 +117,7 @@
<Problem reference="com.intellij.openapi.extensions.ExtensionPointName#findExtensionOrFail" reason="Absent in 182." />
<Problem reference="com.intellij.openapi.ui.popup.PopupChooserBuilder#PopupChooserBuilder(javax.swing.JList)" reason="Generified in 182. Use PopupChooserBuilderWrapper instead." />
<Problem reference="com.intellij.openapi.editor.event.EditorFactoryListener" reason="Default implementations were added in 183. Use EditorFactoryListenerWrapper for inheritance instead." />
<Problem reference="com.intellij.codeInspection.reference.RefFile#getPsiElement" reason="Absent in 182. Use psiFile extension instead." />
</list>
</option>
</inspection_tool>
+1 -1
View File
@@ -2650,7 +2650,7 @@
displayName="Local variable naming convention"
level="WEAK WARNING"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.PackageNameInspection"
<globalInspection implementationClass="org.jetbrains.kotlin.idea.inspections.PackageNameInspection"
language="kotlin"
groupPath="Kotlin"
groupName="Naming conventions"
@@ -5,21 +5,30 @@
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.analysis.AnalysisScope
import com.intellij.codeInspection.*
import com.intellij.codeInspection.reference.RefEntity
import com.intellij.codeInspection.reference.RefFile
import com.intellij.codeInspection.reference.RefPackage
import com.intellij.openapi.editor.event.DocumentAdapter
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.ui.LabeledComponent
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.ui.EditorTextField
import com.siyeh.ig.BaseGlobalInspection
import com.siyeh.ig.psiutils.TestUtils
import org.intellij.lang.annotations.Language
import org.intellij.lang.regexp.RegExpFileType
import org.jdom.Element
import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.idea.core.packageMatchesDirectoryOrImplicit
import org.jetbrains.kotlin.idea.quickfix.RenameIdentifierFix
import org.jetbrains.kotlin.idea.refactoring.isInjectedFragment
import org.jetbrains.kotlin.idea.util.compat.psiFile
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
@@ -104,7 +113,7 @@ class NamingConventionInspectionSettings(
}
}
protected fun getNameMismatchMessage(name: String): String {
fun getNameMismatchMessage(name: String): String {
if (namePattern != defaultNamePattern) {
return getDefaultErrorMessage()
}
@@ -145,7 +154,7 @@ sealed class NamingConventionInspection(
@Suppress("MemberVisibilityCanBePrivate")
var namePattern: String = defaultNamePattern
protected val namingSettings = NamingConventionInspectionSettings(
private val namingSettings = NamingConventionInspectionSettings(
entityName, defaultNamePattern,
setNamePatternCallback = { value ->
namePattern = value
@@ -297,37 +306,17 @@ class LocalVariableNameInspection :
START_LOWER, NO_UNDERSCORES, NO_BAD_CHARACTERS
)
class PackageNameInspection :
NamingConventionInspection("Package", "[a-z_][a-zA-Z\\d_]*(\\.[a-z_][a-zA-Z\\d_]*)*") {
companion object {
val PART_RULES = arrayOf(NO_BAD_CHARACTERS_OR_UNDERSCORE, NO_START_UPPER)
}
private class PackageNameInspectionLocal(
val parentInspection: InspectionProfileEntry,
val namingSettings: NamingConventionInspectionSettings
) : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
return packageDirectiveVisitor { directive ->
val packageNameExpression = directive.packageNameExpression ?: return@packageDirectiveVisitor
val qualifiedName = directive.qualifiedName
if (qualifiedName.isEmpty() || namingSettings.nameRegex?.matches(qualifiedName) != false) {
return@packageDirectiveVisitor
}
val partErrorMessage = if (namePattern == namingSettings.defaultNamePattern) {
directive.packageNames.asSequence()
.mapNotNull { simpleName ->
val referencedName = simpleName.getReferencedName()
findRuleMessage(referencedName, PackageNameInspection.PART_RULES)
}
.firstOrNull()
} else {
null
}
val checkResult = checkPackageDirective(directive, namingSettings) ?: return@packageDirectiveVisitor
val descriptionTemplate = if (partErrorMessage != null) {
"Package name <code>#ref</code> part $partErrorMessage #loc"
} else {
"Package name <code>#ref</code> ${namingSettings.getDefaultErrorMessage()} #loc"
}
val descriptionTemplate = checkResult.toProblemTemplateString()
holder.registerProblem(
packageNameExpression,
@@ -337,10 +326,126 @@ class PackageNameInspection :
}
}
companion object {
data class CheckResult(val errorMessage: String, val isForPart: Boolean)
fun CheckResult.toProblemTemplateString(): String {
return if (isForPart) {
"Package name <code>#ref</code> part $errorMessage #loc"
} else {
"Package name <code>#ref</code> $errorMessage #loc"
}
}
fun checkPackageDirective(directive: KtPackageDirective, namingSettings: NamingConventionInspectionSettings): CheckResult? {
return checkQualifiedName(directive.qualifiedName, namingSettings)
}
fun checkQualifiedName(qualifiedName: String, namingSettings: NamingConventionInspectionSettings): CheckResult? {
if (qualifiedName.isEmpty() || namingSettings.nameRegex?.matches(qualifiedName) != false) {
return null
}
val partErrorMessage = if (namingSettings.namePattern == namingSettings.defaultNamePattern) {
qualifiedName.split('.').asSequence()
.mapNotNull { part -> findRuleMessage(part, PackageNameInspection.PART_RULES) }
.firstOrNull()
} else {
null
}
return if (partErrorMessage != null) {
CheckResult(partErrorMessage, true)
} else {
CheckResult(namingSettings.getDefaultErrorMessage(), false)
}
}
}
private class RenamePackageFix : RenameIdentifierFix() {
override fun getElementToRename(element: PsiElement): PsiElement? {
val packageDirective = element as? KtPackageDirective ?: return null
return JavaPsiFacade.getInstance(element.project).findPackage(packageDirective.qualifiedName)
}
}
override fun getShortName(): String = parentInspection.shortName
override fun getDisplayName(): String = parentInspection.displayName
}
class PackageNameInspection : BaseGlobalInspection() {
companion object {
const val DEFAULT_PACKAGE_NAME_PATTERN = "[a-z_][a-zA-Z\\d_]*(\\.[a-z_][a-zA-Z\\d_]*)*"
val PART_RULES = arrayOf(NO_BAD_CHARACTERS_OR_UNDERSCORE, NO_START_UPPER)
private fun PackageNameInspectionLocal.Companion.CheckResult.toErrorMessage(qualifiedName: String): String {
return if (isForPart) {
"Package name <code>$qualifiedName</code> part $errorMessage"
} else {
"Package name <code>$qualifiedName</code> $errorMessage"
}
}
}
var namePattern: String = DEFAULT_PACKAGE_NAME_PATTERN
private val namingSettings = NamingConventionInspectionSettings(
"Package",
DEFAULT_PACKAGE_NAME_PATTERN,
setNamePatternCallback = { value ->
namePattern = value
}
)
override fun checkElement(
refEntity: RefEntity,
analysisScope: AnalysisScope,
inspectionManager: InspectionManager,
globalInspectionContext: GlobalInspectionContext
): Array<CommonProblemDescriptor>? {
when (refEntity) {
is RefFile -> {
val psiFile = refEntity.psiFile
if (psiFile is KtFile && !psiFile.isInjectedFragment && !psiFile.packageMatchesDirectoryOrImplicit()) {
val packageDirective = psiFile.packageDirective
if (packageDirective != null) {
val qualifiedName = packageDirective.qualifiedName
val checkResult = PackageNameInspectionLocal.checkPackageDirective(packageDirective, namingSettings)
if (checkResult != null) {
return arrayOf(inspectionManager.createProblemDescriptor(checkResult.toErrorMessage(qualifiedName)))
}
}
}
}
is RefPackage -> {
@NonNls val name = StringUtil.getShortName(refEntity.getQualifiedName())
if (name.isEmpty() || InspectionsBundle.message("inspection.reference.default.package") == name) {
return null
}
val checkResult = PackageNameInspectionLocal.checkQualifiedName(name, namingSettings)
if (checkResult != null) {
return arrayOf(inspectionManager.createProblemDescriptor(checkResult.toErrorMessage(name)))
}
}
else -> {
return null
}
}
return null
}
override fun readSettings(element: Element) {
super.readSettings(element)
namingSettings.namePattern = namePattern
}
override fun createOptionsPanel() = namingSettings.createOptionsPanel()
override fun getSharedLocalInspectionTool(): LocalInspectionTool {
return PackageNameInspectionLocal(this, namingSettings)
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.util.compat
import com.intellij.codeInspection.reference.RefFile
import com.intellij.psi.PsiFile
// BUNCH: 182
@Suppress("IncompatibleAPI")
val RefFile.psiFile: PsiFile?
get() = psiElement
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.util.compat
import com.intellij.codeInspection.reference.RefFile
import com.intellij.psi.PsiFile
// BUNCH: 182
@Suppress("IncompatibleAPI")
val RefFile.psiFile: PsiFile?
get() = element