diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/IllegalIdentifierInspection.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/IllegalIdentifierInspection.kt index 2b1810d4790..03b120903f3 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/IllegalIdentifierInspection.kt +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/IllegalIdentifierInspection.kt @@ -16,19 +16,18 @@ package org.jetbrains.kotlin.android.inspection -import com.intellij.codeInsight.FileModificationService -import com.intellij.codeInspection.* -import com.intellij.ide.DataManager +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.fileEditor.FileEditorManager -import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor -import com.intellij.refactoring.rename.RenameHandlerRegistry import org.jetbrains.kotlin.android.getAndroidFacetForFile import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection +import org.jetbrains.kotlin.idea.quickfix.RenameIdentifierFix import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtPsiUtil +import org.jetbrains.kotlin.psi.KtVisitorVoid class IllegalIdentifierInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { @@ -71,28 +70,4 @@ class IllegalIdentifierInspection : AbstractKotlinInspection() { } } - class RenameIdentifierFix : LocalQuickFix { - override fun getName() = "Rename" - override fun getFamilyName() = name - - override fun startInWriteAction(): Boolean = false - - override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - val element = descriptor.psiElement ?: return - val file = element.containingFile ?: return - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return - val editorManager = FileEditorManager.getInstance(project) - val fileEditor = editorManager.getSelectedEditor(file.virtualFile) ?: return - val dataContext = DataManager.getInstance().getDataContext(fileEditor.component) - val renameHandler = RenameHandlerRegistry.getInstance().getRenameHandler(dataContext) - - val editor = editorManager.selectedTextEditor - if (editor != null) { - renameHandler?.invoke(project, editor, file, dataContext) - } - else { - renameHandler?.invoke(project, arrayOf(element.parent), dataContext) - } - } - } } \ No newline at end of file diff --git a/idea/resources/inspectionDescriptions/ClassName.html b/idea/resources/inspectionDescriptions/ClassName.html new file mode 100644 index 00000000000..9841c1a8fde --- /dev/null +++ b/idea/resources/inspectionDescriptions/ClassName.html @@ -0,0 +1,5 @@ + +
+Reports class names that do not follow the recommended naming conventions. + + \ No newline at end of file diff --git a/idea/resources/inspectionDescriptions/ConstPropertyName.html b/idea/resources/inspectionDescriptions/ConstPropertyName.html new file mode 100644 index 00000000000..a8f928670d3 --- /dev/null +++ b/idea/resources/inspectionDescriptions/ConstPropertyName.html @@ -0,0 +1,5 @@ + + +Reportsconst property names that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/resources/inspectionDescriptions/EnumEntryName.html b/idea/resources/inspectionDescriptions/EnumEntryName.html
new file mode 100644
index 00000000000..0510ca8765d
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/EnumEntryName.html
@@ -0,0 +1,5 @@
+
+
+Reports enum entry names that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/resources/inspectionDescriptions/FunctionName.html b/idea/resources/inspectionDescriptions/FunctionName.html
new file mode 100644
index 00000000000..a1005280514
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/FunctionName.html
@@ -0,0 +1,5 @@
+
+
+Reports function names that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/resources/inspectionDescriptions/LocalVariableName.html b/idea/resources/inspectionDescriptions/LocalVariableName.html
new file mode 100644
index 00000000000..ce5760ee887
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/LocalVariableName.html
@@ -0,0 +1,5 @@
+
+
+Reports local variable names that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/resources/inspectionDescriptions/ObjectPropertyName.html b/idea/resources/inspectionDescriptions/ObjectPropertyName.html
new file mode 100644
index 00000000000..21db1f56673
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/ObjectPropertyName.html
@@ -0,0 +1,5 @@
+
+
+Reports names of properties in objects and companion objects that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/resources/inspectionDescriptions/PackageName.html b/idea/resources/inspectionDescriptions/PackageName.html
new file mode 100644
index 00000000000..5dc69325fc4
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/PackageName.html
@@ -0,0 +1,5 @@
+
+
+Reports package names that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/resources/inspectionDescriptions/PrivatePropertyName.html b/idea/resources/inspectionDescriptions/PrivatePropertyName.html
new file mode 100644
index 00000000000..547afe6e78e
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/PrivatePropertyName.html
@@ -0,0 +1,5 @@
+
+
+Reports private property names that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/resources/inspectionDescriptions/PropertyName.html b/idea/resources/inspectionDescriptions/PropertyName.html
new file mode 100644
index 00000000000..abb09fa1c08
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/PropertyName.html
@@ -0,0 +1,5 @@
+
+
+Reports property names that do not follow the recommended naming conventions.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index f3f4657e9c3..1d1f10961f0 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2456,6 +2456,79 @@
language="kotlin"
/>
+ #ref doesn't match regex '$namePattern' #loc",
+ RenameIdentifierFix())
+ }
+ }
+
+ override fun createOptionsPanel() = NamingConventionOptionsPanel(this)
+}
+
+class ClassNameInspection : NamingConventionInspection("Class", "[A-Z][A-Za-z\\d]*") {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitClassOrObject(classOrObject: KtClassOrObject) {
+ verifyName(classOrObject, holder)
+ }
+
+ override fun visitEnumEntry(enumEntry: KtEnumEntry) {
+ // do nothing
+ }
+ }
+ }
+}
+
+class EnumEntryNameInspection : NamingConventionInspection("Enum entry", "[A-Z]([A-Za-z\\d]*|[A-Z_\\d]*)") {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitEnumEntry(enumEntry: KtEnumEntry) {
+ verifyName(enumEntry, holder)
+ }
+ }
+ }
+}
+
+class FunctionNameInspection : NamingConventionInspection("Function", "[a-z][A-Za-z\\d]*") {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitNamedFunction(function: KtNamedFunction) {
+ if (TestUtils.isInTestSourceContent(function) && function.nameIdentifier?.text?.startsWith("`") == true) {
+ return
+ }
+ verifyName(function, holder)
+ }
+ }
+ }
+}
+
+abstract class PropertyNameInspectionBase protected constructor(private val kind: PropertyKind,
+ entityName: String,
+ defaultNamePattern: String)
+ : NamingConventionInspection(entityName, defaultNamePattern) {
+
+ protected enum class PropertyKind { NORMAL, PRIVATE, OBJECT, CONST, LOCAL }
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitProperty(property: KtProperty) {
+ if (property.getKind() == kind) {
+ verifyName(property, holder)
+ }
+ }
+ }
+ }
+
+ private fun KtProperty.getKind(): PropertyKind = when {
+ isLocal -> PropertyKind.LOCAL
+
+ containingClassOrObject is KtObjectDeclaration -> PropertyKind.OBJECT
+
+ hasModifier(KtTokens.CONST_KEYWORD) -> PropertyKind.CONST
+
+ visibilityModifierType() == KtTokens.PRIVATE_KEYWORD -> PropertyKind.PRIVATE
+
+ else -> PropertyKind.NORMAL
+ }
+}
+
+class PropertyNameInspection : PropertyNameInspectionBase(PropertyKind.NORMAL, "Property", "[a-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 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 PackageNameInspection : NamingConventionInspection("Package", "[a-z][A-Za-z\\d\\.]*") {
+ 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)) {
+ holder.registerProblem(directive.packageNameExpression!!,
+ "Package name #ref doesn't match regex '$namePattern' #loc",
+ RenamePackageFix())
+ }
+ }
+ }
+ }
+
+ 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)
+ }
+ }
+}
+
+class NamingConventionOptionsPanel(owner: NamingConventionInspection) : JPanel() {
+ init {
+ layout = BorderLayout()
+
+ val regexField = EditorTextField(owner.namePattern, null, RegExpFileType.INSTANCE).apply {
+ setOneLineMode(true)
+ }
+ regexField.document.addDocumentListener(object : DocumentAdapter() {
+ override fun documentChanged(e: DocumentEvent?) {
+ owner.namePattern = regexField.text
+ }
+ })
+ val labeledComponent = LabeledComponent.create(regexField, "Pattern:", BorderLayout.WEST)
+ add(labeledComponent, BorderLayout.NORTH)
+ }
+}
diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/RenameIdentifierFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/RenameIdentifierFix.kt
new file mode 100644
index 00000000000..fb6cff4adc7
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/RenameIdentifierFix.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.idea.quickfix
+
+import com.intellij.codeInsight.FileModificationService
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.ide.DataManager
+import com.intellij.openapi.fileEditor.FileEditorManager
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElement
+import com.intellij.refactoring.RefactoringFactory
+import com.intellij.refactoring.rename.RenameHandlerRegistry
+
+open class RenameIdentifierFix : LocalQuickFix {
+ override fun getName() = "Rename"
+ override fun getFamilyName() = name
+
+ override fun startInWriteAction(): Boolean = false
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val element = descriptor.psiElement ?: return
+ val file = element.containingFile ?: return
+ if (!FileModificationService.getInstance().prepareFileForWrite(file)) return
+ val editorManager = FileEditorManager.getInstance(project)
+ val fileEditor = editorManager.getSelectedEditor(file.virtualFile) ?: return renameWithoutEditor(element)
+ val dataContext = DataManager.getInstance().getDataContext(fileEditor.component)
+ val renameHandler = RenameHandlerRegistry.getInstance().getRenameHandler(dataContext)
+
+ val editor = editorManager.selectedTextEditor
+ if (editor != null) {
+ renameHandler?.invoke(project, editor, file, dataContext)
+ }
+ else {
+ val elementToRename = getElementToRename(element) ?: return
+ renameHandler?.invoke(project, arrayOf(elementToRename), dataContext)
+ }
+ }
+
+ protected open fun getElementToRename(element: PsiElement): PsiElement? = element.parent
+
+ private fun renameWithoutEditor(element: PsiElement) {
+ val elementToRename = getElementToRename(element)
+ val factory = RefactoringFactory.getInstance(element.project)
+ val renameRefactoring = factory.createRename(elementToRename, null, true, true)
+ renameRefactoring.run()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspections/naming/class/inspectionData/expected.xml b/idea/testData/inspections/naming/class/inspectionData/expected.xml
new file mode 100644
index 00000000000..26b463bc8c8
--- /dev/null
+++ b/idea/testData/inspections/naming/class/inspectionData/expected.xml
@@ -0,0 +1,26 @@
+