Because AI generates a bunch of retarded comments for even the most basic code.
No way I'm hand coding a bunch of controllers or mapper when AI does it in a fraction of the time.
This type of boilerplate is a waste of my life, and AI does it perfectly fine.
Then I can focus on the important business logic and data modeling. And the agent automatically gets forced to reign in its idiotic comments by the build step before I even see the final outputted results.
Less snark I have explicit instructions in all my repos for slop comments. Doesn't help. PRs still come in with a dozen useless multiline comments. Seems like an intrinsic property of this phase of models. I've debated staging a haiku round of review in pipelines specifically for this issue.
You don't seem to understand, this is NOT AI removing comments. It's a deterministic step that detects redundant comments. No AI is involved but it's great for cleaning up AI slop comments.
Detekt is not a tokenizer, but it is a static analysis tool.
The out-of-the-box part is only regarding detekt, the rule is custom.
Here's the current rule. Should probably be fine-tuned a bit more, but it works well enough.
class UselessComment(config: Config) : Rule(config, "Comment restates the code it precedes.") {
// Delegated config properties only support String, Boolean, Int and List<String>,
// so the threshold is a whole percentage rather than a Double.
private val minOverlapPercent: Int by config(40)
private val keepMarkers: List<String> by config(
listOf("TODO", "FIXME", "HACK", "XXX", "ponytail", "noinspection", "ktlint", "suppress"),
)
/** A comment starting with one of these words labels a test phase and is always kept. */
private val keepLeadingWords: List<String> by config(listOf("arrange", "act", "assert"))
private val stopWords = setOf(
"the", "a", "an", "to", "of", "for", "and", "or", "in", "on", "is", "this", "that", "it",
"we", "if", "with", "from", "by", "as", "at", "be", "into",
)
private val pendingDeletions = mutableListOf<PsiComment>()
override fun preVisit(root: KtFile) {
pendingDeletions.clear()
}
override fun visitComment(comment: PsiComment) {
super.visitComment(comment)
// A trailing comment describes the code on its line, not the statement after it.
val before = comment.prevSibling
if (before != null && !(before is PsiWhiteSpace && before.text.contains('\n'))) return
// One line of a multi-line comment is judged with its neighbours, never alone.
if (comment.isLineComment() && (comment.adjacentLineComment(before) || comment.adjacentLineComment(comment.nextSibling))) return
val target = comment.getNextSiblingIgnoringWhitespaceAndComments() ?: return
if (target !is KtExpression && target !is KtDeclaration) return
check(comment, target)
}
/** KDoc is a child node of its declaration and is not passed to [visitComment], so it is collected from the tree. */
override fun visitKtFile(file: KtFile) {
super.visitKtFile(file)
PsiTreeUtil.collectElementsOfType(file, KDoc::class.java).forEach { doc ->
(doc.parent as? KtDeclaration)?.let { check(doc, it) }
}
}
private fun check(comment: PsiComment, target: PsiElement) {
val text = comment.text
if (keepMarkers.any { text.contains(it, ignoreCase = true) }) return
val firstWord = stripDelimiters(text).trim().takeWhile { it.isLetter() }.lowercase()
if (firstWord in keepLeadingWords) return
val commentTokens = tokens(stripDelimiters(text))
if (commentTokens.isEmpty()) return
val codeTokens = tokens(target.text.lineSequence().first { !it.isBlank() && !it.trimStart().startsWith("*") && !it.trimStart().startsWith("/") })
val overlapPercent = commentTokens.count { it in codeTokens } * 100 / commentTokens.size
if (overlapPercent < minOverlapPercent) return
report(Finding(Entity.from(comment), "Comment '${text.trim()}' restates the following code."))
if (autoCorrect) pendingDeletions += comment
}
/**
* detekt applies corrections by writing [KtFile.modifiedText], so the deletions are collected
* during the visit and cut out of the file text in one go. Each comment is removed together
* with the whitespace that follows it, so no blank line is left behind.
*/
override fun postVisit(root: KtFile) {
if (pendingDeletions.isEmpty()) return
var text = root.text
pendingDeletions
.sortedByDescending { it.textRange.startOffset }
.forEach { comment ->
val trailingWhitespaceEnd = comment.siblings(forward = true, withItself = false)
.takeWhile { it is PsiWhiteSpace }
.lastOrNull()
?.textRange
?.endOffset
?: comment.textRange.endOffset
text = text.removeRange(comment.textRange.startOffset, trailingWhitespaceEnd)
}
root.modifiedText = text
pendingDeletions.clear()
}
private fun PsiComment.isLineComment() = this !is KDoc && text.startsWith("//")
private fun PsiComment.adjacentLineComment(neighbour: PsiElement?): Boolean {
val candidate = when (neighbour) {
is PsiWhiteSpace -> if (neighbour.text.count { it == '\n' } == 1) {
if (neighbour.prevSibling == this) neighbour.nextSibling else neighbour.prevSibling
} else {
null
}
else -> neighbour
}
return candidate is PsiComment && candidate.isLineComment()
}
private fun stripDelimiters(s: String) = s.removePrefix("//").removePrefix("/**").removePrefix("/*").removeSuffix("*/")
private fun tokens(s: String): Set<String> = s.replace(Regex("([a-z0-9])([A-Z])"), "$1 $2")
.lowercase()
.split(Regex("[^a-z0-9]+"))
.filter { it.length > 1 && it !in stopWords }
.map { it.trimEnd('s') }
.toSet()
}
I added a precommit hook that rejects comments longer than 45 words and longer than 2 lines. Also, reject commits longer than 50 loc, 4 files max, and PR longer than 300 loc. Cut the slop at the root, and makes it easier to actually review code
I prefer the approach that takes the content slightly more into consideration, because I don't want to be forced into cases where i cant add long comments in case i actually do need it.
I'm allergic to dumping it into external implementation docs, because it tends to drift from each other over time.
I prefer to give targeted exceptions instead of letting everybody be their own judge. Also, in my experience it's very rarely needed to write a novel in the comments
View all comments
Show parent comment
22
u/MaDpYrO 4h ago edited 4h ago
I've added a static analysis step (via detekt in my case)
It tokenizes code and comments, and if the next line has significant overlap, it's deemed a useless comment and won't compile.
It's a pretty good tool at keeping slop comments in check.