Created
February 15, 2020 12:02
-
-
Save athkalia/33073edd0ed61abfd3333f3338bde048 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.babylon.checks.detectors | |
import com.android.tools.lint.client.api.UElementHandler | |
import com.android.tools.lint.detector.api.Category | |
import com.android.tools.lint.detector.api.Detector | |
import com.android.tools.lint.detector.api.Implementation | |
import com.android.tools.lint.detector.api.Issue | |
import com.android.tools.lint.detector.api.JavaContext | |
import com.android.tools.lint.detector.api.Scope | |
import com.android.tools.lint.detector.api.Severity | |
import com.android.tools.lint.detector.api.SourceCodeScanner | |
import com.android.tools.lint.detector.api.TextFormat | |
import org.jetbrains.uast.UComment | |
import org.jetbrains.uast.UElement | |
import org.jetbrains.uast.UFile | |
import java.text.SimpleDateFormat | |
import java.util.Date | |
import java.util.EnumSet | |
import java.util.Locale | |
import java.util.concurrent.TimeUnit | |
/** | |
* Lint rule to detekt that all TODOs in the project have the correct format. | |
*/ | |
class TodoFormatDetector : Detector(), SourceCodeScanner { | |
private val expiryDateFormatter = SimpleDateFormat("MMM-yyyy", Locale.US) | |
private val invalidSlackUserName = "Invalid or missing slack user name. " + | |
"Valid slack user names are $ALLOWED_SLACK_USER_NAMES. Please update the lint rule if your name is missing." | |
private val invalidTodoFormat = "Please update the TODO as per the TODO template." | |
private val invalidExpiryDate = "Invalid or missing expiry date. Valid expiry date format is MMM-YYYY" | |
private val multilineComment = "Todo is not allowed in a multiline comment. " + | |
"Add TODO using a single line comment and provide additional information as a separate comment." | |
private val futureExpiryDate = "Expiry date cannot be more than $FUTURE_TODO_THRESHOLD_IN_DAYS days old from today. " + | |
"Dream big dreams, but never forget that realistic short-term goals are the keys to our success." | |
companion object { | |
private const val FUTURE_TODO_THRESHOLD_IN_DAYS = 180 | |
val ALLOWED_SLACK_USER_NAMES = listOf( | |
"@sakis" | |
) | |
val ISSUE: Issue = Issue.create( | |
id = "TodoFormat", | |
briefDescription = "Use the correct template for TODOs.", | |
explanation = "TODO template -> // TODO @slack_user_name MMM-YYYY <comment>.", | |
category = Category.CORRECTNESS, | |
priority = 1, | |
severity = Severity.ERROR, | |
implementation = Implementation(TodoFormatDetector::class.java, EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)) | |
) | |
} | |
override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UFile::class.java) | |
override fun createUastHandler(context: JavaContext): UElementHandler? = object : UElementHandler() { | |
@Suppress("MagicNumber") | |
override fun visitFile(node: UFile) { | |
val errorList = mutableListOf<String>() | |
node.allCommentsInFile.forEach { | |
when { | |
it.text.startsWith("// TODO") -> { | |
if (isSlackUserNameInvalid(it, 2)) { | |
errorList.add(invalidSlackUserName) | |
} | |
if (isExpiryDateFormatInvalid(it, 3)) { | |
errorList.add(invalidExpiryDate) | |
} else if (!isExpiryDateInAllowedRange(it, 3)) { | |
errorList.add(futureExpiryDate) | |
} | |
} | |
it.text.startsWith("//TODO") -> { | |
if (isSlackUserNameInvalid(it, 1)) { | |
errorList.add(invalidSlackUserName) | |
} | |
if (isExpiryDateFormatInvalid(it, 2)) { | |
errorList.add(invalidExpiryDate) | |
} else if (!isExpiryDateInAllowedRange(it, 2)) { | |
errorList.add(futureExpiryDate) | |
} | |
} | |
it.text.contains("TODO") -> { | |
if (it.text.contains("/*")) { | |
errorList.add(multilineComment) | |
} else { | |
errorList.add(invalidTodoFormat) | |
} | |
} | |
} | |
if (errorList.isNotEmpty()) { | |
var errorMessage = "\nMore info:\n" | |
errorList.forEachIndexed { index, error -> | |
errorMessage = "$errorMessage ${index + 1}. $error\n" | |
} | |
report(context, errorMessage, it) | |
errorList.clear() | |
} | |
} | |
} | |
} | |
@Suppress("TooGenericExceptionCaught") | |
private fun isExpiryDateInAllowedRange(it: UComment, dateIndex: Int): Boolean { | |
val expiryDateString = it.text.split(" ").getOrNull(dateIndex) | |
val currentDateString = expiryDateFormatter.format(Date()) | |
return try { | |
val expiryDate = expiryDateFormatter.parse(expiryDateString) | |
val currentDate = expiryDateFormatter.parse(currentDateString) | |
val differenceInMillis = expiryDate.time - currentDate.time | |
when { | |
differenceInMillis > 0 -> { | |
val differenceInDays = TimeUnit.DAYS.convert(differenceInMillis, TimeUnit.MILLISECONDS) | |
differenceInDays < FUTURE_TODO_THRESHOLD_IN_DAYS | |
} | |
else -> true | |
} | |
} catch (e: Exception) { | |
false | |
} | |
} | |
private fun isExpiryDateFormatInvalid(it: UComment, dateIndex: Int): Boolean { | |
val expiryDateString = it.text.split(" ").getOrNull(dateIndex) | |
return try { | |
expiryDateFormatter.parse(expiryDateString) | |
false | |
} catch (e: java.lang.Exception) { | |
true | |
} | |
} | |
private fun isSlackUserNameInvalid(it: UComment, usernameIndex: Int): Boolean { | |
val slackUserName = it.text.split(" ").getOrNull(usernameIndex) | |
return slackUserName?.toLowerCase() !in ALLOWED_SLACK_USER_NAMES | |
} | |
private fun report(context: JavaContext?, moreInfo: String = "", node: UComment) { | |
context?.report( | |
issue = ISSUE, | |
scope = node, | |
location = context.getLocation(node), | |
message = ISSUE.getExplanation(TextFormat.TEXT) + moreInfo | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment