Report more friendly messages from naming conventions inspection
#KT-19736 Fixed
This commit is contained in:
@@ -26,9 +26,28 @@ import java.awt.BorderLayout
|
|||||||
import java.util.regex.PatternSyntaxException
|
import java.util.regex.PatternSyntaxException
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
data class NamingRule(val message: String, val matcher: (String) -> Boolean)
|
||||||
|
|
||||||
|
private val START_UPPER = NamingRule("should start with an uppercase letter") {
|
||||||
|
it.getOrNull(0)?.isUpperCase() == false
|
||||||
|
}
|
||||||
|
|
||||||
|
private val START_LOWER = NamingRule("should start with a lowercase letter") {
|
||||||
|
it.getOrNull(0)?.isLowerCase() == false
|
||||||
|
}
|
||||||
|
|
||||||
|
private val NO_UNDERSCORES = NamingRule("should not contain underscores") {
|
||||||
|
'_' in it
|
||||||
|
}
|
||||||
|
|
||||||
|
private val NO_MIDDLE_UNDERSCORES = NamingRule("should not contain underscores in the middle or the end") {
|
||||||
|
'_' in it.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
abstract class NamingConventionInspection(
|
abstract class NamingConventionInspection(
|
||||||
private val entityName: String,
|
private val entityName: String,
|
||||||
defaultNamePattern: String
|
private val defaultNamePattern: String,
|
||||||
|
private vararg val rules: NamingRule
|
||||||
) : AbstractKotlinInspection() {
|
) : AbstractKotlinInspection() {
|
||||||
protected var nameRegex: Regex? = defaultNamePattern.toRegex()
|
protected var nameRegex: Regex? = defaultNamePattern.toRegex()
|
||||||
var namePattern: String = defaultNamePattern
|
var namePattern: String = defaultNamePattern
|
||||||
@@ -45,18 +64,34 @@ abstract class NamingConventionInspection(
|
|||||||
val name = element.name
|
val name = element.name
|
||||||
val nameIdentifier = element.nameIdentifier
|
val nameIdentifier = element.nameIdentifier
|
||||||
if (name != null && nameIdentifier != null && nameRegex?.matches(name) == false) {
|
if (name != null && nameIdentifier != null && nameRegex?.matches(name) == false) {
|
||||||
|
val message = getNameMismatchMessage(name)
|
||||||
holder.registerProblem(
|
holder.registerProblem(
|
||||||
element.nameIdentifier!!,
|
element.nameIdentifier!!,
|
||||||
"$entityName name <code>#ref</code> doesn't match regex '$namePattern' #loc",
|
"$entityName name <code>#ref</code> $message #loc",
|
||||||
RenameIdentifierFix()
|
RenameIdentifierFix()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun getNameMismatchMessage(name: String): String {
|
||||||
|
if (namePattern == defaultNamePattern) {
|
||||||
|
for (rule in rules) {
|
||||||
|
if (rule.matcher(name)) {
|
||||||
|
return rule.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "doesn't match regex '$namePattern'"
|
||||||
|
}
|
||||||
|
|
||||||
override fun createOptionsPanel() = NamingConventionOptionsPanel(this)
|
override fun createOptionsPanel() = NamingConventionOptionsPanel(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClassNameInspection : NamingConventionInspection("Class", "[A-Z][A-Za-z\\d]*") {
|
class ClassNameInspection : NamingConventionInspection(
|
||||||
|
"Class",
|
||||||
|
"[A-Z][A-Za-z\\d]*",
|
||||||
|
START_UPPER, NO_UNDERSCORES
|
||||||
|
) {
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||||
return object : KtVisitorVoid() {
|
return object : KtVisitorVoid() {
|
||||||
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
|
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
|
||||||
@@ -70,7 +105,11 @@ class ClassNameInspection : NamingConventionInspection("Class", "[A-Z][A-Za-z\\d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnumEntryNameInspection : NamingConventionInspection("Enum entry", "[A-Z]([A-Za-z\\d]*|[A-Z_\\d]*)") {
|
class EnumEntryNameInspection : NamingConventionInspection(
|
||||||
|
"Enum entry",
|
||||||
|
"[A-Z]([A-Za-z\\d]*|[A-Z_\\d]*)",
|
||||||
|
START_UPPER
|
||||||
|
) {
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||||
return object : KtVisitorVoid() {
|
return object : KtVisitorVoid() {
|
||||||
override fun visitEnumEntry(enumEntry: KtEnumEntry) {
|
override fun visitEnumEntry(enumEntry: KtEnumEntry) {
|
||||||
@@ -80,7 +119,11 @@ class EnumEntryNameInspection : NamingConventionInspection("Enum entry", "[A-Z](
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FunctionNameInspection : NamingConventionInspection("Function", "[a-z][A-Za-z\\d]*") {
|
class FunctionNameInspection : NamingConventionInspection(
|
||||||
|
"Function",
|
||||||
|
"[a-z][A-Za-z\\d]*",
|
||||||
|
START_LOWER, NO_UNDERSCORES
|
||||||
|
) {
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||||
return object : KtVisitorVoid() {
|
return object : KtVisitorVoid() {
|
||||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||||
@@ -96,8 +139,9 @@ class FunctionNameInspection : NamingConventionInspection("Function", "[a-z][A-Z
|
|||||||
abstract class PropertyNameInspectionBase protected constructor(
|
abstract class PropertyNameInspectionBase protected constructor(
|
||||||
private val kind: PropertyKind,
|
private val kind: PropertyKind,
|
||||||
entityName: String,
|
entityName: String,
|
||||||
defaultNamePattern: String
|
defaultNamePattern: String,
|
||||||
) : NamingConventionInspection(entityName, defaultNamePattern) {
|
vararg rules: NamingRule
|
||||||
|
) : NamingConventionInspection(entityName, defaultNamePattern, *rules) {
|
||||||
|
|
||||||
protected enum class PropertyKind { NORMAL, PRIVATE, OBJECT, CONST, LOCAL }
|
protected enum class PropertyKind { NORMAL, PRIVATE, OBJECT, CONST, LOCAL }
|
||||||
|
|
||||||
@@ -124,25 +168,32 @@ abstract class PropertyNameInspectionBase protected constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertyNameInspection : PropertyNameInspectionBase(PropertyKind.NORMAL, "Property", "[a-z][A-Za-z\\d]*")
|
class PropertyNameInspection :
|
||||||
|
PropertyNameInspectionBase(PropertyKind.NORMAL, "Property", "[a-z][A-Za-z\\d]*", START_LOWER, NO_UNDERSCORES)
|
||||||
|
|
||||||
class ObjectPropertyNameInspection : PropertyNameInspectionBase(PropertyKind.OBJECT, "Object property", "[A-Za-z][_A-Za-z\\d]*")
|
class ObjectPropertyNameInspection :
|
||||||
|
PropertyNameInspectionBase(PropertyKind.OBJECT, "Object property", "[A-Za-z][_A-Za-z\\d]*")
|
||||||
|
|
||||||
class PrivatePropertyNameInspection : PropertyNameInspectionBase(PropertyKind.PRIVATE, "Private property", "_?[a-z][A-Za-z\\d]*")
|
class PrivatePropertyNameInspection :
|
||||||
|
PropertyNameInspectionBase(PropertyKind.PRIVATE, "Private property", "_?[a-z][A-Za-z\\d]*", NO_MIDDLE_UNDERSCORES)
|
||||||
|
|
||||||
class ConstPropertyNameInspection : PropertyNameInspectionBase(PropertyKind.CONST, "Const property", "[A-Z][_A-Z\\d]*")
|
class ConstPropertyNameInspection :
|
||||||
|
PropertyNameInspectionBase(PropertyKind.CONST, "Const property", "[A-Z][_A-Z\\d]*")
|
||||||
|
|
||||||
class LocalVariableNameInspection : PropertyNameInspectionBase(PropertyKind.LOCAL, "Local variable", "[a-z][A-Za-z\\d]*")
|
class LocalVariableNameInspection :
|
||||||
|
PropertyNameInspectionBase(PropertyKind.LOCAL, "Local variable", "[a-z][A-Za-z\\d]*", START_LOWER, NO_UNDERSCORES)
|
||||||
|
|
||||||
class PackageNameInspection : NamingConventionInspection("Package", "[a-z][A-Za-z\\d]*(\\.[a-z][A-Za-z\\d]*)*") {
|
class PackageNameInspection :
|
||||||
|
NamingConventionInspection("Package", "[a-z][A-Za-z\\d]*(\\.[a-z][A-Za-z\\d]*)*", NO_UNDERSCORES) {
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||||
return object : KtVisitorVoid() {
|
return object : KtVisitorVoid() {
|
||||||
override fun visitPackageDirective(directive: KtPackageDirective) {
|
override fun visitPackageDirective(directive: KtPackageDirective) {
|
||||||
val qualifiedName = directive.qualifiedName
|
val qualifiedName = directive.qualifiedName
|
||||||
if (qualifiedName.isNotEmpty() && nameRegex?.matches(qualifiedName) == false) {
|
if (qualifiedName.isNotEmpty() && nameRegex?.matches(qualifiedName) == false) {
|
||||||
|
val message = getNameMismatchMessage(qualifiedName)
|
||||||
holder.registerProblem(
|
holder.registerProblem(
|
||||||
directive.packageNameExpression!!,
|
directive.packageNameExpression!!,
|
||||||
"Package name <code>#ref</code> doesn't match regex '$namePattern' #loc",
|
"Package name <code>#ref</code> $message #loc",
|
||||||
RenamePackageFix()
|
RenamePackageFix()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Class naming convention</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Class naming convention</problem_class>
|
||||||
<description>Class name <code>a</code> doesn't match regex '[A-Z][A-Za-z\d]*' #loc</description>
|
<description>Class name <code>a</code> should start with an uppercase letter #loc</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
<problem>
|
||||||
<file>test.kt</file>
|
<file>test.kt</file>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Class naming convention</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Class naming convention</problem_class>
|
||||||
<description>Class name <code>b</code> doesn''t match regex '[A-Z][A-Za-z\d]*' #loc</description>
|
<description>Class name <code>b</code> should start with an uppercase letter #loc</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
<problem>
|
||||||
<file>test.kt</file>
|
<file>test.kt</file>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
<line>12</line>
|
<line>12</line>
|
||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
<description>Enum entry name <code>quuxDoo</code> doesn't match regex '[A-Z]([A-Za-z\d]*|[A-Z_\d]*)' #loc</description>
|
<description>Enum entry name <code>quuxDoo</code> should start with an uppercase letter #loc</description>
|
||||||
</problem>
|
</problem>
|
||||||
</problems>
|
</problems>
|
||||||
@@ -4,13 +4,13 @@
|
|||||||
<line>1</line>
|
<line>1</line>
|
||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
<description>Function name <code>Foo</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc</description>
|
<description>Function name <code>Foo</code> should start with a lowercase letter #loc</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
<problem>
|
||||||
<file>test.kt</file>
|
<file>test.kt</file>
|
||||||
<line>3</line>
|
<line>3</line>
|
||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
<description>Function name <code>FOO_BAR</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc</description>
|
<description>Function name <code>FOO_BAR</code> should not contain underscores #loc</description>
|
||||||
</problem>
|
</problem>
|
||||||
</problems>
|
</problems>
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
<line>1</line>
|
<line>1</line>
|
||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
<description>Property name <code>Foo</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc</description>
|
<description>Property name <code>Foo</code> should start with a lowercase letter #loc</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
<problem>
|
||||||
<file>test.kt</file>
|
<file>test.kt</file>
|
||||||
<line>3</line>
|
<line>3</line>
|
||||||
<module>light_idea_test_case</module>
|
<module>light_idea_test_case</module>
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
<description>Property name <code>FOO_BAR</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc</description>
|
<description>Property name <code>FOO_BAR</code> should not contain underscores #loc</description>
|
||||||
</problem>
|
</problem>
|
||||||
</problems>
|
</problems>
|
||||||
|
|||||||
Reference in New Issue
Block a user