diff --git a/desloppify/languages/kotlin/__init__.py b/desloppify/languages/kotlin/__init__.py index cd9e40cd5..3c850a18a 100644 --- a/desloppify/languages/kotlin/__init__.py +++ b/desloppify/languages/kotlin/__init__.py @@ -2,6 +2,7 @@ from desloppify.languages._framework.generic_support.core import generic_lang from desloppify.languages._framework.treesitter import KOTLIN_SPEC +from desloppify.languages.kotlin._zones import KOTLIN_ZONE_RULES generic_lang( name="kotlin", @@ -20,9 +21,11 @@ depth="shallow", detect_markers=["build.gradle.kts", "build.gradle"], treesitter_spec=KOTLIN_SPEC, + zone_rules=KOTLIN_ZONE_RULES, ) __all__ = [ "generic_lang", "KOTLIN_SPEC", + "KOTLIN_ZONE_RULES", ] diff --git a/desloppify/languages/kotlin/_zones.py b/desloppify/languages/kotlin/_zones.py new file mode 100644 index 000000000..ca5559cb3 --- /dev/null +++ b/desloppify/languages/kotlin/_zones.py @@ -0,0 +1,44 @@ +"""Zone/path classification rules for Kotlin. + +Kotlin projects are laid out by Gradle source sets, not by a top-level ``test/`` +directory. Kotlin Multiplatform in particular names every test source set +``Test`` — ``src/commonTest/kotlin``, ``src/jvmTest/kotlin``, +``src/iosTest/kotlin``, ``src/androidUnitTest/kotlin`` — none of which contain the +``/test/`` path segment the common rules look for. Without these rules every test +file is scored as production code. +""" + +from __future__ import annotations + +from desloppify.engine.policy.zones import COMMON_ZONE_RULES, Zone, ZoneRule + +KOTLIN_ZONE_RULES = [ + # Gradle/KMP test source sets: src/test/, src/androidTest/, src/commonTest/, + # src/jvmTest/, src/iosTest/, src/androidUnitTest/, src/nativeTest/, ... + # The pattern has no leading "." / trailing "_" so it matches as a path substring. + ZoneRule( + Zone.TEST, + [ + "/src/test/", + "/src/androidTest/", + "/src/testFixtures/", + "Test/kotlin/", + "Test/java/", + "Test/resources/", + ], + ), + ZoneRule(Zone.GENERATED, ["/generated/", "/build/generated/"]), + ZoneRule( + Zone.CONFIG, + [ + "build.gradle.kts", + "settings.gradle.kts", + "build.gradle", + "settings.gradle", + "gradle.properties", + "gradle/libs.versions.toml", + ], + ), +] + COMMON_ZONE_RULES + +__all__ = ["KOTLIN_ZONE_RULES"] diff --git a/desloppify/tests/lang/common/test_kotlin_zones.py b/desloppify/tests/lang/common/test_kotlin_zones.py new file mode 100644 index 000000000..d0be7d747 --- /dev/null +++ b/desloppify/tests/lang/common/test_kotlin_zones.py @@ -0,0 +1,67 @@ +"""Regression tests for Kotlin/Gradle zone classification. + +Kotlin projects are organised by Gradle source sets rather than a top-level +``test/`` directory, so the common rules classify every test file as production. +""" + +from __future__ import annotations + +import pytest + +from desloppify.engine.policy.zones import Zone, classify_file +from desloppify.languages.kotlin._zones import KOTLIN_ZONE_RULES + + +def _zone(rel_path: str) -> Zone: + return classify_file(rel_path, KOTLIN_ZONE_RULES) + + +@pytest.mark.parametrize( + "rel_path", + [ + # Kotlin Multiplatform source sets + "shared/src/commonTest/kotlin/com/example/FormEngineTest.kt", + "sync/src/jvmTest/kotlin/com/example/SubmissionStoreTest.kt", + "sync/src/iosTest/kotlin/com/example/DeviceCryptoTest.kt", + "composeApp/src/androidUnitTest/kotlin/com/example/RendererTest.kt", + "shared/src/nativeTest/kotlin/com/example/ParserTest.kt", + # Plain Gradle JVM / Android layouts + "app/src/test/java/com/example/HelperTest.kt", + "app/src/androidTest/java/com/example/LoginFlowTest.kt", + "lib/src/testFixtures/kotlin/com/example/Fixtures.kt", + ], +) +def test_gradle_test_source_sets_are_test_zone(rel_path): + assert _zone(rel_path) == Zone.TEST + + +@pytest.mark.parametrize( + "rel_path", + [ + "shared/src/commonMain/kotlin/com/example/FormEngine.kt", + "sync/src/jvmMain/kotlin/com/example/SubmissionStore.kt", + "composeApp/src/androidMain/kotlin/com/example/Renderer.kt", + "composeApp/src/iosMain/kotlin/com/example/MapSeam.ios.kt", + ], +) +def test_main_source_sets_stay_production(rel_path): + assert _zone(rel_path) == Zone.PRODUCTION + + +@pytest.mark.parametrize( + "rel_path", + [ + "build.gradle.kts", + "settings.gradle.kts", + "androidApp/build.gradle.kts", + "gradle.properties", + "gradle/libs.versions.toml", + ], +) +def test_gradle_build_files_are_config_zone(rel_path): + assert _zone(rel_path) == Zone.CONFIG + + +def test_latest_is_not_mistaken_for_a_test_source_set(): + """A directory merely ending in 'Test' must still need the source-set suffix.""" + assert _zone("shared/src/commonMain/kotlin/com/example/LatestKotlinThing.kt") == Zone.PRODUCTION