From 8f4e514978ffe2eb856061e5ead8ceeb0e75441e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:40:41 -0800 Subject: [PATCH] fix: resolve all compiler warnings in CI build - Replace deprecated compose.foundation/material3/materialIconsExtended with version catalog entries (libs.compose.*) - Add @OptIn for ExperimentalWasmDsl in app/web and ExperimentalKotlinGradlePluginApi in tools/mdst - Remove unnecessary elvis operators on non-null UnauthorizedException.message - Remove unnecessary !! on non-null exception messages in DesfireProtocolTest - Remove redundant type checks (assertTrue(x is T)) where compiler already knows the type from generic return types - Remove unnecessary casts after smart casts from assertTrue contracts - Remove unnecessary safe calls on non-null receivers - Clean up unused imports via ktlintFormat Co-Authored-By: Claude Opus 4.6 --- .../farebot/test/ClipperTransitTest.kt | 4 +- .../farebot/test/CompassTransitTest.kt | 2 - .../farebot/test/FlipperIntegrationTest.kt | 14 - .../test/MetrodroidDumpIntegrationTest.kt | 9 - .../farebot/test/MykiTransitTest.kt | 1 - .../farebot/test/NextfareTransitTest.kt | 7 - .../farebot/test/OctopusTransitTest.kt | 1 - .../farebot/test/OpalTransitTest.kt | 5 +- .../farebot/test/OrcaTransitTest.kt | 2 - .../farebot/test/SampleDumpIntegrationTest.kt | 10 +- .../farebot/test/TestAssetLoader.kt | 8 +- app/web/build.gradle.kts | 2 + .../farebot/card/desfire/DesfireCardReader.kt | 4 +- .../card/desfire/DesfireProtocolTest.kt | 14 +- migrate_strings.py | 153 ----------- remove_string_resource.py | 243 ------------------ tools/mdst/build.gradle.kts | 2 + 17 files changed, 18 insertions(+), 463 deletions(-) delete mode 100644 migrate_strings.py delete mode 100644 remove_string_resource.py diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt index 4cbd97187..b7311c68d 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt @@ -33,7 +33,6 @@ import com.codebutler.farebot.test.CardTestHelper.standardFile import com.codebutler.farebot.transit.TransitCurrency import com.codebutler.farebot.transit.Trip import com.codebutler.farebot.transit.clipper.ClipperTransitFactory -import com.codebutler.farebot.transit.clipper.ClipperTransitInfo import com.codebutler.farebot.transit.clipper.ClipperTrip import kotlinx.coroutines.test.runTest import kotlin.math.abs @@ -195,7 +194,7 @@ class ClipperTransitTest { agency = 0x04, fareValue = 350, ) - val fareStr = trip.fare?.formatCurrencyString() ?: "" + val fareStr = trip.fare.formatCurrencyString() // Should format as USD assertTrue( fareStr.contains("3.50") || fareStr.contains("3,50"), @@ -229,7 +228,6 @@ class ClipperTransitTest { assertEquals("572691763", identity.serialNumber) val info = factory.parseInfo(card) - assertTrue(info is ClipperTransitInfo, "TransitData must be instance of ClipperTransitInfo") assertEquals("572691763", info.serialNumber) assertEquals("Clipper", info.cardName.resolveAsync()) diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/CompassTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/CompassTransitTest.kt index 963119781..b48214ce9 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/CompassTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/CompassTransitTest.kt @@ -83,8 +83,6 @@ class CompassTransitTest { // Test transit info parsing val info = factory.parseInfo(card) - assertNotNull(info, "Transit info should not be null") - assertTrue(info is CompassUltralightTransitInfo, "Info should be CompassUltralightTransitInfo") assertEquals(cardData[0], info.serialNumber, "Serial number should match") // Test identity parsing diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/FlipperIntegrationTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/FlipperIntegrationTest.kt index 8c6cfee23..7798c4f8f 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/FlipperIntegrationTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/FlipperIntegrationTest.kt @@ -28,11 +28,8 @@ import com.codebutler.farebot.shared.serialize.FlipperNfcParser import com.codebutler.farebot.transit.TransitCurrency import com.codebutler.farebot.transit.Trip import com.codebutler.farebot.transit.clipper.ClipperTransitFactory -import com.codebutler.farebot.transit.clipper.ClipperTransitInfo import com.codebutler.farebot.transit.orca.OrcaTransitFactory -import com.codebutler.farebot.transit.orca.OrcaTransitInfo import com.codebutler.farebot.transit.suica.SuicaTransitFactory -import com.codebutler.farebot.transit.suica.SuicaTransitInfo import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -74,8 +71,6 @@ class FlipperIntegrationTest { assertEquals("10043012", identity.serialNumber) val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse ORCA transit info") - assertTrue(info is OrcaTransitInfo) // Balance: $26.25 USD val balances = info.balances @@ -111,8 +106,6 @@ class FlipperIntegrationTest { assertEquals("1205019883", identity.serialNumber) val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse Clipper transit info") - assertTrue(info is ClipperTransitInfo) // Balance: $2.25 USD val balances = info.balances @@ -288,8 +281,6 @@ class FlipperIntegrationTest { assertNull(identity.serialNumber) val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse Suica transit info") - assertTrue(info is SuicaTransitInfo) // Balance: 870 JPY val balances = info.balances @@ -527,9 +518,6 @@ class FlipperIntegrationTest { assertNull(identity.serialNumber) val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse PASMO transit info") - assertTrue(info is SuicaTransitInfo) - // Balance: 500 JPY val balances = info.balances assertNotNull(balances) @@ -674,8 +662,6 @@ class FlipperIntegrationTest { assertNull(identity.serialNumber) val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse ICOCA transit info") - assertTrue(info is SuicaTransitInfo) // Balance: 827 JPY val balances = info.balances diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/MetrodroidDumpIntegrationTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/MetrodroidDumpIntegrationTest.kt index 900a0c9f3..a1007efbf 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/MetrodroidDumpIntegrationTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/MetrodroidDumpIntegrationTest.kt @@ -26,12 +26,10 @@ import com.codebutler.farebot.card.classic.ClassicCard import com.codebutler.farebot.card.classic.raw.RawClassicBlock import com.codebutler.farebot.card.classic.raw.RawClassicCard import com.codebutler.farebot.card.classic.raw.RawClassicSector -import com.codebutler.farebot.card.ultralight.UltralightCard import com.codebutler.farebot.card.ultralight.UltralightPage import com.codebutler.farebot.card.ultralight.raw.RawUltralightCard import com.codebutler.farebot.transit.TransitCurrency import com.codebutler.farebot.transit.troika.TroikaTransitFactory -import com.codebutler.farebot.transit.troika.TroikaTransitInfo import com.codebutler.farebot.transit.ventra.VentraUltralightTransitInfo import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -93,7 +91,6 @@ class MetrodroidDumpIntegrationTest { ) val card = rawCard.parse() - assertTrue(card is UltralightCard, "Expected UltralightCard") val factory = VentraUltralightTransitInfo.FACTORY assertTrue(factory.check(card), "Ventra factory should recognize this card") @@ -103,8 +100,6 @@ class MetrodroidDumpIntegrationTest { assertNotNull(identity.serialNumber, "Should have a serial number") val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse Ventra transit info") - assertTrue(info is VentraUltralightTransitInfo) // Balance: $8.44 USD (844 cents) val balances = info.balances @@ -141,8 +136,6 @@ class MetrodroidDumpIntegrationTest { assertNotNull(identity.serialNumber, "Should have serial number") val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse Troika transit info") - assertTrue(info is TroikaTransitInfo) // Balance: 0.00 RUB (0 kopeks) val balances = info.balances @@ -174,8 +167,6 @@ class MetrodroidDumpIntegrationTest { assertNotNull(identity.serialNumber, "Should have serial number") val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse Troika transit info") - assertTrue(info is TroikaTransitInfo) // Balance: 50.00 RUB (5000 kopeks) val balances = info.balances diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/MykiTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/MykiTransitTest.kt index 0222fe321..db8d1b40b 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/MykiTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/MykiTransitTest.kt @@ -73,7 +73,6 @@ class MykiTransitTest { // Test TransitData val info = factory.parseInfo(card) - assertTrue(info is MykiTransitInfo, "TransitData must be instance of MykiTransitInfo") assertEquals("308425123456780", info.serialNumber) } } diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/NextfareTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/NextfareTransitTest.kt index fa1e8681c..8c9098373 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/NextfareTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/NextfareTransitTest.kt @@ -26,12 +26,9 @@ import com.codebutler.farebot.card.classic.ClassicCard import com.codebutler.farebot.card.classic.DataClassicSector import com.codebutler.farebot.test.CardTestHelper.hexToBytes import com.codebutler.farebot.transit.laxtap.LaxTapTransitFactory -import com.codebutler.farebot.transit.laxtap.LaxTapTransitInfo import com.codebutler.farebot.transit.mspgoto.MspGotoTransitFactory -import com.codebutler.farebot.transit.mspgoto.MspGotoTransitInfo import com.codebutler.farebot.transit.nextfare.NextfareTransitInfo import com.codebutler.farebot.transit.seqgo.SeqGoTransitFactory -import com.codebutler.farebot.transit.seqgo.SeqGoTransitInfo import kotlinx.datetime.TimeZone import kotlin.test.Test import kotlin.test.assertEquals @@ -175,7 +172,6 @@ class NextfareTransitTest { val seqGoFactory = SeqGoTransitFactory() assertTrue(seqGoFactory.check(c1), "Card is seqgo") val d1 = seqGoFactory.parseInfo(c1) - assertTrue(d1 is SeqGoTransitInfo, "Card is SeqGoTransitInfo") assertEquals("0160 0012 3456 7893", d1.serialNumber) val balances1 = d1.balances assertNotNull(balances1) @@ -190,7 +186,6 @@ class NextfareTransitTest { ) assertTrue(seqGoFactory.check(c2), "Card is seqgo") val d2 = seqGoFactory.parseInfo(c2) - assertTrue(d2 is SeqGoTransitInfo, "Card is SeqGoTransitInfo") assertEquals("0160 0098 7654 3213", d2.serialNumber) val balances2 = d2.balances assertNotNull(balances2) @@ -211,7 +206,6 @@ class NextfareTransitTest { val laxTapFactory = LaxTapTransitFactory() assertTrue(laxTapFactory.check(c), "Card is laxtap") val d = laxTapFactory.parseInfo(c) - assertTrue(d is LaxTapTransitInfo, "Card is LaxTapTransitInfo") assertEquals("0160 0323 4663 8769", d.serialNumber) val balances = d.balances assertNotNull(balances) @@ -231,7 +225,6 @@ class NextfareTransitTest { val mspGotoFactory = MspGotoTransitFactory() assertTrue(mspGotoFactory.check(c), "Card is mspgoto") val d = mspGotoFactory.parseInfo(c) - assertTrue(d is MspGotoTransitInfo, "Card is MspGotoTransitInfo") assertEquals("0160 0112 3581 3212", d.serialNumber) val balances = d.balances assertNotNull(balances) diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/OctopusTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/OctopusTransitTest.kt index 6a8ef0680..bbae7bdf7 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/OctopusTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/OctopusTransitTest.kt @@ -91,7 +91,6 @@ class OctopusTransitTest { // Test TransitData val info = factory.parseInfo(card) - assertTrue(info is OctopusTransitInfo, "TransitData must be instance of OctopusTransitInfo") assertNotNull(info.balances) assertTrue(info.balances!!.isNotEmpty()) diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/OpalTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/OpalTransitTest.kt index 514111ff8..8f481c898 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/OpalTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/OpalTransitTest.kt @@ -151,7 +151,6 @@ class OpalTransitTest { // Test TransitInfo val info = factory.parseInfo(card) - assertTrue(info is OpalTransitInfo, "TransitData must be instance of OpalTransitInfo") assertEquals("3085 2200 1234 5670", info.serialNumber) assertEquals(TransitCurrency.AUD(336), info.balances?.first()?.balance) @@ -191,7 +190,7 @@ class OpalTransitTest { // 2018-03-31 09:00 AEDT (UTC+11) // = 2018-03-30 22:00 UTC var card = createOpalCard(hexToBytes("85D25E07230520A70044DA380419FFFF")) - var info = factory.parseInfo(card) as OpalTransitInfo + var info = factory.parseInfo(card) var expectedTime = Instant.parse("2018-03-30T22:00:00Z") assertEquals( expectedTime, @@ -204,7 +203,7 @@ class OpalTransitTest { // 2018-04-01 09:00 AEST (UTC+10) // = 2018-03-31 23:00 UTC card = createOpalCard(hexToBytes("85D25E07430520A70048DA380419FFFF")) - info = factory.parseInfo(card) as OpalTransitInfo + info = factory.parseInfo(card) expectedTime = Instant.parse("2018-03-31T23:00:00Z") assertEquals( expectedTime, diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/OrcaTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/OrcaTransitTest.kt index 0d3f16958..b28737917 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/OrcaTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/OrcaTransitTest.kt @@ -29,7 +29,6 @@ import com.codebutler.farebot.test.CardTestHelper.standardFile import com.codebutler.farebot.transit.TransitCurrency import com.codebutler.farebot.transit.Trip import com.codebutler.farebot.transit.orca.OrcaTransitFactory -import com.codebutler.farebot.transit.orca.OrcaTransitInfo import kotlinx.coroutines.test.runTest import kotlin.math.abs import kotlin.test.Test @@ -94,7 +93,6 @@ class OrcaTransitTest { // Test TransitInfo val info = factory.parseInfo(card) - assertTrue(info is OrcaTransitInfo, "TransitData must be instance of OrcaTransitInfo") assertEquals("12030625", info.serialNumber) assertEquals("ORCA", info.cardName.resolveAsync()) assertEquals(TransitCurrency.USD(23432), info.balances?.firstOrNull()?.balance) diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/SampleDumpIntegrationTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/SampleDumpIntegrationTest.kt index f8b7f005f..824f6388e 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/SampleDumpIntegrationTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/SampleDumpIntegrationTest.kt @@ -37,7 +37,6 @@ import com.codebutler.farebot.transit.bilheteunico.BilheteUnicoSPTransitFactory import com.codebutler.farebot.transit.bilheteunico.BilheteUnicoSPTransitInfo import com.codebutler.farebot.transit.calypso.mobib.MobibTransitInfo import com.codebutler.farebot.transit.easycard.EasyCardTransitFactory -import com.codebutler.farebot.transit.easycard.EasyCardTransitInfo import com.codebutler.farebot.transit.ezlink.EZLinkTransitFactory import com.codebutler.farebot.transit.ezlink.EZLinkTransitInfo import com.codebutler.farebot.transit.hsl.HSLTransitFactory @@ -58,7 +57,6 @@ import com.codebutler.farebot.transit.seqgo.SeqGoTransitFactory import com.codebutler.farebot.transit.seqgo.SeqGoTransitInfo import com.codebutler.farebot.transit.serialonly.HoloTransitFactory import com.codebutler.farebot.transit.serialonly.HoloTransitInfo -import com.codebutler.farebot.transit.serialonly.SerialOnlyTransitInfo import com.codebutler.farebot.transit.serialonly.TrimetHopTransitFactory import com.codebutler.farebot.transit.serialonly.TrimetHopTransitInfo import com.codebutler.farebot.transit.tmoney.TMoneyTransitFactory @@ -320,7 +318,6 @@ class SampleDumpIntegrationTest : CardDumpTest() { assertNotNull(identity.serialNumber) // Serial-only card: no balance, no trips, but has emptyStateMessage - assertTrue(info is SerialOnlyTransitInfo) assertNotNull(info.emptyStateMessage, "Serial-only card should have an emptyStateMessage") assertNull(info.trips, "Serial-only card should have null trips") assertTrue(info.balances.isNullOrEmpty(), "Serial-only card should have no balances") @@ -377,7 +374,7 @@ class SampleDumpIntegrationTest : CardDumpTest() { val result = importer.importMfcDump(bytes) assertTrue(result is ImportResult.Success, "Failed to import MFC dump: $result") - val rawCard = (result as ImportResult.Success).cards.first() + val rawCard = result.cards.first() val card = rawCard.parse() as ClassicCard val factory = EasyCardTransitFactory() @@ -387,8 +384,6 @@ class SampleDumpIntegrationTest : CardDumpTest() { assertNotNull(identity.name) val info = factory.parseInfo(card) - assertNotNull(info, "Failed to parse EasyCard transit info") - assertTrue(info is EasyCardTransitInfo) // Balance: 245 TWD val balances = info.balances @@ -518,7 +513,6 @@ class SampleDumpIntegrationTest : CardDumpTest() { assertEquals("308425123456780", identity.serialNumber) // Serial-only card: has emptyStateMessage, no trips - assertTrue(info is SerialOnlyTransitInfo) assertNotNull(info.emptyStateMessage, "Serial-only card should have an emptyStateMessage") assertNull(info.trips, "Serial-only card should have null trips") } @@ -567,11 +561,9 @@ class SampleDumpIntegrationTest : CardDumpTest() { assertEquals("Hop Fastpass", identity.name.resolveAsync()) assertEquals("01-001-12345678-RA", identity.serialNumber) - assertTrue(info is TrimetHopTransitInfo) assertEquals("01-001-12345678-RA", info.serialNumber) // Serial-only card: has emptyStateMessage, no trips - assertTrue(info is SerialOnlyTransitInfo) assertNotNull(info.emptyStateMessage, "Serial-only card should have an emptyStateMessage") assertNull(info.trips, "Serial-only card should have null trips") } diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/TestAssetLoader.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/TestAssetLoader.kt index 498a87d1c..bd583ff93 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/TestAssetLoader.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/TestAssetLoader.kt @@ -124,7 +124,7 @@ object TestAssetLoader { val importer = CardImporter.create(KotlinxCardSerializer(json)) val result = importer.importCards(jsonString) assertTrue(result is ImportResult.Success, "Failed to import card from $resourcePath: $result") - return (result as ImportResult.Success).cards.first() + return result.cards.first() } /** @@ -247,8 +247,6 @@ abstract class CardDumpTest { val card = rawCard.parse() assertTrue(factory.check(card), "Card did not match factory: ${factory::class.simpleName}") val transitInfo = factory.parseInfo(card) - assertNotNull(transitInfo, "Failed to parse transit info") - assertTrue(transitInfo is T, "Transit info is not of expected type") return transitInfo } @@ -273,8 +271,6 @@ abstract class CardDumpTest { val card = rawCard.parse() as C assertTrue(factory.check(card), "Card did not match factory: ${factory::class.simpleName}") val transitInfo = factory.parseInfo(card) - assertNotNull(transitInfo, "Failed to parse transit info") - assertTrue(transitInfo is T, "Transit info is not of expected type") return transitInfo } @@ -291,8 +287,6 @@ abstract class CardDumpTest { val card = rawCard.parse() as C assertTrue(factory.check(card), "Card did not match factory: ${factory::class.simpleName}") val transitInfo = factory.parseInfo(card) - assertNotNull(transitInfo, "Failed to parse transit info") - assertTrue(transitInfo is T, "Transit info is not of expected type") return Pair(card, transitInfo) } } diff --git a/app/web/build.gradle.kts b/app/web/build.gradle.kts index e4eaa38b4..c56e9ed66 100644 --- a/app/web/build.gradle.kts +++ b/app/web/build.gradle.kts @@ -1,3 +1,5 @@ +@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) + plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.compose.multiplatform) diff --git a/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCardReader.kt b/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCardReader.kt index 213187244..33db1135a 100644 --- a/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCardReader.kt +++ b/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCardReader.kt @@ -130,7 +130,7 @@ object DesfireCardReader { val fileData = readFileData(desfireProtocol, fileId, fileSettings) RawDesfireFile.create(fileId, fileSettings, fileData) } catch (ex: UnauthorizedException) { - RawDesfireFile.createUnauthorized(fileId, fileSettings, ex.message ?: "Access denied") + RawDesfireFile.createUnauthorized(fileId, fileSettings, ex.message) } catch (ex: Exception) { RawDesfireFile.createInvalid(fileId, fileSettings, ex.toString()) } @@ -169,7 +169,7 @@ object DesfireCardReader { // All commands failed return if (lastException is UnauthorizedException) { - RawDesfireFile.createUnauthorized(fileId, null, lastException.message ?: "Access denied") + RawDesfireFile.createUnauthorized(fileId, null, lastException.message) } else { RawDesfireFile.createInvalid(fileId, null, lastException.toString()) } diff --git a/card/desfire/src/commonTest/kotlin/com/codebutler/farebot/card/desfire/DesfireProtocolTest.kt b/card/desfire/src/commonTest/kotlin/com/codebutler/farebot/card/desfire/DesfireProtocolTest.kt index efa063aa9..3f579b8f9 100644 --- a/card/desfire/src/commonTest/kotlin/com/codebutler/farebot/card/desfire/DesfireProtocolTest.kt +++ b/card/desfire/src/commonTest/kotlin/com/codebutler/farebot/card/desfire/DesfireProtocolTest.kt @@ -115,7 +115,7 @@ class DesfireProtocolTest { assertFailsWith { protocol.getFileList() } - assertTrue(ex.message!!.contains("Invalid response")) + assertEquals(ex.message?.contains("Invalid response"), true) } // -- Status code: OPERATION_OK -- @@ -185,7 +185,7 @@ class DesfireProtocolTest { assertFailsWith { protocol.getFileList() } - assertTrue(ex.message!!.contains("Permission denied")) + assertTrue(ex.message.contains("Permission denied")) } // -- Status code: AUTHENTICATION_ERROR (0xAE) -- @@ -210,7 +210,7 @@ class DesfireProtocolTest { assertFailsWith { protocol.getFileList() } - assertTrue(ex.message!!.contains("Authentication error")) + assertTrue(ex.message.contains("Authentication error")) } // -- Status code: AID_NOT_FOUND (0xA0) -- @@ -235,7 +235,7 @@ class DesfireProtocolTest { assertFailsWith { protocol.selectApp(0x000001) } - assertTrue(ex.message!!.contains("AID not found")) + assertTrue(ex.message.contains("AID not found")) } // -- Status code: FILE_NOT_FOUND (0xF0) -- @@ -260,7 +260,7 @@ class DesfireProtocolTest { assertFailsWith { protocol.readFile(1) } - assertTrue(ex.message!!.contains("File not found")) + assertTrue(ex.message.contains("File not found")) } // -- Status code: unknown -- @@ -275,8 +275,8 @@ class DesfireProtocolTest { assertFailsWith { protocol.getFileList() } - assertTrue(ex.message!!.contains("Unknown status code")) - assertTrue(ex.message!!.contains("bb")) + assertEquals(ex.message?.contains("Unknown status code"), true) + assertEquals(ex.message?.contains("bb"), true) } @Test diff --git a/migrate_strings.py b/migrate_strings.py deleted file mode 100644 index 503b48252..000000000 --- a/migrate_strings.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -""" -Mechanical migration script: Replace getStringBlocking/getPluralStringBlocking/stringResource.getString -with FormattedString equivalents throughout the codebase. - -This handles: -1. getStringBlocking(... ) -> FormattedString(... ) -2. getPluralStringBlocking(... ) -> FormattedString.plural(... ) -3. stringResource.getString(... ) -> FormattedString(... ) -4. Import replacements -5. Property type fixups: override val cardName: String -> override val cardName: FormattedString -6. Remove stringResource constructor parameters and fields -7. Update override val signatures for Trip/Subscription/TransitInfo properties -""" - -import os -import re -import sys - -WORKTREE = os.path.dirname(os.path.abspath(__file__)) - -# Files to skip (already manually updated or infrastructure files to delete) -SKIP_FILES = { - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/ListItem.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/HeaderListItem.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/ListItemInterface.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/FareBotUiTree.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/FormattedString.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/DefaultStringResource.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/StringResource.kt', - 'base/src/androidMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/iosMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/jvmMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/wasmJsMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'transit/src/commonMain/kotlin/com/codebutler/farebot/transit/TransitInfo.kt', - 'transit/src/commonMain/kotlin/com/codebutler/farebot/transit/TransitIdentity.kt', - 'transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Trip.kt', - 'transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Subscription.kt', - 'transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt', - 'app/src/commonTest/kotlin/com/codebutler/farebot/test/TestStringResource.kt', - # Skip non-Kotlin files - 'CLAUDE.md', - 'migrate_strings.py', -} - -# Properties that changed from String to FormattedString -FORMATTEDSTRING_OVERRIDE_PROPS = [ - 'cardName', - 'routeName', - 'agencyName', - 'shortAgencyName', - 'fareString', - 'subscriptionName', - 'saleAgencyName', - 'warning', - 'emptyStateMessage', -] - -def should_skip(filepath): - rel = os.path.relpath(filepath, WORKTREE) - return rel in SKIP_FILES - -def process_file(filepath): - with open(filepath, 'r') as f: - content = f.read() - - original = content - modified = False - - # 1. Replace getStringBlocking( -> FormattedString( - if 'getStringBlocking(' in content: - content = content.replace('getStringBlocking(', 'FormattedString(') - modified = True - - # 2. Replace getPluralStringBlocking( -> FormattedString.plural( - if 'getPluralStringBlocking(' in content: - content = content.replace('getPluralStringBlocking(', 'FormattedString.plural(') - modified = True - - # 3. Replace stringResource.getString( -> FormattedString( - if 'stringResource.getString(' in content: - content = content.replace('stringResource.getString(', 'FormattedString(') - modified = True - - # 4. Fix override val properties that changed type from String to FormattedString - for prop in FORMATTEDSTRING_OVERRIDE_PROPS: - # override val propName: String -> override val propName: FormattedString - pattern = rf'(override\s+val\s+{prop}\s*:\s*)String(\b)' - replacement = rf'\1FormattedString\2' - new_content = re.sub(pattern, replacement, content) - if new_content != content: - content = new_content - modified = True - - # 5. Fix "val description: String get() = getStringBlocking" -> already handled by step 1 - # But need to fix the return type - # PaymentMethod.description is handled in Subscription.kt (manually updated) - - # 6. Update imports - if modified: - # Remove old imports - old_imports = [ - 'import com.codebutler.farebot.base.util.getStringBlocking', - 'import com.codebutler.farebot.base.util.getPluralStringBlocking', - ] - for old_import in old_imports: - if old_import in content: - content = content.replace(old_import + '\n', '') - - # Add FormattedString import if not already present and file uses it - if 'FormattedString(' in content or 'FormattedString.plural(' in content: - fs_import = 'import com.codebutler.farebot.base.util.FormattedString' - if fs_import not in content: - # Find the last import line and add after it - lines = content.split('\n') - last_import_idx = -1 - for i, line in enumerate(lines): - if line.startswith('import '): - last_import_idx = i - if last_import_idx >= 0: - lines.insert(last_import_idx + 1, fs_import) - content = '\n'.join(lines) - - if content != original: - with open(filepath, 'w') as f: - f.write(content) - return True - return False - -def main(): - count = 0 - for root, dirs, files in os.walk(WORKTREE): - # Skip build directories, .git, metrodroid, etc. - dirs[:] = [d for d in dirs if d not in { - 'build', '.git', '.gradle', 'metrodroid', 'kotlin-js-store', - 'docs', '.devcontainer', 'config', '.claude', '.idea', - }] - for fname in files: - if not fname.endswith('.kt'): - continue - filepath = os.path.join(root, fname) - if should_skip(filepath): - continue - if process_file(filepath): - rel = os.path.relpath(filepath, WORKTREE) - print(f' Modified: {rel}') - count += 1 - print(f'\nTotal files modified: {count}') - -if __name__ == '__main__': - main() diff --git a/remove_string_resource.py b/remove_string_resource.py deleted file mode 100644 index 55c7ad8b3..000000000 --- a/remove_string_resource.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env python3 -""" -Remove stringResource: StringResource parameter/field from all transit factories, -transit info classes, trip classes, subscription classes, card classes, and DI graphs. - -This handles: -1. Constructor/function parameter: `stringResource: StringResource` (with various modifiers) -2. Constructor argument passing: `stringResource = stringResource,` or `stringResource,` -3. Import removal: StringResource, DefaultStringResource -4. Property declarations: `val stringResource = DefaultStringResource()` -5. getAdvancedUi(stringResource: StringResource) -> getAdvancedUi() -6. getAdvancedUi(stringResource) -> getAdvancedUi() -7. Refill.getAgencyName(stringResource) patterns -""" - -import os -import re -import sys - -WORKTREE = os.path.dirname(os.path.abspath(__file__)) - -# Files to skip -SKIP_FILES = { - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/FormattedString.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/StringResource.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/DefaultStringResource.kt', - 'base/src/commonMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/androidMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/iosMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/jvmMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'base/src/wasmJsMain/kotlin/com/codebutler/farebot/base/util/GetStringBlocking.kt', - 'CLAUDE.md', - 'migrate_strings.py', - 'remove_string_resource.py', -} - -def should_skip(filepath): - rel = os.path.relpath(filepath, WORKTREE) - return rel in SKIP_FILES - -def process_file(filepath): - with open(filepath, 'r') as f: - content = f.read() - - original = content - - # 1. Remove imports - imports_to_remove = [ - 'import com.codebutler.farebot.base.util.StringResource', - 'import com.codebutler.farebot.base.util.DefaultStringResource', - 'import com.codebutler.farebot.base.util.getStringBlocking', - 'import com.codebutler.farebot.base.util.getPluralStringBlocking', - ] - for imp in imports_to_remove: - content = content.replace(imp + '\n', '') - - # 2. Remove stringResource parameter lines from constructors/functions - # Patterns like: - # private val stringResource: StringResource, - # val stringResource: StringResource, - # override val stringResource: StringResource, - # stringResource: StringResource, - # stringResource: StringResource = DefaultStringResource(), - # private val stringResource: StringResource = DefaultStringResource(), - lines = content.split('\n') - new_lines = [] - i = 0 - while i < len(lines): - line = lines[i] - stripped = line.strip() - - # Match stringResource parameter/field declarations - if re.match(r'^(private\s+val\s+|val\s+|override\s+val\s+)?stringResource\s*:\s*StringResource(\s*=\s*DefaultStringResource\(\))?\s*,?\s*$', stripped): - # If the line ends with comma, just remove the line - # If not, check if previous line ends with comma that needs removing - if not stripped.endswith(',') and new_lines: - # Remove trailing comma from previous non-blank line - for j in range(len(new_lines) - 1, -1, -1): - if new_lines[j].strip(): - new_lines[j] = new_lines[j].rstrip().rstrip(',') - break - i += 1 - continue - - new_lines.append(line) - i += 1 - - content = '\n'.join(new_lines) - - # 3. Remove stringResource being passed as constructor argument - # Patterns: - # stringResource = stringResource, - # stringResource = this.stringResource, - # stringResource = stringResource - # stringResource, (standalone on a line, as positional arg) - lines = content.split('\n') - new_lines = [] - i = 0 - while i < len(lines): - line = lines[i] - stripped = line.strip() - - # Match: stringResource = stringResource, or stringResource = this.stringResource, - if re.match(r'^stringResource\s*=\s*(this\.)?stringResource\s*,?\s*$', stripped): - if not stripped.endswith(',') and new_lines: - for j in range(len(new_lines) - 1, -1, -1): - if new_lines[j].strip(): - new_lines[j] = new_lines[j].rstrip().rstrip(',') - break - i += 1 - continue - - # Match standalone: stringResource = DefaultStringResource(), (as arg) - if re.match(r'^stringResource\s*=\s*DefaultStringResource\(\)\s*,?\s*$', stripped): - if not stripped.endswith(',') and new_lines: - for j in range(len(new_lines) - 1, -1, -1): - if new_lines[j].strip(): - new_lines[j] = new_lines[j].rstrip().rstrip(',') - break - i += 1 - continue - - new_lines.append(line) - i += 1 - - content = '\n'.join(new_lines) - - # 4. Remove inline stringResource parameter from function signatures - # e.g., fun getAdvancedUi(stringResource: StringResource): ... - # -> fun getAdvancedUi(): ... - content = re.sub( - r'(\bgetAdvancedUi\s*)\(\s*stringResource\s*:\s*StringResource\s*\)', - r'\1()', - content - ) - - # 5. Remove stringResource argument from getAdvancedUi calls - # e.g., getAdvancedUi(stringResource) -> getAdvancedUi() - # e.g., getAdvancedUi(this.stringResource) -> getAdvancedUi() - content = re.sub( - r'(\bgetAdvancedUi\s*)\(\s*(this\.)?stringResource\s*\)', - r'\1()', - content - ) - - # 6. Remove stringResource from inline constructor params (single line) - # e.g., SuicaTrip(stringResource, ...) -> SuicaTrip(...) - # e.g., Foo(bar, stringResource, baz) -> Foo(bar, baz) - # e.g., Foo(stringResource = stringResource, bar = baz) -> Foo(bar = baz) - - # Pattern: remove "stringResource, " or ", stringResource" from argument lists - # Be careful not to match inside strings - # Handle named arg: stringResource = stringResource, - content = re.sub(r'stringResource\s*=\s*(this\.)?stringResource\s*,\s*', '', content) - # Handle named arg at end: , stringResource = stringResource) - content = re.sub(r',\s*stringResource\s*=\s*(this\.)?stringResource\s*(?=\))', '', content) - # Handle named arg as only arg: (stringResource = stringResource) - content = re.sub(r'\(\s*stringResource\s*=\s*(this\.)?stringResource\s*\)', '()', content) - - # Handle positional: stringResource, (at start of args) - content = re.sub(r'(?<=\()(\s*)stringResource\s*,\s*', r'\1', content) - # Handle positional: , stringResource (at end of args) - content = re.sub(r',\s*stringResource\s*(?=\s*\))', '', content) - # Handle positional as only arg: (stringResource) but NOT (stringResource: Type) - content = re.sub(r'\(\s*stringResource\s*\)(?!\s*[:{])', '()', content) - - # 7. Remove stringResource = DefaultStringResource() as arg - content = re.sub(r'stringResource\s*=\s*DefaultStringResource\(\)\s*,\s*', '', content) - content = re.sub(r',\s*stringResource\s*=\s*DefaultStringResource\(\)\s*(?=\))', '', content) - content = re.sub(r'\(\s*stringResource\s*=\s*DefaultStringResource\(\)\s*\)', '()', content) - - # 8. Remove standalone property assignments - # val stringResource = DefaultStringResource() - # private val stringResource = DefaultStringResource() - content = re.sub(r'\n\s*(private\s+)?val\s+stringResource\s*=\s*DefaultStringResource\(\)\s*\n', '\n', content) - content = re.sub(r'\n\s*(private\s+)?val\s+stringResource\s*:\s*StringResource\s*=\s*DefaultStringResource\(\)\s*\n', '\n', content) - - # 9. Handle Refill abstract methods that take stringResource - # fun getAgencyName(stringResource: StringResource): String? -> fun getAgencyName(): FormattedString? - # These should have been handled by the mechanical migration, but just in case - content = re.sub( - r'(\bgetAgencyName\s*)\(\s*stringResource\s*:\s*StringResource\s*\)', - r'\1()', - content - ) - content = re.sub( - r'(\bgetShortAgencyName\s*)\(\s*stringResource\s*:\s*StringResource\s*\)', - r'\1()', - content - ) - content = re.sub( - r'(\bgetAmountString\s*)\(\s*stringResource\s*:\s*StringResource\s*\)', - r'\1()', - content - ) - - # Also fix call sites - content = re.sub(r'(\bgetAgencyName\s*)\(\s*(this\.)?stringResource\s*\)', r'\1()', content) - content = re.sub(r'(\bgetShortAgencyName\s*)\(\s*(this\.)?stringResource\s*\)', r'\1()', content) - content = re.sub(r'(\bgetAmountString\s*)\(\s*(this\.)?stringResource\s*\)', r'\1()', content) - - # 10. Remove stringResource from inline function params - # e.g., fun foo(stringResource: StringResource, bar: Bar) -> fun foo(bar: Bar) - content = re.sub(r'stringResource\s*:\s*StringResource\s*,\s*', '', content) - content = re.sub(r',\s*stringResource\s*:\s*StringResource\s*(?=\))', '', content) - content = re.sub(r'\(\s*stringResource\s*:\s*StringResource\s*\)', '()', content) - - # Also with default value - content = re.sub(r'stringResource\s*:\s*StringResource\s*=\s*DefaultStringResource\(\)\s*,\s*', '', content) - content = re.sub(r',\s*stringResource\s*:\s*StringResource\s*=\s*DefaultStringResource\(\)\s*(?=\))', '', content) - content = re.sub(r'\(\s*stringResource\s*:\s*StringResource\s*=\s*DefaultStringResource\(\)\s*\)', '()', content) - - # 11. Clean up double blank lines that may have been created - while '\n\n\n' in content: - content = content.replace('\n\n\n', '\n\n') - - if content != original: - with open(filepath, 'w') as f: - f.write(content) - return True - return False - -def main(): - count = 0 - for root, dirs, files in os.walk(WORKTREE): - dirs[:] = [d for d in dirs if d not in { - 'build', '.git', '.gradle', 'metrodroid', 'kotlin-js-store', - 'docs', '.devcontainer', 'config', '.claude', '.idea', 'worktrees', - }] - for fname in files: - if not fname.endswith('.kt'): - continue - filepath = os.path.join(root, fname) - if should_skip(filepath): - continue - if process_file(filepath): - rel = os.path.relpath(filepath, WORKTREE) - print(f' Modified: {rel}') - count += 1 - print(f'\nTotal files modified: {count}') - -if __name__ == '__main__': - main() diff --git a/tools/mdst/build.gradle.kts b/tools/mdst/build.gradle.kts index 34d37e2cc..d72dd079d 100644 --- a/tools/mdst/build.gradle.kts +++ b/tools/mdst/build.gradle.kts @@ -1,3 +1,5 @@ +@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) + plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.serialization)