Files
LibreMail/config/detekt/detekt.yml
T
JMR-devandClaude Opus 4.8 35b869944e feat(reporting): PII-free latency breadcrumbs on the message-open path (#358)
Adds AppLog breadcrumbs to the message-open path so a debug report can show
where the reader's spinner time goes:

- ImapClient.withStore: per-op connect vs. work timing plus a live
  connect-per-op connection gauge (issue #125's provider-ceiling context).
- fetchBodyMarkingSeen: select/body/flag phase timings plus PII-free size
  counts (RFC822 size, body chars, attachment count).
- MailRepositoryImpl.openMessage: end-to-end open latency plus the
  cached-vs-fetched branch, keyed by accountLogRef and logSafeFolderLabel.
- ReaderViewModel: spinner-to-ready latency, split success vs. failure.

All breadcrumbs are PII-free: accounts are logged via the existing
accountLogRef hash, folders via the existing logSafeFolderLabel allowlist,
and everything else is sizes/durations/booleans only.

Fixes the 4 unit-test classes that exercise this code without mocking
android.util.Log (a throwing stub under plain JVM tests): mockkStatic(Log)
is now installed in MailRepositoryImplCoverageTest, ImapClientBackfillTest,
ImapFolderOpenLatencyTest, and ReaderViewModelActionsTest, following the
existing MailBackfillerTest/ImapClientTest conventions. detekt.yml gains two
more ForbiddenImport excludes for the newly Log-importing test files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:46:48 -05:00

103 lines
5.2 KiB
YAML

# Project overrides merged onto detekt's bundled defaults (buildUponDefaultConfig = true in
# app/build.gradle.kts). Only rules that need tuning for this project appear here.
#
# Guiding principle: Compose UI intentionally breaks several OOP-era metrics, and a few default
# thresholds are stricter than this project's chosen style. Those are relaxed here with a reason.
# Genuine smells (swallowed exceptions, an over-complex condition, a misnamed file) are fixed in
# the code, not silenced.
complexity:
LongMethod:
# Declarative @Composable functions are read top-to-bottom and are legitimately long.
ignoreAnnotated: ['Composable']
CyclomaticComplexMethod:
# Branchy layout code (when/if inside a UI tree) isn't algorithmic complexity.
ignoreAnnotated: ['Composable']
TooManyFunctions:
# Screen files group many small @Composable helpers next to their screen. The data layer
# (repositories, DAOs, mappers) and complex mailbox screen/ViewModel are intentionally
# operation-rich cohesive APIs; detekt's default of 11 is far too low for them. Files past
# ~40 functions still flag as genuinely bloated.
ignoreAnnotated: ['Composable']
allowedFunctionsPerFile: 40
allowedFunctionsPerClass: 40
allowedFunctionsPerInterface: 40
LargeClass:
# MailRepositoryImplTest is one cohesive single-SUT suite (a test per MailRepositoryImpl method
# plus folder-resolution / spam cases) already at detekt's LLOC boundary; the reader-path perf
# logging (issue #358) added its required android.util.Log mock + one breadcrumb test, tipping it
# over. Excluded rather than artificially split — same "operation-rich cohesive suite" rationale as
# the TooManyFunctions relaxation above.
excludes: ['**/data/repository/MailRepositoryImplTest.kt']
naming:
FunctionNaming:
# @Composable functions are PascalCase by Compose convention.
ignoreAnnotated: ['Composable']
style:
ForbiddenImport:
# Strangler guard for epic #324: production code must log through
# org.libremail.reporting.AppLog, which mirrors every line into the debug-report
# RingLogBuffer. A raw android.util.Log import writes to Logcat only and bypasses that
# buffer, so it never reaches a user-reviewed DebugReport. Excludes cover the ONE allowed
# wrapper (AppLog) plus the unit tests that import Log solely to mockkStatic/verify it —
# AppLog forwards to Log, a throwing stub under plain JVM unit tests, so those tests must
# mock it. They do not bypass the facade. Do NOT add production files here; migrate them to
# AppLog instead.
active: true
forbiddenImports:
- value: 'android.util.Log'
reason: >-
Log via org.libremail.reporting.AppLog so lines reach the debug-report RingLogBuffer;
raw android.util.Log bypasses it (epic #324).
excludes:
# The facade itself — the single sanctioned wrapper around android.util.Log.
- '**/reporting/AppLog.kt'
# Facade tests: mockkStatic(Log) to verify AppLog forwards to Logcat.
- '**/reporting/AppLogTest.kt'
- '**/reporting/AppLogUninstalledTest.kt'
- '**/restart/RestartActivityLoggingTest.kt'
# Unit tests that mockkStatic(Log::class) because the code under test logs via AppLog,
# which forwards to android.util.Log (a no-op throwing stub under plain JVM unit tests).
- '**/ui/lock/AppLockViewModelTest.kt'
- '**/ui/accountsetup/AccountSetupViewModelTest.kt'
- '**/data/sync/SyncWorkerTest.kt'
- '**/data/sync/MailSyncerTest.kt'
- '**/data/sync/MailBackfillerTest.kt'
- '**/data/sync/MailPrunerTest.kt'
- '**/data/sync/MailMaintenanceGateTest.kt'
- '**/data/sync/MailSyncConcurrencyTest.kt'
- '**/data/sync/PruneWorkerTest.kt'
- '**/data/sync/BackfillWorkerTest.kt'
# Reader-path perf logging (issue #358): the repository's openMessage and the reader ViewModel
# log via AppLog, so their unit tests mockkStatic(Log) too.
- '**/data/repository/MailRepositoryImplTest.kt'
- '**/data/repository/MailRepositoryImplCoverageTest.kt'
- '**/ui/reader/ReaderViewModelTest.kt'
- '**/ui/reader/ReaderViewModelActionsTest.kt'
MagicNumber:
# dp / sp / duration literals are idiomatic inline in Compose.
ignoreAnnotated: ['Composable']
ignorePropertyDeclaration: true
ignoreNamedArgument: true
ReturnCount:
# Allow guard-clause-style early returns; detekt's default of 2 is overly strict.
max: 4
LoopWithTooManyJumpStatements:
# Clear early-continue / early-return loops (e.g. MIME-part walking) read fine; default 1 is strict.
maxJumpCount: 3
ThrowsCount:
# Guard-clause throws don't count; allow a few more for functions validating several
# preconditions (e.g. the OAuth token exchange checks response, auth code, and email).
excludeGuardClauses: true
max: 3
exceptions:
TooGenericExceptionCaught:
# Background sync, the push service, and the IMAP client intentionally catch broadly at their
# boundaries to stay resilient to arbitrary mail-library/network failures; each catch is logged
# and handled (retry with backoff, or SMTP fallback). The SwallowedException rule stays active
# (exceptions are logged, not dropped).
active: false