Skip to content

Commit

Permalink
Backend: Make repo pattern detekt rules work on lists
Browse files Browse the repository at this point in the history
  • Loading branch information
CalMWolfs committed Nov 25, 2024
1 parent 40f3918 commit 1112d9c
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 67 deletions.
130 changes: 130 additions & 0 deletions detekt/src/main/kotlin/RepoPatternBaseElement.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package at.hannibal2.skyhanni.detektrules

import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyDelegate
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
import org.jetbrains.kotlin.psi.KtStringTemplateExpression

abstract class RepoPatternBaseElement(
val variableName: String,
val regexTests: List<String>,
val failingRegexTests: List<String>,
) {

fun hasRegexTests(): Boolean {
return regexTests.isNotEmpty()
}

companion object {
fun findRegexTestInKDoc(property: KtProperty): Pair<List<String>, List<String>> {
val kDoc = property.docComment ?: return listOf<String>() to listOf()

val regexTests = mutableListOf<String>()
val failingRegexTests = mutableListOf<String>()

kDoc.getDefaultSection().getContent().lines().forEach { line ->
if (line.contains("REGEX-TEST: ")) {
regexTests.add(line.substringAfter("REGEX-TEST: "))
}
if (line.contains("REGEX-FAIL: ")) {
failingRegexTests.add(line.substringAfter("REGEX-FAIL: "))
}
}

return regexTests to failingRegexTests
}

fun KtPropertyDelegate.asRepoPatternListElement(): RepoPatternBaseElement? {
val expression = this.expression as? KtDotQualifiedExpression ?: return null
return when {
expression.text.contains(".pattern(") -> {
SingleElement.asElement(expression)
}

expression.text.contains(".list(") -> {
ListElement.asElement(expression)
}

else -> null
}
}
}
}

class SingleElement(
variableName: String,
rawPattern: String,
regexTests: List<String>,
failingRegexTests: List<String>,
) : RepoPatternBaseElement(variableName, regexTests, failingRegexTests) {

val pattern by lazy { rawPattern.toPattern() }

companion object {
fun asElement(expression: KtDotQualifiedExpression): SingleElement? {
val callExpression = expression.selectorExpression as? KtCallExpression ?: return null
if (callExpression.valueArguments.size != 2) return null

val patternArg = callExpression.valueArguments[1].getArgumentExpression() ?: return null

// We only want to match on plain strings, not string templates
if (patternArg !is KtStringTemplateExpression) return null
if (patternArg.entries.any { it is KtStringTemplateEntryWithExpression }) return null

val rawPattern = patternArg.entries.joinToString("") { entry ->
when (entry) {
is KtLiteralStringTemplateEntry -> entry.text
is KtEscapeStringTemplateEntry -> entry.unescapedValue
else -> "" // Skip any other types of entries
}
}.removeSurrounding("\"").replace("\n", "")

val parent = expression.parent as? KtProperty ?: return null
val variableName = parent.name ?: "unknownPattern"

val (regexTests, failingRegexTests) = findRegexTestInKDoc(parent)
return SingleElement(variableName, rawPattern, regexTests, failingRegexTests)
}
}
}

class ListElement(
variableName: String,
rawPatterns: List<String>,
regexTests: List<String>,
failingRegexTests: List<String>,
) : RepoPatternBaseElement(variableName, regexTests, failingRegexTests) {

val patterns by lazy { rawPatterns.map { it.toPattern() } }

companion object {
fun asElement(expression: KtDotQualifiedExpression): ListElement? {
val callExpression = expression.selectorExpression as? KtCallExpression ?: return null
if (callExpression.valueArguments.size != 2) return null

val patternArg = callExpression.valueArguments[1].getArgumentExpression() ?: return null

// We only want to match on plain strings, not string templates
if (patternArg !is KtStringTemplateExpression) return null
if (patternArg.entries.any { it is KtStringTemplateEntryWithExpression }) return null

val rawPatterns = patternArg.entries.mapNotNull { entry ->
when (entry) {
is KtLiteralStringTemplateEntry -> entry.text
is KtEscapeStringTemplateEntry -> entry.unescapedValue
else -> null // Skip any other types of entries
}
}.map { it.removeSurrounding("\"").replace("\n", "") }

val parent = expression.parent as? KtProperty ?: return null
val variableName = parent.name ?: "unknownPattern"

val (regexTests, failingRegexTests) = findRegexTestInKDoc(parent)
return ListElement(variableName, rawPatterns, regexTests, failingRegexTests)
}
}
}
65 changes: 0 additions & 65 deletions detekt/src/main/kotlin/RepoPatternElement.kt

This file was deleted.

124 changes: 124 additions & 0 deletions detekt/src/main/kotlin/SingleRepoPatternElement.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package at.hannibal2.skyhanni.detektrules

import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyDelegate
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
import org.jetbrains.kotlin.psi.KtStringTemplateExpression

