diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/NamingConventionInspections.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/NamingConventionInspections.kt index db707c44d7e..bc817feb0e4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/NamingConventionInspections.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/NamingConventionInspections.kt @@ -26,9 +26,28 @@ import java.awt.BorderLayout import java.util.regex.PatternSyntaxException 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( private val entityName: String, - defaultNamePattern: String + private val defaultNamePattern: String, + private vararg val rules: NamingRule ) : AbstractKotlinInspection() { protected var nameRegex: Regex? = defaultNamePattern.toRegex() var namePattern: String = defaultNamePattern @@ -45,18 +64,34 @@ abstract class NamingConventionInspection( val name = element.name val nameIdentifier = element.nameIdentifier if (name != null && nameIdentifier != null && nameRegex?.matches(name) == false) { + val message = getNameMismatchMessage(name) holder.registerProblem( element.nameIdentifier!!, - "$entityName name #ref doesn't match regex '$namePattern' #loc", + "$entityName name #ref $message #loc", 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) } -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 { return object : KtVisitorVoid() { 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 { return object : KtVisitorVoid() { 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 { return object : KtVisitorVoid() { override fun visitNamedFunction(function: KtNamedFunction) { @@ -96,8 +139,9 @@ class FunctionNameInspection : NamingConventionInspection("Function", "[a-z][A-Z abstract class PropertyNameInspectionBase protected constructor( private val kind: PropertyKind, entityName: String, - defaultNamePattern: String -) : NamingConventionInspection(entityName, defaultNamePattern) { + defaultNamePattern: String, + vararg rules: NamingRule +) : NamingConventionInspection(entityName, defaultNamePattern, *rules) { 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 { return object : KtVisitorVoid() { override fun visitPackageDirective(directive: KtPackageDirective) { val qualifiedName = directive.qualifiedName if (qualifiedName.isNotEmpty() && nameRegex?.matches(qualifiedName) == false) { + val message = getNameMismatchMessage(qualifiedName) holder.registerProblem( directive.packageNameExpression!!, - "Package name #ref doesn't match regex '$namePattern' #loc", + "Package name #ref $message #loc", RenamePackageFix() ) } diff --git a/idea/testData/inspections/naming/class/inspectionData/expected.xml b/idea/testData/inspections/naming/class/inspectionData/expected.xml index 26b463bc8c8..79b8108cf57 100644 --- a/idea/testData/inspections/naming/class/inspectionData/expected.xml +++ b/idea/testData/inspections/naming/class/inspectionData/expected.xml @@ -5,7 +5,7 @@ light_idea_test_case Class naming convention - Class name <code>a</code> doesn't match regex '[A-Z][A-Za-z\d]*' #loc + Class name <code>a</code> should start with an uppercase letter #loc test.kt @@ -13,7 +13,7 @@ light_idea_test_case Class naming convention - Class name <code>b</code> doesn''t match regex '[A-Z][A-Za-z\d]*' #loc + Class name <code>b</code> should start with an uppercase letter #loc test.kt diff --git a/idea/testData/inspections/naming/enumEntry/inspectionData/expected.xml b/idea/testData/inspections/naming/enumEntry/inspectionData/expected.xml index 2fb137e8437..04575d07063 100644 --- a/idea/testData/inspections/naming/enumEntry/inspectionData/expected.xml +++ b/idea/testData/inspections/naming/enumEntry/inspectionData/expected.xml @@ -11,6 +11,6 @@ 12 light_idea_test_case - Enum entry name <code>quuxDoo</code> doesn't match regex '[A-Z]([A-Za-z\d]*|[A-Z_\d]*)' #loc + Enum entry name <code>quuxDoo</code> should start with an uppercase letter #loc \ No newline at end of file diff --git a/idea/testData/inspections/naming/function/inspectionData/expected.xml b/idea/testData/inspections/naming/function/inspectionData/expected.xml index 0695fa7edd6..d297390225e 100644 --- a/idea/testData/inspections/naming/function/inspectionData/expected.xml +++ b/idea/testData/inspections/naming/function/inspectionData/expected.xml @@ -4,13 +4,13 @@ 1 light_idea_test_case - Function name <code>Foo</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc + Function name <code>Foo</code> should start with a lowercase letter #loc test.kt 3 light_idea_test_case - Function name <code>FOO_BAR</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc + Function name <code>FOO_BAR</code> should not contain underscores #loc diff --git a/idea/testData/inspections/naming/property/inspectionData/expected.xml b/idea/testData/inspections/naming/property/inspectionData/expected.xml index 208f68275dd..c83909d433a 100644 --- a/idea/testData/inspections/naming/property/inspectionData/expected.xml +++ b/idea/testData/inspections/naming/property/inspectionData/expected.xml @@ -4,13 +4,13 @@ 1 light_idea_test_case - Property name <code>Foo</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc + Property name <code>Foo</code> should start with a lowercase letter #loc test.kt 3 light_idea_test_case - Property name <code>FOO_BAR</code> doesn't match regex '[a-z][A-Za-z\d]*' #loc + Property name <code>FOO_BAR</code> should not contain underscores #loc