Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code quality: Detekt update #11976

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

SomeTroglodyte
Copy link
Collaborator

Do we even want to maintain detekt? If yes, this would be a step.

  • Flagged config properties removed - all were unused
  • relaxes thresholds for two complexity metrics: cyclomatic complexity per method, and nesting level. This is a gut-feeling empiric tweak such that just the worst offenders are flagged - once/if we got those treated, they should be nudged back down.
  • nudges the version the githubrunner uses to the current release
  • treats a bunch of the warnings, going by degree of straightforwardness. Any single warning requiring a THINK skipped - best discuss and separate PR.
  • Some "treatments" are suppressions - nice: detekt respects Standard @Suppress; not-so-nice: it doesn't respect the same suppression keywords as Studio inspections.
  • Warnings before: 268, after: 25
Warnings left after this PR

detekt

Metrics

  • 12,453 number of properties

  • 6,469 number of functions

  • 2,886 number of classes

  • 89 number of packages

  • 649 number of kt files

Complexity Report

  • 124,516 lines of code (loc)

  • 92,378 source lines of code (sloc)

  • 71,380 logical lines of code (lloc)

  • 16,826 comment lines of code (cloc)

  • 24,088 cyclomatic complexity (mcc)

  • 19,599 cognitive complexity

  • 25 number of total code smells

  • 18% comment source ratio

  • 337 mcc per 1,000 lloc

  • 0 code smells per 1,000 lloc

Findings (25)

complexity, CyclomaticComplexMethod (9)

Prefer splitting up complex methods into smaller, easier to test methods.

Documentation

  • Unciv/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt:20:9