class SingleRepoPatternElement private constructor(
val variableName: String,
val rawPattern: String,
val regexTests: List<String>,
val failingRegexTests: List<String>,
) {

val pattern by lazy { rawPattern.toPattern() }

companion object {
fun KtPropertyDelegate.asRepoPatternElement(): SingleRepoPatternElement? {
val expression = this.expression as? KtDotQualifiedExpression ?: return null
if (!expression.text.contains(".pattern(")) return null
val callExpression = expression.selectorExpression as? KtCallExpression ?: return null
if (callExpression.valueArguments.size != 2) return null

val patternArg = callExpression.valueArguments[1].getArgumentExpression() ?: return null

// We only want to match on plain strings, not string templates
if (patternArg !is KtStringTemplateExpression) return null
if (patternArg.entries.any { it is KtStringTemplateEntryWithExpression }) return null

val rawPattern = patternArg.entries.joinToString("") { entry ->
when (entry) {
is KtLiteralStringTemplateEntry -> entry.text
is KtEscapeStringTemplateEntry -> entry.unescapedValue
else -> "" // Skip any other types of entries
}
}.removeSurrounding("\"").replace("\n", "")

val parent = parent as? KtProperty ?: return null
val variableName = parent.name ?: "unknownPattern"

val (regexTests, failingRegexTests) = findRegexTestInKDoc(parent)
return SingleRepoPatternElement(variableName, rawPattern, regexTests, failingRegexTests)
}

private fun findRegexTestInKDoc(property: KtProperty): Pair<List<String>, List<String>> {
val kDoc = property.docComment ?: return listOf<String>() to listOf()

val regexTests = mutableListOf<String>()
val failingRegexTests = mutableListOf<String>()

kDoc.getDefaultSection().getContent().lines().forEach { line ->
if (line.contains("REGEX-TEST: ")) {
regexTests.add(line.substringAfter("REGEX-TEST: "))
}
if (line.contains("REGEX-FAIL: ")) {
failingRegexTests.add(line.substringAfter("REGEX-FAIL: "))
}
}
return regexTests to failingRegexTests
}
}
}

class RepoPatternListElement private constructor(
val variableName: String,
val rawPatterns: List<String>,
val regexTests: List<String>,
val failingRegexTests: List<String>,
) {

val patterns by lazy { rawPatterns.map { it.toPattern() } }

companion object {
fun KtPropertyDelegate.asRepoPatternListElement(): RepoPatternListElement? {
val expression = this.expression as? KtDotQualifiedExpression ?: return null
if (!expression.text.contains(".list(")) return null
val callExpression = expression.selectorExpression as? KtCallExpression ?: return null

val patternArgs = callExpression.valueArguments.drop(1).mapNotNull { it.getArgumentExpression() as? KtStringTemplateExpression }
val filteredArgs = patternArgs.filter { patternArg ->
patternArgs is KtStringTemplateExpression && patternArg.entries.none { entry ->
entry is KtStringTemplateEntryWithExpression
}
}

val rawPatterns = filteredArgs.map { patternArg ->
patternArg.entries.joinToString("") { entry ->
when (entry) {
is KtLiteralStringTemplateEntry -> entry.text
is KtEscapeStringTemplateEntry -> entry.unescapedValue
else -> "" // Skip any other types of entries
}
}.removeSurrounding("\"").replace("\n", "")
}

val parent = parent as? KtProperty ?: return null
val variableName = parent.name ?: "unknownPattern"

val (regexTests, failingRegexTests) = findRegexTestInKDoc(parent)
return RepoPatternListElement(variableName, rawPatterns, regexTests, failingRegexTests)
}

private fun findRegexTestInKDoc(property: KtProperty): Pair<List<String>, List<String>> {
val kDoc = property.docComment ?: return listOf<String>() to listOf()

val regexTests = mutableListOf<String>()
val failingRegexTests = mutableListOf<String>()

kDoc.getDefaultSection().getContent().lines().forEach { line ->
if (line.contains("REGEX-TEST: ")) {
regexTests.add(line.substringAfter("REGEX-TEST: "))
}
if (line.contains("REGEX-FAIL: ")) {
failingRegexTests.add(line.substringAfter("REGEX-FAIL: "))
}
}
return regexTests to failingRegexTests
}
}
}
2 changes: 1 addition & 1 deletion detekt/src/main/kotlin/repo/RepoPatternRegexTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package at.hannibal2.skyhanni.detektrules.repo

import at.hannibal2.skyhanni.detektrules.RepoPatternElement.Companion.asRepoPatternElement
import at.hannibal2.skyhanni.detektrules.SingleRepoPatternElement.Companion.asRepoPatternElement
import at.hannibal2.skyhanni.detektrules.SkyHanniRule
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
Expand Down
2 changes: 1 addition & 1 deletion detekt/src/main/kotlin/repo/RepoPatternUnnamedGroup.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package at.hannibal2.skyhanni.detektrules.repo

import at.hannibal2.skyhanni.detektrules.RepoPatternElement.Companion.asRepoPatternElement
import at.hannibal2.skyhanni.detektrules.SingleRepoPatternElement.Companion.asRepoPatternElement
import at.hannibal2.skyhanni.detektrules.SkyHanniRule
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
Expand Down

0 comments on commit 1112d9c

Please sign in to comment.