diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt index 0e9f64511c..5d2c226351 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -30,10 +29,7 @@ import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.block.TangemBlockCardColors import com.tangem.core.ui.components.text.applyBladeBrush -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.wallets.models.UserWalletId @@ -218,7 +214,7 @@ fun getBalanceValueAndFlickerState(balance: UserWalletItemUM.Balance): Pair DASH_SIGN to false is UserWalletItemUM.Balance.Hidden -> THREE_STARS to false is UserWalletItemUM.Balance.Loading -> null to false - is UserWalletItemUM.Balance.Locked -> stringResource(R.string.common_locked) to false + is UserWalletItemUM.Balance.Locked -> stringResourceSafe(R.string.common_locked) to false is UserWalletItemUM.Balance.Loaded -> balance.value to balance.isFlickering } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkCollectionConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkCollectionConverter.kt index c0f8165398..0d21a6dd56 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkCollectionConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkCollectionConverter.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.local.nft.converter import android.content.res.Resources +import com.tangem.core.res.getStringSafe import com.tangem.datasource.R import com.tangem.domain.models.StatusSource import com.tangem.domain.models.network.Network @@ -48,12 +49,12 @@ class NFTSdkCollectionConverter( is NFTCollection.Identifier.EVM -> name.toCollectionName() is NFTCollection.Identifier.TON -> if (collectionId.contractAddress == null) { - resources.getString(R.string.nft_no_collection) + resources.getStringSafe(R.string.nft_no_collection) } else { name.toCollectionName() } is NFTCollection.Identifier.Solana -> if (collectionId.collectionAddress == null) { - resources.getString(R.string.nft_no_collection) + resources.getStringSafe(R.string.nft_no_collection) } else { name.toCollectionName() } @@ -61,7 +62,7 @@ class NFTSdkCollectionConverter( } private fun String?.toCollectionName() = if (this.isNullOrEmpty()) { - resources.getString(R.string.nft_untitled_collection) + resources.getStringSafe(R.string.nft_untitled_collection) } else { this } diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt index a5941e042d..3715753909 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/ui/NFTCollectionsEmpty.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton @@ -40,14 +39,14 @@ internal fun NFTCollectionsEmpty(state: NFTCollectionsUM.Empty, modifier: Modifi ) Text( modifier = Modifier.padding(top = TangemTheme.dimens.spacing24), - text = stringResource(R.string.nft_collections_empty_title), + text = stringResourceSafe(R.string.nft_collections_empty_title), style = TangemTheme.typography.h3, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, ) Text( modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - text = stringResource(R.string.nft_collections_empty_description), + text = stringResourceSafe(R.string.nft_collections_empty_description), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/OnboardingNoteCreateWalletScreen.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/OnboardingNoteCreateWalletScreen.kt index 91644d77bb..97e931d5ac 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/OnboardingNoteCreateWalletScreen.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/OnboardingNoteCreateWalletScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -43,7 +42,8 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod state = WalletArtworksState.Folded, ) Column( - modifier = Modifier.weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT) + modifier = Modifier + .weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT) .fillMaxWidth() .verticalScroll(rememberScrollState()) .padding(horizontal = 32.dp), @@ -51,7 +51,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod ) { SpacerHMax() Text( - text = stringResource(R.string.onboarding_create_wallet_header), + text = stringResourceSafe(R.string.onboarding_create_wallet_header), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -59,7 +59,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod ) SpacerH16() Text( - text = stringResource(R.string.onboarding_create_wallet_body), + text = stringResourceSafe(R.string.onboarding_create_wallet_body), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt index 090ce03d84..67c433d86b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign @@ -134,7 +133,7 @@ private fun Content( } else { state.onAccessCodeFirstChange }, - label = stringResource(id = R.string.onboarding_wallet_info_title_third), + label = stringResourceSafe(id = R.string.onboarding_wallet_info_title_third), isError = state.codesNotMatchError || state.atLeastMinCharsError, visualTransformation = if (state.accessCodeHidden) { PasswordVisualTransformation() @@ -143,9 +142,9 @@ private fun Content( }, caption = when { state.codesNotMatchError && reEnterAccessCodeState -> - stringResource(R.string.onboarding_access_codes_doesnt_match) + stringResourceSafe(R.string.onboarding_access_codes_doesnt_match) state.atLeastMinCharsError && !reEnterAccessCodeState -> - stringResource(R.string.onboarding_access_code_too_short) + stringResourceSafe(R.string.onboarding_access_code_too_short) else -> null }, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt index 6c65dfd14b..d63b3aa736 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampButtonComponent.kt @@ -8,12 +8,12 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.ClickableText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.extensions.appendColored +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.entity.OnrampMainComponentUM @@ -35,7 +35,7 @@ internal fun OnrampButtonComponent(state: OnrampMainComponentUM) { OnrampTosText(providerState) PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.common_buy), + text = stringResourceSafe(id = R.string.common_buy), onClick = state.buyButtonConfig.onClick, enabled = state.buyButtonConfig.enabled, ) @@ -44,9 +44,9 @@ internal fun OnrampButtonComponent(state: OnrampMainComponentUM) { @Composable private fun OnrampTosText(provider: OnrampProviderBlockUM.Content?) { - val termsOfUse = stringResource(R.string.common_terms_of_use) - val privacyPolicy = stringResource(R.string.common_privacy_policy) - val tosText = stringResource(R.string.onramp_legal, termsOfUse, privacyPolicy) + val termsOfUse = stringResourceSafe(R.string.common_terms_of_use) + val privacyPolicy = stringResourceSafe(R.string.common_privacy_policy) + val tosText = stringResourceSafe(R.string.onramp_legal, termsOfUse, privacyPolicy) val clickableAnnotation = buildAnnotatedString { append(tosText.substringBefore(termsOfUse)) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt index cbe8e3f59a..fb730d52bb 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcEstimatedWalletChangesItem.kt @@ -10,19 +10,19 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.extensions.resourceReference import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.divider.DividerWithPadding +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.walletconnect.impl.R @@ -75,7 +75,7 @@ internal fun WcEstimatedWalletChangesItem(item: WcEstimatedWalletChangesUM, modi verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(if (isExpanded) R.string.common_show_less else R.string.common_show_more), + text = stringResourceSafe(if (isExpanded) R.string.common_show_less else R.string.common_show_more), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.tertiary, ) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ef9fae7586..dfa579810a 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -191,6 +191,7 @@ firebase-perf = { module = "com.google.firebase:firebase-perf" } # region Detekt detekt-compose = { module = "ru.kode:detekt-rules-compose", version.ref = "detektComposeRules" } detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } +detekt-api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" } # endregion Detekt # region Test diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt index 44c658db99..264610cc2a 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt @@ -22,13 +22,19 @@ private fun DetektExtension.configure(project: Project) { ignoreFailures = false autoCorrect = true buildUponDefaultConfig = true - config.setFrom(project.rootProject.files("tangem-android-tools/detekt-config.yml")) + config.setFrom( + project.rootProject.files( + "tangem-android-tools/detekt-config.yml", + "plugins/detekt-rules/app-detekt-config.yml" + ) + ) } private fun Project.configureDetektPlugins() { - listOf( + listOfNotNull( findLibrary(alias = "detekt-formatting"), findLibrary(alias = "detekt-compose"), + findProject(":plugins:detekt-rules"), ).forEach { dependencies.add(CONFIGURATION_DETEKT_PLUGINS, it) } diff --git a/plugins/detekt-rules/.gitignore b/plugins/detekt-rules/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/plugins/detekt-rules/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/plugins/detekt-rules/app-detekt-config.yml b/plugins/detekt-rules/app-detekt-config.yml new file mode 100644 index 0000000000..fff4ae407d --- /dev/null +++ b/plugins/detekt-rules/app-detekt-config.yml @@ -0,0 +1,3 @@ +tangem-rules: + UnsafeStringResourceUsage: + active: true \ No newline at end of file diff --git a/plugins/detekt-rules/build.gradle.kts b/plugins/detekt-rules/build.gradle.kts new file mode 100644 index 0000000000..01f1452ed3 --- /dev/null +++ b/plugins/detekt-rules/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + alias(deps.plugins.kotlin.jvm) +} + +dependencies { + implementation(deps.detekt.api) +} \ No newline at end of file diff --git a/plugins/detekt-rules/src/main/java/com/tangem/detekt/TangemRuleSetProvider.kt b/plugins/detekt-rules/src/main/java/com/tangem/detekt/TangemRuleSetProvider.kt new file mode 100644 index 0000000000..62b54964ad --- /dev/null +++ b/plugins/detekt-rules/src/main/java/com/tangem/detekt/TangemRuleSetProvider.kt @@ -0,0 +1,20 @@ +package com.tangem.detekt + +import com.tangem.detekt.rules.UnsafeStringResourceUsage +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider + +class TangemRuleSetProvider : RuleSetProvider { + + override val ruleSetId: String = "tangem-rules" + + override fun instance(config: Config): RuleSet { + return RuleSet( + id = ruleSetId, + rules = listOf( + UnsafeStringResourceUsage(config), + ) + ) + } +} \ No newline at end of file diff --git a/plugins/detekt-rules/src/main/java/com/tangem/detekt/rules/UnsafeStringResourceUsage.kt b/plugins/detekt-rules/src/main/java/com/tangem/detekt/rules/UnsafeStringResourceUsage.kt new file mode 100644 index 0000000000..afaba4f6df --- /dev/null +++ b/plugins/detekt-rules/src/main/java/com/tangem/detekt/rules/UnsafeStringResourceUsage.kt @@ -0,0 +1,32 @@ +package com.tangem.detekt.rules + +import io.gitlab.arturbosch.detekt.api.* +import org.jetbrains.kotlin.psi.* + +class UnsafeStringResourceUsage(config: Config) : Rule(config) { + + override val issue: Issue = Issue( + id = "UnsafeStringResourceUsage", + severity = Severity.Security, + description = "Avoid using stringResource directly in the code.", + debt = Debt.FIVE_MINS, + ) + + val unsafeFunctionNames = listOf("stringResource", "pluralStringResource") + + override fun visitCallExpression(expression: KtCallExpression) { + super.visitCallExpression(expression) + + val functionName = expression.calleeExpression?.text + if (functionName in unsafeFunctionNames) { + report( + CodeSmell( + issue = issue, + entity = Entity.from(expression), + message = "Usage of `$functionName` is unsafe. Use the version of the function with the `Safe` " + + "suffix. For example, `${functionName}Safe`." + ) + ) + } + } +} \ No newline at end of file diff --git a/plugins/detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider b/plugins/detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider new file mode 100644 index 0000000000..a0a356fe78 --- /dev/null +++ b/plugins/detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider @@ -0,0 +1 @@ +com.tangem.detekt.TangemRuleSetProvider \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 80cd025090..40453f033b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -134,6 +134,7 @@ dependencyResolutionManagement { enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") include(":app") +include(":plugins:detekt-rules") // region Core modules include(":core:analytics") diff --git a/tangem-android-tools b/tangem-android-tools index 4eb84b4150..53e359bc2e 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit 4eb84b4150ab0b03400707a6f121d4fd313e3cef +Subproject commit 53e359bc2ead37feeb9e70d7004c91cde07ccf9b