The function automateCivilianUnit appears to be too complex based on Cyclomatic Complexity (complexity: 42). Defined complexity threshold for methods is set to '42'
17         && !unit.hasUnique(UniqueType.AddInCapital)
18         && unit.civ.units.getCivUnits().any { unit.hasUnique(UniqueType.AddInCapital) }
19 
20     fun automateCivilianUnit(unit: MapUnit, dangerousTiles: HashSet<Tile>) {
!!         ^ error
21         if (tryRunAwayIfNeccessary(unit)) return
22 
23         if (shouldClearTileForAddInCapitalUnits(unit, unit.currentTile)) {
  • Unciv/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt:170:9
The function automateUnitMoves appears to be too complex based on Cyclomatic Complexity (complexity: 45). Defined complexity threshold for methods is set to '42'
167             .filter { !isInvalidUpgradeDestination(it) && unit.upgrade.canUpgrade(it) }
168     }
169 
170     fun automateUnitMoves(unit: MapUnit) {
!!!         ^ error
171         check(!unit.civ.isBarbarian) { "Barbarians is not allowed here." }
172 
173         // Might die next turn - move!
  • Unciv/core/src/com/unciv/logic/battle/Battle.kt:107:9
The function attack appears to be too complex based on Cyclomatic Complexity (complexity: 42). Defined complexity threshold for methods is set to '42'
104         }
105     }
106 
107     fun attack(attacker: ICombatant, defender: ICombatant): DamageDealt {
!!!         ^ error
108         debug("%s %s attacked %s %s", attacker.getCivInfo().civName, attacker.getName(), defender.getCivInfo().civName, defender.getName())
109         val attackedTile = defender.getTile()
110         if (attacker is MapUnitCombatant) {
  • Unciv/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt:676:17
The function placeStrategicAndBonuses appears to be too complex based on Cyclomatic Complexity (complexity: 68). Defined complexity threshold for methods is set to '42'
673     }
674 
675 
676     private fun placeStrategicAndBonuses(tileMap: TileMap) {
!!!                 ^ error
677         val strategicResources = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic }
678         // As usual, if there are any relevant json definitions, assume they are complete
679         val fallbackStrategic = ruleset.tileResources.values.none {
  • Unciv/core/src/com/unciv/models/ruleset/Building.kt:259:18
The function getRejectionReasons appears to be too complex based on Cyclomatic Complexity (complexity: 62). Defined complexity threshold for methods is set to '42'
256                 && rejectionReasons.all { it.type == RejectionReasonType.Unbuildable }
257     }
258 
259     override fun getRejectionReasons(cityConstructions: CityConstructions): Sequence<RejectionReason> = sequence {
!!!                  ^ error
260         val cityCenter = cityConstructions.city.getCenterTile()
261         val civ = cityConstructions.city.civ
262 
  • Unciv/core/src/com/unciv/models/ruleset/Ruleset.kt:252:9
The function load appears to be too complex based on Cyclomatic Complexity (complexity: 50). Defined complexity threshold for methods is set to '42'
249     fun allUniques(): Sequence<Unique> = RulesetFile.entries.asSequence().flatMap { it.getUniques(this) }
250     fun allICivilopediaText(): Sequence<ICivilopediaText> = allRulesetObjects() + events.values.flatMap { it.choices }
251 
252     fun load(folderHandle: FileHandle) {
!!!         ^ error
253         // Note: Most files are loaded using createHashmap, which sets originRuleset automatically.
254         // For other files containing IRulesetObject's we'll have to remember to do so manually - e.g. Tech.
255         val modOptionsFile = folderHandle.child("ModOptions.json")
  • Unciv/core/src/com/unciv/models/ruleset/unique/Conditionals.kt:14:9
The function conditionalApplies appears to be too complex based on Cyclomatic Complexity (complexity: 146). Defined complexity threshold for methods is set to '42'
11 
12 object Conditionals {
13 
14     fun conditionalApplies(
!!         ^ error
15         unique: Unique?,
16         conditional: Unique,
17         state: StateForConditionals
  • Unciv/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt:79:9
The function getTriggerFunction appears to be too complex based on Cyclomatic Complexity (complexity: 270). Defined complexity threshold for methods is set to '42'
76      * This is so the unit actions can be displayed as "disabled" if they won't actually do anything
77      * Even if the action itself is performable, there are still cases where it can fail -
78      *   for example unit placement - which is why the action itself needs to return Boolean to indicate success */
79     fun getTriggerFunction(
!!         ^ error
80         unique: Unique,
81         civInfo: Civilization,
82         city: City? = null,
  • Unciv/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt:64:32
The function updateTilesForSelectedUnit appears to be too complex based on Cyclomatic Complexity (complexity: 55). Defined complexity threshold for methods is set to '42'
61         zoom(scaleX) // zoom to current scale, to set the size of the city buttons after "next turn"
62     }
63 
64     private fun WorldMapHolder.updateTilesForSelectedUnit(unit: MapUnit) {
!!                                ^ error
65 
66         val tileGroup = tileGroups[unit.getTile()] ?: return
67 

complexity, LargeClass (7)

One class should have one responsibility. Large classes tend to handle many things at once. Split up large classes into smaller classes that are easier to understand.

Documentation

  • Unciv/core/src/com/unciv/logic/civilization/Civilization.kt:76:7
Class Civilization is too large. Consider splitting it into smaller pieces.
73     Distant
74 }
75 
76 class Civilization : IsPartOfGameInfoSerialization {
!!       ^ error
77 
78     @Transient
79     private var workerAutomationCache: WorkerAutomation? = null
  • Unciv/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt:52:7
Class MapRegions is too large. Consider splitting it into smaller pieces.
49     }
50 }
51 
52 class MapRegions (val ruleset: Ruleset) {
!!       ^ error
53     companion object {
54         val minimumFoodForRing = mapOf(1 to 1, 2 to 4, 3 to 4)
55         val minimumProdForRing = mapOf(1 to 0, 2 to 0, 3 to 2)
  • Unciv/core/src/com/unciv/logic/map/mapunit/MapUnit.kt:38:7
Class MapUnit is too large. Consider splitting it into smaller pieces.
35 /**
36  * The immutable properties and mutable game state of an individual unit present on the map
37  */
38 class MapUnit : IsPartOfGameInfoSerialization {
!!       ^ error
39 
40     //region Persisted fields
41 
  • Unciv/core/src/com/unciv/logic/map/tile/Tile.kt:41:7
Class Tile is too large. Consider splitting it into smaller pieces.
38 import kotlin.math.min
39 import kotlin.random.Random
40 
41 class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
!!       ^ error
42     //region Serialized fields
43     var militaryUnit: MapUnit? = null
44     var civilianUnit: MapUnit? = null
  • Unciv/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt:40:8
Class UniqueTriggerActivation is too large. Consider splitting it into smaller pieces.
37 import kotlin.random.Random
38 
39 // Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects
40 object UniqueTriggerActivation {
!!        ^ error
41 
42     fun triggerUnique(
43         unique: Unique,
  • Unciv/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt:25:7
Class RulesetValidator is too large. Consider splitting it into smaller pieces.
22 import com.unciv.models.tilesets.TileSetConfig
23 import com.unciv.ui.images.AtlasPreview
24 
25 class RulesetValidator(val ruleset: Ruleset) {
!!       ^ error
26 
27     private val uniqueValidator = UniqueValidator(ruleset)
28 
  • Unciv/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt:73:7
Class CityConstructionsTable is too large. Consider splitting it into smaller pieces.
70  *   The queue is scrollable, limited to one third of the stage height.
71  * - Available constructions display, scrolling, grouped with expanders and therefore of dynamic height.
72  */
73 class CityConstructionsTable(private val cityScreen: CityScreen) {
!!       ^ error
74     /* -1 = Nothing, >= 0 queue entry (0 = current construction) */
75     private var selectedQueueEntry = -1 // None
76     private var preferredBuyStat = Stat.Gold  // Used for keyboard buy

complexity, NestedBlockDepth (1)

Excessive nesting leads to hidden complexity. Prefer extracting code to make it easier to understand.

Documentation

  • Unciv/core/src/com/unciv/logic/multiplayer/apiv2/ApiV2.kt:393:25
Function handleWebSocket is nested too deeply.
390     /**
391      * Handle a newly established WebSocket connection
392      */
393     private suspend fun handleWebSocket(session: ClientWebSocketSession) {
!!!                         ^ error
394         sendChannel?.close()
395         sendChannel = session.outgoing
396 

naming, MemberNameEqualsClassName (2)

A member should not be given the same name as its parent class or object.

Documentation

  • Unciv/core/src/com/unciv/logic/battle/Nuke.kt:58:5
A member is named after the object. This might result in confusion. Please rename the member.
55         return canNuke
56     }
57 
58     @Suppress("FunctionName")   // Yes we want this name to stand out
!!     ^ error
59     fun NUKE(attacker: MapUnitCombatant, targetTile: Tile) {
60         val attackingCiv = attacker.getCivInfo()
61         val nukeStrength = attacker.unit.getMatchingUniques(UniqueType.NuclearWeapon)
  • Unciv/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt:16:5
A member is named after the object. This might result in confusion. Please rename the member.
13 
14 object DeclareWar {
15 
16     /** Declares war with the other civ in this diplomacy manager.
!!     ^ error
17      * Handles all war effects and diplomatic changes with other civs and such.
18      *
19      * @param declareWarReason Changes what sort of effects the war has depending on how it was initiated.

naming, TopLevelPropertyNaming (2)

Top level property names should follow the naming convention set in the projects configuration.

Documentation

  • Unciv/core/src/com/unciv/ui/screens/worldscreen/worldmap/OverlayButtonData.kt:31:19
Top level constant names should match the pattern: [A-Z][_A-Z0-9]*
28     fun createButton(worldMapHolder: WorldMapHolder): Actor
29 }
30 
31 private const val buttonSize = 60f
!!                   ^ error
32 private const val smallerCircleSizes = 25f
33 
34 class MoveHereOverlayButtonData(private val unitToTurnsToDestination: HashMap<MapUnit, Int>, val tile: Tile) :
  • Unciv/core/src/com/unciv/ui/screens/worldscreen/worldmap/OverlayButtonData.kt:32:19
Top level constant names should match the pattern: [A-Z][_A-Z0-9]*
29 }
30 
31 private const val buttonSize = 60f
32 private const val smallerCircleSizes = 25f
!!                   ^ error
33 
34 class MoveHereOverlayButtonData(private val unitToTurnsToDestination: HashMap<MapUnit, Int>, val tile: Tile) :
35     OverlayButtonData {

naming, VariableNaming (1)

Variable names should follow the naming convention set in the projects configuration.

Documentation

  • Unciv/core/src/com/unciv/utils/Concurrency.kt:156:9
Variable names should match the pattern: (?:[a-z][A-Za-z0-9]*|[A-Z][A-Z0-9]*)
153     val DAEMON: CoroutineDispatcher = createThreadpoolDispatcher("threadpool-daemon-", isDaemon = true)
154 
155     /** Runs coroutines on a non-daemon thread pool. */
156     val NON_DAEMON: CoroutineDispatcher = createThreadpoolDispatcher("threadpool-nondaemon-", isDaemon = false)
!!!         ^ error
157 
158     /** Runs coroutines on the GDX GL thread. */
159     val GL: CoroutineDispatcher = CrashHandlingDispatcher(GLDispatcher(DAEMON))

style, UnusedPrivateClass (1)

Private class is unused and should be removed.

Documentation

  • Unciv/core/src/com/unciv/json/LastSeenImprovement.kt:62:1
Private class HashMapVector2 is unused.
59     override fun hashCode() = map.hashCode()
60 }
61 
62 /** Compatibility kludge required for backward compatibility. Without this, Gdx won't even run our overridden `read` above. */
!! ^ error
63 private class HashMapVector2 : LastSeenImprovement()
64 

style, UnusedPrivateProperty (2)

Property is unused and should be removed.

Documentation

  • Unciv/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt:80:18
Private property `i` is unused.
77             for (terrain in baseTerrainsToChosenTiles.keys)
78                 baseTerrainsToChosenTiles[terrain] = 0
79 
80             for (i in 1..number) {
!!                  ^ error
81                 val orderedKeys = baseTerrainsToChosenTiles.entries
82                     .sortedBy { it.value }.map { it.key }
83                 val firstKeyWithTilesLeft = orderedKeys
  • Unciv/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnMenu.kt:13:17
Private property `nextTurnButton` is unused.
10 class NextTurnMenu(
11     stage: Stage,
12     positionNextTo: Actor,
13     private val nextTurnButton: NextTurnButton,
!!                 ^ error
14     private val worldScreen: WorldScreen
15 ) : AnimatedMenuPopup(stage, getActorTopRight(positionNextTo)) {
16 

generated with detekt version 1.23.6 on 2024-07-17 12:43:48 UTC

Each of these remaining has a relatively simple reason it's not treated right away here. Go ahead and Ask.

@SomeTroglodyte SomeTroglodyte marked this pull request as draft July 17, 2024 13:10
@SomeTroglodyte
Copy link
Collaborator Author

Oh my I inverted all the kotlin.preconditions.check thingies... Later.

Copy link
Owner

@yairm210 yairm210 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look like good changes with it without detekt
I'm not sure if I do want to keep it tbh, it's annoying and not super helpful

@SomeTroglodyte
Copy link
Collaborator Author

These look like good changes with it without detekt

Some are, some are a bit fuzzy, but worth a thought. Take it as you wish - simply merging would probably provoke a bunch of conflicts in other PR's, or take them one by one in little steps...

I'm not sure if I do want to keep it tbh, it's annoying and not super helpful

As check running with every single push it's overkill, methinks too - mainly because we don't get the actual report in readable form out of it. We could, however, make running it locally easier with some shell scripts, then once in a while use it to reduce code smells. OR - we could include the run as is, make it nonfatal & run only for releases, but push the report into the repo so it's always current - per release.

Copy link
Owner

@yairm210 yairm210 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ready for this if you are

Copy link

github-actions bot commented Aug 7, 2024

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@yairm210 yairm210 marked this pull request as ready for review August 7, 2024 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants