From 4b9d008be21597f4b2652058602b2b99188f1f6b Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 1 May 2026 02:46:32 +0000 Subject: [PATCH] refactor(workspace): sweep low-risk clippy::pedantic findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conservative cleanup pass — applied only the high-signal pedantic lints whose fixes either remove genuine waste or read more naturally, skipping anything stylistic that would bloat the diff. - map_unwrap_or: 29 .map(...).unwrap_or(...) sites collapsed to .map_or / .is_some_and / .map_or_else equivalents - uninlined_format_args: 7 production format!/write!/println! sites rewritten to the inline-argument style; assert! sites in test code intentionally untouched - match_same_arms: 2 redundant arms collapsed where the bodies were identical and the merger didn't obscure intent Public API is unchanged. No dependencies added or removed. The pedantic warning count dropped from 840 to 807 (-33). Out-of-scope findings — needless_pass_by_value on Bevy Res params, false-positive explicit_iter_loop on Bevy Query iterators, items_after_statements inside test mods, and the "ask before changing" merge logic in solitaire_sync — were intentionally deferred. Co-Authored-By: Claude Opus 4.7 (1M context) --- solitaire_app/src/main.rs | 3 +-- solitaire_data/src/auth_tokens.rs | 5 +++-- solitaire_data/src/storage.rs | 3 +-- solitaire_engine/src/achievement_plugin.rs | 12 ++++-------- solitaire_engine/src/audio_plugin.rs | 3 +-- .../src/card_animation/interaction.rs | 3 +-- solitaire_engine/src/feedback_anim_plugin.rs | 4 +--- solitaire_engine/src/game_plugin.rs | 6 ++---- solitaire_engine/src/leaderboard_plugin.rs | 12 ++++-------- solitaire_engine/src/profile_plugin.rs | 6 +++--- solitaire_engine/src/selection_plugin.rs | 2 +- solitaire_engine/src/settings_plugin.rs | 19 ++++++++----------- solitaire_engine/src/splash_plugin.rs | 4 +--- solitaire_engine/src/table_plugin.rs | 12 +++--------- solitaire_engine/src/ui_focus.rs | 10 ++-------- solitaire_engine/src/ui_modal.rs | 3 +-- solitaire_engine/src/weekly_goals_plugin.rs | 4 +--- solitaire_server/src/error.rs | 5 +++-- 18 files changed, 41 insertions(+), 75 deletions(-) diff --git a/solitaire_app/src/main.rs b/solitaire_app/src/main.rs index 358ace6..45039f5 100644 --- a/solitaire_app/src/main.rs +++ b/solitaire_app/src/main.rs @@ -149,8 +149,7 @@ fn install_crash_log_hook() { // parseable and avoids pulling in chrono just for this. let secs = SystemTime::now() .duration_since(UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0); + .map_or(0, |d| d.as_secs()); let _ = writeln!(file, "----- t={secs} -----\n{info}\n"); } default_hook(info); diff --git a/solitaire_data/src/auth_tokens.rs b/solitaire_data/src/auth_tokens.rs index 653f22a..af3f37c 100644 --- a/solitaire_data/src/auth_tokens.rs +++ b/solitaire_data/src/auth_tokens.rs @@ -40,8 +40,9 @@ const SERVICE: &str = "solitaire_quest_server"; fn map_keyring_err(err: keyring_core::Error, username: &str) -> TokenError { let msg = err.to_string(); match err { - keyring_core::Error::NoStorageAccess(_) => TokenError::KeychainUnavailable(msg), - keyring_core::Error::NoDefaultStore => TokenError::KeychainUnavailable(msg), + keyring_core::Error::NoStorageAccess(_) | keyring_core::Error::NoDefaultStore => { + TokenError::KeychainUnavailable(msg) + } keyring_core::Error::NoEntry => TokenError::NotFound(username.to_string()), _ => TokenError::Keyring(msg), } diff --git a/solitaire_data/src/storage.rs b/solitaire_data/src/storage.rs index ca1d041..6fd09b0 100644 --- a/solitaire_data/src/storage.rs +++ b/solitaire_data/src/storage.rs @@ -138,8 +138,7 @@ fn cleanup_tmp_files_in(dir: &Path) { if path .file_name() .and_then(|n| n.to_str()) - .map(|n| n.ends_with(".json.tmp")) - .unwrap_or(false) + .is_some_and(|n| n.ends_with(".json.tmp")) { let _ = fs::remove_file(&path); } diff --git a/solitaire_engine/src/achievement_plugin.rs b/solitaire_engine/src/achievement_plugin.rs index 8306560..a6e5f2f 100644 --- a/solitaire_engine/src/achievement_plugin.rs +++ b/solitaire_engine/src/achievement_plugin.rs @@ -212,9 +212,7 @@ fn evaluate_on_win( /// Convenience: resolve an achievement ID to its human-readable name. /// Used by the toast renderer in `animation_plugin`. pub fn display_name_for(id: &str) -> String { - achievement_by_id(id) - .map(|d| d.name.to_string()) - .unwrap_or_else(|| id.to_string()) + achievement_by_id(id).map_or_else(|| id.to_string(), |d| d.name.to_string()) } /// Marker on the "Done" button inside the Achievements modal. @@ -292,12 +290,10 @@ fn spawn_achievements_screen( for record in &sorted { let def = achievement_by_id(&record.id); - let (name, description) = def - .map(|d| (d.name, d.description)) - .unwrap_or((&record.id, "")); + let (name, description) = def.map_or((record.id.as_str(), ""), |d| (d.name, d.description)); // Hide secret locked achievements so they remain a surprise. - let is_secret = def.map(|d| d.secret).unwrap_or(false); + let is_secret = def.is_some_and(|d| d.secret); if is_secret && !record.unlocked { continue; } @@ -401,7 +397,7 @@ fn tooltip_for_row(unlocked: bool, def: Option<&AchievementDef>) -> String { None => "Earned!".to_string(), } } else { - let description = def.map(|d| d.description).unwrap_or(""); + let description = def.map_or("", |d| d.description); let how = if description.is_empty() { "How to unlock: keep playing.".to_string() } else { diff --git a/solitaire_engine/src/audio_plugin.rs b/solitaire_engine/src/audio_plugin.rs index 6fed715..2d33813 100644 --- a/solitaire_engine/src/audio_plugin.rs +++ b/solitaire_engine/src/audio_plugin.rs @@ -327,8 +327,7 @@ fn handle_mute_keys( let shift = keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight); let (sfx_vol, music_vol) = settings .as_ref() - .map(|s| (s.0.sfx_volume, s.0.music_volume)) - .unwrap_or((1.0, 0.5)); + .map_or((1.0, 0.5), |s| (s.0.sfx_volume, s.0.music_volume)); if shift { // Shift+M: toggle music mute only, SFX unaffected. diff --git a/solitaire_engine/src/card_animation/interaction.rs b/solitaire_engine/src/card_animation/interaction.rs index 7a05fd1..7067e4f 100644 --- a/solitaire_engine/src/card_animation/interaction.rs +++ b/solitaire_engine/src/card_animation/interaction.rs @@ -189,8 +189,7 @@ pub(crate) fn apply_hover_scale( hover_state.scale = if let Some(entity) = target_entity { cards .get(entity) - .map(|(_, t)| t.scale.x) - .unwrap_or(hover_target) + .map_or(hover_target, |(_, t)| t.scale.x) } else { 1.0 }; diff --git a/solitaire_engine/src/feedback_anim_plugin.rs b/solitaire_engine/src/feedback_anim_plugin.rs index 72206a5..9cf4576 100644 --- a/solitaire_engine/src/feedback_anim_plugin.rs +++ b/solitaire_engine/src/feedback_anim_plugin.rs @@ -380,9 +380,7 @@ fn start_deal_anim( let stock_start = Vec3::new(stock_pos.x, stock_pos.y, 0.0); let speed = settings.as_ref().map(|s| &s.0.animation_speed); - let stagger_secs = speed - .map(deal_stagger_secs_for_speed) - .unwrap_or(DEAL_STAGGER_SECS); + let stagger_secs = speed.map_or(DEAL_STAGGER_SECS, deal_stagger_secs_for_speed); for (index, (entity, card_marker, transform)) in card_entities.iter().enumerate() { let final_pos = transform.translation; diff --git a/solitaire_engine/src/game_plugin.rs b/solitaire_engine/src/game_plugin.rs index 076496b..1f31ca3 100644 --- a/solitaire_engine/src/game_plugin.rs +++ b/solitaire_engine/src/game_plugin.rs @@ -153,8 +153,7 @@ fn tick_elapsed_time( fn seed_from_system_time() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) - .map(|d| d.as_nanos() as u64) - .unwrap_or(0) + .map_or(0, |d| d.as_nanos() as u64) } #[allow(clippy::too_many_arguments)] @@ -201,8 +200,7 @@ fn handle_new_game( // where SettingsPlugin is not installed. let draw_mode = settings .as_ref() - .map(|s| s.0.draw_mode.clone()) - .unwrap_or_else(|| game.0.draw_mode.clone()); + .map_or_else(|| game.0.draw_mode.clone(), |s| s.0.draw_mode.clone()); let mode = ev.mode.unwrap_or(game.0.mode); game.0 = GameState::new_with_mode(seed, draw_mode, mode); // Delete any previously saved in-progress state — this is a fresh game. diff --git a/solitaire_engine/src/leaderboard_plugin.rs b/solitaire_engine/src/leaderboard_plugin.rs index 7ccf248..9542dff 100644 --- a/solitaire_engine/src/leaderboard_plugin.rs +++ b/solitaire_engine/src/leaderboard_plugin.rs @@ -153,8 +153,7 @@ fn toggle_leaderboard_screen( // Spawn the panel immediately with whatever data we have so far. let remote_available = provider .as_ref() - .map(|p| p.0.backend_name() != "local") - .unwrap_or(false); + .is_some_and(|p| p.0.backend_name() != "local"); spawn_leaderboard_screen(&mut commands, &data, remote_available, font_res.as_deref()); // Start a background fetch if not already in flight. @@ -215,8 +214,7 @@ fn update_leaderboard_panel( } let remote_available = provider .as_ref() - .map(|p| p.0.backend_name() != "local") - .unwrap_or(false); + .is_some_and(|p| p.0.backend_name() != "local"); for entity in &screens { commands.entity(entity).despawn(); spawn_leaderboard_screen(&mut commands, &data, remote_available, font_res.as_deref()); @@ -473,12 +471,10 @@ fn spawn_leaderboard_screen( let time_str = entry .best_time_secs - .map(format_secs) - .unwrap_or_else(|| "-".to_string()); + .map_or_else(|| "-".to_string(), format_secs); let score_str = entry .best_score - .map(|s| s.to_string()) - .unwrap_or_else(|| "-".to_string()); + .map_or_else(|| "-".to_string(), |s| s.to_string()); card.spawn(Node { flex_direction: FlexDirection::Row, diff --git a/solitaire_engine/src/profile_plugin.rs b/solitaire_engine/src/profile_plugin.rs index f8fb1be..7438206 100644 --- a/solitaire_engine/src/profile_plugin.rs +++ b/solitaire_engine/src/profile_plugin.rs @@ -208,7 +208,7 @@ fn spawn_profile_screen( let records = &ar.0; let unlocked_count = records.iter().filter(|r| r.unlocked).count(); card.spawn(( - Text::new(format!("{} / 18 unlocked", unlocked_count)), + Text::new(format!("{unlocked_count} / 18 unlocked")), font_row.clone(), TextColor(ACCENT_PRIMARY), )); @@ -216,7 +216,7 @@ fn spawn_profile_screen( let mut any_unlocked = false; for record in records { let def = achievement_by_id(record.id.as_str()); - let is_secret = def.map(|d| d.secret).unwrap_or(false); + let is_secret = def.is_some_and(|d| d.secret); if is_secret && !record.unlocked { continue; } @@ -224,7 +224,7 @@ fn spawn_profile_screen( continue; } any_unlocked = true; - let name = def.map(|d| d.name).unwrap_or(record.id.as_str()); + let name = def.map_or(record.id.as_str(), |d| d.name); let date_str = match record.unlock_date { Some(dt) => format!(" ({})", dt.format("%Y-%m-%d")), None => String::new(), diff --git a/solitaire_engine/src/selection_plugin.rs b/solitaire_engine/src/selection_plugin.rs index 9060b1e..71ae2c8 100644 --- a/solitaire_engine/src/selection_plugin.rs +++ b/solitaire_engine/src/selection_plugin.rs @@ -257,7 +257,7 @@ fn handle_selection_keys( // --- Priority 2: tableau stack move --- // Count the full contiguous face-up run in the source pile. - let run_len = face_up_run_len(game.0.piles.get(pile).map(|p| p.cards.as_slice()).unwrap_or(&[])); + let run_len = face_up_run_len(game.0.piles.get(pile).map_or(&[], |p| p.cards.as_slice())); let bottom_card = game .0 .piles diff --git a/solitaire_engine/src/settings_plugin.rs b/solitaire_engine/src/settings_plugin.rs index b98f3d1..1a79bfc 100644 --- a/solitaire_engine/src/settings_plugin.rs +++ b/solitaire_engine/src/settings_plugin.rs @@ -360,16 +360,13 @@ fn sync_settings_panel_visibility( if screen.0 { if panels.is_empty() { let status_label = sync_status - .map(|s| sync_status_label(&s.0)) - .unwrap_or_else(|| "Status: local only".to_string()); + .map_or_else(|| "Status: local only".to_string(), |s| sync_status_label(&s.0)); let unlocked_backs = progress .as_ref() - .map(|p| p.0.unlocked_card_backs.as_slice()) - .unwrap_or(&[0]); + .map_or(&[0][..], |p| p.0.unlocked_card_backs.as_slice()); let unlocked_bgs = progress .as_ref() - .map(|p| p.0.unlocked_backgrounds.as_slice()) - .unwrap_or(&[0]); + .map_or(&[0][..], |p| p.0.unlocked_backgrounds.as_slice()); spawn_settings_panel( &mut commands, &settings.0, @@ -530,7 +527,7 @@ fn handle_settings_buttons( persist(&path, &settings.0); changed.write(SettingsChangedEvent(settings.0.clone())); if let Ok(mut t) = sfx_text.single_mut() { - **t = format!("{:.2}", after); + **t = format!("{after:.2}"); } } } @@ -541,7 +538,7 @@ fn handle_settings_buttons( persist(&path, &settings.0); changed.write(SettingsChangedEvent(settings.0.clone())); if let Ok(mut t) = sfx_text.single_mut() { - **t = format!("{:.2}", after); + **t = format!("{after:.2}"); } } } @@ -552,7 +549,7 @@ fn handle_settings_buttons( persist(&path, &settings.0); changed.write(SettingsChangedEvent(settings.0.clone())); if let Ok(mut t) = music_text.single_mut() { - **t = format!("{:.2}", after); + **t = format!("{after:.2}"); } } } @@ -563,7 +560,7 @@ fn handle_settings_buttons( persist(&path, &settings.0); changed.write(SettingsChangedEvent(settings.0.clone())); if let Ok(mut t) = music_text.single_mut() { - **t = format!("{:.2}", after); + **t = format!("{after:.2}"); } } } @@ -1082,7 +1079,7 @@ fn volume_row( )); row.spawn(( marker, - Text::new(format!("{:.2}", value)), + Text::new(format!("{value:.2}")), value_font, TextColor(TEXT_PRIMARY), )); diff --git a/solitaire_engine/src/splash_plugin.rs b/solitaire_engine/src/splash_plugin.rs index 2fad87a..0aa079b 100644 --- a/solitaire_engine/src/splash_plugin.rs +++ b/solitaire_engine/src/splash_plugin.rs @@ -259,9 +259,7 @@ fn dismiss_splash_on_input( return; } - let touch_pressed = touches - .map(|t| t.iter_just_pressed().next().is_some()) - .unwrap_or(false); + let touch_pressed = touches.is_some_and(|t| t.iter_just_pressed().next().is_some()); let dismissed = keys.get_just_pressed().next().is_some() || mouse.get_just_pressed().next().is_some() || touch_pressed; diff --git a/solitaire_engine/src/table_plugin.rs b/solitaire_engine/src/table_plugin.rs index 181b450..27048ef 100644 --- a/solitaire_engine/src/table_plugin.rs +++ b/solitaire_engine/src/table_plugin.rs @@ -142,14 +142,10 @@ fn setup_table( let window_size = windows .iter() .next() - .map(default_window_size) - .unwrap_or(Vec2::new(1280.0, 800.0)); + .map_or(Vec2::new(1280.0, 800.0), default_window_size); let layout = compute_layout(window_size); - let selected_bg = settings - .as_ref() - .map(|s| s.0.selected_background) - .unwrap_or(0); + let selected_bg = settings.as_ref().map_or(0, |s| s.0.selected_background); let image_handle = bg_images .as_ref() @@ -341,9 +337,7 @@ fn apply_hint_pile_highlight( if pile_marker.0 != ev.dest_pile { continue; } - let original_color = existing - .map(|h| h.original_color) - .unwrap_or(sprite.color); + let original_color = existing.map_or(sprite.color, |h| h.original_color); sprite.color = HINT_PILE_HIGHLIGHT_COLOUR; commands.entity(entity).insert(HintPileHighlight { timer: 2.0, diff --git a/solitaire_engine/src/ui_focus.rs b/solitaire_engine/src/ui_focus.rs index 5c03d14..eaaa341 100644 --- a/solitaire_engine/src/ui_focus.rs +++ b/solitaire_engine/src/ui_focus.rs @@ -364,8 +364,7 @@ fn handle_focus_keys( .filter(|e| { focusables .get(*e) - .map(|(_, disabled)| !disabled) - .unwrap_or(false) + .is_ok_and(|(_, disabled)| !disabled) }) .collect(); if !row_cycle.is_empty() @@ -466,12 +465,7 @@ fn handle_focus_keys( // Stable sort by `Focusable::order` so explicit priorities (e.g. // HUD spawn-order: 0..5) drive the cycle. The pre-sort by entity // index above is the tiebreaker for entries sharing an `order`. - group.sort_by_key(|e| { - focusables - .get(*e) - .map(|(f, _)| f.order) - .unwrap_or(i32::MAX) - }); + group.sort_by_key(|e| focusables.get(*e).map_or(i32::MAX, |(f, _)| f.order)); if group.is_empty() { // Still consume the key so the card-selection plugin doesn't diff --git a/solitaire_engine/src/ui_modal.rs b/solitaire_engine/src/ui_modal.rs index ff01f28..05ac682 100644 --- a/solitaire_engine/src/ui_modal.rs +++ b/solitaire_engine/src/ui_modal.rs @@ -409,8 +409,7 @@ pub fn apply_modal_enter_speed( ) { let speed = settings .as_ref() - .map(|s| s.0.animation_speed) - .unwrap_or(AnimSpeed::Normal); + .map_or(AnimSpeed::Normal, |s| s.0.animation_speed); for mut entering in &mut q { entering.duration = scaled_duration(MOTION_MODAL_SECS, speed); } diff --git a/solitaire_engine/src/weekly_goals_plugin.rs b/solitaire_engine/src/weekly_goals_plugin.rs index 1a0d6b4..b7dd006 100644 --- a/solitaire_engine/src/weekly_goals_plugin.rs +++ b/solitaire_engine/src/weekly_goals_plugin.rs @@ -121,9 +121,7 @@ fn evaluate_weekly_goals( /// Resolve a goal id to its description (used for toasts). pub fn weekly_goal_description(id: &str) -> String { - weekly_goal_by_id(id) - .map(|g| g.description.to_string()) - .unwrap_or_else(|| id.to_string()) + weekly_goal_by_id(id).map_or_else(|| id.to_string(), |g| g.description.to_string()) } #[cfg(test)] diff --git a/solitaire_server/src/error.rs b/solitaire_server/src/error.rs index 70e7f1e..22aeb04 100644 --- a/solitaire_server/src/error.rs +++ b/solitaire_server/src/error.rs @@ -51,8 +51,9 @@ pub enum AppError { impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, message) = match &self { - AppError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()), - AppError::InvalidCredentials => (StatusCode::UNAUTHORIZED, self.to_string()), + AppError::Unauthorized | AppError::InvalidCredentials => { + (StatusCode::UNAUTHORIZED, self.to_string()) + } AppError::UsernameTaken => (StatusCode::CONFLICT, self.to_string()), AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()), AppError::Database(e) => {