From 8ca401a742c56baf061aeca27473628ad42595d0 Mon Sep 17 00:00:00 2001 From: Tunglies Date: Fri, 13 Nov 2026 05:29:47 +0000 Subject: [PATCH] chore(rust): upgrade to 1.100 and consolidate Clippy rules (#8636) --- .clippy.toml | 1 + .devcontainer/devcontainer.json | 2 +- .github/workflows/autobuild.yml | 6 +- .github/workflows/dev.yml | 4 +- .github/workflows/release.yml | 6 +- .github/workflows/signpath-test.yml | 2 +- Cargo.lock | 1 - Cargo.toml | 1 + crates/clash-verge-logging/src/lib.rs | 8 +-- .../clash-verge-media-unlock/src/chatgpt.rs | 5 +- .../src/disney_plus.rs | 3 +- .../clash-verge-media-unlock/src/netflix.rs | 9 ++- .../src/lib.rs | 3 +- rust-toolchain.toml | 2 +- src-tauri/Cargo.toml | 3 +- src-tauri/examples/frontend-contract.rs | 8 +-- src-tauri/src/cmd/profile.rs | 10 ++-- src-tauri/src/cmd/proxy.rs | 3 +- src-tauri/src/config/config.rs | 2 +- src-tauri/src/config/prfitem.rs | 7 ++- src-tauri/src/core/auto_backup.rs | 3 +- src-tauri/src/core/listener.rs | 10 ++-- src-tauri/src/core/manager/config.rs | 10 ++-- src-tauri/src/core/manager/lifecycle.rs | 23 ++++---- src-tauri/src/core/proxy_view.rs | 4 +- src-tauri/src/core/runtime_bundle.rs | 3 +- src-tauri/src/core/service.rs | 5 +- src-tauri/src/core/sysopt.rs | 6 +- src-tauri/src/core/tray/mod.rs | 55 +++++++++---------- src-tauri/src/core/validate.rs | 9 ++- src-tauri/src/enhance/mod.rs | 24 +++----- src-tauri/src/enhance/seq.rs | 15 ++--- src-tauri/src/feat/backup.rs | 6 +- src-tauri/src/feat/core_upgrade.rs | 4 +- src-tauri/src/feat/icon.rs | 2 +- src-tauri/src/feat/listener.rs | 14 ++--- src-tauri/src/feat/tun.rs | 10 ++-- src-tauri/src/main.rs | 2 +- src-tauri/src/utils/help.rs | 6 +- src-tauri/src/utils/window_manager.rs | 6 +- 40 files changed, 137 insertions(+), 166 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index d5454d32f..e5747222f 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,2 +1,3 @@ avoid-breaking-exported-api = true +allow-unwrap-in-tests = true cognitive-complexity-threshold = 25 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b7b9ad914..b24d033e8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,7 @@ "pnpmVersion": "12.18.0" }, "ghcr.io/devcontainers/features/rust:1": { - "version": "1.99.1", + "version": "1.100.0", "profile": "default" }, "ghcr.io/devcontainers/features/git:1": {}, diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 916415989..050553371 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -154,7 +154,7 @@ jobs: - name: Install Rust Stable uses: dtolnay/rust-toolchain@master with: - toolchain: '1.99.1' + toolchain: '1.100.0' targets: ${{ matrix.target }} - name: Rust Cache @@ -240,7 +240,7 @@ jobs: - name: Install Rust Stable uses: dtolnay/rust-toolchain@master with: - toolchain: '1.99.1' + toolchain: '1.100.0' targets: ${{ matrix.target }} - name: Rust Cache @@ -373,7 +373,7 @@ jobs: - name: Install Rust Stable uses: dtolnay/rust-toolchain@master with: - toolchain: '1.99.1' + toolchain: '1.100.0' targets: ${{ matrix.target }} - name: Rust Cache diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 437663f26..0bac9d6b7 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -74,7 +74,7 @@ jobs: - name: Install Rust Stable if: github.event.inputs[matrix.input] == 'true' - uses: dtolnay/rust-toolchain@1.99.1 + uses: dtolnay/rust-toolchain@1.100.0 - name: Rust Cache if: github.event.inputs[matrix.input] == 'true' @@ -121,7 +121,7 @@ jobs: if: github.event.inputs[matrix.input] == 'true' run: | # Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed - rustup target add ${{ matrix.target }} --toolchain 1.99.1 || rustup target add ${{ matrix.target }} + rustup target add ${{ matrix.target }} --toolchain 1.100.0 || rustup target add ${{ matrix.target }} rustup target list --installed echo "Rust target ${{ matrix.target }} installed." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ba3768af..6b02ab3a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -203,7 +203,7 @@ jobs: - name: Install Rust Stable uses: dtolnay/rust-toolchain@master with: - toolchain: '1.99.1' + toolchain: '1.100.0' targets: ${{ matrix.target }} - name: Rust Cache @@ -308,7 +308,7 @@ jobs: - name: Install Rust Stable uses: dtolnay/rust-toolchain@master with: - toolchain: '1.99.1' + toolchain: '1.100.0' targets: ${{ matrix.target }} - name: Rust Cache @@ -439,7 +439,7 @@ jobs: - name: Install Rust Stable uses: dtolnay/rust-toolchain@master with: - toolchain: '1.99.1' + toolchain: '1.100.0' targets: ${{ matrix.target }} - name: Rust Cache diff --git a/.github/workflows/signpath-test.yml b/.github/workflows/signpath-test.yml index 9cb96b09d..2f377cfdb 100644 --- a/.github/workflows/signpath-test.yml +++ b/.github/workflows/signpath-test.yml @@ -22,7 +22,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@master with: - toolchain: '1.99.1' + toolchain: '1.100.0' targets: x86_64-pc-windows-msvc - name: Install Node diff --git a/Cargo.lock b/Cargo.lock index 01882f7c9..fe2cd0de7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,7 +1123,6 @@ dependencies = [ "rfd 0.18.0", "runas", "rustls", - "scopeguard", "serde", "serde_json", "serde_yaml_ng", diff --git a/Cargo.toml b/Cargo.toml index dde225c61..576c9beb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,6 +132,7 @@ large_const_arrays = "warn" large_futures = "deny" large_stack_arrays = "warn" large_types_passed_by_value = "deny" +map_unwrap_or = "deny" missing_const_for_fn = "deny" mutex_atomic = "deny" mutex_integer = "deny" diff --git a/crates/clash-verge-logging/src/lib.rs b/crates/clash-verge-logging/src/lib.rs index 6c4c4ba55..edcdc1ace 100644 --- a/crates/clash-verge-logging/src/lib.rs +++ b/crates/clash-verge-logging/src/lib.rs @@ -206,10 +206,10 @@ impl Logger { .map(|s| (*s).to_string()) .or_else(|| info.payload().downcast_ref::().cloned()) .unwrap_or_else(|| "Unknown panic payload".to_string()); - let location = info - .location() - .map(|loc| format!("{}:{}", loc.file(), loc.line())) - .unwrap_or_else(|| "Unknown location".to_string()); + let location = info.location().map_or_else( + || "Unknown location".to_string(), + |loc| format!("{}:{}", loc.file(), loc.line()), + ); logging!(error, Type::System, "Panic occurred at {}: {}", location, payload); Self::global().flush_logs(); std::thread::sleep(Duration::from_millis(100)); diff --git a/crates/clash-verge-media-unlock/src/chatgpt.rs b/crates/clash-verge-media-unlock/src/chatgpt.rs index 08e4f8292..1841a8cfa 100644 --- a/crates/clash-verge-media-unlock/src/chatgpt.rs +++ b/crates/clash-verge-media-unlock/src/chatgpt.rs @@ -13,14 +13,13 @@ pub(super) async fn check_chatgpt(client: &Client) -> UnlockItem { let web_status = get_text(client, "https://api.openai.com/compliance/cookie_requirements") .await - .map(|body| { + .map_or("Failed", |body| { if body.to_ascii_lowercase().contains("unsupported_country") { "Unsupported Country/Region" } else { "Yes" } - }) - .unwrap_or("Failed"); + }); UnlockItem::checked(CHATGPT_WEB_NAME, web_status, region) } diff --git a/crates/clash-verge-media-unlock/src/disney_plus.rs b/crates/clash-verge-media-unlock/src/disney_plus.rs index a81886f52..fd884c38c 100644 --- a/crates/clash-verge-media-unlock/src/disney_plus.rs +++ b/crates/clash-verge-media-unlock/src/disney_plus.rs @@ -157,11 +157,10 @@ async fn is_unavailable(client: &Client) -> bool { .get("https://disneyplus.com") .send() .await - .map(|response| { + .map_or(true, |response| { let url = response.url().as_str(); url.contains("preview") || url.contains("unavailable") }) - .unwrap_or(true) } async fn fallback_region(client: &Client) -> Option { diff --git a/crates/clash-verge-media-unlock/src/netflix.rs b/crates/clash-verge-media-unlock/src/netflix.rs index 7c7cefd3f..dc6a51acb 100644 --- a/crates/clash-verge-media-unlock/src/netflix.rs +++ b/crates/clash-verge-media-unlock/src/netflix.rs @@ -114,11 +114,10 @@ async fn check_netflix_cdn(client: &Client) -> UnlockItem { } match response.json::().await { - Ok(data) => data - .targets - .first() - .map(|target| UnlockItem::checked_region(NETFLIX_NAME, "Yes", &target.location.country)) - .unwrap_or_else(|| item("Unknown")), + Ok(data) => data.targets.first().map_or_else( + || item("Unknown"), + |target| UnlockItem::checked_region(NETFLIX_NAME, "Yes", &target.location.country), + ), Err(e) => { logging!(error, Type::Network, "解析 Fast.com API 响应失败: {e}"); item("Failed (解析错误)") diff --git a/crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs b/crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs index 0417b707b..15ef79ffe 100644 --- a/crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs +++ b/crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs @@ -118,8 +118,7 @@ fn is_binary_admin() -> bool { #[cfg(windows)] Token::with_current_process() .and_then(|token| token.privilege_level()) - .map(|level| level != PrivilegeLevel::NotPrivileged) - .unwrap_or(false) + .is_ok_and(|level| level != PrivilegeLevel::NotPrivileged) } #[inline] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9ec55b3b6..640a93b2a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.99.1" +channel = "1.100.0" components = ["rustfmt", "clippy"] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7d9e6080a..9d93ec9a7 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/clash-verge-rev/clash-verge-rev.git" default-run = "clash-verge" build = "build.rs" edition = "2024" -rust-version = "1.99" +rust-version = "1.100" [lib] name = "app_lib" @@ -86,7 +86,6 @@ sha2 = "0.11.1" getrandom = "0.4.3" futures = "0.3.34" gethostname = "1.1.0" -scopeguard = "1.2.0" tauri-plugin-notification = "2.8.0" backon = { version = "1.6.0", features = ["tokio-sleep"] } tauri-plugin-http = "2.8.3" diff --git a/src-tauri/examples/frontend-contract.rs b/src-tauri/examples/frontend-contract.rs index a531ffe56..933b424fe 100644 --- a/src-tauri/examples/frontend-contract.rs +++ b/src-tauri/examples/frontend-contract.rs @@ -2,10 +2,10 @@ // bundles (PR #8131), so this generator must stay an example, never a bin. fn main() -> Result<(), Box> { let mut args = std::env::args_os().skip(1); - let output = args - .next() - .map(std::path::PathBuf::from) - .unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/services/bindings.ts")); + let output = args.next().map_or_else( + || std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/services/bindings.ts"), + std::path::PathBuf::from, + ); if args.next().is_some() { return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "expected at most one output path").into()); } diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 106302b12..489d0ef8c 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -18,9 +18,11 @@ use crate::{ }; use clash_verge_draft::{Draft, SharedDraft}; use clash_verge_logging::{Type, logging, logging_error}; -use scopeguard::defer; use smartstring::alias::String; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::{ + mem::DropGuard, + sync::atomic::{AtomicBool, Ordering}, +}; static CURRENT_SWITCHING_PROFILE: AtomicBool = AtomicBool::new(false); @@ -400,9 +402,9 @@ pub async fn patch_profiles_config(profiles: ProfileSelection) -> CmdResult) -> BTreeMap Result<()> { .and_then(|meta| meta.modified()) .ok() .and_then(|time| time.duration_since(UNIX_EPOCH).ok()) - .map(|dur| dur.as_secs()) - .unwrap_or(0); + .map_or(0, |dur| dur.as_secs()); files.push((path, modified)); } diff --git a/src-tauri/src/core/listener.rs b/src-tauri/src/core/listener.rs index 8fb97c82e..03899ea3d 100644 --- a/src-tauri/src/core/listener.rs +++ b/src-tauri/src/core/listener.rs @@ -539,18 +539,16 @@ fn bind_socket(claim: &BindClaim) -> io::Result { } fn is_bind_conflict(error: &io::Error) -> bool { - if error.kind() == io::ErrorKind::AddrInUse { - return true; - } - #[cfg(windows)] { use windows_sys::Win32::Networking::WinSock::WSAEACCES; - error.raw_os_error() == Some(WSAEACCES) + error.kind() == io::ErrorKind::AddrInUse || error.raw_os_error() == Some(WSAEACCES) } #[cfg(not(windows))] - false + { + error.kind() == io::ErrorKind::AddrInUse + } } #[cfg(windows)] diff --git a/src-tauri/src/core/manager/config.rs b/src-tauri/src/core/manager/config.rs index 82496fc93..f42f34ee2 100644 --- a/src-tauri/src/core/manager/config.rs +++ b/src-tauri/src/core/manager/config.rs @@ -14,10 +14,10 @@ use anyhow::{Result, anyhow}; use clash_verge_draft::DraftTransaction; use clash_verge_logging::{Type, logging}; use clash_verge_service_ipc::StageRuntimeOutcome; -use scopeguard::defer; use smartstring::alias::String; use std::{ collections::HashSet, + mem::DropGuard, path::PathBuf, sync::Arc, time::{Duration, Instant}, @@ -191,9 +191,9 @@ impl CoreManager { logging!(debug, Type::Core, "Configuration update is already running"); return Ok(ValidationOutcome::Busy); } - defer! { + let _config_update = DropGuard::new((), |()| { self.finish_config_update(); - } + }); if !force && !self.should_update_config() { logging!(debug, Type::Core, "Skipping config update due to debounce"); @@ -314,9 +314,9 @@ impl CoreManager { logging!(debug, Type::Core, "Configuration update is already running"); return Ok(ValidationOutcome::Busy); } - defer! { + let _config_update = DropGuard::new((), |()| { self.finish_config_update(); - } + }); let runtime = Config::runtime().await; let transaction = DraftTransaction::begin(vec![&runtime])?; diff --git a/src-tauri/src/core/manager/lifecycle.rs b/src-tauri/src/core/manager/lifecycle.rs index 84b0c2346..8e6074d51 100644 --- a/src-tauri/src/core/manager/lifecycle.rs +++ b/src-tauri/src/core/manager/lifecycle.rs @@ -6,9 +6,8 @@ use crate::core::proxy_control::{self, SysproxyFailure}; use crate::core::service::{SERVICE_MANAGER, ServiceStatus}; use anyhow::Result; use clash_verge_logging::{Type, logging}; -use scopeguard::defer; use smartstring::alias::String; -use std::path::Path; +use std::{mem::DropGuard, path::Path}; #[cfg(target_os = "windows")] use tauri_plugin_clash_verge_sysinfo::is_current_app_handle_admin; @@ -356,9 +355,9 @@ impl CoreManager { if !self.try_start_config_update() { anyhow::bail!("configuration update is already running"); } - defer! { + let _config_update = DropGuard::new((), |()| { self.finish_config_update(); - } + }); self.start_core_during_config_update().await } @@ -383,9 +382,9 @@ impl CoreManager { service_rejection, )); } - defer! { + let _config_update = DropGuard::new((), |()| { self.finish_config_update(); - } + }); // Lock order is config then lifecycle. disable_tun_and_persist commits the shared Verge // draft without claiming it, so without this it can commit another transaction's staged patch. let config_write = Config::lock_config_write().await; @@ -463,9 +462,9 @@ impl CoreManager { if !self.try_start_config_update() { anyhow::bail!("configuration update is already running"); } - defer! { + let _config_update = DropGuard::new((), |()| { self.finish_config_update(); - } + }); // Lock order is config then lifecycle, for the unclaimed draft write below. let _config_write = Config::lock_config_write().await; let _life = self.lifecycle_lock.lock().await; @@ -795,9 +794,9 @@ impl CoreManager { if !self.try_start_config_update() { anyhow::bail!("configuration update is already running"); } - defer! { + let _config_update = DropGuard::new((), |()| { self.finish_config_update(); - } + }); self.restart_core_during_config_update().await } @@ -966,9 +965,9 @@ impl CoreManager { if !self.try_start_config_update() { return HandoffOutcome::NotReady; } - defer! { + let _config_update = DropGuard::new((), |()| { self.finish_config_update(); - } + }); // Lock order is config then lifecycle. let _life = self.lifecycle_lock.lock().await; diff --git a/src-tauri/src/core/proxy_view.rs b/src-tauri/src/core/proxy_view.rs index 0fe394280..f5dd8d092 100644 --- a/src-tauri/src/core/proxy_view.rs +++ b/src-tauri/src/core/proxy_view.rs @@ -331,9 +331,7 @@ fn build_provider_records( providers: Option, records: &mut BTreeMap, ) -> (Vec, ProviderCandidates) { - let providers = providers - .map(|providers| providers.providers.into_iter().collect::>()) - .unwrap_or_default(); + let providers = providers.map_or_default(|providers| providers.providers.into_iter().collect::>()); let mut views = Vec::new(); let mut candidates = BTreeMap::new(); diff --git a/src-tauri/src/core/runtime_bundle.rs b/src-tauri/src/core/runtime_bundle.rs index 5038a6231..aa22bb109 100644 --- a/src-tauri/src/core/runtime_bundle.rs +++ b/src-tauri/src/core/runtime_bundle.rs @@ -126,8 +126,7 @@ fn allocate_provider_destination(destination: &str, url: &str, reserved: &mut Ha let path = Path::new(destination); let extension = path .extension() - .map(|ext| format!(".{}", ext.to_string_lossy())) - .unwrap_or_default(); + .map_or_default(|ext| format!(".{}", ext.to_string_lossy())); let mut suffix = String::new(); let mut sequence = 0; loop { diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 05cf15d57..1dee595b3 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -1470,10 +1470,7 @@ async fn create_sync_temp(target: &Path) -> Result<(PathBuf, tokio::fs::File)> { .await .with_context(|| format!("failed to create provider cache directory {}", parent.display()))?; } - let name = target - .file_name() - .map(|name| name.to_string_lossy()) - .unwrap_or_default(); + let name = target.file_name().map_or_default(|name| name.to_string_lossy()); use crate::utils::retry::{RetryError, RetryPolicy, retry}; retry( RetryPolicy::fixed(std::num::NonZeroUsize::MIN.saturating_add(7), std::time::Duration::ZERO), diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index c32b4ac15..5809d38a1 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -7,9 +7,9 @@ use crate::{ use anyhow::Result; use clash_verge_logging::{Type, logging}; use parking_lot::RwLock; -use scopeguard::defer; use smartstring::alias::String; use std::{ + mem::DropGuard, sync::{ Arc, atomic::{AtomicBool, Ordering}, @@ -541,9 +541,9 @@ impl Sysopt { { return Ok(()); } - defer! { + let _reset_sysproxy = DropGuard::new((), |()| { self.reset_sysproxy.store(false, Ordering::SeqCst); - } + }); let mut proxy_state = self.proxy_state_lock.lock().await; let _guard_operation = self.guard_operation_lock.lock().await; let drained = self.stop_proxy_guard_locked().await; diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 3ff99b6ed..5245d748d 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -210,8 +210,7 @@ impl Tray { .latest_arc() .0 .get("mode") - .map(|val| val.as_str().unwrap_or("rule")) - .unwrap_or("rule") + .map_or("rule", |val| val.as_str().unwrap_or("rule")) .to_owned() }; let profiles_config = Config::profiles().await; @@ -414,26 +413,23 @@ impl Tray { } fn create_hotkeys(hotkeys: &Option>) -> HashMap<&str, &str> { - hotkeys - .as_ref() - .map(|h| { - h.iter() - .filter_map(|item| { - let mut parts = item.split(','); - match (parts.next(), parts.next()) { - (Some(func), Some(key)) => { - // 托盘菜单的 `accelerator` 在 Linux/Windows 下不支持小键盘按键,Windows 下也不支持 Win 键(前端上报为 `CMD`) - let uppercase_key = key.to_uppercase(); - let unsupported = - uppercase_key.contains("NUMPAD") || (cfg!(windows) && uppercase_key.contains("CMD")); - if unsupported { None } else { Some((func, key)) } - } - _ => None, + hotkeys.as_ref().map_or_default(|h| { + h.iter() + .filter_map(|item| { + let mut parts = item.split(','); + match (parts.next(), parts.next()) { + (Some(func), Some(key)) => { + // 托盘菜单的 `accelerator` 在 Linux/Windows 下不支持小键盘按键,Windows 下也不支持 Win 键(前端上报为 `CMD`) + let uppercase_key = key.to_uppercase(); + let unsupported = + uppercase_key.contains("NUMPAD") || (cfg!(windows) && uppercase_key.contains("CMD")); + if unsupported { None } else { Some((func, key)) } } - }) - .collect::>() - }) - .unwrap_or_default() + _ => None, + } + }) + .collect::>() + }) } fn create_profile_menu_item( @@ -493,12 +489,14 @@ fn create_subcreate_proxy_menu_item( .proxies .get(proxy_str) .and_then(|h| h.history.last()) - .map(|h| match h.delay { - 0 => "-ms".into(), - delay if delay >= 10000 => "-ms".into(), - _ => format!("{}ms", h.delay), - }) - .unwrap_or_else(|| "-ms".into()); + .map_or_else( + || "-ms".into(), + |h| match h.delay { + 0 => "-ms".into(), + delay if delay >= 10000 => "-ms".into(), + _ => format!("{}ms", h.delay), + }, + ); let display_text = format!("{} | {}", proxy_str, delay_text); @@ -611,7 +609,7 @@ async fn create_tray_menu( config .get("proxy-groups") .and_then(|groups| groups.as_sequence()) - .map(|groups| { + .map_or_default(|groups| { groups .iter() .filter_map(|group| group.get("name")) @@ -620,7 +618,6 @@ async fn create_tray_menu( .map(|(index, name)| (name.into(), index)) .collect::>() }) - .unwrap_or_default() }); (proxy_nodes_data, runtime_proxy_groups_order) diff --git a/src-tauri/src/core/validate.rs b/src-tauri/src/core/validate.rs index cfa8d7a6c..834a5f362 100644 --- a/src-tauri/src/core/validate.rs +++ b/src-tauri/src/core/validate.rs @@ -1,10 +1,10 @@ use anyhow::Result; -use scopeguard::defer; use serde::Serialize; use smartstring::alias::String; use std::{ fmt, hash::{DefaultHasher, Hash as _, Hasher as _}, + mem::DropGuard, sync::LazyLock, sync::atomic::{AtomicBool, Ordering}, }; @@ -421,9 +421,9 @@ impl CoreConfigValidator { logging!(info, Type::Validate, "验证已在进行中,跳过新的验证请求"); return Ok(ValidationOutcome::Busy); } - defer! { + let _validation = DropGuard::new((), |()| { self.finish(); - } + }); logging!(info, Type::Validate, "生成临时配置文件用于验证"); // The test run reads the app dir, which never sees updates made by a service core. @@ -449,8 +449,7 @@ fn has_ext>(path: P, ext: &str) -> bool { path.as_ref() .extension() .and_then(|s| s.to_str()) - .map(|s| s.eq_ignore_ascii_case(ext)) - .unwrap_or(false) + .is_some_and(|s| s.eq_ignore_ascii_case(ext)) } fn contains_any_keyword<'a>(buf: &'a [u8], keywords: &'a [&str]) -> bool { diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index 6fab9c2be..08b790823 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -634,7 +634,7 @@ fn cleanup_proxy_groups(mut config: Mapping) -> Mapping { let proxy_names = config .get("proxies") .and_then(|v| v.as_sequence()) - .map(|seq| { + .map_or_default(|seq| { seq.iter() .filter_map(|item| match item { Value::Mapping(map) => map @@ -645,13 +645,12 @@ fn cleanup_proxy_groups(mut config: Mapping) -> Mapping { _ => None, }) .collect::>() - }) - .unwrap_or_default(); + }); let group_names = config .get("proxy-groups") .and_then(|v| v.as_sequence()) - .map(|seq| { + .map_or_default(|seq| { seq.iter() .filter_map(|item| { item.as_mapping() @@ -660,19 +659,17 @@ fn cleanup_proxy_groups(mut config: Mapping) -> Mapping { .map(std::convert::Into::into) }) .collect::>() - }) - .unwrap_or_default(); + }); let provider_names = config .get("proxy-providers") .and_then(Value::as_mapping) - .map(|map| { + .map_or_default(|map| { map.keys() .filter_map(Value::as_str) .map(std::convert::Into::into) .collect::>() - }) - .unwrap_or_default(); + }); let mut allowed_names = proxy_names; allowed_names.extend(group_names); @@ -716,15 +713,13 @@ fn ensure_fake_ip_range6(dns: &mut Mapping) { let is_fake_ip = dns .get("enhanced-mode") .and_then(|v| v.as_str()) - .map(|m| m == "fake-ip") - .unwrap_or(true); + .is_none_or(|m| m == "fake-ip"); // Hand-edited YAML may leave the key present but empty. let range6_missing = dns .get("fake-ip-range6") .and_then(|v| v.as_str()) - .map(|s| s.trim().is_empty()) - .unwrap_or(true); + .is_none_or(|s| s.trim().is_empty()); if ipv6_enabled && is_fake_ip && range6_missing { dns.insert(Value::from("fake-ip-range6"), Value::from("2001:2::0/64")); @@ -747,8 +742,7 @@ fn take_mapping(config: &mut Mapping, key: &str) -> Mapping { config .get_mut(key) .and_then(Value::as_mapping_mut) - .map(std::mem::take) - .unwrap_or_default() + .map_or_default(std::mem::take) } fn merge_dns_config(mut config: Mapping, mut dns_config: Mapping) -> (Mapping, Mapping) { diff --git a/src-tauri/src/enhance/seq.rs b/src-tauri/src/enhance/seq.rs index 826f2fc47..11e083151 100644 --- a/src-tauri/src/enhance/seq.rs +++ b/src-tauri/src/enhance/seq.rs @@ -20,14 +20,10 @@ fn collect_proxy_names(seq: &Sequence) -> Vec { } fn is_selector_group(group_map: &Mapping) -> bool { - group_map - .get("type") - .and_then(Value::as_str) - .map(|value| { - let value = value.to_ascii_lowercase(); - value == "select" || value == "selector" - }) - .unwrap_or(false) + group_map.get("type").and_then(Value::as_str).is_some_and(|value| { + let value = value.to_ascii_lowercase(); + value == "select" || value == "selector" + }) } pub fn use_seq(seq: SeqMap, mut config: Mapping, field: &str) -> Mapping { @@ -155,7 +151,6 @@ mod tests { use serde_yaml_ng::Value; #[test] - #[allow(clippy::unwrap_used)] #[allow(clippy::expect_used)] fn test_delete_proxy_and_references() { let config_str = r#" @@ -233,7 +228,6 @@ proxy-groups: } #[test] - #[allow(clippy::unwrap_used)] #[allow(clippy::expect_used)] fn test_add_new_proxies_to_first_selector_group() { let config_str = r#" @@ -303,7 +297,6 @@ proxy-groups: } #[test] - #[allow(clippy::unwrap_used)] #[allow(clippy::expect_used)] fn test_preserve_non_sequence_proxy_groups() { let config_str = r#" diff --git a/src-tauri/src/feat/backup.rs b/src-tauri/src/feat/backup.rs index 11f82f78b..0042de6dc 100644 --- a/src-tauri/src/feat/backup.rs +++ b/src-tauri/src/feat/backup.rs @@ -235,8 +235,7 @@ pub async fn import_local_backup(source: String) -> Result { let ext = source_path .extension() .and_then(|ext| ext.to_str()) - .map(|ext| ext.to_ascii_lowercase()) - .unwrap_or_default(); + .map_or_default(|ext| ext.to_ascii_lowercase()); if ext != "zip" { return Err(anyhow!("Only .zip backup files are supported")); } @@ -314,8 +313,7 @@ pub async fn list_local_backup() -> Result> { }; let last_modified = metadata .modified() - .map(|time| chrono::DateTime::::from(time).to_rfc3339()) - .unwrap_or_default(); + .map_or_default(|time| chrono::DateTime::::from(time).to_rfc3339()); backups.push(LocalBackupFile { filename: file_name.into(), path: path.to_string_lossy().into(), diff --git a/src-tauri/src/feat/core_upgrade.rs b/src-tauri/src/feat/core_upgrade.rs index dfb415aa0..645274ec1 100644 --- a/src-tauri/src/feat/core_upgrade.rs +++ b/src-tauri/src/feat/core_upgrade.rs @@ -487,8 +487,8 @@ mod tests { #[test] fn package_urls_pin_the_resolved_version() { - let release = package_url(false, "v1.19.30").unwrap_or_default(); - let alpha = package_url(true, "alpha-c0e43eb").unwrap_or_default(); + let release = package_url(false, "v1.19.30").unwrap(); + let alpha = package_url(true, "alpha-c0e43eb").unwrap(); assert!(release.contains("/releases/download/v1.19.30/"), "{release}"); assert!( release.ends_with("-v1.19.30.gz") || release.ends_with("-v1.19.30.zip"), diff --git a/src-tauri/src/feat/icon.rs b/src-tauri/src/feat/icon.rs index 2ac973002..0f8bb2da3 100644 --- a/src-tauri/src/feat/icon.rs +++ b/src-tauri/src/feat/icon.rs @@ -44,7 +44,7 @@ fn ensure_icon_cache_target(icon_cache_dir: &Path, file_name: &str) -> CmdResult } fn normalized_text_prefix(content: &[u8]) -> std::string::String { - let content = content.strip_prefix(&[0xEF, 0xBB, 0xBF]).unwrap_or(content); + let content = content.trim_prefix(&[0xEF, 0xBB, 0xBF]); let start = content .iter() .position(|byte| !byte.is_ascii_whitespace()) diff --git a/src-tauri/src/feat/listener.rs b/src-tauri/src/feat/listener.rs index 2e23d82a7..8da7cd0aa 100644 --- a/src-tauri/src/feat/listener.rs +++ b/src-tauri/src/feat/listener.rs @@ -19,7 +19,7 @@ use crate::{ use anyhow::{Context as _, Result, anyhow, bail}; use clash_verge_draft::DraftTransaction; use clash_verge_logging::{Type, logging}; -use scopeguard::{ScopeGuard, defer, guard}; +use std::mem::DropGuard; pub async fn probe_listener(request: ListenerProbe) -> Result { AsyncHandler::spawn_blocking(move || probe_listener_sync(&request)) @@ -35,9 +35,9 @@ pub async fn save_proxy_ports(settings: ProxyPortSettings) -> Result Result Result ExitCode { #[cfg(not(all(feature = "perf-harness", target_os = "macos")))] fn main() -> ExitCode { - let default_parallelism = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1); + let default_parallelism = std::thread::available_parallelism().map_or(1, |n| n.get()); let worker_limit = std::cmp::min(default_parallelism, 4); let blocking_limit = 2 * worker_limit; diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 6ad2feaa3..2d5f4cb76 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -3,10 +3,10 @@ use crate::{config::with_encryption, process::AsyncHandler}; use anyhow::{Context as _, Result, anyhow, bail}; use clash_verge_logging::{Type, logging}; use nanoid::nanoid; -use scopeguard::{ScopeGuard, guard}; use serde::{Serialize, de::DeserializeOwned}; use serde_yaml_ng::{Mapping, Value}; use std::{ + mem::DropGuard, path::{Path, PathBuf}, str::FromStr, }; @@ -168,7 +168,7 @@ pub async fn save_yaml_str(path: &Path, yaml_str: &str) -> Result<()> { } }; - let temporary = guard(temporary, |path| { + let temporary = DropGuard::new(temporary, |path| { let _ = std::fs::remove_file(path); }); let mut file = tokio::fs::File::from_std(file); @@ -195,7 +195,7 @@ pub async fn save_yaml_str(path: &Path, yaml_str: &str) -> Result<()> { std::fs::rename(temporary.as_path(), path) .with_context(|| format!("failed to replace file \"{}\"", path.display()))?; - let _ = ScopeGuard::into_inner(temporary); + let _ = DropGuard::dismiss(temporary); Ok(()) } diff --git a/src-tauri/src/utils/window_manager.rs b/src-tauri/src/utils/window_manager.rs index 0c386a897..05a16181d 100644 --- a/src-tauri/src/utils/window_manager.rs +++ b/src-tauri/src/utils/window_manager.rs @@ -234,15 +234,15 @@ impl WindowManager { } pub fn is_main_window_visible(window: Option<&WebviewWindow>) -> bool { - window.map(|w| w.is_visible().unwrap_or(false)).unwrap_or(false) + window.is_some_and(|w| w.is_visible().unwrap_or(false)) } pub fn is_main_window_focused(window: Option<&WebviewWindow>) -> bool { - window.map(|w| w.is_focused().unwrap_or(false)).unwrap_or(false) + window.is_some_and(|w| w.is_focused().unwrap_or(false)) } fn is_main_window_minimized(window: Option<&WebviewWindow>) -> bool { - window.map(|w| w.is_minimized().unwrap_or(false)).unwrap_or(false) + window.is_some_and(|w| w.is_minimized().unwrap_or(false)) } /// Keep new windows hidden until the frontend overlay renders, avoiding a theme flash.