diff --git a/.idea/markdown-navigator-enh.xml b/.idea/markdown-navigator-enh.xml new file mode 100644 index 0000000..a8fcc84 --- /dev/null +++ b/.idea/markdown-navigator-enh.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/markdown-navigator.xml b/.idea/markdown-navigator.xml new file mode 100644 index 0000000..a2fc086 --- /dev/null +++ b/.idea/markdown-navigator.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/lint/MainActivity.kt b/app/src/main/java/com/example/lint/MainActivity.kt index d100c8e..53048e4 100644 --- a/app/src/main/java/com/example/lint/MainActivity.kt +++ b/app/src/main/java/com/example/lint/MainActivity.kt @@ -1,89 +1,137 @@ package com.example.lint +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.os.Bundle +import android.util.Log import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { - companion object { + private val someaaa = "photo11111111111111111111111111111111111111111111111111111111111" - const val SS_S_SFDAS = 2 + companion object { - val some = 2 + const val ASAS = 2 * 1 - private const val EXTRA_PHOTO = "photo" + val some = 2 - fun createIntent(context: Context, photo: String): Intent { - return Intent(context, MainActivity ::class.java).putExtra(EXTRA_PHOTO, photo) - } - fun someFunction() {} + private val FREQUENCIES = listOf(50, 60) - fun createLauncher() = { someFunction() } - } + private const val EXTRA_PHOTO = + "photo1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" - val SSs = 2 + fun ppp() { + String.toString()?.toString() + } - fun spme() { - String?.toString() + } - } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + fun someFunction() { + } - val cTX: Context = this - String - .toString() + fun some(): Int = 1 + val s = 2 - val list = listOf() + fun createIntent(context: Context, photo: String): Intent = Intent() - list.forEach { line -> - val s = line - } - } + fun spme() { + String.toString() + val tag = emptyList() + tag.forEach { + Log.d("some", it) + } - private fun SSsome ( ) { - MainActivity .createIntent(this, "") - } - class SomeClass() { - val soURL = 2 - } + } + override fun onCreate(savedInstanceState: Bundle?) { - fun emptyFun(): Int { - try { - val s = 2 - } catch (e: Exception) { - throw e - } + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) - return when(1) { - 1 -> 2 - else -> 0 - } + val cTX: Context = this - } + var croppingBlock: (() -> Int)? = null - protected val s2: String = "" + try { + } catch (e: Exception) { + val some = 2 + } - fun Get() { - val s = 2 - s.toString() - //nothing - } + val list = listOf() - fun aAA() { + list.forEach { line -> + val s = line + } + } - } + private fun asome() { + createIntent(this, "") + emptyURL({ val s = 2 }, { val s = 2 }) + } + private fun emptyURL(function: () -> Unit, function2: () -> Unit) { + //nothing + } + + @SuppressLint("OMEGA_ABBREVIATION_AS_WORD") + val soURL = 2 + + class SomeClass() { + + } + + + fun em(): Int { + try { + val s = 2 + } catch (e: Exception) { + throw e + } + + return when (1) { + 1 -> 2 + else -> 0 + } + + } + + protected val s2: String = "" + + + fun agset() { + val s = 2 + s.toString() + //nothing + } + + fun aAa() { + //nothing + } + + data class GeneralSettings( + var doorStatus: Int = 1, + var keepDoorOpen: Boolean = false, + var keepDoorOpenSip: Boolean = false, + var doorOpenDuration: Int = 5, + var callDuration: Int = 5, + var talkDuration: Int = 5, + var conciergeApartment: Int = 100, + val conciergeApartment1: Int = 100, + var conciergeApartment2: Int = 100, + val conciergeApartment3: Int = 100, + var conciergeApartment5: Int = 100 + ) + + class BasePresenter } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6757ae8..3e3f8f2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -8,7 +8,7 @@ tools:context=".MainActivity"> + + \ No newline at end of file diff --git a/checks/src/main/java/com/omegar/lint/checks/LintIssueRegistry.kt b/checks/src/main/java/com/omegar/lint/checks/LintIssueRegistry.kt index 27b8cdc..2400a86 100644 --- a/checks/src/main/java/com/omegar/lint/checks/LintIssueRegistry.kt +++ b/checks/src/main/java/com/omegar/lint/checks/LintIssueRegistry.kt @@ -11,8 +11,9 @@ import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.elements_for import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.name.`class`.NameFileSufixDetector import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.name.abbreviation.AbbreviationDetector import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.name.field.CompanionObjectFieldsDetector -import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.file_class_interface.ComponentPositionDetector -import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.function_params.PositionArgumentDetector +import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.class_interface.ComponentPositionDetector +import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.file_package.PackageComponentDetector +import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.function_params.ArgumentPositionDetector import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.restrictions.class_length.MaxClassLengthDetector import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.restrictions.class_methods_count.MaxMethodCountDetector import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.restrictions.classes_in_package_count.MaxClassInPackageDetector @@ -31,36 +32,38 @@ import com.omegar.lint.checks.detector.code_guidelines.xml_style.name_resource.t import com.omegar.lint.checks.detector.project_guidelines.file_name.`class`.NameFileUpperCamelCaseDetector import com.omegar.lint.checks.detector.project_guidelines.file_name.resource.layout.NameResourceLayoutDetector +@Suppress("UnstableApiUsage") class LintIssueRegistry : IssueRegistry() { override val issues: List get() = listOf( NameFileUpperCamelCaseDetector.ISSUE, - AbbreviationDetector.ISSUE, // TODO need testing - PositionArgumentDetector.ISSUE, - MaxFunctionsArgumentsDetector.ISSUE, + AbbreviationDetector.ISSUE, // TODO need rewriting & lint quick fix + ArgumentPositionDetector.ISSUE, // TODO add lint quick fix + MaxFunctionsArgumentsDetector.ISSUE, // TODO ??lint quick fix?? ExceptionCatchDetector.ISSUE, - ComponentPositionDetector.ISSUE, + ComponentPositionDetector.ISSUE, //TODO add lint quick fix for + PackageComponentDetector.ISSUE, NameIdentifierXmlDetector.ISSUE, NameResourceStringXmlDetector.ISSUE, - NameResourceStyleXmlDetector.ISSUE, - MaxMethodCountDetector.ISSUE, - EmptyBodyFunctionDetector.ISSUE, + NameResourceStyleXmlDetector.ISSUE, //TODO change lint quick fix for + MaxMethodCountDetector.ISSUE, // TODO ??lint quick fix?? + EmptyBodyFunctionDetector.ISSUE, //TODO add lint quick fix for CompanionObjectFieldsDetector.ISSUE, - MaxFunctionLengthDetector.ISSUE, - MaxClassLengthDetector.ISSUE, + MaxFunctionLengthDetector.ISSUE, // TODO ??lint quick fix?? + MaxClassLengthDetector.ISSUE, // TODO ??lint quick fix?? SimplificationsFunctionDetector.ISSUE, - MaxLineLengthDetector.ISSUE, + MaxLineLengthDetector.ISSUE, // TODO ??lint quick fix?? AnnotationDetector.ISSUE, SpaceMethodDetector.ISSUE, NameFileSufixDetector.ISSUE, AttributesPositionXmlDetector.ISSUE, - MaxClassInPackageDetector.ISSUE, // TODO need testing - MaxPackageCountDetector.ISSUE, // TODO need testing - SimplificationsControlInstructionsDetector.ISSUE, // TODO need testing + MaxClassInPackageDetector.ISSUE, // TODO ??lint quick fix?? + MaxPackageCountDetector.ISSUE, // TODO ??lint quick fix?? + SimplificationsControlInstructionsDetector.ISSUE, //TODO add lint quick fix for IntentExtraParametersDetector.ISSUE, ArgumentsBundleKeyPrefixDetector.ISSUE, - LambdaDetector.ISSUE, - NameResourceLayoutDetector.ISSUE + LambdaDetector.ISSUE, //TODO add lint quick fix for + NameResourceLayoutDetector.ISSUE //TODO add lint quick fix for ) override val api: Int diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_rules/exception/ExceptionCatchDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_rules/exception/ExceptionCatchDetector.kt index 00b3061..b3c1fcb 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_rules/exception/ExceptionCatchDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_rules/exception/ExceptionCatchDetector.kt @@ -61,7 +61,7 @@ class ExceptionCatchDetector : Detector(), Detector.UastScanner { context.report( ISSUE, body, - context.getLocation(it as UElement), + context.getLocation(body), GENERALIZED_EXCEPTION_MESSAGE, createEmptyBodyFix() ) diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/abbreviation/AbbreviationDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/abbreviation/AbbreviationDetector.kt index 3f1345e..0db925e 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/abbreviation/AbbreviationDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/abbreviation/AbbreviationDetector.kt @@ -12,7 +12,7 @@ class AbbreviationDetector : Detector(), Detector.UastScanner { @JvmField val ISSUE: Issue = Issue.create( id = "OMEGA_ABBREVIATION_AS_WORD", - briefDescription = "Use this abbreviation does not match the coding convention", + briefDescription = "Use of this abbreviation does not match the coding convention", explanation = """ Don't use abbreviations. http://wiki.omega-r.club/dev-android-code#rec228153340 @@ -25,66 +25,65 @@ class AbbreviationDetector : Detector(), Detector.UastScanner { ) ) - private val ABBREVIATION_REGEX = Regex("""[A-Z][A-Z]""") - private val ANNOTATION_REGEX = Regex("""^@""") - private const val OPEN_SCOPE_LABEL = "(" - private const val EQUAL_LABEL = "=" - private const val SPACE_LABEL = " " + private val ABBREVIATION_REGEX = Regex("[A-Z][A-Z]") + private val EXTENSION_REGEX = Regex("fun.*\\.|val.*?\\.|var.*?\\.") + private val ANNOTATION_REGEX = Regex("^@") + private const val SPACE_LABEL = " " //exclusion - private const val MILLISECONDS_LABEL = "MSec" - private const val UELEMENT_LABEL = "UElement" - private const val TODO_LABEL = "TODO" private const val CLASS_LABEL = "class" private const val ENUM_LABEL = "enum class" val exclusionsList = listOf( - MILLISECONDS_LABEL, - TODO_LABEL, - UELEMENT_LABEL + "MSec", + "TODO", + "UElement" + ) + val specialSymbolList = listOf( + "(", + "<", + "=", + ":" ) - } override fun getApplicableUastTypes(): List> = listOf(UDeclaration::class.java) - override fun createUastHandler(context: JavaContext): UElementHandler { - return object : UElementHandler() { - override fun visitDeclaration(node: UDeclaration) { - val parent = node.parent ?: return - val fileLines = parent.text.lines() - val nameLine = fileLines.firstOrNull { it.contains(CLASS_LABEL) } - - if (nameLine != null && nameLine.contains(ENUM_LABEL)) { - return - } - - val lines = node.text?.lines() ?: return - - var checkText = getNameString(lines) ?: return - - checkText = deleteAfterSymbol(checkText, EQUAL_LABEL) - checkText = deleteAfterSymbol(checkText, OPEN_SCOPE_LABEL) - - checkText = deleteExclusions(checkText) - - if (checkText.contains(ABBREVIATION_REGEX) && !node.isStatic) { - context.report( - ISSUE, - node as UElement, - context.getNameLocation(node), - checkText + "\n" + ISSUE.getExplanation(TextFormat.TEXT) - ) - } - } - } - + override fun createUastHandler(context: JavaContext) = object : UElementHandler() { + override fun visitDeclaration(node: UDeclaration) { + val parent = node.parent ?: return + val nameLine = parent.text.lines().firstOrNull { it.contains(CLASS_LABEL) } + + if (nameLine != null && nameLine.contains(ENUM_LABEL)) { + return + } + + val lines = node.text?.lines() ?: return + + var checkText = getNameString(lines) ?: return + + checkText = checkForExtension(checkText) + checkText = deleteSpecialSymbols(checkText) + checkText = deleteExclusions(checkText) + + if (checkText.contains(ABBREVIATION_REGEX) && !node.isStatic) { + context.report( + ISSUE, + node as UElement, + context.getNameLocation(node), + checkText + "\n" + ISSUE.getExplanation(TextFormat.TEXT), + createLintFix(checkText) + ) + } + } } - private fun deleteAfterSymbol(checkText: String, symbol: String): String { + private fun deleteSpecialSymbols(checkText: String): String { var text = checkText - if (text.indexOf(symbol) > 0) { - text = checkText.substring(0, checkText.indexOf(symbol)) + specialSymbolList.forEach { symbol -> + if (text.contains(symbol) && text.indexOf(symbol) > 0) { + text = checkText.substring(0, checkText.indexOf(symbol)) + } } return text } @@ -105,4 +104,34 @@ class AbbreviationDetector : Detector(), Detector.UastScanner { } return resultText } + + private fun checkForExtension(checkText: String): String = + if (checkText.contains(EXTENSION_REGEX)) checkText.substring(checkText.indexOf('.') + 1) + else checkText + + private fun getNewName(oldName: String): String { + var resultName = "" + val charArray = oldName.toCharArray() + + for (i in 1 until oldName.length) { + val currentChar = charArray[i] + val previousChar = charArray[i - 1] + + resultName += if (currentChar.isUpperCase() && previousChar.isUpperCase()) { + currentChar.toLowerCase() + } else { + currentChar + } + } + + return resultName + } + + private fun createLintFix(oldName: String): LintFix { + return LintFix.create() + .replace() + .text(oldName) + .with(getNewName(oldName)) + .build() + } } diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/class/NameFileSufixDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/class/NameFileSufixDetector.kt index e3d7b65..ca7c770 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/class/NameFileSufixDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/class/NameFileSufixDetector.kt @@ -106,11 +106,11 @@ class NameFileSufixDetector : Detector(), Detector.UastScanner { node, context.getNameLocation(node), "$REPORT_MESSAGE_BEGIN $value.\n${ISSUE.getExplanation(TextFormat.TEXT)}", - createContextReport(node.name, value) + createLintFix(node.name, value) ) } - private fun createContextReport(part: String?, value: String): LintFix { + private fun createLintFix(part: String?, value: String): LintFix { return fix() .replace() .text(part) diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/field/CompanionObjectFieldsDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/field/CompanionObjectFieldsDetector.kt index cb7f73f..e41a86b 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/field/CompanionObjectFieldsDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/name/field/CompanionObjectFieldsDetector.kt @@ -5,7 +5,9 @@ import com.android.tools.lint.detector.api.* import org.jetbrains.uast.UClass import org.jetbrains.uast.UElement +import java.util.Locale +@Suppress("UnstableApiUsage") class CompanionObjectFieldsDetector : Detector(), Detector.UastScanner { companion object { /** Issue describing the problem and pointing to the detector implementation */ @@ -14,8 +16,8 @@ class CompanionObjectFieldsDetector : Detector(), Detector.UastScanner { id = "OMEGA_NAME_CONSTANTS_SCREAMING_SNAKE_CASE", briefDescription = "The line size does not match the coding convention", explanation = """ - The immutable fields in the Companion Object and compile-time constants are named in the - SCREAMING_SNAKE_CASE style. + The immutable fields in the Companion Object and compile-time constants + should be named in the SCREAMING_SNAKE_CASE style. http://wiki.omega-r.club/dev-android-code#rec226457239 """, category = Category.CORRECTNESS, @@ -31,56 +33,63 @@ class CompanionObjectFieldsDetector : Detector(), Detector.UastScanner { private const val VAL_LABEL = "val" private const val COMPANION_NAME_LABEL = "Companion" - private val UPPER_REGEX = Regex("""^([A-Z]*_*)*$""") + private val UPPER_REGEX = Regex("^([A-Z0-9]*_*)*$") } override fun getApplicableUastTypes(): List> = listOf(UClass::class.java) - override fun createUastHandler(context: JavaContext): UElementHandler { - return object : UElementHandler() { - override fun visitClass(node: UClass) { - val innerClass = node.innerClasses.firstOrNull() ?: return - val name = innerClass.name ?: return + override fun createUastHandler(context: JavaContext) = object : UElementHandler() { + override fun visitClass(node: UClass) { + val innerClass = node.innerClasses.firstOrNull() ?: return + val name = innerClass.name ?: return - if (name != COMPANION_NAME_LABEL) { - return - } + if (name != COMPANION_NAME_LABEL) { + return + } - val declarations = innerClass.uastDeclarations.distinctBy { it.text } - - declarations.forEach { declaration -> - val text = declaration.text ?: return - val lines = text.lines() - lines.forEach { line -> - if (isIncorrectConstantName(line)) { - context.report( - ISSUE, - node, - context.getNameLocation(declaration), - "$line\n${ISSUE.getExplanation(TextFormat.TEXT)}" - ) - } + val declarations = innerClass.uastDeclarations.distinctBy { it.text } + + declarations.forEach { declaration -> + val text = declaration.text ?: return + val lines = text.lines() + lines.forEach { line -> + val identifierName = getName(line) + if (identifierName.isNotEmpty() && !identifierName.matches(UPPER_REGEX)) { + context.report( + ISSUE, + node, + context.getNameLocation(declaration), + "$line\n${ISSUE.getExplanation(TextFormat.TEXT)}", + createLintFix(identifierName) + ) } } } } } - private fun isIncorrectConstantName(line: String): Boolean { + private fun getName(line: String): String { if (!line.contains(CONST_VAL_LABEL)) { - return false + return "" } val substrings = line.split(" ") val valIndex = substrings.indexOf(VAL_LABEL) if (valIndex == substrings.size - 1 || valIndex <= 0) { - return false + return "" } - val identifierName = substrings[valIndex + 1] + return substrings[valIndex + 1] + } - return !identifierName.matches(UPPER_REGEX) + private fun createLintFix(name: String): LintFix { + return LintFix.create() + .replace() + .text(name) + .with(name.toUpperCase(Locale.ROOT)) + .build() } } + diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/class_interface/ComponentPositionDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/class_interface/ComponentPositionDetector.kt new file mode 100644 index 0000000..85491d5 --- /dev/null +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/class_interface/ComponentPositionDetector.kt @@ -0,0 +1,217 @@ +package com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.class_interface + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.* +import org.jetbrains.uast.* +import java.lang.Integer.min + +@Suppress("UnstableApiUsage") +class ComponentPositionDetector : Detector(), Detector.UastScanner { + companion object { + /** Issue describing the problem and pointing to the detector implementation */ + @JvmField + val ISSUE: Issue = Issue.create( + id = "OMEGA_USE_CLASS_COMPONENTS_IN_CORRECT_ORDER", + briefDescription = "Place class members in correct order", + explanation = """ + Order warning. + http://wiki.omega-r.club/dev-android-code#rec228155171 + """, + category = Category.CORRECTNESS, + priority = 7, + severity = Severity.WARNING, + implementation = Implementation( + ComponentPositionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + private const val COMPANION_NAME = "Companion" + private const val MODIFIERS_RANK_MESSAGE = + "Class members with access modifiers should be positioned in the following order:\n" + + "abstract, override, public, internal, protected, private.\n" + + // Access modifiers rank + private val modifiers = arrayOf( + Pair("abstract", 1), + Pair("override", 2), + Pair("public", 3), + Pair("internal", 4), + Pair("protected", 5), + Pair("private", 6), + Pair("", 3), + ) + + // 1. companion object members + private const val COMPANION_OBJECT = "const val" + private const val COMPANION_OBJECT_RANK = 1 + private const val COMPANION_OBJECT_MESSAGE = "Companion object should be the first" + private val COMPANION_OBJECT_REGEX_LIST = makeRegexList(COMPANION_OBJECT) + private val COMPANION_OBJECT_PARAMS = + StaticParams(COMPANION_OBJECT_REGEX_LIST, COMPANION_OBJECT_RANK, modifiers.last().second,COMPANION_OBJECT_MESSAGE) + + // 2. val + var variables + private const val VAL = "val" + private const val VAR = "var" + private const val VARIABLES_RANK = 2 + private const val VARIABLES_MESSAGE = + "Variables should be positioned earlier than constructors, functions, enums, interfaces and classes" + private val VAL_REGEX_LIST = makeRegexList(VAL) + private val VAR_REGEX_LIST = makeRegexList(VAR) + private val VAL_PARAMS = StaticParams(VAL_REGEX_LIST, VARIABLES_RANK, modifiers.last().second, VARIABLES_MESSAGE) + private val VAR_PARAMS = StaticParams(VAR_REGEX_LIST, VARIABLES_RANK, modifiers.last().second, VARIABLES_MESSAGE) + + // 3. constructors and inits + private const val CONSTRUCTOR = "constructor" + private const val CONSTRUCTOR_RANK = 3 + private const val CONSTRUCTOR_MESSAGE = "Constructors should be positioned earlier than functions, enums, interfaces and classes" + private val CONSTRUCTOR_REGEX_LIST = makeRegexList(CONSTRUCTOR) + private val CONSTRUCTOR_PARAMS = StaticParams(CONSTRUCTOR_REGEX_LIST, CONSTRUCTOR_RANK, modifiers.last().second, CONSTRUCTOR_MESSAGE) + + // 4. functions + private const val FUNCTION = "fun" + private const val FUNCTION_RANK = 4 + private const val FUNCTION_MESSAGE = "Functions should be positioned earlier than enums, interfaces and classes" + private val FUNCTION_REGEX_LIST = makeRegexList(FUNCTION) + private val FUNCTION_PARAMS = StaticParams(FUNCTION_REGEX_LIST, FUNCTION_RANK, modifiers.last().second, FUNCTION_MESSAGE) + + // 5. enums + private const val ENUM = "enum" + private const val ENUM_RANK = 5 + private const val ENUM_MESSAGE = "Enums should be positioned earlier than interfaces and classes" + private val ENUM_REGEX_LIST = makeRegexList(ENUM) + private val ENUM_PARAMS = StaticParams(ENUM_REGEX_LIST, ENUM_RANK, modifiers.last().second, ENUM_MESSAGE) + + // 6. interfaces + private const val INTERFACE = "interface" + private const val INTERFACE_RANK = 6 + private const val INTERFACE_MESSAGE = "Interfaces should be positioned earlier than classes" + private val INTERFACE_REGEX_LIST = makeRegexList(INTERFACE) + private val INTERFACE_PARAMS = StaticParams(INTERFACE_REGEX_LIST, INTERFACE_RANK, modifiers.last().second, INTERFACE_MESSAGE) + + // 7. classes + private const val CLASS = "class" + private const val CLASS_RANK = 7 + private val CLASS_REGEX_LIST = makeRegexList(CLASS) + + private fun makeRegexList(value: String): List { + return listOf( + Regex("^${modifiers[0].first} $value"), + Regex("^${modifiers[1].first} $value"), + Regex("^${modifiers[2].first} $value"), + Regex("^${modifiers[3].first} $value"), + Regex("^${modifiers[4].first} $value"), + Regex("^${modifiers[5].first} $value"), + Regex("^${modifiers[6].first}${value}") + ) + } + + } + + /** + * Sorting declarations by file's lines + * node.uastDeclarations gives elements in wrong order + * https://github.com/JetBrains/intellij-community/blob/master/uast/uast-java/src/org/jetbrains/uast/java/declarations/JavaUClass.kt + */ + + override fun getApplicableUastTypes(): List> = listOf(UClass::class.java) + + override fun createUastHandler(context: JavaContext) = object: UElementHandler() { + override fun visitClass(node: UClass) { + val name = node.name ?: return + val lines = node.text.lines().toMutableList().apply { + removeFirst() + removeLast() + removeIf { it == "" } + } + val sortedDeclarationList = getSortedDeclarationList(lines, node.uastDeclarations) + + if (name != COMPANION_NAME) { + var currentRank = Pair(0, 0) + sortedDeclarationList.forEach { declaration -> + val text = declaration.text ?: return + val currentParams = CurrentParams(context, text, node, declaration) + + /** 1) It cannot find companion object*/ + currentRank = checkOrder(currentParams, COMPANION_OBJECT_PARAMS, currentRank) + + /** 2) Variables */ + currentRank = checkOrder(currentParams, VAL_PARAMS, currentRank) + currentRank = checkOrder(currentParams, VAR_PARAMS, currentRank) + + /** 3) Constructors */ + currentRank = checkOrder(currentParams, CONSTRUCTOR_PARAMS, currentRank) + + /** 4 Functions */ + currentRank = checkOrder(currentParams, FUNCTION_PARAMS, currentRank) + + /** 5 Enums */ + currentRank = checkOrder(currentParams, ENUM_PARAMS, currentRank) + + /** 6) Interfaces */ + currentRank = checkOrder(currentParams, INTERFACE_PARAMS, currentRank) + + /** 7) Classes */ + val classRegex = CLASS_REGEX_LIST.firstOrNull { text.contains(it) } + if (classRegex != null) { + currentRank = Pair(CLASS_RANK, modifiers.last().second) + } + } + } + } + } + + private fun getSortedDeclarationList( + lines: List, + listUDeclaration: List, + ): List { + val list = mutableListOf() + lines.forEach { line -> + listUDeclaration.forEach { declaration -> + val text = declaration.text + if (text.substring(0, min(text.length, line.length)).trim() == line.trim()) { + list.add(declaration) + } + } + } + return list + } + + private fun checkOrder(currentParams: CurrentParams, staticParams: StaticParams, currentRank: Pair): Pair { + val isVariable = staticParams.regexList.firstOrNull { currentParams.text.contains(it) } + if (isVariable != null) { + staticParams.modifierRank = modifiers.first { currentParams.text.contains(it.first) }.second + if (currentRank.first <= staticParams.rank && currentRank.second <= staticParams.modifierRank) { + return Pair(staticParams.rank, staticParams.modifierRank) + } else if (currentRank.second > staticParams.modifierRank) { + makeContextReport(currentParams.context, currentParams.node, currentParams.declaration, MODIFIERS_RANK_MESSAGE) + } else { + makeContextReport(currentParams.context, currentParams.node, currentParams.declaration, staticParams.message) + } + } + return currentRank + } + + private fun makeContextReport(context: JavaContext, node: UClass, declaration: UDeclaration, message: String) { + context.report( + ISSUE, + node, + context.getNameLocation(declaration), + "$message. ${ISSUE.getExplanation(TextFormat.TEXT)}" + ) + } + + private class StaticParams( + val regexList: List, + val rank: Int, + var modifierRank: Int, + val message: String + ) + + private class CurrentParams( + val context: JavaContext, + val text: String, + val node: UClass, + val declaration: UDeclaration + ) +} diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/file_class_interface/ComponentPositionDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/file_class_interface/ComponentPositionDetector.kt deleted file mode 100644 index 442b5e8..0000000 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/file_class_interface/ComponentPositionDetector.kt +++ /dev/null @@ -1,208 +0,0 @@ -package com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.file_class_interface - -import com.android.tools.lint.client.api.UElementHandler -import com.android.tools.lint.detector.api.* -import org.jetbrains.uast.* -import java.lang.Integer.min - -class ComponentPositionDetector : Detector(), Detector.UastScanner { - companion object { - /** Issue describing the problem and pointing to the detector implementation */ - @JvmField - val ISSUE: Issue = Issue.create( - id = "OMEGA_USE_COMPONENTS_IN_CORRECT_ORDER", - briefDescription = "The line size does not match the coding convention", - explanation = """ - Order warning. - http://wiki.omega-r.club/dev-android-code#rec228155171 - """, - category = Category.CORRECTNESS, - priority = 7, - severity = Severity.WARNING, - implementation = Implementation( - ComponentPositionDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) - ) - - private const val COMPANION_NAME = "Companion" - - // 1. companion object - private const val COMPANION_OBJECT = "companion object" - private const val COMPANION_OBJECT_RANK = 1 - private const val COMPANION_OBJECT_MESSAGE = "companion object should be the first" - private val COMPANION_OBJECT_REGEX_LIST = makeRegexList(COMPANION_OBJECT) - private val COMPANION_OBJECT_PARAMS = - StaticParams(COMPANION_OBJECT_REGEX_LIST, COMPANION_OBJECT_RANK, COMPANION_OBJECT_MESSAGE) - - // 2. val + var variables - private const val VAL = "val" - private const val VAR = "var" - private const val VARIABLES_RANK = 2 - private const val VARIABLES_MESSAGE = - "Variables should be earlier than constructors, functions, enums, interfaces and classes" - private val VAL_REGEX_LIST = makeRegexList(VAL) - private val VAR_REGEX_LIST = makeRegexList(VAR) - private val VAL_PARAMS = StaticParams(VAL_REGEX_LIST, VARIABLES_RANK, VARIABLES_MESSAGE) - private val VAR_PARAMS = StaticParams(VAR_REGEX_LIST, VARIABLES_RANK, VARIABLES_MESSAGE) - - // 3. constructors and inits - private const val CONSTRUCTOR = "constructor" - private const val CONSTRUCTOR_RANK = 3 - private const val CONSTRUCTOR_MESSAGE = "Constructor should be earlier than functions, enums, interfaces and classes" - private val CONSTRUCTOR_REGEX_LIST = makeRegexList(CONSTRUCTOR) - private val CONSTRUCTOR_PARAMS = StaticParams(CONSTRUCTOR_REGEX_LIST, CONSTRUCTOR_RANK, CONSTRUCTOR_MESSAGE) - - // 4. functions - private const val FUNCTION = "fun" - private const val FUNCTION_RANK = 4 - private const val FUNCTION_MESSAGE = "Functions should be earlier than enums, interfaces and classes" - private val FUNCTION_REGEX_LIST = makeRegexList(FUNCTION) - private val FUNCTION_PARAMS = StaticParams(FUNCTION_REGEX_LIST, FUNCTION_RANK, FUNCTION_MESSAGE) - - // 5. enums - private const val ENUM = "enum" - private const val ENUM_RANK = 5 - private const val ENUM_MESSAGE = "Enum should be earlier than interfaces and classes" - private val ENUM_REGEX_LIST = makeRegexList(ENUM) - private val ENUM_PARAMS = StaticParams(ENUM_REGEX_LIST, ENUM_RANK, ENUM_MESSAGE) - - // 6. interfaces - private const val INTERFACE = "interface" - private const val INTERFACE_RANK = 6 - private const val INTERFACE_MESSAGE = "Enum should be earlier than classes" - private val INTERFACE_REGEX_LIST = makeRegexList(INTERFACE) - private val INTERFACE_PARAMS = StaticParams(INTERFACE_REGEX_LIST, INTERFACE_RANK, INTERFACE_MESSAGE) - - // 7. classes - private const val CLASS = "class" - private const val CLASS_RANK = 7 - private val CLASS_REGEX_LIST = makeRegexList(CLASS) - private val CLASS_PARAMS = StaticParams(CLASS_REGEX_LIST, CLASS_RANK, "") - - private fun makeRegexList(value: String): List { - return listOf( - Regex("^abstract $value"), - Regex("^override $value"), - Regex("^public $value"), - Regex("^internal $value"), - Regex("^protected $value"), - Regex("^private $value"), - Regex("^${value}") - ) - } - - } - - /** - * Sorting declarations by file's lines - * node.uastDeclarations give elements in wrong order - * https://github.com/JetBrains/intellij-community/blob/master/uast/uast-java/src/org/jetbrains/uast/java/declarations/JavaUClass.kt - */ - - override fun getApplicableUastTypes(): List> = listOf(UClass::class.java) - - override fun createUastHandler(context: JavaContext): UElementHandler { - return object : UElementHandler() { - override fun visitClass(node: UClass) { - val name = node.name ?: return - val lines = node.parent?.text?.lines() ?: return - val sortedDeclarationList = getSortedDeclarationList(lines, node.uastDeclarations, name) - - if (name != COMPANION_NAME) { - var currentRank = 0 - sortedDeclarationList.forEach { declaration -> - val text = declaration.text ?: return - val currentParams = CurrentParams(context, text, currentRank, node, declaration) - - /** 1) it's can find companion object*/ - currentRank = checkOrder(currentParams, COMPANION_OBJECT_PARAMS) - - /** 2) Variables*/ - currentRank = checkOrder(currentParams, VAL_PARAMS) - currentRank = checkOrder(currentParams, VAR_PARAMS) - - /** 3) Constructor */ - currentRank = checkOrder(currentParams, CONSTRUCTOR_PARAMS) - - /** 4 Function */ - currentRank = checkOrder(currentParams, FUNCTION_PARAMS) - - /** 5 Enum */ - currentRank = checkOrder(currentParams, ENUM_PARAMS) - - /** 6) Interface */ - currentRank = checkOrder(currentParams, INTERFACE_PARAMS) - - /** 7) Class */ - val classRegex = CLASS_REGEX_LIST.firstOrNull { text.contains(it) } - if (classRegex != null) { - currentRank = CLASS_RANK - } - } - } - } - } - } - - private fun getSortedDeclarationList( - lines: List, - listUDeclaration: List, - name: String - ): List { - val list = mutableListOf() - lines.forEach { line -> - if (line != "") { - listUDeclaration.forEach { declaration -> - val dt = declaration.text - if (dt != null) { - if ((dt.substring(0, min(dt.length, line.length)).trim() == line.trim()) && - !(line.contains("class $name")) - ) { - list.add(declaration) - } - } - } - } - } - - return list.distinctBy { it.text } - } - - private fun checkOrder(currentParams: CurrentParams, staticParams: StaticParams): Int { - val isVariable = staticParams.regexList.firstOrNull { currentParams.text.contains(it) } - if (isVariable != null) { - if (currentParams.currentRank <= staticParams.rank) { - return staticParams.rank - } else { - makeContextReport(currentParams.context, currentParams.node, currentParams.declaration, staticParams.message) - } - } - return currentParams.currentRank - } - - private fun makeContextReport(context: JavaContext, node: UClass, declaration: UDeclaration, message: String) { - context.report( - ISSUE, - node, - context.getNameLocation(declaration), - "$message ${ISSUE.getExplanation(TextFormat.TEXT)}" - ) - } - - private class StaticParams( - val regexList: List, - val rank: Int, - val message: String - ) - - private class CurrentParams( - val context: JavaContext, - val text: String, - val currentRank: Int, - val node: UClass, - val declaration: UDeclaration - ) -} - - diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/file_package/PackageComponentDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/file_package/PackageComponentDetector.kt new file mode 100644 index 0000000..8302555 --- /dev/null +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/file_package/PackageComponentDetector.kt @@ -0,0 +1,79 @@ +package com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.file_package + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.* +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiField +import org.jetbrains.uast.* + +@Suppress("UnstableApiUsage") +class PackageComponentDetector : Detector(), Detector.UastScanner { + + companion object { + @JvmField + val ISSUE = Issue.create( + id = "OMEGA_USE_FILE_COMPONENTS_IN_CORRECT_ORDER", + briefDescription = "Place file components in correct order", + explanation = """ + Order warning. + http://wiki.omega-r.club/dev-android-code#rec228155171 + """, + category = Category.CORRECTNESS, + priority = 7, + severity = Severity.WARNING, + implementation = Implementation( + PackageComponentDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + private const val CLASS_LABEL = "class" + private const val CONSTANT_MESSAGE = "Constants should be positioned before classes, interfaces or any public elements" + private const val ELEMENTS_MESSAGE = "All public elements should be positioned after classes and interfaces" + } + + override fun getApplicableUastTypes(): List> = listOf(UFile::class.java) + + override fun createUastHandler(context: JavaContext) = object : UElementHandler() { + override fun visitFile(node: UFile) { + val publicElements = node.classes.drop(1) + val classes = node.classes.filter { !it.isInterface }.filter { !it.isEnum } + val fields = node.classes[0].fields + + fields.forEach fieldScope@ { uField -> + publicElements.forEach { uElement -> + if (uField.getStartPosition() > uElement.getStartPosition()) { + makeContextReport(context, node, uField, CONSTANT_MESSAGE) + return@fieldScope + } + } + } + + publicElements.forEach elementsScope@ { uElement -> + classes.drop(1).forEach classesScope@ { uClass -> + if (!uClass.text.startsWith(CLASS_LABEL) && !uElement.text.startsWith(CLASS_LABEL)) { + return@classesScope + } + if (!uElement.isInterface && !uElement.text.startsWith(CLASS_LABEL) && uClass.getStartPosition() > uElement.getStartPosition()) { + makeContextReport(context, node, uElement, ELEMENTS_MESSAGE) + return@elementsScope + } + } + } + } + + private fun PsiField.getStartPosition() = context.getNameLocation(this).start?.line ?: 0 + + private fun PsiClass.getStartPosition() = context.getNameLocation(this).start?.line ?: 0 + + } + + private fun makeContextReport(context: JavaContext, node: UFile, declaration: UElement, message: String) { + context.report( + ISSUE, + node, + context.getLocation(declaration), + "$message ${ISSUE.getExplanation(TextFormat.TEXT)}" + ) + } +} \ No newline at end of file diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/function_params/PositionArgumentDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/function_params/ArgumentPositionDetector.kt similarity index 94% rename from checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/function_params/PositionArgumentDetector.kt rename to checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/function_params/ArgumentPositionDetector.kt index 23cefc5..14a0e55 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/function_params/PositionArgumentDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/order/function_params/ArgumentPositionDetector.kt @@ -2,13 +2,12 @@ package com.omegar.lint.checks.detector.code_guidelines.kotlin_style.order.funct import com.android.tools.lint.client.api.UElementHandler import com.android.tools.lint.detector.api.* -import org.jetbrains.uast.UCallExpression import org.jetbrains.uast.UElement import org.jetbrains.uast.UMethod import org.jetbrains.uast.UParameter @Suppress("UnstableApiUsage") -class PositionArgumentDetector : Detector(), Detector.UastScanner { +class ArgumentPositionDetector : Detector(), Detector.UastScanner { companion object { /** Issue describing the problem and pointing to the detector implementation */ @JvmField @@ -23,7 +22,7 @@ class PositionArgumentDetector : Detector(), Detector.UastScanner { priority = 6, severity = Severity.WARNING, implementation = Implementation( - PositionArgumentDetector::class.java, + ArgumentPositionDetector::class.java, Scope.JAVA_FILE_SCOPE ) ) diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/class_methods_count/MaxMethodCountDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/class_methods_count/MaxMethodCountDetector.kt index f717906..36fd3b6 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/class_methods_count/MaxMethodCountDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/class_methods_count/MaxMethodCountDetector.kt @@ -2,10 +2,9 @@ package com.omegar.lint.checks.detector.code_guidelines.kotlin_style.restriction import com.android.tools.lint.client.api.UElementHandler import com.android.tools.lint.detector.api.* +import org.jetbrains.uast.* -import org.jetbrains.uast.UClass -import org.jetbrains.uast.UElement - +@Suppress("UnstableApiUsage") class MaxMethodCountDetector : Detector(), Detector.UastScanner { companion object { /** Issue describing the problem and pointing to the detector implementation */ @@ -26,21 +25,41 @@ class MaxMethodCountDetector : Detector(), Detector.UastScanner { ) ) + private const val DATA_CLASS_FUNCTION_VALUE = "public final fun copy" + private const val KEYWORD_VAR = "var" + private const val KEYWORD_VAL = "val" + private const val METHOD_GET = "get" + private const val METHOD_SET = "set" private const val MAX_METHOD_COUNT = 30 } override fun getApplicableUastTypes(): List> = listOf(UClass::class.java) + override fun createUastHandler(context: JavaContext) = object : UElementHandler() { + override fun visitClass(node: UClass) { + val resultMethods = mutableListOf() + val text = node.uastDeclarations.distinctBy { it.text } + var propertyCount = 0 - override fun createUastHandler(context: JavaContext): UElementHandler { - return object : UElementHandler() { - override fun visitClass(node: UClass) { - val methods = node.methods - if (methods.size > MAX_METHOD_COUNT) { - context.report(ISSUE, node, context.getNameLocation(node), ISSUE.getExplanation(TextFormat.TEXT)) + text.forEachIndexed { _, uDeclaration -> + if (uDeclaration.text.contains(KEYWORD_VAL) && uDeclaration.text.contains(METHOD_GET)) { + propertyCount++ + } else if (uDeclaration.text.contains(KEYWORD_VAR)) { + if (uDeclaration.text.contains(METHOD_GET)) propertyCount++ + if (uDeclaration.text.contains(METHOD_SET)) propertyCount++ + } + } + node.methods.forEach { + if (it.asRenderString().contains(DATA_CLASS_FUNCTION_VALUE)) { + return@visitClass } + if (!it.isVarArgs && !it.isConstructor) { + resultMethods.add(it) + } + } + if (resultMethods.size - propertyCount > MAX_METHOD_COUNT) { + context.report(ISSUE, node, context.getNameLocation(node), ISSUE.getExplanation(TextFormat.TEXT)) } } } -} - +} \ No newline at end of file diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/emptiness/EmptyBodyFunctionDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/emptiness/EmptyBodyFunctionDetector.kt index 8bea675..31924b3 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/emptiness/EmptyBodyFunctionDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/emptiness/EmptyBodyFunctionDetector.kt @@ -45,7 +45,7 @@ class EmptyBodyFunctionDetector : Detector(), Detector.UastScanner { val text = node.text ?: return if (text.contains(EMPTY_BODY_REGEX) && (body.asRenderString().matches(EMPTY_BODY_REGEX))) { - context.report(ISSUE, node, context.getLocation(body), ISSUE.getExplanation(TextFormat.TEXT)) + context.report(ISSUE, node, context.getLocation(body), text, createFix(text)) } } @@ -53,10 +53,26 @@ class EmptyBodyFunctionDetector : Detector(), Detector.UastScanner { val renderText = node.asRenderString() if (renderText.contains(EMPTY_BRANCH_REGEX)) { - context.report(ISSUE, node, context.getLocation(node), ISSUE.getExplanation(TextFormat.TEXT)) + context.report( + ISSUE, + node, + context.getLocation(node), + ISSUE.getExplanation(TextFormat.TEXT), + createFix(renderText) + ) } } } } + + private fun createFix(body: String): LintFix? { + val indexOfScope = body.indexOf("{") +// val newBody = "${body.removeRange(indexOfScope, body.length)}\n//nothing\n}}" + return LintFix.create() + .replace() + .text(body) + .with("newBody") + .build() + } } diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/params_count/MaxFunctionsArgumentsDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/params_count/MaxFunctionsArgumentsDetector.kt index 92660d8..8b32a5d 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/params_count/MaxFunctionsArgumentsDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/restrictions/params_count/MaxFunctionsArgumentsDetector.kt @@ -25,6 +25,8 @@ class MaxFunctionsArgumentsDetector : Detector(), Detector.UastScanner { ) ) + // lint can't understand data class constructors because it understand them as fun with name "copy" + private const val DATA_CLASS_FUNCTION_VALUE = "public final fun copy" private const val MAX_COUNT_OF_ARGUMENTS = 5 } @@ -33,7 +35,7 @@ class MaxFunctionsArgumentsDetector : Detector(), Detector.UastScanner { override fun createUastHandler(context: JavaContext): UElementHandler { return object : UElementHandler() { override fun visitMethod(node: UMethod) { - if (node.isConstructor) { + if (node.isConstructor || node.asRenderString().contains(DATA_CLASS_FUNCTION_VALUE)) { return } diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/control_instructions/SimplificationsControlInstructionsDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/control_instructions/SimplificationsControlInstructionsDetector.kt index 5492323..599390b 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/control_instructions/SimplificationsControlInstructionsDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/control_instructions/SimplificationsControlInstructionsDetector.kt @@ -26,10 +26,18 @@ class SimplificationsControlInstructionsDetector : Detector(), Detector.UastScan ) private val EMPTY_BRANCH_REGEX = Regex("""\{\s*\}""") + private val END_SPACE_REGEX = Regex("""\s*$""") + private val MORE_THAN_ONE_SPACE_REGEX = Regex("""\s+""") private const val ELSE_LABEL = "-> {" private const val ELSE_TEXT = "else -> {" private const val DELTA = 2 + private const val OPEN_SCOPE_SYMBOL = "{" + private const val CLOSE_SCOPE_SYMBOL = "}" + private const val EQUALS_SYMBOL = "=" + private const val NEW_LINE_SYMBOL = "\n" + private const val SPACE_SYMBOL = " " + } override fun getApplicableUastTypes(): List> = listOf(USwitchClauseExpression::class.java) @@ -65,9 +73,27 @@ class SimplificationsControlInstructionsDetector : Detector(), Detector.UastScan private fun exceedMaxLineLength(text: String): Boolean { val lines = text.lines() - if(lines.size > 1) { + if (lines.size > 1) { return lines[0].length + lines[1].trim().length - DELTA < MAX_LENGTH } return false } + + private fun createFix(text: String): LintFix? { + return fix() + .replace() + .text(text) + .with(getSimpleString(text)) + .build() + } + + private fun getSimpleString(text: String): String { + return text + .replace(OPEN_SCOPE_SYMBOL, EQUALS_SYMBOL) + .replace(NEW_LINE_SYMBOL, "") + .replace(CLOSE_SCOPE_SYMBOL, "") + .replace(END_SPACE_REGEX, "") + .replace(MORE_THAN_ONE_SPACE_REGEX, SPACE_SYMBOL) + } + } diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/function/SimplificationsFunctionDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/function/SimplificationsFunctionDetector.kt index d6c75d1..4e3879e 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/function/SimplificationsFunctionDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/simplifications/function/SimplificationsFunctionDetector.kt @@ -2,6 +2,7 @@ package com.omegar.lint.checks.detector.code_guidelines.kotlin_style.simplificat import com.android.tools.lint.client.api.UElementHandler import com.android.tools.lint.detector.api.* +import com.omegar.lint.checks.detector.code_guidelines.kotlin_style.restrictions.line_length.MaxLineLengthDetector.Companion.MAX_LENGTH import org.jetbrains.uast.UElement import org.jetbrains.uast.UMethod @@ -10,24 +11,33 @@ class SimplificationsFunctionDetector : Detector(), Detector.UastScanner { /** Issue describing the problem and pointing to the detector implementation */ @JvmField val ISSUE: Issue = Issue.create( - "OMEGA_CAN_USE_EXPRESSION_FUNCTION", - "When a function contains only one expression, it can be represented as an \"expression function\".", - """ + "OMEGA_CAN_USE_EXPRESSION_FUNCTION", + "When a function contains only one expression, it can be represented as an \"expression function\".", + """ You can change it to "expression function" http://wiki.omega-r.club/dev-android-code#rec228389255 """, - Category.CORRECTNESS, - 7, - Severity.INFORMATIONAL, - Implementation( - SimplificationsFunctionDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) - ) + Category.CORRECTNESS, + 7, + Severity.INFORMATIONAL, + Implementation( + SimplificationsFunctionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) private val ONE_EXPRESSION_REGEX = Regex("""\{\s*return\s*.*""") private val RETURN_REGEX = Regex("""\s*return""") private const val MAX_LINE_COUNT_IN_EXPRESSION_FUNCTION = 3 + + private const val OPEN_SCOPE_SYMBOL = "{" + private const val CLOSE_SCOPE_SYMBOL = "}" + private const val EQUALS_SYMBOL = "=" + private const val NEW_LINE_SYMBOL = "\n" + private const val SPACE_SYMBOL = " " + + private val END_SPACE_REGEX = Regex("""\s*$""") + private val MORE_THAN_ONE_SPACE_REGEX = Regex("""\s+""") } override fun getApplicableUastTypes(): List> = listOf(UMethod::class.java) @@ -37,32 +47,41 @@ class SimplificationsFunctionDetector : Detector(), Detector.UastScanner { override fun visitMethod(node: UMethod) { val text = node.text ?: return val linesCount = text.count { it == '\n' } + 1 - if (linesCount <= MAX_LINE_COUNT_IN_EXPRESSION_FUNCTION && text.contains(ONE_EXPRESSION_REGEX)) { + if (linesCount <= MAX_LINE_COUNT_IN_EXPRESSION_FUNCTION + && text.contains(ONE_EXPRESSION_REGEX) + ) { + val newText = getSimpleFunctionString(text) + + if (newText.length > MAX_LENGTH) return + context.report( - ISSUE, - node, - context.getLocation(node), - ISSUE.getExplanation(TextFormat.TEXT), - createFix(text) - ) + ISSUE, + node, + context.getLocation(node), + ISSUE.getExplanation(TextFormat.TEXT), + createFix(text, newText) + ) } } } } - private fun createFix(text: String): LintFix { - val newText = text - .replace("{", "=") - .replace(RETURN_REGEX, "") - .replace("\n", "") - .replace("}", "") - .replace(Regex("""\s*$"""), "") - + private fun createFix(text: String, newText: String): LintFix? { return fix() .replace() .text(text) .with(newText) .build() + } + private fun getSimpleFunctionString(text: String): String { + return text + .replace(OPEN_SCOPE_SYMBOL, EQUALS_SYMBOL) + .replace(RETURN_REGEX, "") + .replace(NEW_LINE_SYMBOL, "") + .replace(CLOSE_SCOPE_SYMBOL, "") + .replace(END_SPACE_REGEX, "") + .replace(MORE_THAN_ONE_SPACE_REGEX, SPACE_SYMBOL) } + } \ No newline at end of file diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/use_spaces/around_operands/SpaceMethodDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/use_spaces/around_operands/SpaceMethodDetector.kt index c1b49bd..1e1dca9 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/use_spaces/around_operands/SpaceMethodDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/kotlin_style/use_spaces/around_operands/SpaceMethodDetector.kt @@ -5,7 +5,7 @@ import com.android.tools.lint.detector.api.* import org.jetbrains.uast.UClass import org.jetbrains.uast.UElement -class SpaceMethodDetector : Detector(), Detector.UastScanner { +open class SpaceMethodDetector : Detector(), Detector.UastScanner { companion object { /** Issue describing the problem and pointing to the detector implementation */ @JvmField @@ -30,17 +30,26 @@ class SpaceMethodDetector : Detector(), Detector.UastScanner { Regex("""([a-z]|[A-Z]|"|'|\)|=|>|\?)\s\{$""") private val RIGHT_FUNCTIONS_OPEN_SCOPE_REGEX = Regex("""([a-z]|[A-Z]|\d)\s*\(""") - private val POINT_BEGIN_REGEX = Regex("""^\s*\.""") - private val QUESTION_MARK_POINT_BEGIN_REGEX = Regex("""^\s*\?\.""") - private const val DELETE_SPACES_MESSAGE = "Remove extra spaces." private const val FUNCTION_VALUE = "fun" private const val OPEN_SCOPE_VALUE = "(" private const val QUOTE_VALUE = "\"" + private val OPEN_BRACE_REGEX = Regex("""\s*\{""") + private val OPEN_SCOPE_REGEX = Regex("""\s*\(""") + + private val KEY_SYMBOLS_MAP = mapOf( + "." to Regex("""(\s+\.\s*|\s*\.\s+)"""), + "::" to Regex("""(\s+::\s*|\s*::\s+)"""), + "?." to Regex("""(\s+\?\.\s*|\s*\?\.\s+)""") + ) + + private val KEY_BEGIN_SYMBOLS_MAP = mapOf( + "." to Regex("""^\s*\."""), + "::" to Regex("""^\s*::"""), + "?." to Regex("""^\s*\?\.""") + ) - private val CHAR_ARRAY = arrayOf(".", "::", "?.") - private val REGEXPS = CHAR_ARRAY.map { it to Regex("""\s*$it\s""") }.toMap() private val COMMENTS_REGEX = Regex("""([//]|[/\*])""") } @@ -55,51 +64,35 @@ class SpaceMethodDetector : Detector(), Detector.UastScanner { var beginPosition = 0 lines.forEach { line -> val length = line.length - REGEXPS.forEach { pair -> + + KEY_SYMBOLS_MAP.forEach { pair -> if (line.contains(pair.value) && !line.contains(COMMENTS_REGEX)) { val beforeIndex = line.indexOf(" ${pair.key}") val afterIndex = line.indexOf("${pair.key} ") - - when { - beforeIndex > 0 - && afterIndex <= 0 - && !line.contains(POINT_BEGIN_REGEX) - && !line.contains(QUESTION_MARK_POINT_BEGIN_REGEX) -> { - makeContextReport( - Params( - context, - node, - beginPosition + beforeIndex, - pair.key.length + 1, - line, - beforeIndex - ) - ) - } - - afterIndex > 0 && beforeIndex <= 0 -> { - makeContextReport( - Params( - context, - node, - beginPosition + afterIndex, - pair.key.length + 1, - line, + val match = pair.value.find(line) + + if (match != null && (beforeIndex > 0 || afterIndex > 0)) { + var index = line.indexOf(match.value) + if (index >= 0 && !(pair.key == "." && line[line.indexOf(pair.key) - 1] == '?')) { + val selectedText = if (checkIsSymbolFirst(line, pair.key)) { + index = if (beforeIndex > 0) { + beforeIndex - 1 + } else { afterIndex - ) - ) - } - - afterIndex > 0 && beforeIndex > 0 -> { + } + match.value.substring(match.value.indexOf(pair.key) - 1, match.value.length - 1) + } else { + match.value + } makeContextReport( Params( context, node, - beginPosition + beforeIndex, - pair.key.length + 2, + beginPosition + index, + selectedText.length, line, - beforeIndex + index ) ) } @@ -116,6 +109,11 @@ class SpaceMethodDetector : Detector(), Detector.UastScanner { } } + private fun checkIsSymbolFirst(line: String, key: String): Boolean { + val beginSymbolRegex = KEY_BEGIN_SYMBOLS_MAP[key] ?: return false + return line.contains(beginSymbolRegex) + } + private fun checkSpaceForScopes( context: JavaContext, line: String, @@ -125,9 +123,13 @@ class SpaceMethodDetector : Detector(), Detector.UastScanner { val length = line.length if (line.contains(RIGHT_FUNCTIONS_OPEN_SCOPE_REGEX) && line.contains(FUNCTION_VALUE)) { val functionLine = line.substring(0, line.indexOf(OPEN_SCOPE_VALUE) + 1) - val index = functionLine.indexOf(" (") - if (index > 0) { - makeContextReport(Params(context, node, beginPosition + index, 2, line, index)) + val spaceIndex = functionLine.indexOf(" $OPEN_SCOPE_VALUE") + if (spaceIndex > 0) { + val match = OPEN_SCOPE_REGEX.find(line) + if (match != null) { + val index = spaceIndex - match.value.length + 2 + makeContextReport(Params(context, node, beginPosition + index, match.value.length, line, index)) + } } } @@ -135,22 +137,17 @@ class SpaceMethodDetector : Detector(), Detector.UastScanner { && line.contains(END_FUNCTION_DECLARATION_REGEX) && !line.matches(END_FUNCTION_DECLARATION_REGEX) ) { - if (line[line.indexOf("{") - 1].toString() != OPEN_SCOPE_VALUE) - makeContextReport( - Params( - context, - node, - beginPosition + length - 1, - 1, - line, - length - 1 - ) - ) + val match = OPEN_BRACE_REGEX.find(line) + if (match != null) { + val index = length - match.value.length + makeContextReport(Params(context, node, beginPosition + index, match.value.length, line, index, " ")) + } } } private fun makeContextReport(params: Params) { if (!isInQuote(params.line, params.index)) { + params.context.report( ISSUE, params.node, @@ -159,7 +156,8 @@ class SpaceMethodDetector : Detector(), Detector.UastScanner { params.beginPosition, params.length ), - ISSUE.getExplanation(TextFormat.TEXT) + ISSUE.getExplanation(TextFormat.TEXT), + createLintFix(params.line, params.index, params.length, params.toReplaceString) ) } } @@ -177,15 +175,26 @@ class SpaceMethodDetector : Detector(), Detector.UastScanner { return inside } } + return false } + private fun createLintFix(line: String, index: Int, length: Int, toReplaceString: String): LintFix { + val substringWithSpace = line.substring(index, index + length) + return LintFix.create() + .replace() + .text(substringWithSpace) + .with(substringWithSpace.trim() + toReplaceString) + .build() + } + class Params( val context: JavaContext, val node: UClass, val beginPosition: Int, val length: Int, val line: String, - val index: Int + val index: Int, + val toReplaceString: String = "" ) } \ No newline at end of file diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/identifier/NameIdentifierXmlDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/identifier/NameIdentifierXmlDetector.kt index 6c2d08e..9eff89a 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/identifier/NameIdentifierXmlDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/identifier/NameIdentifierXmlDetector.kt @@ -3,13 +3,15 @@ package com.omegar.lint.checks.detector.code_guidelines.xml_style.name_resource. import com.android.resources.ResourceFolderType import com.android.tools.lint.detector.api.* import org.w3c.dom.Attr +import java.util.Locale +@Suppress("UnstableApiUsage") class NameIdentifierXmlDetector : ResourceXmlDetector() { companion object { val ISSUE = Issue.create( id = "OMEGA_NAME_VARIABLES_CORRECTLY", - briefDescription = "Detects wrongs name of view's identifier", + briefDescription = "Detects wrong name of view's identifier", explanation = """ Name of identifier should begin with prefix, which depends of view name. http://wiki.omega-r.club/dev-android-code#rec228390320 @@ -23,7 +25,8 @@ class NameIdentifierXmlDetector : ResourceXmlDetector() { ) ) - const val REPORT_MESSAGE = "Wrong prefix of identifier name. Should begin with: " + private val CAMEL_REGEX = Regex("(?<=[a-zA-Z])[A-Z]") + private const val REPORT_MESSAGE = "Wrong prefix of identifier name. Should begin with: " //elements private const val TEXT_VIEW_ELEMENT = "TextView" @@ -44,6 +47,7 @@ class NameIdentifierXmlDetector : ResourceXmlDetector() { private const val LAYOUT_PREFIX = "@+id/layout" private const val FLOATING_ACTION_BUTTON_PREFIX = "@+id/fab" private const val IMAGE_BUTTON_PREFIX = "@+id/button" + private const val IDENTIFIER_PREFIX = "@+id/" } @@ -56,67 +60,74 @@ class NameIdentifierXmlDetector : ResourceXmlDetector() { val attributeValue = attribute.nodeValue ?: return if (attribute.name == "android:id") { when (attribute.ownerElement.tagName) { - TEXT_VIEW_ELEMENT -> { - if (!attributeValue.contains(TEXT_VIEW_PREFIX)) { - makeContextReport(context, attribute, TEXT_VIEW_PREFIX) - } - } - - IMAGE_VIEW_ELEMENT -> { - if (!attributeValue.contains(IMAGE_VIEW_PREFIX)) { - makeContextReport(context, attribute, IMAGE_VIEW_PREFIX) - } - } - - BUTTON_ELEMENT -> { - if (!attributeValue.contains(BUTTON_PREFIX)) { - makeContextReport(context, attribute, BUTTON_PREFIX) - } - } - - EDIT_TEXT_ELEMENT -> { - if (!attributeValue.contains(EDIT_TEXT_PREFIX)) { - makeContextReport(context, attribute, EDIT_TEXT_PREFIX) - } - } - + TEXT_VIEW_ELEMENT -> checkPrefix(context, attributeValue, attribute, TEXT_VIEW_PREFIX) + IMAGE_VIEW_ELEMENT -> checkPrefix(context, attributeValue, attribute, IMAGE_VIEW_PREFIX) + BUTTON_ELEMENT -> checkPrefix(context, attributeValue, attribute, BUTTON_PREFIX) + EDIT_TEXT_ELEMENT -> checkPrefix(context, attributeValue, attribute, EDIT_TEXT_PREFIX) + FLOATING_ACTION_BUTTON_ELEMENT -> checkPrefix(context, attributeValue, attribute, FLOATING_ACTION_BUTTON_PREFIX) + IMAGE_BUTTON_ELEMENT -> checkPrefix(context, attributeValue, attribute, IMAGE_BUTTON_PREFIX) LAYOUT_ELEMENT, TABLE_LAYOUT_ELEMENT, LINEAR_LAYOUT_ELEMENT -> { - if (!attributeValue.contains(LAYOUT_PREFIX)) { - makeContextReport(context, attribute, LAYOUT_PREFIX) - } + checkPrefix(context, attributeValue, attribute, LAYOUT_PREFIX) } - - FLOATING_ACTION_BUTTON_ELEMENT -> { - if (!attributeValue.contains(FLOATING_ACTION_BUTTON_PREFIX)) { - makeContextReport(context, attribute, FLOATING_ACTION_BUTTON_PREFIX) - } + else -> { + val tagName = IDENTIFIER_PREFIX + attribute.ownerElement.tagName.split(".").last().replace("View", "") + val prefix = tagName.convertCamelToSnakeCase() + checkPrefix(context, attributeValue, attribute, prefix) } + } + } + } - IMAGE_BUTTON_ELEMENT -> { - if (!attributeValue.contains(IMAGE_BUTTON_PREFIX)) { - makeContextReport(context, attribute, IMAGE_BUTTON_PREFIX) - } - } + // String extensions + private fun String.convertCamelToSnakeCase(): String { + return CAMEL_REGEX.replace(this) { + it.value + }.toLowerCase(Locale.ROOT) + } + + private fun checkPrefix(context: XmlContext, attributeValue: String, attribute: Attr, prefix: String) { + if (!attributeValue.contains(prefix) && attributeValue.replace("_", "").contains(prefix)) { + makeContextReport(context, attribute, prefix) { + createFixForUnderscore(attributeValue, prefix) + } + } + else if (!attributeValue.contains(prefix)) { + makeContextReport(context, attribute, prefix) { + createFix(attributeValue, prefix) } } } - private fun makeContextReport(context: XmlContext, attribute: Attr, message: String) { + private fun makeContextReport(context: XmlContext, attribute: Attr, message: String, fix:() -> LintFix) { context.report( issue = ISSUE, scope = attribute, location = context.getValueLocation(attribute), message = "$REPORT_MESSAGE $message\n${ISSUE.getExplanation(TextFormat.TEXT)}", - quickfixData = createFix(attribute.nodeValue, message) + quickfixData = fix() ) } - private fun createFix(attributeValue: String, correctPrefix: String): LintFix { - val oldText = attributeValue.replace("@+id/", "") + private fun createFixForUnderscore(attributeValue: String, prefix: String): LintFix { + var finalValue = "" + attributeValue.forEach { char -> + if (!finalValue.contains(prefix) && char != '_' || finalValue.contains(prefix)) { + finalValue += char + } + } + return fix() + .replace() + .text(attributeValue) + .with(finalValue) + .build() + } + + private fun createFix(attributeValue: String, prefix: String): LintFix { + val attributeValueWithoutPrefix = attributeValue.replace(IDENTIFIER_PREFIX, "") return fix() .replace() .text(attributeValue) - .with("${correctPrefix}_$oldText") + .with("${prefix}_$attributeValueWithoutPrefix") .build() } } \ No newline at end of file diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/resource/NameResourceStringXmlDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/resource/NameResourceStringXmlDetector.kt index 7e6dc09..45959fa 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/resource/NameResourceStringXmlDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/code_guidelines/xml_style/name_resource/resource/NameResourceStringXmlDetector.kt @@ -5,6 +5,7 @@ import com.android.tools.lint.detector.api.* import org.w3c.dom.Element import org.w3c.dom.Node +@Suppress("UnstableApiUsage") class NameResourceStringXmlDetector : ResourceXmlDetector() { companion object { @@ -32,7 +33,9 @@ class NameResourceStringXmlDetector : ResourceXmlDetector() { "label", "button", "action", - "hint" + "hint", + "format", + "mask" ) private const val ATTRIBUTE_NAME_VAL = "name" //Attribute @@ -54,7 +57,7 @@ class NameResourceStringXmlDetector : ResourceXmlDetector() { val stringText = element.getAttribute(ATTRIBUTE_NAME_VAL) ?: return - if ((stringText == APP_NAME) || CORRECT_PREFIXES_LIST.firstOrNull() { stringText.contains(it) } != null) { + if ((stringText == APP_NAME) || CORRECT_PREFIXES_LIST.firstOrNull { stringText.contains(it) } != null) { return } diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/class/NameFileUpperCamelCaseDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/class/NameFileUpperCamelCaseDetector.kt index 35edfa1..3c72e43 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/class/NameFileUpperCamelCaseDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/class/NameFileUpperCamelCaseDetector.kt @@ -73,7 +73,7 @@ class NameFileUpperCamelCaseDetector : Detector(), Detector.UastScanner { } } - return resultName + return "${charArray[0]}$resultName" } } diff --git a/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/resource/layout/NameResourceLayoutDetector.kt b/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/resource/layout/NameResourceLayoutDetector.kt index fd26e30..cb8284e 100644 --- a/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/resource/layout/NameResourceLayoutDetector.kt +++ b/checks/src/main/java/com/omegar/lint/checks/detector/project_guidelines/file_name/resource/layout/NameResourceLayoutDetector.kt @@ -117,15 +117,25 @@ class NameResourceLayoutDetector : Detector(), Detector.UastScanner { private fun checkLayoutName(arguments: List, newClassName: String, node: UCallExpression, context: JavaContext) { arguments.forEach { argument -> val argumentName = argument.asRenderString() - if (argumentName.contains(LAYOUT_PREFIX_VAL) && LAYOUT_PREFIX_VAL + newClassName != argumentName) { + val layoutName = LAYOUT_PREFIX_VAL + newClassName + if (argumentName.contains(LAYOUT_PREFIX_VAL) && layoutName != argumentName) { context.report( ISSUE, node, context.getNameLocation(argument), - "$LAYOUT_PREFIX_VAL$newClassName\n${ISSUE.getExplanation(TextFormat.TEXT)}" + "$LAYOUT_PREFIX_VAL$newClassName\n${ISSUE.getExplanation(TextFormat.TEXT)}", + createLintFix(argumentName, layoutName) ) } } } + + private fun createLintFix(argumentName: String, layoutName: String): LintFix? { + return LintFix.create() + .replace() + .text(argumentName) + .with(layoutName) + .build() + } }