From 5336712212c555f6e8d0f72c94cb7dd8d7504f11 Mon Sep 17 00:00:00 2001 From: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:22:32 +1100 Subject: Backend: Add custom import ordering detekt rule (#2775) --- detekt/baseline.xml | 2 - detekt/detekt.yml | 7 ++ detekt/src/main/kotlin/PreprocessingPatterns.kt | 19 ++++ .../main/kotlin/formatting/CustomCommentSpacing.kt | 12 +-- .../main/kotlin/imports/CustomImportOrdering.kt | 114 +++++++++++++++++++++ .../main/kotlin/imports/ImportRuleSetProvider.kt | 17 +++ .../features/dungeon/DungeonLividFinder.kt | 4 +- .../at/hannibal2/skyhanni/utils/EntityUtils.kt | 1 - .../at/hannibal2/skyhanni/utils/TabListData.kt | 11 +- versions/1.8.9/detekt/baseline.xml | 2 - 10 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 detekt/src/main/kotlin/PreprocessingPatterns.kt create mode 100644 detekt/src/main/kotlin/imports/CustomImportOrdering.kt create mode 100644 detekt/src/main/kotlin/imports/ImportRuleSetProvider.kt diff --git a/detekt/baseline.xml b/detekt/baseline.xml index c9151e8ae..338c7d66d 100644 --- a/detekt/baseline.xml +++ b/detekt/baseline.xml @@ -30,8 +30,6 @@ CyclomaticComplexMethod:VampireSlayerFeatures.kt$VampireSlayerFeatures$private fun EntityOtherPlayerMP.process() CyclomaticComplexMethod:VisualWordGui.kt$VisualWordGui$override fun drawScreen(unusedX: Int, unusedY: Int, partialTicks: Float) Filename:AreaChangeEvents.kt$at.hannibal2.skyhanni.events.skyblock.AreaChangeEvents.kt - ImportOrdering:DungeonLividFinder.kt$import at.hannibal2.skyhanni.events.CheckRenderEntityEvent import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.BlockUtils.getBlockStateAt import at.hannibal2.skyhanni.utils.ColorUtils.withAlpha import at.hannibal2.skyhanni.utils.EntityUtils import at.hannibal2.skyhanni.utils.LocationUtils.distanceSqToPlayer import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzColor.Companion.toLorenzColor import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.drawLineToEye import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled import at.hannibal2.skyhanni.utils.RenderUtils.exactLocation import net.minecraft.block.BlockStainedGlass import net.minecraft.client.entity.EntityOtherPlayerMP import net.minecraft.client.entity.EntityPlayerSP import net.minecraft.client.Minecraft import net.minecraft.entity.item.EntityArmorStand import net.minecraft.potion.Potion import net.minecraft.util.AxisAlignedBB import net.minecraftforge.fml.common.eventhandler.SubscribeEvent - ImportOrdering:EntityUtils.kt$import at.hannibal2.skyhanni.data.mob.MobFilter.isRealPlayer import at.hannibal2.skyhanni.events.SkyHanniRenderEntityEvent import at.hannibal2.skyhanni.features.dungeon.DungeonAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ItemUtils.getSkullTexture import at.hannibal2.skyhanni.utils.LocationUtils.canBeSeen import at.hannibal2.skyhanni.utils.LocationUtils.distanceTo import at.hannibal2.skyhanni.utils.LocationUtils.distanceToIgnoreY import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth import at.hannibal2.skyhanni.utils.LorenzUtils.derpy import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.compat.getArmorOrFullInventory import at.hannibal2.skyhanni.utils.compat.getLoadedPlayers import at.hannibal2.skyhanni.utils.compat.getNameAsString import at.hannibal2.skyhanni.utils.compat.isOnMainThread import at.hannibal2.skyhanni.utils.compat.normalizeAsArray import net.minecraft.block.state.IBlockState import net.minecraft.client.Minecraft import net.minecraft.client.entity.EntityOtherPlayerMP import net.minecraft.client.multiplayer.WorldClient import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase import net.minecraft.entity.item.EntityArmorStand import net.minecraft.entity.monster.EntityEnderman import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack import net.minecraft.potion.Potion import net.minecraft.tileentity.TileEntity import net.minecraft.util.AxisAlignedBB import net.minecraftforge.client.event.RenderLivingEvent //#if FORGE import net.minecraftforge.fml.common.eventhandler.Event import net.minecraftforge.fml.common.eventhandler.SubscribeEvent InjectDispatcher:ClipboardUtils.kt$ClipboardUtils$IO InjectDispatcher:GardenNextJacobContest.kt$GardenNextJacobContest$IO InjectDispatcher:HypixelBazaarFetcher.kt$HypixelBazaarFetcher$IO diff --git a/detekt/detekt.yml b/detekt/detekt.yml index ac5c9de53..014ca8c5a 100644 --- a/detekt/detekt.yml +++ b/detekt/detekt.yml @@ -12,6 +12,11 @@ FormattingRules: CustomCommentSpacing: active: true +ImportRules: + active: true + CustomImportOrdering: + active: true + style: MagicNumber: # I, Linnea Gräf, of sound mind and body, disagree with disabling this rule @@ -73,6 +78,8 @@ formatting: active: false SpacingBetweenDeclarationsWithComments: # also nah active: false + ImportOrdering: # handled by custom rule + active: false complexity: CyclomaticComplexMethod: # default threshold of 15, caught almost every complex method diff --git a/detekt/src/main/kotlin/PreprocessingPatterns.kt b/detekt/src/main/kotlin/PreprocessingPatterns.kt new file mode 100644 index 000000000..695f088cf --- /dev/null +++ b/detekt/src/main/kotlin/PreprocessingPatterns.kt @@ -0,0 +1,19 @@ +package at.hannibal2.skyhanni.detektrules + +enum class PreprocessingPattern(val text: String) { + IF("#if"), + ELSE("#else"), + ELSEIF("#elseif"), + ENDIF("#endif"), + DOLLAR_DOLLAR("$$"), + ; + + val asComment: String + get() = "//$text" + + companion object { + fun String.containsPreprocessingPattern(): Boolean { + return entries.any { this.contains(it.text) } + } + } +} diff --git a/detekt/src/main/kotlin/formatting/CustomCommentSpacing.kt b/detekt/src/main/kotlin/formatting/CustomCommentSpacing.kt index aaf7896cf..db9f2f8a7 100644 --- a/detekt/src/main/kotlin/formatting/CustomCommentSpacing.kt +++ b/detekt/src/main/kotlin/formatting/CustomCommentSpacing.kt @@ -1,5 +1,6 @@ package at.hannibal2.skyhanni.detektrules.formatting +import at.hannibal2.skyhanni.detektrules.PreprocessingPattern.Companion.containsPreprocessingPattern import io.gitlab.arturbosch.detekt.api.CodeSmell import io.gitlab.arturbosch.detekt.api.Config import io.gitlab.arturbosch.detekt.api.Debt @@ -17,18 +18,9 @@ class CustomCommentSpacing(config: Config) : Rule(config) { Debt.FIVE_MINS ) - private val allowedPatterns = listOf( - "#if", - "#else", - "#elseif", - "#endif", - "$$" - ) override fun visitComment(comment: PsiComment) { - if (allowedPatterns.any { comment.text.contains(it) }) { - return - } + if (comment.text.containsPreprocessingPattern()) return /** * REGEX-TEST: // Test comment diff --git a/detekt/src/main/kotlin/imports/CustomImportOrdering.kt b/detekt/src/main/kotlin/imports/CustomImportOrdering.kt new file mode 100644 index 000000000..9e0302153 --- /dev/null +++ b/detekt/src/main/kotlin/imports/CustomImportOrdering.kt @@ -0,0 +1,114 @@ +package at.hannibal2.skyhanni.detektrules.imports + +import at.hannibal2.skyhanni.detektrules.PreprocessingPattern +import at.hannibal2.skyhanni.detektrules.PreprocessingPattern.Companion.containsPreprocessingPattern +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.KtImportList + +class CustomImportOrdering(config: Config) : Rule(config) { + override val issue = Issue( + "CustomImportOrdering", + Severity.Style, + "Enforces correct import ordering, taking into account preprocessed imports.", + Debt.FIVE_MINS, + ) + + companion object { + private val importOrder = ImportSorter() + + private val packageImportOrdering = listOf("java.", "javax.", "kotlin.") + + private class ImportSorter : Comparator { + override fun compare( + import1: KtImportDirective, + import2: KtImportDirective, + ): Int { + val importPath1 = import1.importPath!!.pathStr + val importPath2 = import2.importPath!!.pathStr + + val isTypeAlias1 = import1.aliasName != null + val isTypeAlias2 = import2.aliasName != null + + val index1 = packageImportOrdering.indexOfFirst { importPath1.startsWith(it) } + val index2 = packageImportOrdering.indexOfFirst { importPath2.startsWith(it) } + + return when { + isTypeAlias1 && isTypeAlias2 -> importPath1.compareTo(importPath2) + isTypeAlias1 && !isTypeAlias2 -> 1 + !isTypeAlias1 && isTypeAlias2 -> -1 + index1 == -1 && index2 == -1 -> importPath1.compareTo(importPath2) + index1 == -1 -> -1 + index2 == -1 -> 1 + else -> index1.compareTo(index2) + } + } + } + } + + private fun isImportsCorrectlyOrdered(imports: List, rawText: List): Boolean { + if (rawText.any { it.isBlank() }) { + return false + } + + var inPreprocess = false + val linesToIgnore = mutableListOf() + + for (line in rawText) { + if (line.contains(PreprocessingPattern.IF.asComment)) { + inPreprocess = true + continue + } + if (line.contains(PreprocessingPattern.ENDIF.asComment)) { + inPreprocess = false + continue + } + if (line.contains(PreprocessingPattern.DOLLAR_DOLLAR.asComment)) { + continue + } + if (inPreprocess) { + linesToIgnore.add(line) + } + } + + val originalImports = rawText.filter { !it.containsPreprocessingPattern() && !linesToIgnore.contains(it) } + val formattedOriginal = originalImports.joinToString("\n") { it } + + val expectedImports = imports.sortedWith(importOrder).map { "import ${it.importPath}" } + val formattedExpected = expectedImports.filter { !linesToIgnore.contains(it) }.joinToString("\n") + + return formattedOriginal == formattedExpected + } + + override fun visitImportList(importList: KtImportList) { + + val testEntity = Entity.from(importList) + + val rawText = importList.text.trim() + if (rawText.isBlank()) { + return + } + + val importsCorrect = isImportsCorrectlyOrdered(importList.imports, rawText.lines()) + + if (!importsCorrect) { + report( + CodeSmell( + issue, + testEntity, + "Imports must be ordered in lexicographic order without any empty lines in-between " + + "with \"java\", \"javax\", \"kotlin\" and aliases in the end. This should then be followed by " + + "pre-processed imports.", + ), + ) + } + + super.visitImportList(importList) + } +} diff --git a/detekt/src/main/kotlin/imports/ImportRuleSetProvider.kt b/detekt/src/main/kotlin/imports/ImportRuleSetProvider.kt new file mode 100644 index 000000000..3b50cf22c --- /dev/null +++ b/detekt/src/main/kotlin/imports/ImportRuleSetProvider.kt @@ -0,0 +1,17 @@ +package at.hannibal2.skyhanni.detektrules.imports + +import com.google.auto.service.AutoService +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider + +@AutoService(RuleSetProvider::class) +class ImportRuleSetProvider : RuleSetProvider { + override val ruleSetId: String = "ImportRules" + + override fun instance(config: Config): RuleSet { + return RuleSet(ruleSetId, listOf( + CustomImportOrdering(config) + )) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonLividFinder.kt b/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonLividFinder.kt index 6049268c8..e934e0027 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonLividFinder.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonLividFinder.kt @@ -1,11 +1,11 @@ package at.hannibal2.skyhanni.features.dungeon +import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.events.CheckRenderEntityEvent import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper -import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.BlockUtils.getBlockStateAt @@ -20,9 +20,9 @@ import at.hannibal2.skyhanni.utils.RenderUtils.drawLineToEye import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled import at.hannibal2.skyhanni.utils.RenderUtils.exactLocation import net.minecraft.block.BlockStainedGlass +import net.minecraft.client.Minecraft import net.minecraft.client.entity.EntityOtherPlayerMP import net.minecraft.client.entity.EntityPlayerSP -import net.minecraft.client.Minecraft import net.minecraft.entity.item.EntityArmorStand import net.minecraft.potion.Potion import net.minecraft.util.AxisAlignedBB diff --git a/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt index a49ceace8..15dc807bb 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt @@ -36,7 +36,6 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent //#endif @SkyHanniModule -@Suppress("ImportOrdering") object EntityUtils { @Deprecated("Old. Instead use entity detection feature instead.") diff --git a/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt b/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt index e1dab5dca..d09d348b5 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt @@ -19,11 +19,15 @@ import kotlinx.coroutines.launch import net.minecraft.client.Minecraft import net.minecraft.client.network.NetworkPlayerInfo import net.minecraft.network.play.server.S38PacketPlayerListItem -import net.minecraft.world.WorldSettings import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly import kotlin.time.Duration.Companion.seconds +//#if MC<1.12 +import net.minecraft.world.WorldSettings +//#else +//$$ import net.minecraft.world.GameType +//#endif @SkyHanniModule object TabListData { @@ -92,8 +96,13 @@ object TabListData { val team1 = o1.playerTeam val team2 = o2.playerTeam return ComparisonChain.start().compareTrueFirst( + //#if MC<1.12 o1.gameType != WorldSettings.GameType.SPECTATOR, o2.gameType != WorldSettings.GameType.SPECTATOR + //#else + //$$ o1.gameType != GameType.SPECTATOR, + //$$ o2.gameType != GameType.SPECTATOR + //#endif ) .compare( if (team1 != null) team1.registeredName else "", diff --git a/versions/1.8.9/detekt/baseline.xml b/versions/1.8.9/detekt/baseline.xml index c9151e8ae..338c7d66d 100644 --- a/versions/1.8.9/detekt/baseline.xml +++ b/versions/1.8.9/detekt/baseline.xml @@ -30,8 +30,6 @@ CyclomaticComplexMethod:VampireSlayerFeatures.kt$VampireSlayerFeatures$private fun EntityOtherPlayerMP.process() CyclomaticComplexMethod:VisualWordGui.kt$VisualWordGui$override fun drawScreen(unusedX: Int, unusedY: Int, partialTicks: Float) Filename:AreaChangeEvents.kt$at.hannibal2.skyhanni.events.skyblock.AreaChangeEvents.kt - ImportOrdering:DungeonLividFinder.kt$import at.hannibal2.skyhanni.events.CheckRenderEntityEvent import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.BlockUtils.getBlockStateAt import at.hannibal2.skyhanni.utils.ColorUtils.withAlpha import at.hannibal2.skyhanni.utils.EntityUtils import at.hannibal2.skyhanni.utils.LocationUtils.distanceSqToPlayer import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzColor.Companion.toLorenzColor import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.drawLineToEye import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled import at.hannibal2.skyhanni.utils.RenderUtils.exactLocation import net.minecraft.block.BlockStainedGlass import net.minecraft.client.entity.EntityOtherPlayerMP import net.minecraft.client.entity.EntityPlayerSP import net.minecraft.client.Minecraft import net.minecraft.entity.item.EntityArmorStand import net.minecraft.potion.Potion import net.minecraft.util.AxisAlignedBB import net.minecraftforge.fml.common.eventhandler.SubscribeEvent - ImportOrdering:EntityUtils.kt$import at.hannibal2.skyhanni.data.mob.MobFilter.isRealPlayer import at.hannibal2.skyhanni.events.SkyHanniRenderEntityEvent import at.hannibal2.skyhanni.features.dungeon.DungeonAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ItemUtils.getSkullTexture import at.hannibal2.skyhanni.utils.LocationUtils.canBeSeen import at.hannibal2.skyhanni.utils.LocationUtils.distanceTo import at.hannibal2.skyhanni.utils.LocationUtils.distanceToIgnoreY import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth import at.hannibal2.skyhanni.utils.LorenzUtils.derpy import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.compat.getArmorOrFullInventory import at.hannibal2.skyhanni.utils.compat.getLoadedPlayers import at.hannibal2.skyhanni.utils.compat.getNameAsString import at.hannibal2.skyhanni.utils.compat.isOnMainThread import at.hannibal2.skyhanni.utils.compat.normalizeAsArray import net.minecraft.block.state.IBlockState import net.minecraft.client.Minecraft import net.minecraft.client.entity.EntityOtherPlayerMP import net.minecraft.client.multiplayer.WorldClient import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase import net.minecraft.entity.item.EntityArmorStand import net.minecraft.entity.monster.EntityEnderman import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack import net.minecraft.potion.Potion import net.minecraft.tileentity.TileEntity import net.minecraft.util.AxisAlignedBB import net.minecraftforge.client.event.RenderLivingEvent //#if FORGE import net.minecraftforge.fml.common.eventhandler.Event import net.minecraftforge.fml.common.eventhandler.SubscribeEvent InjectDispatcher:ClipboardUtils.kt$ClipboardUtils$IO InjectDispatcher:GardenNextJacobContest.kt$GardenNextJacobContest$IO InjectDispatcher:HypixelBazaarFetcher.kt$HypixelBazaarFetcher$IO -- cgit