diff --git a/Cargo.toml b/Cargo.toml index 17337df..d8f1e93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,21 @@ version = "0.1.0" license = "MIT" rust-version = "1.95" +# Pedantic correctness lints applied across every member crate via +# `[lints] workspace = true`. `unsafe_code` is "deny" rather than "forbid" +# so the three Android JNI FFI modules can opt back in with a scoped +# `#![allow(unsafe_code)]` — `forbid` cannot be locally overridden, which +# would break the Android build. Pure crates (core, sync) carry no `unsafe` +# and so remain effectively forbidden in practice. +[workspace.lints.rust] +unsafe_code = "deny" +single_use_lifetimes = "warn" +trivial_casts = "warn" +unused_lifetimes = "warn" +unused_qualifications = "warn" +variant_size_differences = "warn" +unexpected_cfgs = "warn" + [workspace.dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/solitaire_app/Cargo.toml b/solitaire_app/Cargo.toml index 2556920..124a5ea 100644 --- a/solitaire_app/Cargo.toml +++ b/solitaire_app/Cargo.toml @@ -99,3 +99,6 @@ icon = "@mipmap/ic_launcher" # in portrait orientation. Remove (or add a landscape layout) before # enabling auto-rotate. orientation = "portrait" + +[lints] +workspace = true diff --git a/solitaire_app/src/lib.rs b/solitaire_app/src/lib.rs index 0082814..f0433b6 100644 --- a/solitaire_app/src/lib.rs +++ b/solitaire_app/src/lib.rs @@ -144,7 +144,7 @@ fn build_app_with_settings( // Android windows always fill the screen; max_width/max_height // default to 0.0, which panics Bevy's clamp when min > max. #[cfg(not(target_os = "android"))] - resize_constraints: bevy::window::WindowResizeConstraints { + resize_constraints: WindowResizeConstraints { min_width: 800.0, min_height: 600.0, ..default() @@ -166,7 +166,7 @@ fn build_app_with_settings( // default makes it walk *out* of the APK's assets root and // all loads fail silently — which is what produced the // solid-red card-back fallback in the v0.22.3 screenshot. - .set(bevy::asset::AssetPlugin { + .set(AssetPlugin { #[cfg(not(target_os = "android"))] file_path: "../assets".to_string(), ..default() diff --git a/solitaire_assetgen/Cargo.toml b/solitaire_assetgen/Cargo.toml index f7c2e85..b953e5f 100644 --- a/solitaire_assetgen/Cargo.toml +++ b/solitaire_assetgen/Cargo.toml @@ -30,3 +30,6 @@ path = "src/bin/gen_seeds.rs" [[bin]] name = "gen_difficulty_seeds" path = "src/bin/gen_difficulty_seeds.rs" + +[lints] +workspace = true diff --git a/solitaire_core/Cargo.toml b/solitaire_core/Cargo.toml index 555c1e8..35a7cb3 100644 --- a/solitaire_core/Cargo.toml +++ b/solitaire_core/Cargo.toml @@ -16,3 +16,6 @@ serde = { workspace = true } thiserror = { workspace = true } klondike = { workspace = true } card_game = { workspace = true } + +[lints] +workspace = true diff --git a/solitaire_data/Cargo.toml b/solitaire_data/Cargo.toml index 89342e7..f1836dc 100644 --- a/solitaire_data/Cargo.toml +++ b/solitaire_data/Cargo.toml @@ -47,3 +47,6 @@ sqlx = { workspace = true } jsonwebtoken = { workspace = true } uuid = { workspace = true } chrono = { workspace = true } + +[lints] +workspace = true diff --git a/solitaire_data/src/android_keystore.rs b/solitaire_data/src/android_keystore.rs index 05de32a..f9e49a0 100644 --- a/solitaire_data/src/android_keystore.rs +++ b/solitaire_data/src/android_keystore.rs @@ -1,3 +1,8 @@ +// JNI FFI requires `unsafe` to reconstruct `JavaVM` / `JByteArray` handles +// from raw pointers handed over by the Android runtime. Scoped to this +// module so the rest of the workspace stays `deny(unsafe_code)`. +#![allow(unsafe_code)] + /// Android Keystore token storage via JNI. /// /// Tokens are serialised to JSON, encrypted with AES-256/GCM/NoPadding using a diff --git a/solitaire_data/src/lib.rs b/solitaire_data/src/lib.rs index 415597e..4ae1554 100644 --- a/solitaire_data/src/lib.rs +++ b/solitaire_data/src/lib.rs @@ -58,7 +58,7 @@ pub trait SyncProvider: Send + Sync { /// so backends without a server (e.g. `LocalOnlyProvider`) are /// silently no-op'd by the engine's push-on-win system, matching /// the same pattern `pull` / `push` follow. - async fn push_replay(&self, _replay: &crate::replay::Replay) -> Result { + async fn push_replay(&self, _replay: &Replay) -> Result { Err(SyncError::UnsupportedPlatform) } } @@ -94,7 +94,7 @@ impl SyncProvider for Box { async fn delete_account(&self) -> Result<(), SyncError> { (**self).delete_account().await } - async fn push_replay(&self, replay: &crate::replay::Replay) -> Result { + async fn push_replay(&self, replay: &Replay) -> Result { (**self).push_replay(replay).await } } diff --git a/solitaire_engine/Cargo.toml b/solitaire_engine/Cargo.toml index 306c7e0..cf0ed78 100644 --- a/solitaire_engine/Cargo.toml +++ b/solitaire_engine/Cargo.toml @@ -52,3 +52,6 @@ web-sys = { version = "0.3", features = ["Storage", "Window"] } async-trait = { workspace = true } tempfile = { workspace = true } solitaire_core = { workspace = true, features = ["test-support"] } + +[lints] +workspace = true diff --git a/solitaire_engine/src/achievement_plugin.rs b/solitaire_engine/src/achievement_plugin.rs index 7c7a882..00151ed 100644 --- a/solitaire_engine/src/achievement_plugin.rs +++ b/solitaire_engine/src/achievement_plugin.rs @@ -116,7 +116,7 @@ impl Plugin for AchievementPlugin { // achievements-scroll system also runs cleanly under // `MinimalPlugins` in tests. .add_message::() - .add_message::() + .add_message::() // Run after GameMutation (so GameWonEvent is available), after // StatsUpdate (so stats reflect this win), and after ProgressUpdate // (so daily_challenge_streak is up to date for daily_devotee). @@ -671,7 +671,7 @@ mod tests { .add_plugins(AchievementPlugin::headless()); // StatsPlugin's UI toggle system reads ButtonInput; under // MinimalPlugins it isn't auto-registered. - app.init_resource::>(); + app.init_resource::>(); app.update(); app } @@ -819,7 +819,7 @@ mod tests { app.world_mut() .resource_mut::() .0 - .set_test_draw_mode(solitaire_core::DrawStockConfig::DrawThree); + .set_test_draw_mode(DrawStockConfig::DrawThree); app.world_mut().write_message(GameWonEvent { score: 500, @@ -868,7 +868,7 @@ mod tests { app.world_mut() .resource_mut::() .0 - .set_test_draw_mode(solitaire_core::DrawStockConfig::DrawThree); + .set_test_draw_mode(DrawStockConfig::DrawThree); app.world_mut().write_message(GameWonEvent { score: 500, @@ -912,7 +912,7 @@ mod tests { // Put the active game in Zen mode. evaluate_on_win reads // GameStateResource.mode directly to populate last_win_is_zen. app.world_mut().resource_mut::().0.mode = - solitaire_core::game_state::GameMode::Zen; + GameMode::Zen; app.world_mut().write_message(GameWonEvent { score: 0, @@ -946,7 +946,7 @@ mod tests { // Default GameMode is Classic; assert and rely on it. assert_eq!( app.world().resource::().0.mode, - solitaire_core::game_state::GameMode::Classic + GameMode::Classic ); app.world_mut().write_message(GameWonEvent { @@ -1250,7 +1250,7 @@ mod tests { .add_plugins(crate::progress_plugin::ProgressPlugin::headless()) .add_plugins(crate::settings_plugin::SettingsPlugin::headless()) .add_plugins(AchievementPlugin::headless()); - app.init_resource::>(); + app.init_resource::>(); app.update(); app } diff --git a/solitaire_engine/src/android_clipboard.rs b/solitaire_engine/src/android_clipboard.rs index eb94b87..d829cfe 100644 --- a/solitaire_engine/src/android_clipboard.rs +++ b/solitaire_engine/src/android_clipboard.rs @@ -1,3 +1,8 @@ +// JNI FFI requires `unsafe` to reconstruct `JavaVM` / `JObject` handles from +// raw pointers handed over by the Android runtime. Scoped to this module so +// the rest of the workspace stays `deny(unsafe_code)`. +#![allow(unsafe_code)] + /// Android clipboard bridge via JNI. /// /// Writes text to the system clipboard by calling into `ClipboardManager` diff --git a/solitaire_engine/src/animation_plugin.rs b/solitaire_engine/src/animation_plugin.rs index 87d6d68..eb31866 100644 --- a/solitaire_engine/src/animation_plugin.rs +++ b/solitaire_engine/src/animation_plugin.rs @@ -354,7 +354,7 @@ fn handle_win_cascade( end: target.truncate(), elapsed: 0.0, duration, - curve: crate::card_animation::MotionCurve::Expressive, + curve: MotionCurve::Expressive, delay: i as f32 * step, start_z: start.z, end_z: target.z, diff --git a/solitaire_engine/src/assets/sources.rs b/solitaire_engine/src/assets/sources.rs index 37ffeb2..051991d 100644 --- a/solitaire_engine/src/assets/sources.rs +++ b/solitaire_engine/src/assets/sources.rs @@ -115,6 +115,10 @@ macro_rules! embed_classic_svg { } /// Every Dark-theme SVG file bundled into the binary. +// The `as &[u8]` in `embed_dark_svg!` coerces each fixed-size +// `&[u8; N]` (N varies per file) to a uniform `&[u8]` so the tuples fit +// this array type. The cast is load-bearing, not trivial. +#[allow(trivial_casts)] const DARK_THEME_SVGS: &[(&str, &[u8])] = &[ embed_dark_svg!("back.svg"), embed_dark_svg!("clubs_ace.svg"), @@ -172,6 +176,8 @@ const DARK_THEME_SVGS: &[(&str, &[u8])] = &[ ]; /// Every Classic-theme SVG file bundled into the binary. +// See `DARK_THEME_SVGS`: the `as &[u8]` cast is load-bearing. +#[allow(trivial_casts)] const CLASSIC_THEME_SVGS: &[(&str, &[u8])] = &[ embed_classic_svg!("back.svg"), embed_classic_svg!("clubs_ace.svg"), diff --git a/solitaire_engine/src/assets/svg_loader.rs b/solitaire_engine/src/assets/svg_loader.rs index cb912a3..b04791d 100644 --- a/solitaire_engine/src/assets/svg_loader.rs +++ b/solitaire_engine/src/assets/svg_loader.rs @@ -192,7 +192,7 @@ fn shared_fontdb() -> Arc { fn bundled_font_resolver() -> usvg::FontResolver<'static> { use usvg::FontResolver; - usvg::FontResolver { + FontResolver { select_font: Box::new(|_font, db| db.faces().next().map(|face| face.id)), select_fallback: FontResolver::default_fallback_selector(), } @@ -282,7 +282,7 @@ mod tests { /// tightens. #[test] fn settings_satisfies_loader_bounds() { - fn assert_loader_settings() {} + fn assert_loader_settings() {} assert_loader_settings::(); } } diff --git a/solitaire_engine/src/auto_complete_plugin.rs b/solitaire_engine/src/auto_complete_plugin.rs index 68df902..b73efae 100644 --- a/solitaire_engine/src/auto_complete_plugin.rs +++ b/solitaire_engine/src/auto_complete_plugin.rs @@ -177,7 +177,7 @@ mod tests { .add_plugins(GamePlugin) .add_plugins(TablePlugin) .add_plugins(AutoCompletePlugin); - app.init_resource::>(); + app.init_resource::>(); app.update(); app } diff --git a/solitaire_engine/src/avatar_plugin.rs b/solitaire_engine/src/avatar_plugin.rs index e380c06..9aee3ab 100644 --- a/solitaire_engine/src/avatar_plugin.rs +++ b/solitaire_engine/src/avatar_plugin.rs @@ -36,7 +36,7 @@ pub struct AvatarFetchEvent { pub url: String, } -impl bevy::prelude::Message for AvatarFetchEvent {} +impl Message for AvatarFetchEvent {} /// In-flight avatar download task. Returns the raw image bytes on success, /// or `None` on any network / decode error. diff --git a/solitaire_engine/src/card_animation/interaction.rs b/solitaire_engine/src/card_animation/interaction.rs index 7e18936..1827758 100644 --- a/solitaire_engine/src/card_animation/interaction.rs +++ b/solitaire_engine/src/card_animation/interaction.rs @@ -73,7 +73,7 @@ pub struct HoverState { #[derive(Debug, Clone)] pub enum BufferedInput { Move { - from: crate::events::MoveRequestEvent, + from: MoveRequestEvent, }, Draw, Undo, diff --git a/solitaire_engine/src/card_plugin.rs b/solitaire_engine/src/card_plugin.rs index ab89cfd..f371b95 100644 --- a/solitaire_engine/src/card_plugin.rs +++ b/solitaire_engine/src/card_plugin.rs @@ -483,7 +483,7 @@ impl Plugin for CardPlugin { update_stock_empty_indicator.after(GameMutation), update_stock_count_badge .after(GameMutation) - .run_if(resource_changed::), + .run_if(resource_changed::), collect_resize_events.after(LayoutSystem::UpdateOnResize), snap_cards_on_window_resize.after(collect_resize_events), ), @@ -2431,7 +2431,7 @@ fn update_tableau_fan_frac( .into_iter() .map(|tableau| { game.0 - .pile(solitaire_core::KlondikePile::Tableau(tableau)) + .pile(KlondikePile::Tableau(tableau)) .into_iter() .filter(|(_, face_up)| *face_up) .count() @@ -2566,7 +2566,7 @@ mod tests { #[test] fn card_positions_includes_all_52_cards_at_game_start() { // At game start waste is empty, so all 52 cards are across stock + tableau. - let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne); + let g = GameState::new(42, DrawStockConfig::DrawOne); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); assert_eq!(positions.len(), 52); @@ -2580,7 +2580,7 @@ mod tests { for _ in 0..3 { let _ = g.draw(); } - let waste_ids: std::collections::HashSet = + let waste_ids: HashSet = g.waste_cards().iter().map(|c| c.0.clone()).collect(); assert_eq!(waste_ids.len(), 3); @@ -2624,7 +2624,7 @@ mod tests { "need at least 3 waste cards for this test" ); - let waste_ids: std::collections::HashSet = + let waste_ids: HashSet = waste_pile.iter().map(|c| c.0.clone()).collect(); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); @@ -2677,7 +2677,7 @@ mod tests { let count = waste_pile.len(); assert!(count >= 2, "need at least 2 waste cards"); - let waste_ids: std::collections::HashSet = + let waste_ids: HashSet = waste_pile.iter().map(|c| c.0.clone()).collect(); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); @@ -2715,7 +2715,7 @@ mod tests { for _ in 0..3 { let _ = g.draw(); } - let waste_ids: std::collections::HashSet = + let waste_ids: HashSet = g.waste_cards().iter().map(|c| c.0.clone()).collect(); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); @@ -2740,7 +2740,7 @@ mod tests { #[test] fn card_positions_tableau_cards_are_fanned_downward() { - let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne); + let g = GameState::new(42, DrawStockConfig::DrawOne); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); @@ -3083,7 +3083,7 @@ mod tests { #[test] fn facedown_cards_use_tighter_fan_than_uniform_faceup_fan() { - let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne); + let g = GameState::new(42, DrawStockConfig::DrawOne); let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); @@ -3153,7 +3153,7 @@ mod tests { fn fire_window_resize(app: &mut App, width: f32, height: f32) { // Any Entity will do — the snap system reads only width/height. - let window = bevy::ecs::entity::Entity::from_raw_u32(0) + let window = Entity::from_raw_u32(0) .expect("Entity::from_raw_u32(0) is a valid placeholder"); app.world_mut().write_message(WindowResized { window, @@ -3171,9 +3171,9 @@ mod tests { // entity IDs must remain alive. let mut app = app(); - let labels_before: std::collections::HashSet = app + let labels_before: HashSet = app .world_mut() - .query_filtered::>() + .query_filtered::>() .iter(app.world()) .collect(); assert!( @@ -3184,9 +3184,9 @@ mod tests { fire_window_resize(&mut app, 1024.0, 768.0); advance_past_resize_throttle(&mut app); - let labels_after: std::collections::HashSet = app + let labels_after: HashSet = app .world_mut() - .query_filtered::>() + .query_filtered::>() .iter(app.world()) .collect(); @@ -3336,9 +3336,9 @@ mod tests { // Each shadow's parent must be a CardEntity, so the child relation // is wired correctly. - let cards: HashSet = app + let cards: HashSet = app .world_mut() - .query_filtered::>() + .query_filtered::>() .iter(app.world()) .collect(); let mut q = app @@ -3423,7 +3423,7 @@ mod tests { let card_entity = { let mut q = app .world_mut() - .query::<(bevy::prelude::Entity, &CardEntity)>(); + .query::<(Entity, &CardEntity)>(); q.iter(app.world()) .find(|(_, c)| c.card == *card) .map(|(e, _)| e) @@ -3533,7 +3533,7 @@ mod tests { #[test] fn stock_card_count_helper_reads_zero_for_empty_stock() { - let g = GameState::new(42, solitaire_core::DrawStockConfig::DrawOne); + let g = GameState::new(42, DrawStockConfig::DrawOne); let mut g_empty_stock = g.clone(); g_empty_stock.set_test_stock_cards(Vec::new()); assert_eq!(stock_card_count(&g_empty_stock), 0); @@ -3553,9 +3553,9 @@ mod tests { // Allocate five different strong handles by passing each a // distinct dummy `Image`. We never render these; we only // compare ids. - let mut images = bevy::asset::Assets::::default(); - let backs: [Handle; 5] = - std::array::from_fn(|_| images.add(bevy::image::Image::default())); + let mut images = Assets::::default(); + let backs: [Handle; 5] = + std::array::from_fn(|_| images.add(Image::default())); CardImageSet { faces: std::array::from_fn(|_| std::array::from_fn(|_| Handle::default())), backs, @@ -3569,8 +3569,8 @@ mod tests { // card must render with the theme's back regardless of which // legacy back the player picked in Settings. let mut set = image_set_with_distinct_back_handles(); - let mut images = bevy::asset::Assets::::default(); - let theme_back: Handle = images.add(bevy::image::Image::default()); + let mut images = Assets::::default(); + let theme_back: Handle = images.add(Image::default()); set.theme_back = Some(theme_back.clone()); let face_down = make_card(Suit::Spades, Rank::Ace); @@ -3634,8 +3634,8 @@ mod tests { use std::collections::HashMap; let mut set = image_set_with_distinct_back_handles(); - let mut images = bevy::asset::Assets::::default(); - let theme_back: Handle = images.add(bevy::image::Image::default()); + let mut images = Assets::::default(); + let theme_back: Handle = images.add(Image::default()); let theme = CardTheme { meta: ThemeMeta { @@ -3645,7 +3645,7 @@ mod tests { version: "0".into(), card_aspect: (2, 3), }, - faces: HashMap::>::new(), + faces: HashMap::>::new(), back: theme_back.clone(), }; @@ -3813,7 +3813,7 @@ mod tests { let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); - let waste_ids: std::collections::HashSet = + let waste_ids: HashSet = g.waste_cards().iter().map(|c| c.0.clone()).collect(); let mut waste_zs: Vec = positions @@ -3863,7 +3863,7 @@ mod tests { let stock_x = layout.pile_positions[&KlondikePile::Stock].x; - let waste_ids: std::collections::HashSet = + let waste_ids: HashSet = g.waste_cards().iter().map(|c| c.0.clone()).collect(); let mut waste_positions: Vec<_> = card_positions(&g, &layout) @@ -3893,7 +3893,7 @@ mod tests { let layout = crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true); let positions = card_positions(&g, &layout); - let waste_ids: std::collections::HashSet = + let waste_ids: HashSet = g.waste_cards().iter().map(|c| c.0.clone()).collect(); let mut waste_zs: Vec = positions diff --git a/solitaire_engine/src/cursor_plugin.rs b/solitaire_engine/src/cursor_plugin.rs index 8bd4987..d475006 100644 --- a/solitaire_engine/src/cursor_plugin.rs +++ b/solitaire_engine/src/cursor_plugin.rs @@ -80,7 +80,7 @@ impl Plugin for CursorPlugin { Update, ( update_cursor_icon, - update_drop_highlights.run_if(resource_changed::), + update_drop_highlights.run_if(resource_changed::), update_drop_target_overlays, ), ); diff --git a/solitaire_engine/src/feedback_anim_plugin.rs b/solitaire_engine/src/feedback_anim_plugin.rs index 5ef2689..b51f28b 100644 --- a/solitaire_engine/src/feedback_anim_plugin.rs +++ b/solitaire_engine/src/feedback_anim_plugin.rs @@ -639,7 +639,7 @@ fn lerp_color(from: Color, to: Color, t: f32) -> Color { fn pile_cards( game: &solitaire_core::game_state::GameState, pile: &KlondikePile, -) -> Vec<(solitaire_core::Card, bool)> { +) -> Vec<(Card, bool)> { match pile { KlondikePile::Stock => game.waste_cards(), _ => game.pile(*pile), diff --git a/solitaire_engine/src/game_plugin.rs b/solitaire_engine/src/game_plugin.rs index 3ccdeab..19e20ed 100644 --- a/solitaire_engine/src/game_plugin.rs +++ b/solitaire_engine/src/game_plugin.rs @@ -201,7 +201,7 @@ impl Plugin for GamePlugin { .add_message::() .add_message::() .add_message::() - .add_message::() + .add_message::() .add_message::() .add_message::() .add_message::() @@ -530,7 +530,7 @@ fn handle_new_game( // hides that information and reads naturally as "dealt from the // deck." Skipped when LayoutResource isn't present (headless tests). if let Some(layout) = layout.as_ref() - && let Some(stock) = layout.0.pile_positions.get(&solitaire_core::KlondikePile::Stock) + && let Some(stock) = layout.0.pile_positions.get(&KlondikePile::Stock) { for mut tx in &mut card_transforms { tx.translation.x = stock.x; @@ -868,7 +868,7 @@ fn handle_move( mut game: ResMut, mut changed: MessageWriter, mut won: MessageWriter, - mut flipped: MessageWriter, + mut flipped: MessageWriter, mut foundation_done: MessageWriter, mut recording: ResMut, path: Option>, @@ -909,7 +909,7 @@ fn handle_move( .last() .is_some_and(|c| c.0 == fcard && c.1) { - flipped.write(crate::events::CardFlippedEvent(fcard)); + flipped.write(CardFlippedEvent(fcard)); } // If this move landed on a foundation pile and that pile is // now complete (Ace → King, 13 cards), fire the per-suit @@ -1530,7 +1530,7 @@ mod tests { // Persistence tests // ----------------------------------------------------------------------- - fn tmp_gs_path(name: &str) -> std::path::PathBuf { + fn tmp_gs_path(name: &str) -> PathBuf { std::env::temp_dir().join(format!("engine_test_gs_{name}.json")) } @@ -1684,7 +1684,7 @@ mod tests { let events = app .world() - .resource::>(); + .resource::>(); let mut cursor = events.get_cursor(); let fired: Vec<_> = cursor.read(events).collect(); assert!( diff --git a/solitaire_engine/src/help_plugin.rs b/solitaire_engine/src/help_plugin.rs index 6640561..a22950a 100644 --- a/solitaire_engine/src/help_plugin.rs +++ b/solitaire_engine/src/help_plugin.rs @@ -51,7 +51,7 @@ impl Plugin for HelpPlugin { // plugin under `DefaultPlugins`; register them explicitly so // scroll systems run cleanly under `MinimalPlugins` in tests. .add_message::() - .add_message::() + .add_message::() .add_systems( Update, ( diff --git a/solitaire_engine/src/home_plugin.rs b/solitaire_engine/src/home_plugin.rs index fd8d89f..97e151c 100644 --- a/solitaire_engine/src/home_plugin.rs +++ b/solitaire_engine/src/home_plugin.rs @@ -1878,7 +1878,7 @@ mod tests { let states: Vec<(HomeMode, bool)> = app .world_mut() - .query::<(&HomeModeCard, bevy::ecs::query::Has)>() + .query::<(&HomeModeCard, Has)>() .iter(app.world()) .map(|(c, d)| (c.0, d)) .collect(); diff --git a/solitaire_engine/src/input_plugin.rs b/solitaire_engine/src/input_plugin.rs index f2ea1d9..f1ee247 100644 --- a/solitaire_engine/src/input_plugin.rs +++ b/solitaire_engine/src/input_plugin.rs @@ -2428,8 +2428,8 @@ mod tests { app.init_resource::(); app.init_resource::(); app.init_resource::>(); - app.insert_resource(crate::layout::LayoutResource( - crate::layout::compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true), + app.insert_resource(LayoutResource( + compute_layout(Vec2::new(1280.0, 800.0), 0.0, 0.0, true), )); app.insert_resource(GameStateResource(GameState::new(42, DrawStockConfig::DrawOne))); app.add_systems(Update, handle_keyboard_hint); diff --git a/solitaire_engine/src/layout.rs b/solitaire_engine/src/layout.rs index 8ca000e..4863e29 100644 --- a/solitaire_engine/src/layout.rs +++ b/solitaire_engine/src/layout.rs @@ -745,7 +745,7 @@ mod tests { ); // The HUD band top clearance (distance from window top to card top) // must match as well — this is the quantity directly visible in Bug 2. - let card_top = |layout: &super::Layout| { + let card_top = |layout: &Layout| { layout.pile_positions[&KlondikePile::Stock].y + layout.card_size.y / 2.0 }; assert!( diff --git a/solitaire_engine/src/leaderboard_plugin.rs b/solitaire_engine/src/leaderboard_plugin.rs index d4cb1ec..503822d 100644 --- a/solitaire_engine/src/leaderboard_plugin.rs +++ b/solitaire_engine/src/leaderboard_plugin.rs @@ -862,7 +862,7 @@ fn handle_display_name_confirm( .leaderboard_display_name .clone() .unwrap_or_else(|| { - if let solitaire_data::settings::SyncBackend::SolitaireServer { + if let SyncBackend::SolitaireServer { ref username, .. } = settings.0.sync_backend @@ -1091,7 +1091,7 @@ mod tests { .add_plugins(crate::achievement_plugin::AchievementPlugin::headless()) .add_plugins(SyncPlugin::new(NoOpProvider)) .add_plugins(LeaderboardPlugin); - app.init_resource::>(); + app.init_resource::>(); app.update(); app } diff --git a/solitaire_engine/src/replay_overlay/tests.rs b/solitaire_engine/src/replay_overlay/tests.rs index 8364d5b..fbd18e4 100644 --- a/solitaire_engine/src/replay_overlay/tests.rs +++ b/solitaire_engine/src/replay_overlay/tests.rs @@ -2312,8 +2312,8 @@ fn format_suit_glyph_all_suits() { fn format_foundations_row_empty_board() { let game = solitaire_core::game_state::GameState::new_with_mode( 42, - solitaire_core::DrawStockConfig::DrawOne, - solitaire_core::game_state::GameMode::Classic, + DrawStockConfig::DrawOne, + GameMode::Classic, ); assert_eq!(format_foundations_row(&game), "F: -- -- -- --"); } @@ -2324,8 +2324,8 @@ fn format_foundations_row_empty_board() { fn format_stock_waste_row_initial_state() { let game = solitaire_core::game_state::GameState::new_with_mode( 42, - solitaire_core::DrawStockConfig::DrawOne, - solitaire_core::game_state::GameMode::Classic, + DrawStockConfig::DrawOne, + GameMode::Classic, ); let text = format_stock_waste_row(&game); assert!( diff --git a/solitaire_engine/src/safe_area.rs b/solitaire_engine/src/safe_area.rs index b499bb3..9ee383d 100644 --- a/solitaire_engine/src/safe_area.rs +++ b/solitaire_engine/src/safe_area.rs @@ -1,5 +1,10 @@ //! Safe-area insets. //! +// JNI FFI (Android only) requires `unsafe` to reconstruct `JavaVM` / +// `JObject` handles from raw pointers handed over by the runtime. Scoped to +// this module so the rest of the workspace stays `deny(unsafe_code)`. +#![allow(unsafe_code)] +//! //! Reports the OS-reserved regions around the playable surface (status //! bar at the top, gesture / navigation bar at the bottom on Android, //! display cutouts, etc.) so UI anchored to a screen edge can avoid diff --git a/solitaire_engine/src/selection_plugin.rs b/solitaire_engine/src/selection_plugin.rs index c87fdfe..ee275ec 100644 --- a/solitaire_engine/src/selection_plugin.rs +++ b/solitaire_engine/src/selection_plugin.rs @@ -163,7 +163,7 @@ impl Plugin for SelectionPlugin { update_selection_highlight.after(GameMutation).run_if( resource_changed:: .or(resource_changed::) - .or(resource_changed::), + .or(resource_changed::), ), ), ); @@ -534,7 +534,7 @@ fn handle_selection_keys( /// destination after a lift. Players who want a different column simply /// press the right-arrow key once or twice. pub(crate) fn legal_destinations_for( - _bottom: &solitaire_core::Card, + _bottom: &Card, source: &KlondikePile, game: &GameState, stack_count: usize, @@ -579,7 +579,7 @@ pub(crate) fn legal_destinations_for( /// Walks backwards from the last element and stops at the first face-down card /// (or when the slice is exhausted). Returns at least `1` when the top card is /// face-up; returns `0` for an empty slice or when the top card is face-down. -fn face_up_run_len(cards: &[(solitaire_core::Card, bool)]) -> usize { +fn face_up_run_len(cards: &[(Card, bool)]) -> usize { let mut count = 0; for (_, face_up) in cards.iter().rev() { if *face_up { @@ -598,8 +598,8 @@ fn face_up_run_len(cards: &[(solitaire_core::Card, bool)]) -> usize { /// handler can attempt a foundation move first and fall through to a /// multi-card stack move rather than accepting a single-card tableau move. fn try_foundation_dest( - card: &solitaire_core::Card, - game: &solitaire_core::game_state::GameState, + card: &Card, + game: &GameState, ) -> Option { let source = game.pile_containing_card(card.clone())?; for foundation in [ diff --git a/solitaire_engine/src/settings_plugin.rs b/solitaire_engine/src/settings_plugin.rs index 0688680..1a87e5d 100644 --- a/solitaire_engine/src/settings_plugin.rs +++ b/solitaire_engine/src/settings_plugin.rs @@ -379,8 +379,8 @@ impl Plugin for SettingsPlugin { .add_message::() .add_message::() .add_message::() - .add_message::() - .add_message::() + .add_message::() + .add_message::() // `WindowResized` / `WindowMoved` are real Bevy window events // and emitted by the windowing backend under `DefaultPlugins`, // but we register them explicitly here so the geometry watcher @@ -2662,7 +2662,7 @@ fn handle_scan_themes( let themes_dir = user_theme_dir(); - let zips: Vec = match std::fs::read_dir(&themes_dir) { + let zips: Vec = match std::fs::read_dir(&themes_dir) { Ok(entries) => entries .flatten() .map(|e| e.path()) @@ -3019,7 +3019,7 @@ mod tests { unit: MouseScrollUnit::Line, x: 0.0, y: -3.0, - window: bevy::ecs::entity::Entity::PLACEHOLDER, + window: Entity::PLACEHOLDER, }); app.update(); // ScrollPosition must remain at 0.0 — panel was closed. @@ -3052,7 +3052,7 @@ mod tests { unit: MouseScrollUnit::Line, x: 0.0, y: -2.0, - window: bevy::ecs::entity::Entity::PLACEHOLDER, + window: Entity::PLACEHOLDER, }); app.update(); let offset = app @@ -3364,7 +3364,7 @@ mod tests { fn fire_resize(app: &mut App, width: f32, height: f32) { app.world_mut().write_message(WindowResized { - window: bevy::ecs::entity::Entity::PLACEHOLDER, + window: Entity::PLACEHOLDER, width, height, }); @@ -3372,7 +3372,7 @@ mod tests { fn fire_move(app: &mut App, x: i32, y: i32) { app.world_mut().write_message(WindowMoved { - window: bevy::ecs::entity::Entity::PLACEHOLDER, + window: Entity::PLACEHOLDER, position: IVec2::new(x, y), }); } @@ -3494,7 +3494,7 @@ mod tests { unit: MouseScrollUnit::Line, x: 0.0, y: 5.0, - window: bevy::ecs::entity::Entity::PLACEHOLDER, + window: Entity::PLACEHOLDER, }); app.update(); let offset = app diff --git a/solitaire_engine/src/splash_plugin.rs b/solitaire_engine/src/splash_plugin.rs index ccc1b08..d290102 100644 --- a/solitaire_engine/src/splash_plugin.rs +++ b/solitaire_engine/src/splash_plugin.rs @@ -1010,8 +1010,8 @@ mod tests { use solitaire_data::Settings; let mut app = App::new(); app.add_plugins(MinimalPlugins) - .add_plugins(bevy::asset::AssetPlugin::default()) - .init_asset::() + .add_plugins(AssetPlugin::default()) + .init_asset::() .add_plugins(SplashPlugin); app.init_resource::>(); app.init_resource::>(); diff --git a/solitaire_engine/src/stats_plugin.rs b/solitaire_engine/src/stats_plugin.rs index edbc3c9..4920c96 100644 --- a/solitaire_engine/src/stats_plugin.rs +++ b/solitaire_engine/src/stats_plugin.rs @@ -203,7 +203,7 @@ impl Plugin for StatsPlugin { // `DefaultPlugins`; register it explicitly so the stats-scroll // system also runs cleanly under `MinimalPlugins` in tests. .add_message::() - .add_message::() + .add_message::() // record_abandoned must read `move_count` BEFORE handle_new_game // clobbers it with a fresh game. These are NOT in StatsUpdate because // StatsUpdate (as a set) is ordered after GameMutation by external @@ -464,7 +464,7 @@ fn repaint_replay_selector_detail( /// Pure helper: render the detail line for the selected replay. Returns /// `"{duration} win on {date}"` plus a `" \u{2022} Shareable"` badge /// when a share URL is present. Empty when the history slice is empty. -pub fn replay_selector_detail(replays: &[solitaire_data::Replay], index: usize) -> String { +pub fn replay_selector_detail(replays: &[Replay], index: usize) -> String { let Some(r) = replays.get(index.min(replays.len().saturating_sub(1))) else { return String::new(); }; @@ -1325,7 +1325,7 @@ mod tests { fn draw_three_win_increments_draw_three_wins_only() { let mut app = headless_app(); app.world_mut() - .resource_mut::() + .resource_mut::() .0 .set_test_draw_mode(solitaire_core::DrawStockConfig::DrawThree); @@ -1371,7 +1371,7 @@ mod tests { let mut app = headless_app(); app.world_mut() - .resource_mut::() + .resource_mut::() .0 .set_test_move_count(3); @@ -1501,7 +1501,7 @@ mod tests { fn zen_win_event_updates_zen_best_score_only() { let mut app = headless_app(); app.world_mut() - .resource_mut::() + .resource_mut::() .0 .mode = solitaire_core::game_state::GameMode::Zen; @@ -1697,7 +1697,7 @@ mod tests { stats.0.win_streak_current = 3; } app.world_mut() - .resource_mut::() + .resource_mut::() .0 .set_test_move_count(1); @@ -1723,7 +1723,7 @@ mod tests { stats.0.win_streak_current = 1; } app.world_mut() - .resource_mut::() + .resource_mut::() .0 .set_test_move_count(1); @@ -1948,9 +1948,9 @@ mod tests { /// /// Uses a fixed seed, DrawOne mode, Classic game, 2026-05-08 date. /// `time_seconds` and `share_url` are the only varying fields across tests. - fn make_test_replay(time_seconds: u64, share_url: Option) -> solitaire_data::Replay { + fn make_test_replay(time_seconds: u64, share_url: Option) -> Replay { let date = chrono::NaiveDate::from_ymd_opt(2026, 5, 8).expect("valid date"); - let mut r = solitaire_data::Replay::new( + let mut r = Replay::new( 1, solitaire_core::DrawStockConfig::DrawOne, solitaire_core::game_state::GameMode::Classic, diff --git a/solitaire_engine/src/sync_plugin.rs b/solitaire_engine/src/sync_plugin.rs index 68a31b9..271a3d7 100644 --- a/solitaire_engine/src/sync_plugin.rs +++ b/solitaire_engine/src/sync_plugin.rs @@ -496,7 +496,7 @@ mod tests { .add_plugins(crate::achievement_plugin::AchievementPlugin::headless()) .add_plugins(SyncPlugin::new(provider)); // MinimalPlugins does not register keyboard input. - app.init_resource::>(); + app.init_resource::>(); app.update(); app } @@ -629,8 +629,8 @@ mod tests { replays: vec![initial], }; save_replay_history_to(&path, &history).expect("seed history on disk"); - app.insert_resource(crate::stats_plugin::ReplayHistoryResource(history)); - app.insert_resource(crate::stats_plugin::LatestReplayPath(Some(path.clone()))); + app.insert_resource(ReplayHistoryResource(history)); + app.insert_resource(LatestReplayPath(Some(path.clone()))); // Pre-resolved task carrying the URL the production path would // get back from the server. @@ -659,7 +659,7 @@ mod tests { // In-memory contract: replays[0].share_url is now Some(url). let live = app .world() - .resource::(); + .resource::(); assert_eq!( live.0.replays.first().and_then(|r| r.share_url.clone()), Some(url.clone()), diff --git a/solitaire_engine/src/table_plugin.rs b/solitaire_engine/src/table_plugin.rs index 8aa375b..854f4d3 100644 --- a/solitaire_engine/src/table_plugin.rs +++ b/solitaire_engine/src/table_plugin.rs @@ -11,9 +11,7 @@ use solitaire_core::Suit; use crate::events::{HintVisualEvent, StateChangedEvent}; use crate::hud_plugin::HudVisibility; -#[cfg(test)] -use crate::layout::TABLE_COLOUR; -use crate::layout::{Layout, LayoutResource, LayoutSystem, compute_layout}; +use crate::layout::{Layout, LayoutResource, LayoutSystem, TABLE_COLOUR, compute_layout}; use crate::resources::GameStateResource; use crate::safe_area::SafeAreaInsets; use crate::settings_plugin::{SettingsChangedEvent, SettingsResource}; @@ -177,9 +175,9 @@ fn setup_table( Camera2d, Camera { clear_color: ClearColorConfig::Custom(Color::srgb( - crate::layout::TABLE_COLOUR[0], - crate::layout::TABLE_COLOUR[1], - crate::layout::TABLE_COLOUR[2], + TABLE_COLOUR[0], + TABLE_COLOUR[1], + TABLE_COLOUR[2], )), ..default() }, diff --git a/solitaire_engine/src/theme/importer.rs b/solitaire_engine/src/theme/importer.rs index 3a25bb2..b2c4de9 100644 --- a/solitaire_engine/src/theme/importer.rs +++ b/solitaire_engine/src/theme/importer.rs @@ -236,7 +236,7 @@ pub fn import_theme_into(zip_path: &Path, target_root: &Path) -> Result( +fn enforce_archive_size_limit( archive: &mut zip::ZipArchive, ) -> Result<(), ImportError> { let mut total: u64 = 0; @@ -257,7 +257,7 @@ fn enforce_archive_size_limit( /// (after normalisation) escapes its root. Catches `..`, absolute /// paths, drive prefixes on Windows, and the awkward case where /// `enclosed_name` returns `None` because the entry is suspicious. -fn enforce_zip_slip_safe( +fn enforce_zip_slip_safe( archive: &mut zip::ZipArchive, ) -> Result<(), ImportError> { for i in 0..archive.len() { @@ -291,7 +291,7 @@ fn is_safe_relative_path(p: &Path) -> bool { /// `theme.ron` entry at its root. /// - [`ImportError::ManifestParse`] when the bytes don't form valid /// RON for `ThemeManifest`. -fn read_manifest( +fn read_manifest( archive: &mut zip::ZipArchive, ) -> Result { // We can't use `?` directly across `by_name` because a missing @@ -318,7 +318,7 @@ fn read_manifest( /// /// Returns [`ImportError::MissingFile`] when the archive has no entry /// matching the path. -fn read_archive_entry( +fn read_archive_entry( archive: &mut zip::ZipArchive, path: &Path, ) -> Result, ImportError> { @@ -351,7 +351,7 @@ fn archive_key(path: &Path) -> String { /// parent directories as needed. The destination path is rebuilt from /// the safe components we already vetted in /// [`enforce_zip_slip_safe`], not from the raw entry name. -fn write_archive_entry( +fn write_archive_entry( archive: &mut zip::ZipArchive, name: &str, staging: &Path, @@ -384,7 +384,7 @@ fn write_archive_entry( /// Variant of [`write_archive_entry`] keyed by `Path` for the /// manifest-declared face/back paths. -fn write_archive_entry_pathbuf( +fn write_archive_entry_pathbuf( archive: &mut zip::ZipArchive, path: &Path, staging: &Path, diff --git a/solitaire_engine/src/theme/plugin.rs b/solitaire_engine/src/theme/plugin.rs index f72e015..a8b5b37 100644 --- a/solitaire_engine/src/theme/plugin.rs +++ b/solitaire_engine/src/theme/plugin.rs @@ -484,7 +484,7 @@ mod tests { let mut image_set = empty_card_image_set(); // Snapshot the legacy back ids so we can prove they don't // change when a theme is applied. - let legacy_ids_before: [bevy::asset::AssetId; 5] = + let legacy_ids_before: [AssetId; 5] = std::array::from_fn(|i| image_set.backs[i].id()); let theme = empty_theme(); assert!(image_set.theme_back.is_none(), "theme_back starts empty"); diff --git a/solitaire_engine/src/time_attack_plugin.rs b/solitaire_engine/src/time_attack_plugin.rs index 4c3c0cf..3738374 100644 --- a/solitaire_engine/src/time_attack_plugin.rs +++ b/solitaire_engine/src/time_attack_plugin.rs @@ -521,7 +521,7 @@ mod tests { // the session timer or the running win count. // ----------------------------------------------------------------------- - fn tmp_ta_path(name: &str) -> std::path::PathBuf { + fn tmp_ta_path(name: &str) -> PathBuf { std::env::temp_dir().join(format!("engine_test_ta_{name}.json")) } diff --git a/solitaire_engine/src/ui_focus.rs b/solitaire_engine/src/ui_focus.rs index 3efb74f..d78e67a 100644 --- a/solitaire_engine/src/ui_focus.rs +++ b/solitaire_engine/src/ui_focus.rs @@ -1324,7 +1324,7 @@ mod tests { let row = world.spawn((FocusRow, Node::default())).id(); world.entity_mut(scrim).add_child(row); - let make_swatch = |w: &mut World, marker: fn(&mut bevy::ecs::world::EntityWorldMut)| { + let make_swatch = |w: &mut World, marker: fn(&mut EntityWorldMut)| { let mut e = w.spawn(( Button, Node::default(), diff --git a/solitaire_engine/src/ui_modal.rs b/solitaire_engine/src/ui_modal.rs index 4bfde69..f268f6a 100644 --- a/solitaire_engine/src/ui_modal.rs +++ b/solitaire_engine/src/ui_modal.rs @@ -982,9 +982,9 @@ mod tests { outline_width: 0.0, outline_offset: 0.0, unrounded_size: card_size, - border: bevy::sprite::BorderRect::default(), - border_radius: bevy::ui::ResolvedBorderRadius::default(), - padding: bevy::sprite::BorderRect::default(), + border: BorderRect::default(), + border_radius: ResolvedBorderRadius::default(), + padding: BorderRect::default(), inverse_scale_factor: 1.0, }; // `is_empty` guard inside Bevy treats zero-size diff --git a/solitaire_engine/src/ui_theme.rs b/solitaire_engine/src/ui_theme.rs index ce0f56e..9f57784 100644 --- a/solitaire_engine/src/ui_theme.rs +++ b/solitaire_engine/src/ui_theme.rs @@ -249,12 +249,12 @@ pub const BORDER_SUBTLE_HC: Color = Color::srgba(0.627, 0.627, 0.627, 1.0); pub struct HighContrastBorder { /// Border colour to use when high-contrast mode is *off* — the /// site's normal idle / active-state colour. - pub default_color: bevy::prelude::Color, + pub default_color: Color, } impl HighContrastBorder { /// Convenience constructor — `HighContrastBorder::with_default(BORDER_SUBTLE)`. - pub const fn with_default(default_color: bevy::prelude::Color) -> Self { + pub const fn with_default(default_color: Color) -> Self { Self { default_color } } } @@ -282,18 +282,18 @@ impl HighContrastBorder { pub struct HighContrastBackground { /// Background colour to use when high-contrast mode is *off* — /// the site's normal idle / active-state colour. - pub default_color: bevy::prelude::Color, + pub default_color: Color, /// Background colour to use when high-contrast mode is *on*. /// Defaults to [`BORDER_SUBTLE_HC`] via [`with_default`]. /// /// [`with_default`]: HighContrastBackground::with_default - pub hc_color: bevy::prelude::Color, + pub hc_color: Color, } impl HighContrastBackground { /// Convenience constructor — HC colour defaults to /// [`BORDER_SUBTLE_HC`]. - pub const fn with_default(default_color: bevy::prelude::Color) -> Self { + pub const fn with_default(default_color: Color) -> Self { Self { default_color, hc_color: BORDER_SUBTLE_HC, @@ -305,8 +305,8 @@ impl HighContrastBackground { /// marker which bumps `STATE_SUCCESS` → `STATE_SUCCESS_HC` rather /// than to a neutral gray. pub const fn with_hc( - default_color: bevy::prelude::Color, - hc_color: bevy::prelude::Color, + default_color: Color, + hc_color: Color, ) -> Self { Self { default_color, diff --git a/solitaire_engine/src/win_summary_plugin.rs b/solitaire_engine/src/win_summary_plugin.rs index 13fe48a..25fe070 100644 --- a/solitaire_engine/src/win_summary_plugin.rs +++ b/solitaire_engine/src/win_summary_plugin.rs @@ -551,7 +551,7 @@ fn spawn_win_summary_after_delay( // speed the duration is zero anyway, suppressing the shake. let speed = settings .as_ref() - .map_or(solitaire_data::AnimSpeed::Normal, |s| s.0.animation_speed); + .map_or(AnimSpeed::Normal, |s| s.0.animation_speed); let scaled = scaled_duration(SHAKE_DURATION_SECS, speed); shake.remaining = scaled; shake.total = scaled; diff --git a/solitaire_server/Cargo.toml b/solitaire_server/Cargo.toml index a39908a..1b67601 100644 --- a/solitaire_server/Cargo.toml +++ b/solitaire_server/Cargo.toml @@ -32,3 +32,6 @@ dotenvy = { workspace = true } [dev-dependencies] tower = { version = "0.5", features = ["util"] } + +[lints] +workspace = true diff --git a/solitaire_server/src/lib.rs b/solitaire_server/src/lib.rs index e52d87f..4120aae 100644 --- a/solitaire_server/src/lib.rs +++ b/solitaire_server/src/lib.rs @@ -54,7 +54,7 @@ struct UserIdKeyExtractor { impl KeyExtractor for UserIdKeyExtractor { type Key = String; - fn extract(&self, req: &axum::http::Request) -> Result { + fn extract(&self, req: &Request) -> Result { if let Some(user_id) = self.try_extract_user_id(req.headers()) { return Ok(user_id); } diff --git a/solitaire_server/tests/server_tests.rs b/solitaire_server/tests/server_tests.rs index d3c04c9..270baed 100644 --- a/solitaire_server/tests/server_tests.rs +++ b/solitaire_server/tests/server_tests.rs @@ -565,7 +565,7 @@ async fn register_login_push_pull_full_roundtrip() { }, achievements: vec![], progress: PlayerProgress::default(), - last_modified: chrono::Utc::now(), + last_modified: Utc::now(), }; let push_resp = post_authed( @@ -1299,7 +1299,7 @@ async fn expired_access_token_returns_401() { exp: usize, kind: String, } - let exp = (chrono::Utc::now() - chrono::Duration::hours(2)).timestamp() as usize; + let exp = (Utc::now() - chrono::Duration::hours(2)).timestamp() as usize; let expired_token = encode( &Header::default(), &ExpiredClaims { @@ -1375,7 +1375,7 @@ async fn refresh_with_expired_refresh_token_returns_401() { exp: usize, kind: String, } - let exp = (chrono::Utc::now() - chrono::Duration::hours(2)).timestamp() as usize; + let exp = (Utc::now() - chrono::Duration::hours(2)).timestamp() as usize; let expired_token = encode( &Header::default(), &ExpiredRefreshClaims { diff --git a/solitaire_sync/Cargo.toml b/solitaire_sync/Cargo.toml index d647306..7a499ab 100644 --- a/solitaire_sync/Cargo.toml +++ b/solitaire_sync/Cargo.toml @@ -9,3 +9,6 @@ serde = { workspace = true } uuid = { workspace = true } chrono = { workspace = true } thiserror = { workspace = true } + +[lints] +workspace = true diff --git a/solitaire_sync/src/merge.rs b/solitaire_sync/src/merge.rs index b17b562..9d03386 100644 --- a/solitaire_sync/src/merge.rs +++ b/solitaire_sync/src/merge.rs @@ -755,14 +755,14 @@ mod tests { fn progress_total_xp_takes_max() { let mut local = default_payload(); local.progress.total_xp = 1500; - local.progress.level = crate::progress::level_for_xp(1500); + local.progress.level = level_for_xp(1500); let mut remote = default_payload(); remote.progress.total_xp = 2500; - remote.progress.level = crate::progress::level_for_xp(2500); + remote.progress.level = level_for_xp(2500); let (merged, _) = merge(&local, &remote); assert_eq!(merged.progress.total_xp, 2500); - assert_eq!(merged.progress.level, crate::progress::level_for_xp(2500)); + assert_eq!(merged.progress.level, level_for_xp(2500)); } #[test] @@ -815,7 +815,7 @@ mod tests { let (merged, _) = merge(&local, &remote); assert_eq!(merged.progress.total_xp, 5500); - assert_eq!(merged.progress.level, crate::progress::level_for_xp(5500)); + assert_eq!(merged.progress.level, level_for_xp(5500)); } // ----------------------------------------------------------------------- @@ -1051,11 +1051,11 @@ mod tests { // drop the oldest 50 so the cap is preserved. let start = nd(2024, 1, 1); let local_dates: Vec = (0..DAILY_CHALLENGE_HISTORY_CAP as i64) - .map(|i| start + chrono::Duration::days(i)) + .map(|i| start + Duration::days(i)) .collect(); let remote_dates: Vec = (DAILY_CHALLENGE_HISTORY_CAP as i64 ..DAILY_CHALLENGE_HISTORY_CAP as i64 + 50) - .map(|i| start + chrono::Duration::days(i)) + .map(|i| start + Duration::days(i)) .collect(); let mut local = default_payload(); @@ -1073,7 +1073,7 @@ mod tests { // is therefore start + 50 days. assert_eq!( merged.progress.daily_challenge_history.first().copied(), - Some(start + chrono::Duration::days(50)) + Some(start + Duration::days(50)) ); // Most recent retained is the last remote date. assert_eq!( diff --git a/solitaire_wasm/Cargo.toml b/solitaire_wasm/Cargo.toml index a62916a..b7ae860 100644 --- a/solitaire_wasm/Cargo.toml +++ b/solitaire_wasm/Cargo.toml @@ -28,3 +28,6 @@ web-sys = { version = "0.3", features = ["console"] } [features] default = ["console_error_panic_hook"] + +[lints] +workspace = true diff --git a/solitaire_web/Cargo.toml b/solitaire_web/Cargo.toml index 0e36702..d4441f8 100644 --- a/solitaire_web/Cargo.toml +++ b/solitaire_web/Cargo.toml @@ -21,3 +21,6 @@ console_error_panic_hook = "0.1" # renderer to WebGL2 compatibility limits, which is wrong for native builds. [target.'cfg(target_arch = "wasm32")'.dependencies] bevy = { workspace = true, features = ["webgl2"] } + +[lints] +workspace = true