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.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.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.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> </list>
</option> </option>
</inspection_tool> </inspection_tool>
+1 -1
View File
@@ -2650,7 +2650,7 @@
displayName="Local variable naming convention" displayName="Local variable naming convention"
level="WEAK WARNING"/> level="WEAK WARNING"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.PackageNameInspection" <globalInspection implementationClass="org.jetbrains.kotlin.idea.inspections.PackageNameInspection"
language="kotlin" language="kotlin"
groupPath="Kotlin" groupPath="Kotlin"
groupName="Naming conventions" groupName="Naming conventions"
@@ -5,21 +5,30 @@
package org.jetbrains.kotlin.idea.inspections package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.analysis.AnalysisScope
import com.intellij.codeInspection.ProblemsHolder 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.DocumentAdapter
import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.ui.LabeledComponent import com.intellij.openapi.ui.LabeledComponent
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.ui.EditorTextField import com.intellij.ui.EditorTextField
import com.siyeh.ig.BaseGlobalInspection
import com.siyeh.ig.psiutils.TestUtils import com.siyeh.ig.psiutils.TestUtils
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.intellij.lang.regexp.RegExpFileType import org.intellij.lang.regexp.RegExpFileType
import org.jdom.Element 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.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.lexer.KtTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject 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) { if (namePattern != defaultNamePattern) {
return getDefaultErrorMessage() return getDefaultErrorMessage()
} }
@@ -145,7 +154,7 @@ sealed class NamingConventionInspection(
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
var namePattern: String = defaultNamePattern var namePattern: String = defaultNamePattern
protected val namingSettings = NamingConventionInspectionSettings( private val namingSettings = NamingConventionInspectionSettings(
entityName, defaultNamePattern, entityName, defaultNamePattern,
setNamePatternCallback = { value -> setNamePatternCallback = { value ->
namePattern = value namePattern = value
@@ -297,37 +306,17 @@ class LocalVariableNameInspection :
START_LOWER, NO_UNDERSCORES, NO_BAD_CHARACTERS START_LOWER, NO_UNDERSCORES, NO_BAD_CHARACTERS
) )
class PackageNameInspection : private class PackageNameInspectionLocal(
NamingConventionInspection("Package", "[a-z_][a-zA-Z\\d_]*(\\.[a-z_][a-zA-Z\\d_]*)*") { val parentInspection: InspectionProfileEntry,
val namingSettings: NamingConventionInspectionSettings
companion object { ) : AbstractKotlinInspection() {
val PART_RULES = arrayOf(NO_BAD_CHARACTERS_OR_UNDERSCORE, NO_START_UPPER)
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
return packageDirectiveVisitor { directive -> return packageDirectiveVisitor { directive ->
val packageNameExpression = directive.packageNameExpression ?: return@packageDirectiveVisitor 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) { val checkResult = checkPackageDirective(directive, namingSettings) ?: return@packageDirectiveVisitor
directive.packageNames.asSequence()
.mapNotNull { simpleName ->
val referencedName = simpleName.getReferencedName()
findRuleMessage(referencedName, PackageNameInspection.PART_RULES)
}
.firstOrNull()
} else {
null
}
val descriptionTemplate = if (partErrorMessage != null) { val descriptionTemplate = checkResult.toProblemTemplateString()
"Package name <code>#ref</code> part $partErrorMessage #loc"
} else {
"Package name <code>#ref</code> ${namingSettings.getDefaultErrorMessage()} #loc"
}
holder.registerProblem( holder.registerProblem(
packageNameExpression, 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() { private class RenamePackageFix : RenameIdentifierFix() {
override fun getElementToRename(element: PsiElement): PsiElement? { override fun getElementToRename(element: PsiElement): PsiElement? {
val packageDirective = element as? KtPackageDirective ?: return null val packageDirective = element as? KtPackageDirective ?: return null
return JavaPsiFacade.getInstance(element.project).findPackage(packageDirective.qualifiedName) 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