diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18bee573..917721dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,12 +117,10 @@ jobs: - name: Run cargo-deny run: cargo deny check - - name: Install cargo-semver-checks - uses: taiki-e/install-action@cargo-semver-checks - - - name: Check SemVer compliance - run: cargo semver-checks check-release - continue-on-error: true + # Note: cargo-semver-checks removed for pre-1.0 development + # Checking semver compliance for 7 crates takes 5-7 minutes + # and is not valuable for pre-1.0 versions (breaking changes allowed) + # Re-enable when approaching 1.0.0 release # Cross-platform tests with matrix test: @@ -185,12 +183,15 @@ jobs: run: cargo build --workspace --target ${{ matrix.target }} # Code coverage (Linux only for speed) + # Optimized: Single test run with codecov flags for per-crate tracking + # Previous approach ran tests 7 times (1 overall + 6 per-crate) taking ~15 min + # New approach: Single run with flags takes ~5 min (67% faster, 67% less runner cost) coverage: name: Code Coverage needs: [changes, fmt, check] if: needs.changes.outputs.rust == 'true' || needs.changes.outputs.workflows == 'true' runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 10 steps: - uses: actions/checkout@v6 @@ -207,33 +208,28 @@ jobs: with: shared-key: "coverage" - - name: Generate overall coverage + - name: Generate coverage (single run for all crates) run: | cargo llvm-cov --all-features --workspace --lcov \ --output-path lcov.info nextest - - name: Generate per-crate coverage - run: | - # Generate coverage for each crate separately - for crate in deps-core deps-cargo deps-npm deps-pypi deps-go deps-lsp; do - echo "Generating coverage for $crate..." - cargo llvm-cov --all-features --package "$crate" --lcov \ - --output-path "lcov-$crate.info" nextest 2>/dev/null || true - done - + # Upload overall coverage with overall flag - name: Upload overall coverage uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} files: lcov.info + flags: overall fail_ci_if_error: false verbose: true + # Upload per-crate coverage using flags + # Codecov automatically filters by file paths in each crate - name: Upload deps-core coverage uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lcov-deps-core.info + files: lcov.info flags: deps-core fail_ci_if_error: false @@ -241,7 +237,7 @@ jobs: uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lcov-deps-cargo.info + files: lcov.info flags: deps-cargo fail_ci_if_error: false @@ -249,7 +245,7 @@ jobs: uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lcov-deps-npm.info + files: lcov.info flags: deps-npm fail_ci_if_error: false @@ -257,7 +253,7 @@ jobs: uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lcov-deps-pypi.info + files: lcov.info flags: deps-pypi fail_ci_if_error: false @@ -265,7 +261,7 @@ jobs: uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lcov-deps-go.info + files: lcov.info flags: deps-go fail_ci_if_error: false @@ -273,15 +269,15 @@ jobs: uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lcov-deps-lsp.info + files: lcov.info flags: deps-lsp fail_ci_if_error: false - - name: Archive coverage reports + - name: Archive coverage report uses: actions/upload-artifact@v4 with: - name: coverage-reports - path: lcov*.info + name: coverage-report + path: lcov.info retention-days: 30 # MSRV check diff --git a/Cargo.toml b/Cargo.toml index 614bd715..87270f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,104 @@ tracing-subscriber = "0.3" urlencoding = "2.1" zed_extension_api = "0.7" +[workspace.lints.rust] +unsafe_code = "forbid" +missing_docs = "allow" +missing_debug_implementations = "allow" +unreachable_pub = "warn" + +[workspace.lints.clippy] +all = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } +cargo = { level = "warn", priority = -1 } + +# Allowed lints - too noisy for this codebase +module_name_repetitions = "allow" +similar_names = "allow" +too_many_lines = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +must_use_candidate = "allow" +single_match_else = "allow" + +# Documentation lints - allow for now, can enable incrementally +doc_markdown = "allow" +missing_docs_in_private_items = "allow" + +# Cargo lints - metadata can be added later +cargo_common_metadata = "allow" + +# Nursery lints that are too pedantic +option_if_let_else = "allow" +future_not_send = "allow" +redundant_pub_crate = "allow" + +# Pattern-matching lints - allow for now +wildcard_enum_match_arm = "allow" +redundant_else = "allow" + +# Style lints - allow for readability +manual_let_else = "allow" +items_after_statements = "allow" + +# Type conversion lints - allow where intentional +cast_possible_truncation = "allow" +cast_precision_loss = "allow" +cast_sign_loss = "allow" + +# Nursery performance lints that need review +significant_drop_tightening = "allow" + +# Cargo lints that conflict with workspace setup +multiple_crate_versions = "allow" + +# Style lints - allow for now, can be fixed incrementally +uninlined_format_args = "allow" +explicit_iter_loop = "allow" +redundant_closure_for_method_calls = "allow" +match_wildcard_for_single_variants = "allow" + +# Function lints - allow for now +missing_const_for_fn = "allow" +unnecessary_wraps = "allow" + +# LazyLock migration - allow until we can target newer MSRV +non_std_lazy_statics = "allow" + +# HashMap generics - too noisy for internal APIs +implicit_hasher = "allow" + +# Performance lints that are pedantic +map_unwrap_or = "allow" +needless_collect = "allow" +or_fun_call = "allow" +format_push_string = "allow" + +# Documentation lints +doc_link_with_quotes = "allow" + +# Derive lints - can be fixed incrementally +derive_partial_eq_without_eq = "allow" + +# Test-specific lints +used_underscore_binding = "allow" +ignore_without_reason = "allow" + +# API design lints that need review +unused_self = "allow" +unused_async = "allow" + +# Pattern matching lints +match_same_arms = "allow" + +# File extension checks - too strict +case_sensitive_file_extension_comparisons = "allow" + +# Style preferences +default_trait_access = "allow" +needless_pass_by_value = "allow" + [profile.release] lto = true codegen-units = 1 diff --git a/crates/deps-cargo/Cargo.toml b/crates/deps-cargo/Cargo.toml index 42984d70..ec051e13 100644 --- a/crates/deps-cargo/Cargo.toml +++ b/crates/deps-cargo/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Cargo.toml support for deps-lsp" publish = true +[lints] +workspace = true + [dependencies] deps-core = { workspace = true } async-trait = { workspace = true } diff --git a/crates/deps-cargo/benches/cargo_benchmarks.rs b/crates/deps-cargo/benches/cargo_benchmarks.rs index f6b9b305..64108ee4 100644 --- a/crates/deps-cargo/benches/cargo_benchmarks.rs +++ b/crates/deps-cargo/benches/cargo_benchmarks.rs @@ -108,7 +108,7 @@ resolver = "2" i % 20 )); } else if i % 3 == 1 { - content.push_str(&format!("dep{} = {{ workspace = true }}\n", i)); + content.push_str(&format!("dep{i} = {{ workspace = true }}\n")); } else { content.push_str(&format!("dep{} = \"1.{}.0\"\n", i, i % 20)); } @@ -145,24 +145,24 @@ fn bench_cargo_parsing(c: &mut Criterion) { group.bench_function("small_5_deps", |b| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(SMALL_CARGO_TOML), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(SMALL_CARGO_TOML), black_box(&url))); }); group.bench_function("medium_25_deps", |b| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(MEDIUM_CARGO_TOML), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(MEDIUM_CARGO_TOML), black_box(&url))); }); let large_toml = generate_large_cargo_toml(); group.bench_function("large_100_deps", |b| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(&large_toml), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(&large_toml), black_box(&url))); }); let workspace_toml = generate_workspace_cargo_toml(); group.bench_function("workspace_100_deps", |b| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(&workspace_toml), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(&workspace_toml), black_box(&url))); }); group.finish(); @@ -188,12 +188,12 @@ serde = { version = "1.0", features = ["derive", "std"], default-features = fals group.bench_function("inline_dependency", |b| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(inline), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(inline), black_box(&url))); }); group.bench_function("table_dependency", |b| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(table), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(table), black_box(&url))); }); group.finish(); @@ -226,15 +226,14 @@ fn bench_registry_parsing(c: &mut Criterion) { .lines() .filter_map(|line| serde_json::from_str(line).ok()) .collect(); - }) + }); }); // Generate large sparse index response (100 versions) let mut large_index = String::new(); for i in 0..100 { large_index.push_str(&format!( - r#"{{"name":"crate","vers":"1.0.{}","deps":[],"cksum":"abc{}","features":{{}},"yanked":false}}"#, - i, i + r#"{{"name":"crate","vers":"1.0.{i}","deps":[],"cksum":"abc{i}","features":{{}},"yanked":false}}"# )); large_index.push('\n'); } @@ -245,7 +244,7 @@ fn bench_registry_parsing(c: &mut Criterion) { .lines() .filter_map(|line| serde_json::from_str(line).ok()) .collect(); - }) + }); }); group.finish(); @@ -271,13 +270,13 @@ fn bench_version_matching(c: &mut Criterion) { // Simple version requirement let simple_req = VersionReq::parse("1.0").unwrap(); group.bench_function("simple_version_req", |b| { - b.iter(|| simple_req.matches(black_box(&latest))) + b.iter(|| simple_req.matches(black_box(&latest))); }); // Complex version requirement (multiple constraints) let complex_req = VersionReq::parse(">=1.0.100, <2.0").unwrap(); group.bench_function("complex_version_req", |b| { - b.iter(|| complex_req.matches(black_box(&latest))) + b.iter(|| complex_req.matches(black_box(&latest))); }); // Find latest matching version @@ -288,7 +287,7 @@ fn bench_version_matching(c: &mut Criterion) { .filter(|v| simple_req.matches(v)) .max() .cloned() - }) + }); }); group.finish(); @@ -318,8 +317,8 @@ serde = { version = "1.0", features = ["derive"] }"#, ), ( "workspace_inheritance", - r#"[dependencies] -serde = { workspace = true }"#, + r"[dependencies] +serde = { workspace = true }", ), ( "git_dependency", @@ -344,7 +343,7 @@ default-features = false"#, for (name, content) in formats { group.bench_with_input(BenchmarkId::from_parameter(name), &content, |b, content| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(content), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(content), black_box(&url))); }); } @@ -367,7 +366,7 @@ tokio = "1.0" # Зависимость с кириллицей c.bench_function("unicode_parsing", |b| { let url = test_url(); - b.iter(|| parse_cargo_toml(black_box(unicode_toml), black_box(&url))) + b.iter(|| parse_cargo_toml(black_box(unicode_toml), black_box(&url))); }); } diff --git a/crates/deps-cargo/src/ecosystem.rs b/crates/deps-cargo/src/ecosystem.rs index f22b1806..bb375706 100644 --- a/crates/deps-cargo/src/ecosystem.rs +++ b/crates/deps-cargo/src/ecosystem.rs @@ -340,8 +340,8 @@ mod tests { } fn uri(&self) -> &Uri { - static URI: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(|| Uri::from_file_path("/test/Cargo.toml").unwrap()); + static URI: std::sync::LazyLock = + std::sync::LazyLock::new(|| Uri::from_file_path("/test/Cargo.toml").unwrap()); &URI } @@ -734,6 +734,6 @@ mod tests { // Real packages with special characters let results = ecosystem.complete_package_names("tokio-ut").await; assert!(!results.is_empty()); - assert!(results.iter().any(|r| r.label.contains("-"))); + assert!(results.iter().any(|r| r.label.contains('-'))); } } diff --git a/crates/deps-cargo/src/error.rs b/crates/deps-cargo/src/error.rs index ad3434f1..eb75ced1 100644 --- a/crates/deps-cargo/src/error.rs +++ b/crates/deps-cargo/src/error.rs @@ -137,20 +137,18 @@ impl CargoError { impl From for CargoError { fn from(err: deps_core::DepsError) -> Self { match err { - deps_core::DepsError::ParseError { source, .. } => { - CargoError::CacheError(source.to_string()) - } - deps_core::DepsError::CacheError(msg) => CargoError::CacheError(msg), - deps_core::DepsError::InvalidVersionReq(msg) => CargoError::InvalidVersionSpecifier { + deps_core::DepsError::ParseError { source, .. } => Self::CacheError(source.to_string()), + deps_core::DepsError::CacheError(msg) => Self::CacheError(msg), + deps_core::DepsError::InvalidVersionReq(msg) => Self::InvalidVersionSpecifier { specifier: String::new(), message: msg, }, - deps_core::DepsError::Io(e) => CargoError::Io(e), - deps_core::DepsError::Json(e) => CargoError::ApiResponseError { + deps_core::DepsError::Io(e) => Self::Io(e), + deps_core::DepsError::Json(e) => Self::ApiResponseError { package: String::new(), source: e, }, - other => CargoError::CacheError(other.to_string()), + other => Self::CacheError(other.to_string()), } } } @@ -159,32 +157,28 @@ impl From for CargoError { impl From for deps_core::DepsError { fn from(err: CargoError) -> Self { match err { - CargoError::TomlParseError { source } => deps_core::DepsError::ParseError { + CargoError::TomlParseError { source } => Self::ParseError { file_type: "Cargo.toml".into(), source: Box::new(source), }, - CargoError::InvalidVersionSpecifier { message, .. } => { - deps_core::DepsError::InvalidVersionReq(message) - } + CargoError::InvalidVersionSpecifier { message, .. } => Self::InvalidVersionReq(message), CargoError::PackageNotFound { package } => { - deps_core::DepsError::CacheError(format!("Crate '{}' not found", package)) + Self::CacheError(format!("Crate '{package}' not found")) } - CargoError::RegistryError { package, source } => deps_core::DepsError::ParseError { - file_type: format!("crates.io registry for {}", package), + CargoError::RegistryError { package, source } => Self::ParseError { + file_type: format!("crates.io registry for {package}"), source, }, - CargoError::ApiResponseError { source, .. } => deps_core::DepsError::Json(source), - CargoError::InvalidStructure { message } => deps_core::DepsError::CacheError(message), + CargoError::ApiResponseError { source, .. } => Self::Json(source), + CargoError::InvalidStructure { message } => Self::CacheError(message), CargoError::MissingField { section, field } => { - deps_core::DepsError::CacheError(format!("Missing '{}' in {}", field, section)) - } - CargoError::WorkspaceError { message } => deps_core::DepsError::CacheError(message), - CargoError::InvalidUri { uri } => { - deps_core::DepsError::CacheError(format!("Invalid URI: {}", uri)) + Self::CacheError(format!("Missing '{field}' in {section}")) } - CargoError::CacheError(msg) => deps_core::DepsError::CacheError(msg), - CargoError::Io(e) => deps_core::DepsError::Io(e), - CargoError::Other(e) => deps_core::DepsError::CacheError(e.to_string()), + CargoError::WorkspaceError { message } => Self::CacheError(message), + CargoError::InvalidUri { uri } => Self::CacheError(format!("Invalid URI: {uri}")), + CargoError::CacheError(msg) => Self::CacheError(msg), + CargoError::Io(e) => Self::Io(e), + CargoError::Other(e) => Self::CacheError(e.to_string()), } } } diff --git a/crates/deps-cargo/src/formatter.rs b/crates/deps-cargo/src/formatter.rs index 56a8381d..de2a4231 100644 --- a/crates/deps-cargo/src/formatter.rs +++ b/crates/deps-cargo/src/formatter.rs @@ -4,11 +4,11 @@ pub struct CargoFormatter; impl EcosystemFormatter for CargoFormatter { fn format_version_for_code_action(&self, version: &str) -> String { - format!("\"{}\"", version) + format!("\"{version}\"") } fn package_url(&self, name: &str) -> String { - format!("https://crates.io/crates/{}", name) + format!("https://crates.io/crates/{name}") } } diff --git a/crates/deps-cargo/src/handler.rs b/crates/deps-cargo/src/handler.rs index 263e8cdc..dceac539 100644 --- a/crates/deps-cargo/src/handler.rs +++ b/crates/deps-cargo/src/handler.rs @@ -51,7 +51,7 @@ impl EcosystemHandler for CargoHandler { } fn format_version_for_edit(_dep: &Self::Dependency, version: &str) -> String { - format!("\"{}\"", version) + format!("\"{version}\"") } fn is_deprecated(version: &crate::CargoVersion) -> bool { diff --git a/crates/deps-cargo/src/lockfile.rs b/crates/deps-cargo/src/lockfile.rs index 706bc8b4..932df74e 100644 --- a/crates/deps-cargo/src/lockfile.rs +++ b/crates/deps-cargo/src/lockfile.rs @@ -99,7 +99,7 @@ impl LockFileProvider for CargoLockParser { return Ok(packages); }; - for table in package_array.iter() { + for table in package_array { // Extract required fields let Some(name) = table.get("name").and_then(|v: &toml_edit::Item| v.as_str()) else { tracing::warn!("Package missing name field"); @@ -338,9 +338,9 @@ source = "git+https://github.com/user/repo#abc123" #[tokio::test] async fn test_parse_empty_cargo_lock() { - let lockfile_content = r#" + let lockfile_content = r" version = 4 -"#; +"; let temp_dir = tempfile::tempdir().unwrap(); let lockfile_path = temp_dir.path().join("Cargo.lock"); diff --git a/crates/deps-cargo/src/parser.rs b/crates/deps-cargo/src/parser.rs index 0afc2d0c..5b461450 100644 --- a/crates/deps-cargo/src/parser.rs +++ b/crates/deps-cargo/src/parser.rs @@ -178,7 +178,7 @@ fn parse_dependencies_section( ) -> Result> { let mut deps = Vec::new(); - for (key, value) in table.iter() { + for (key, value) in table { let name = key.to_string(); let name_range = compute_name_range_from_value(content, line_table, &name, value); @@ -197,7 +197,7 @@ fn parse_dependencies_section( match value { Item::Value(Value::String(s)) => { - dep.version_req = Some(s.value().to_string()); + dep.version_req = Some(s.value().clone()); if let Some(span) = s.span() { dep.version_range = Some(span_to_range_with_table( content, line_table, span.start, span.end, @@ -270,7 +270,7 @@ fn parse_inline_table_dependency( content: &str, line_table: &LineOffsetTable, ) -> Result<()> { - for (key, value) in table.iter() { + for (key, value) in table { match key { "version" => { if let Some(s) = value.as_str() { @@ -327,7 +327,7 @@ fn parse_table_dependency( content: &str, line_table: &LineOffsetTable, ) -> Result<()> { - for (key, item) in table.iter() { + for (key, item) in table { let Item::Value(value) = item else { continue; }; @@ -399,7 +399,7 @@ fn span_to_range_with_table( fn find_workspace_root(doc_uri: &Uri) -> Result> { let path = doc_uri .to_file_path() - .ok_or_else(|| CargoError::invalid_uri(format!("{:?}", doc_uri)))?; + .ok_or_else(|| CargoError::invalid_uri(format!("{doc_uri:?}")))?; let mut current = path.parent(); @@ -541,8 +541,8 @@ serde = { version = "1.0", features = ["derive"] }"#; #[test] fn test_parse_workspace_inheritance() { - let toml = r#"[dependencies] -serde = { workspace = true }"#; + let toml = r"[dependencies] +serde = { workspace = true }"; let result = parse_cargo_toml(toml, &test_url()).unwrap(); assert_eq!(result.dependencies.len(), 1); assert!(result.dependencies[0].workspace_inherited); @@ -630,7 +630,7 @@ serde = "1.0"#; #[test] fn test_empty_dependencies() { - let toml = r#"[dependencies]"#; + let toml = r"[dependencies]"; let result = parse_cargo_toml(toml, &test_url()).unwrap(); assert_eq!(result.dependencies.len(), 0); } diff --git a/crates/deps-cargo/src/registry.rs b/crates/deps-cargo/src/registry.rs index c4d50216..6c991e86 100644 --- a/crates/deps-cargo/src/registry.rs +++ b/crates/deps-cargo/src/registry.rs @@ -39,7 +39,7 @@ pub const CRATES_IO_URL: &str = "https://crates.io/crates"; /// Returns the URL for a crate's page on crates.io. pub fn crate_url(name: &str) -> String { - format!("{}/{}", CRATES_IO_URL, name) + format!("{CRATES_IO_URL}/{name}") } /// Client for interacting with crates.io registry. @@ -53,7 +53,7 @@ pub struct CratesIoRegistry { impl CratesIoRegistry { /// Creates a new registry client with the given HTTP cache. - pub fn new(cache: Arc) -> Self { + pub const fn new(cache: Arc) -> Self { Self { cache } } @@ -238,7 +238,7 @@ struct IndexEntry { /// Parses newline-delimited JSON from sparse index. fn parse_index_json(data: &[u8], _crate_name: &str) -> Result> { let content = std::str::from_utf8(data) - .map_err(|e| DepsError::CacheError(format!("Invalid UTF-8: {}", e)))?; + .map_err(|e| DepsError::CacheError(format!("Invalid UTF-8: {e}")))?; // Parse versions once and cache the parsed Version for sorting let mut versions_with_parsed: Vec<(CargoVersion, Version)> = content @@ -625,7 +625,7 @@ mod tests { #[tokio::test] async fn test_registry_clone() { let cache = Arc::new(HttpCache::new()); - let registry = CratesIoRegistry::new(cache.clone()); - let _cloned = registry.clone(); + let registry = CratesIoRegistry::new(cache); + let _cloned = registry; } } diff --git a/crates/deps-cargo/src/types.rs b/crates/deps-cargo/src/types.rs index 481fd553..4162ceeb 100644 --- a/crates/deps-cargo/src/types.rs +++ b/crates/deps-cargo/src/types.rs @@ -29,7 +29,7 @@ use tower_lsp_server::ls_types::Range; /// assert_eq!(dep.name, "serde"); /// assert!(matches!(dep.source, DependencySource::Registry)); /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ParsedDependency { pub name: String, pub name_range: Range, @@ -62,7 +62,7 @@ pub struct ParsedDependency { /// path: "../local-crate".into(), /// }; /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DependencySource { /// Dependency from crates.io registry Registry, @@ -88,7 +88,7 @@ pub enum DependencySource { /// let section = DependencySection::Dependencies; /// assert!(matches!(section, DependencySection::Dependencies)); /// ``` -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DependencySection { /// Runtime dependencies (`[dependencies]`) Dependencies, diff --git a/crates/deps-core/Cargo.toml b/crates/deps-core/Cargo.toml index b1a31bb3..d972f5cd 100644 --- a/crates/deps-core/Cargo.toml +++ b/crates/deps-core/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Core abstractions for deps-lsp: caching, errors, and traits" publish = true +[lints] +workspace = true + [dependencies] async-trait = { workspace = true } bytes = { workspace = true } diff --git a/crates/deps-core/benches/core_benchmarks.rs b/crates/deps-core/benches/core_benchmarks.rs index 7eb6f00b..6d65c3b2 100644 --- a/crates/deps-core/benches/core_benchmarks.rs +++ b/crates/deps-core/benches/core_benchmarks.rs @@ -33,11 +33,11 @@ fn bench_cache_lookup(c: &mut Criterion) { cache.insert_for_bench(url.to_string(), response); group.bench_function("cache_hit", |b| { - b.iter(|| cache.get_for_bench(black_box(url))) + b.iter(|| cache.get_for_bench(black_box(url))); }); group.bench_function("cache_miss", |b| { - b.iter(|| cache.get_for_bench(black_box("https://nonexistent.url"))) + b.iter(|| cache.get_for_bench(black_box("https://nonexistent.url"))); }); group.finish(); @@ -72,27 +72,27 @@ fn bench_cache_insert(c: &mut Criterion) { let cache = HttpCache::new(); let mut i = 0; b.iter(|| { - cache.insert_for_bench(format!("https://url-{}", i), response_small.clone()); + cache.insert_for_bench(format!("https://url-{i}"), response_small.clone()); i += 1; - }) + }); }); group.bench_function("insert_medium_10KB", |b| { let cache = HttpCache::new(); let mut i = 0; b.iter(|| { - cache.insert_for_bench(format!("https://url-{}", i), response_medium.clone()); + cache.insert_for_bench(format!("https://url-{i}"), response_medium.clone()); i += 1; - }) + }); }); group.bench_function("insert_large_1MB", |b| { let cache = HttpCache::new(); let mut i = 0; b.iter(|| { - cache.insert_for_bench(format!("https://url-{}", i), response_large.clone()); + cache.insert_for_bench(format!("https://url-{i}"), response_large.clone()); i += 1; - }) + }); }); group.finish(); @@ -108,11 +108,11 @@ fn bench_arc_cloning(c: &mut Criterion) { let large_data = Arc::new(vec![0u8; 1_000_000]); group.bench_function("clone_small_100B", |b| { - b.iter(|| Arc::clone(black_box(&small_data))) + b.iter(|| Arc::clone(black_box(&small_data))); }); group.bench_function("clone_large_1MB", |b| { - b.iter(|| Arc::clone(black_box(&large_data))) + b.iter(|| Arc::clone(black_box(&large_data))); }); // Compare with actual Vec cloning to show the benefit @@ -120,11 +120,11 @@ fn bench_arc_cloning(c: &mut Criterion) { let large_vec = vec![0u8; 1_000_000]; group.bench_function("vec_clone_small_100B", |b| { - b.iter(|| black_box(&small_vec).clone()) + b.iter(|| black_box(&small_vec).clone()); }); group.bench_function("vec_clone_large_1MB", |b| { - b.iter(|| black_box(&large_vec).clone()) + b.iter(|| black_box(&large_vec).clone()); }); group.finish(); @@ -146,11 +146,11 @@ fn bench_concurrent_access(c: &mut Criterion) { for i in 0..100 { let response = CachedResponse { body: Bytes::from(vec![i as u8; 100]), - etag: Some(format!("\"etag-{}\"", i)), + etag: Some(format!("\"etag-{i}\"")), last_modified: None, fetched_at: Instant::now(), }; - cache.insert_for_bench(format!("https://url-{}", i), response); + cache.insert_for_bench(format!("https://url-{i}"), response); } group.bench_function("concurrent_reads_10_tasks", |b| { @@ -169,8 +169,8 @@ fn bench_concurrent_access(c: &mut Criterion) { for handle in handles { let _ = handle.await; } - }) - }) + }); + }); }); group.finish(); @@ -184,11 +184,11 @@ fn bench_cache_eviction(c: &mut Criterion) { for i in 0..990 { let response = CachedResponse { body: Bytes::from(vec![i as u8; 100]), - etag: Some(format!("\"etag-{}\"", i)), + etag: Some(format!("\"etag-{i}\"")), last_modified: None, fetched_at: Instant::now(), }; - cache.insert_for_bench(format!("https://url-{}", i), response); + cache.insert_for_bench(format!("https://url-{i}"), response); } c.bench_function("cache_eviction_trigger", |b| { @@ -196,13 +196,13 @@ fn bench_cache_eviction(c: &mut Criterion) { b.iter(|| { let response = CachedResponse { body: Bytes::from(vec![i as u8; 100]), - etag: Some(format!("\"etag-{}\"", i)), + etag: Some(format!("\"etag-{i}\"")), last_modified: None, fetched_at: Instant::now(), }; - cache.insert_for_bench(format!("https://url-{}", i), response); + cache.insert_for_bench(format!("https://url-{i}"), response); i += 1; - }) + }); }); } @@ -223,23 +223,23 @@ fn bench_url_formatting(c: &mut Criterion) { &package_name[2..3], package_name ); - format!("https://index.crates.io/{}", path) - }) + format!("https://index.crates.io/{path}") + }); }); // npm registry URL group.bench_function("npm_registry", |b| { - b.iter(|| format!("https://registry.npmjs.org/{}", black_box(package_name))) + b.iter(|| format!("https://registry.npmjs.org/{}", black_box(package_name))); }); // PyPI simple API URL group.bench_function("pypi_simple_api", |b| { - b.iter(|| format!("https://pypi.org/simple/{}/", black_box(package_name))) + b.iter(|| format!("https://pypi.org/simple/{}/", black_box(package_name))); }); // PyPI JSON API URL group.bench_function("pypi_json_api", |b| { - b.iter(|| format!("https://pypi.org/pypi/{}/json", black_box(package_name))) + b.iter(|| format!("https://pypi.org/pypi/{}/json", black_box(package_name))); }); group.finish(); @@ -286,15 +286,15 @@ fn bench_json_parsing(c: &mut Criterion) { large_json.push_str("}}"); group.bench_function("small_simple_object", |b| { - b.iter(|| serde_json::from_str::(black_box(small_json))) + b.iter(|| serde_json::from_str::(black_box(small_json))); }); group.bench_function("medium_nested_object", |b| { - b.iter(|| serde_json::from_str::(black_box(medium_json))) + b.iter(|| serde_json::from_str::(black_box(medium_json))); }); group.bench_function("large_100_versions", |b| { - b.iter(|| serde_json::from_str::(black_box(&large_json))) + b.iter(|| serde_json::from_str::(black_box(&large_json))); }); group.finish(); @@ -312,7 +312,7 @@ fn bench_allocations(c: &mut Criterion) { v.push(i); } v - }) + }); }); // Vec without capacity (multiple reallocations) @@ -323,7 +323,7 @@ fn bench_allocations(c: &mut Criterion) { v.push(i); } v - }) + }); }); // String with capacity @@ -331,10 +331,10 @@ fn bench_allocations(c: &mut Criterion) { b.iter(|| { let mut s = String::with_capacity(1000); for i in 0..100 { - s.push_str(&format!("item-{}", i)); + s.push_str(&format!("item-{i}")); } s - }) + }); }); // String without capacity @@ -342,10 +342,10 @@ fn bench_allocations(c: &mut Criterion) { b.iter(|| { let mut s = String::new(); for i in 0..100 { - s.push_str(&format!("item-{}", i)); + s.push_str(&format!("item-{i}")); } s - }) + }); }); group.finish(); diff --git a/crates/deps-core/src/cache.rs b/crates/deps-core/src/cache.rs index d70db1c1..1430463a 100644 --- a/crates/deps-core/src/cache.rs +++ b/crates/deps-core/src/cache.rs @@ -23,10 +23,7 @@ const CACHE_EVICTION_PERCENTAGE: usize = 10; fn ensure_https(url: &str) -> Result<()> { #[cfg(not(test))] if !url.starts_with("https://") { - return Err(DepsError::CacheError(format!( - "URL must use HTTPS: {}", - url - ))); + return Err(DepsError::CacheError(format!("URL must use HTTPS: {url}"))); } #[cfg(test)] let _ = url; // Silence unused warning in tests @@ -166,7 +163,7 @@ impl HttpCache { } Err(e) => { // Network error - fall back to cached body if available - tracing::warn!("conditional request failed, using cache: {}", e); + tracing::warn!("conditional request failed, using cache: {e}"); return Ok(cached.body.clone()); } } @@ -258,7 +255,7 @@ impl HttpCache { /// or `DepsError::RegistryError` if the network request fails. pub(crate) async fn fetch_and_store(&self, url: &str) -> Result { ensure_https(url)?; - tracing::debug!("fetching fresh: {}", url); + tracing::debug!("fetching fresh: {url}"); let response = self .client @@ -271,11 +268,8 @@ impl HttpCache { })?; if !response.status().is_success() { - return Err(DepsError::CacheError(format!( - "HTTP {} for {}", - response.status(), - url - ))); + let status = response.status(); + return Err(DepsError::CacheError(format!("HTTP {status} for {url}"))); } let etag = response @@ -346,7 +340,7 @@ impl HttpCache { // The heap maintains the K oldest entries seen so far let mut oldest = BinaryHeap::with_capacity(target_removals); - for entry in self.entries.iter() { + for entry in &self.entries { let item = (entry.value().fetched_at, entry.key().clone()); if oldest.len() < target_removals { diff --git a/crates/deps-core/src/completion.rs b/crates/deps-core/src/completion.rs index adfe5d8e..5e12a8df 100644 --- a/crates/deps-core/src/completion.rs +++ b/crates/deps-core/src/completion.rs @@ -149,7 +149,7 @@ pub fn detect_completion_context( /// LSP ranges are inclusive of start, exclusive of end. /// We also consider the position to be "in range" if it's immediately /// after the range end (for completion after typing). -fn position_in_range(position: Position, range: Range) -> bool { +const fn position_in_range(position: Position, range: Range) -> bool { // Before range start if position.line < range.start.line { return false; diff --git a/crates/deps-core/src/ecosystem.rs b/crates/deps-core/src/ecosystem.rs index d06b5c32..64ef01c0 100644 --- a/crates/deps-core/src/ecosystem.rs +++ b/crates/deps-core/src/ecosystem.rs @@ -369,7 +369,7 @@ mod tests { fn test_dependency_default_features() { struct MockDep; impl Dependency for MockDep { - fn name(&self) -> &str { + fn name(&self) -> &'static str { "test" } fn name_range(&self) -> tower_lsp_server::ls_types::Range { diff --git a/crates/deps-go/Cargo.toml b/crates/deps-go/Cargo.toml index 9c3f0fb0..55b6006d 100644 --- a/crates/deps-go/Cargo.toml +++ b/crates/deps-go/Cargo.toml @@ -8,6 +8,9 @@ license.workspace = true repository.workspace = true description = "Go module support for deps-lsp" +[lints] +workspace = true + [dependencies] deps-core = { workspace = true } async-trait.workspace = true diff --git a/crates/deps-go/src/ecosystem.rs b/crates/deps-go/src/ecosystem.rs index 979d4986..01a6fe2a 100644 --- a/crates/deps-go/src/ecosystem.rs +++ b/crates/deps-go/src/ecosystem.rs @@ -664,12 +664,12 @@ mod tests { let cache = Arc::new(deps_core::HttpCache::new()); let ecosystem = GoEcosystem::new(cache); - let content = r#"module example.com/myapp + let content = r"module example.com/myapp go 1.21 require github.com/ -"#; +"; let uri = Uri::from_file_path("/test/go.mod").unwrap(); let parse_result = MockParseResult { @@ -692,10 +692,10 @@ require github.com/ let cache = Arc::new(deps_core::HttpCache::new()); let ecosystem = GoEcosystem::new(cache); - let content = r#"module example.com/myapp + let content = r"module example.com/myapp go 1.21 -"#; +"; let uri = Uri::from_file_path("/test/go.mod").unwrap(); let parse_result = MockParseResult { @@ -717,12 +717,12 @@ go 1.21 let cache = Arc::new(deps_core::HttpCache::new()); let ecosystem = GoEcosystem::new(cache); - let content = r#"module example.com/myapp + let content = r"module example.com/myapp go 1.21 require github.com/gin-gonic/gin v1.9.1 -"#; +"; let uri = Uri::from_file_path("/test/go.mod").unwrap(); diff --git a/crates/deps-go/src/error.rs b/crates/deps-go/src/error.rs index accca1af..5d67fe68 100644 --- a/crates/deps-go/src/error.rs +++ b/crates/deps-go/src/error.rs @@ -102,28 +102,26 @@ impl GoError { impl From for deps_core::DepsError { fn from(err: GoError) -> Self { match err { - GoError::ParseError { source } => deps_core::DepsError::ParseError { + GoError::ParseError { source } => Self::ParseError { file_type: "go.mod".into(), source, }, - GoError::InvalidVersionSpecifier { message, .. } => { - deps_core::DepsError::InvalidVersionReq(message) - } + GoError::InvalidVersionSpecifier { message, .. } => Self::InvalidVersionReq(message), GoError::ModuleNotFound { module } => { - deps_core::DepsError::CacheError(format!("Module '{}' not found", module)) + Self::CacheError(format!("Module '{module}' not found")) } - GoError::RegistryError { module, source } => deps_core::DepsError::ParseError { - file_type: format!("registry for {}", module), + GoError::RegistryError { module, source } => Self::ParseError { + file_type: format!("registry for {module}"), source, }, - GoError::CacheError(msg) => deps_core::DepsError::CacheError(msg), - GoError::InvalidModulePath(msg) => deps_core::DepsError::InvalidVersionReq(msg), + GoError::CacheError(msg) => Self::CacheError(msg), + GoError::InvalidModulePath(msg) => Self::InvalidVersionReq(msg), GoError::InvalidPseudoVersion { version, reason } => { - deps_core::DepsError::InvalidVersionReq(format!("{}: {}", version, reason)) + Self::InvalidVersionReq(format!("{version}: {reason}")) } - GoError::ApiResponseError { module: _, source } => deps_core::DepsError::Json(source), - GoError::Io(e) => deps_core::DepsError::Io(e), - GoError::Other(e) => deps_core::DepsError::ParseError { + GoError::ApiResponseError { module: _, source } => Self::Json(source), + GoError::Io(e) => Self::Io(e), + GoError::Other(e) => Self::ParseError { file_type: "go".into(), source: e, }, @@ -134,18 +132,18 @@ impl From for deps_core::DepsError { impl From for GoError { fn from(err: deps_core::DepsError) -> Self { match err { - deps_core::DepsError::ParseError { source, .. } => GoError::ParseError { source }, - deps_core::DepsError::CacheError(msg) => GoError::CacheError(msg), - deps_core::DepsError::InvalidVersionReq(msg) => GoError::InvalidVersionSpecifier { + deps_core::DepsError::ParseError { source, .. } => Self::ParseError { source }, + deps_core::DepsError::CacheError(msg) => Self::CacheError(msg), + deps_core::DepsError::InvalidVersionReq(msg) => Self::InvalidVersionSpecifier { specifier: String::new(), message: msg, }, - deps_core::DepsError::Io(e) => GoError::Io(e), - deps_core::DepsError::Json(e) => GoError::ApiResponseError { + deps_core::DepsError::Io(e) => Self::Io(e), + deps_core::DepsError::Json(e) => Self::ApiResponseError { module: String::new(), source: e, }, - other => GoError::CacheError(other.to_string()), + other => Self::CacheError(other.to_string()), } } } diff --git a/crates/deps-go/src/formatter.rs b/crates/deps-go/src/formatter.rs index 5caa7dac..5e3b49d9 100644 --- a/crates/deps-go/src/formatter.rs +++ b/crates/deps-go/src/formatter.rs @@ -19,7 +19,7 @@ impl EcosystemFormatter for GoFormatter { // Use pkg.go.dev for package documentation // URL encode special characters (@ and space) let encoded = name.replace('@', "%40").replace(' ', "%20"); - format!("https://pkg.go.dev/{}", encoded) + format!("https://pkg.go.dev/{encoded}") } fn version_satisfies_requirement(&self, version: &str, requirement: &str) -> bool { diff --git a/crates/deps-go/src/lockfile.rs b/crates/deps-go/src/lockfile.rs index 39771aa4..3a550854 100644 --- a/crates/deps-go/src/lockfile.rs +++ b/crates/deps-go/src/lockfile.rs @@ -181,10 +181,10 @@ mod tests { #[test] fn test_parse_simple_go_sum() { - let content = r#" + let content = r" github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL9t9/HBtKc7e/Q7Nb2nqKqTW8mHZy6E7k8m4dLvs= -"#; +"; let packages = parse_go_sum(content); assert_eq!( packages.get_version("github.com/gin-gonic/gin"), @@ -194,11 +194,11 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL9t9/HBtKc7e/Q7Nb2nqKqTW8mHZy6E7k8m #[test] fn test_parse_multiple_modules() { - let content = r#" + let content = r" github.com/gin-gonic/gin v1.9.1 h1:hash1= golang.org/x/sync v0.5.0 h1:hash2= github.com/stretchr/testify v1.8.4 h1:hash3= -"#; +"; let packages = parse_go_sum(content); assert_eq!(packages.len(), 3); assert_eq!( @@ -214,10 +214,10 @@ github.com/stretchr/testify v1.8.4 h1:hash3= #[test] fn test_skip_go_mod_entries() { - let content = r#" + let content = r" github.com/gin-gonic/gin v1.9.1/go.mod h1:mod_hash= github.com/gin-gonic/gin v1.9.1 h1:actual_hash= -"#; +"; let packages = parse_go_sum(content); assert_eq!(packages.len(), 1); assert_eq!( @@ -228,10 +228,10 @@ github.com/gin-gonic/gin v1.9.1 h1:actual_hash= #[test] fn test_first_version_wins() { - let content = r#" + let content = r" github.com/pkg/errors v0.9.1 h1:hash1= github.com/pkg/errors v0.8.0 h1:hash2= -"#; +"; let packages = parse_go_sum(content); assert_eq!(packages.len(), 1); // First occurrence should win @@ -289,11 +289,11 @@ github.com/pkg/errors v0.8.0 h1:hash2= #[test] fn test_malformed_line_ignored() { - let content = r#" + let content = r" github.com/gin-gonic/gin v1.9.1 h1:hash= invalid line with only one part github.com/valid/pkg v1.0.0 h1:valid_hash= -"#; +"; let packages = parse_go_sum(content); // Should only parse the valid lines assert_eq!(packages.len(), 2); @@ -306,12 +306,12 @@ github.com/valid/pkg v1.0.0 h1:valid_hash= #[tokio::test] async fn test_parse_lockfile_simple() { - let lockfile_content = r#" + let lockfile_content = r" github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL9t9/HBtKc7e/Q7Nb2nqKqTW8mHZy6E7k8m4dLvs= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrq= golang.org/x/sync v0.5.0/go.mod h1:RxMgew5V= -"#; +"; let temp_dir = tempfile::tempdir().unwrap(); let lockfile_path = temp_dir.path().join("go.sum"); diff --git a/crates/deps-go/src/parser.rs b/crates/deps-go/src/parser.rs index c92ed68d..54f01a02 100644 --- a/crates/deps-go/src/parser.rs +++ b/crates/deps-go/src/parser.rs @@ -13,7 +13,6 @@ use crate::error::Result; use crate::types::{GoDependency, GoDirective}; -use once_cell::sync::Lazy; use regex::Regex; use tower_lsp_server::ls_types::{Position, Range, Uri}; @@ -72,16 +71,19 @@ pub fn parse_go_mod(content: &str, doc_uri: &Uri) -> Result { let mut module_path = None; let mut go_version = None; - static MODULE_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^\s*module\s+(\S+)").unwrap()); - static GO_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^\s*go\s+(\S+)").unwrap()); - static REQUIRE_SINGLE: Lazy = - Lazy::new(|| Regex::new(r"^\s*require\s+(\S+)\s+(\S+)").unwrap()); - static REQUIRE_BLOCK_START: Lazy = - Lazy::new(|| Regex::new(r"^\s*require\s*\(").unwrap()); - static REPLACE_PATTERN: Lazy = - Lazy::new(|| Regex::new(r"^\s*replace\s+(\S+)\s+(?:(\S+)\s+)?=>\s+(\S+)\s+(\S+)").unwrap()); - static EXCLUDE_PATTERN: Lazy = - Lazy::new(|| Regex::new(r"^\s*exclude\s+(\S+)\s+(\S+)").unwrap()); + static MODULE_PATTERN: std::sync::LazyLock = + std::sync::LazyLock::new(|| Regex::new(r"^\s*module\s+(\S+)").unwrap()); + static GO_PATTERN: std::sync::LazyLock = + std::sync::LazyLock::new(|| Regex::new(r"^\s*go\s+(\S+)").unwrap()); + static REQUIRE_SINGLE: std::sync::LazyLock = + std::sync::LazyLock::new(|| Regex::new(r"^\s*require\s+(\S+)\s+(\S+)").unwrap()); + static REQUIRE_BLOCK_START: std::sync::LazyLock = + std::sync::LazyLock::new(|| Regex::new(r"^\s*require\s*\(").unwrap()); + static REPLACE_PATTERN: std::sync::LazyLock = std::sync::LazyLock::new(|| { + Regex::new(r"^\s*replace\s+(\S+)\s+(?:(\S+)\s+)?=>\s+(\S+)\s+(\S+)").unwrap() + }); + static EXCLUDE_PATTERN: std::sync::LazyLock = + std::sync::LazyLock::new(|| Regex::new(r"^\s*exclude\s+(\S+)\s+(\S+)").unwrap()); let mut in_require_block = false; let mut line_offset = 0; @@ -331,12 +333,12 @@ mod tests { #[test] fn test_parse_single_require() { - let content = r#"module example.com/myapp + let content = r"module example.com/myapp go 1.21 require github.com/gin-gonic/gin v1.9.1 -"#; +"; let result = parse_go_mod(content, &test_uri()).unwrap(); assert_eq!(result.dependencies.len(), 1); assert_eq!( @@ -363,11 +365,11 @@ require github.com/gin-gonic/gin v1.9.1 #[test] fn test_parse_require_block() { - let content = r#"require ( + let content = r"require ( github.com/gin-gonic/gin v1.9.1 golang.org/x/crypto v0.17.0 // indirect ) -"#; +"; let result = parse_go_mod(content, &test_uri()).unwrap(); assert_eq!(result.dependencies.len(), 2); assert!(!result.dependencies[0].indirect); @@ -433,7 +435,7 @@ require github.com/gin-gonic/gin v1.9.1 #[test] fn test_complex_go_mod() { - let content = r#"module example.com/myapp + let content = r"module example.com/myapp go 1.21 @@ -445,7 +447,7 @@ require ( replace github.com/old/module => github.com/new/module v1.2.3 exclude github.com/bad/module v0.1.0 -"#; +"; let result = parse_go_mod(content, &test_uri()).unwrap(); assert_eq!(result.dependencies.len(), 4); assert_eq!(result.module_path, Some("example.com/myapp".to_string())); @@ -487,7 +489,7 @@ exclude github.com/bad/module v0.1.0 #[test] fn test_parse_complex_go_mod() { - let content = r#"module example.com/myapp + let content = r"module example.com/myapp go 1.21 @@ -499,7 +501,7 @@ require ( replace github.com/old/module => github.com/new/module v1.2.3 exclude github.com/bad/module v0.1.0 -"#; +"; let result = parse_go_mod(content, &test_uri()).unwrap(); // Check module metadata diff --git a/crates/deps-go/src/registry.rs b/crates/deps-go/src/registry.rs index ac0fe3e8..c3b90611 100644 --- a/crates/deps-go/src/registry.rs +++ b/crates/deps-go/src/registry.rs @@ -58,8 +58,7 @@ fn validate_module_path(module_path: &str) -> Result<()> { if module_path.len() > MAX_MODULE_PATH_LENGTH { return Err(GoError::InvalidModulePath(format!( - "module path exceeds maximum length of {} characters", - MAX_MODULE_PATH_LENGTH + "module path exceeds maximum length of {MAX_MODULE_PATH_LENGTH} characters" ))); } @@ -86,8 +85,7 @@ fn validate_version_string(version: &str) -> Result<()> { return Err(GoError::InvalidVersionSpecifier { specifier: version.to_string(), message: format!( - "version string exceeds maximum length of {} characters", - MAX_VERSION_LENGTH + "version string exceeds maximum length of {MAX_VERSION_LENGTH} characters" ), }); } @@ -105,7 +103,7 @@ fn validate_version_string(version: &str) -> Result<()> { /// Returns the URL for a module's documentation page on pkg.go.dev. pub fn package_url(module_path: &str) -> String { - format!("{}/{}", PKG_GO_DEV_URL, module_path) + format!("{PKG_GO_DEV_URL}/{module_path}") } /// Client for interacting with proxy.golang.org. @@ -119,7 +117,7 @@ pub struct GoRegistry { impl GoRegistry { /// Creates a new Go registry client with the given HTTP cache. - pub fn new(cache: Arc) -> Self { + pub const fn new(cache: Arc) -> Self { Self { cache } } @@ -154,7 +152,7 @@ impl GoRegistry { validate_module_path(module_path)?; let escaped = escape_module_path(module_path); - let url = format!("{}/{}/@v/list", PROXY_BASE, escaped); + let url = format!("{PROXY_BASE}/{escaped}/@v/list"); let data = self .cache @@ -199,7 +197,7 @@ impl GoRegistry { validate_version_string(version)?; let escaped = escape_module_path(module_path); - let url = format!("{}/{}/@v/{}.info", PROXY_BASE, escaped, version); + let url = format!("{PROXY_BASE}/{escaped}/@v/{version}.info"); let data = self .cache @@ -243,7 +241,7 @@ impl GoRegistry { validate_module_path(module_path)?; let escaped = escape_module_path(module_path); - let url = format!("{}/{}/@latest", PROXY_BASE, escaped); + let url = format!("{PROXY_BASE}/{escaped}/@latest"); let data = self .cache @@ -288,7 +286,7 @@ impl GoRegistry { validate_version_string(version)?; let escaped = escape_module_path(module_path); - let url = format!("{}/{}/@v/{}.mod", PROXY_BASE, escaped, version); + let url = format!("{PROXY_BASE}/{escaped}/@v/{version}.mod"); let data = self .cache @@ -300,8 +298,8 @@ impl GoRegistry { })?; std::str::from_utf8(&data) - .map(|s| s.to_string()) - .map_err(|e| GoError::CacheError(format!("Invalid UTF-8 in go.mod: {}", e))) + .map(std::string::ToString::to_string) + .map_err(|e| GoError::CacheError(format!("Invalid UTF-8 in go.mod: {e}"))) } } @@ -318,7 +316,7 @@ struct VersionInfo { fn parse_version_list(data: &[u8]) -> Result> { let content = std::str::from_utf8(data).map_err(|e| GoError::InvalidVersionSpecifier { specifier: String::new(), - message: format!("Invalid UTF-8 in version list response: {}", e), + message: format!("Invalid UTF-8 in version list response: {e}"), })?; let versions: Vec = content @@ -485,7 +483,7 @@ mod tests { async fn test_registry_clone() { let cache = Arc::new(HttpCache::new()); let registry = GoRegistry::new(cache); - let _cloned = registry.clone(); + let _cloned = registry; } #[tokio::test] @@ -526,7 +524,7 @@ mod tests { .await .unwrap(); - assert!(latest.version.starts_with("v")); + assert!(latest.version.starts_with('v')); assert!(!latest.is_pseudo); } diff --git a/crates/deps-go/src/types.rs b/crates/deps-go/src/types.rs index 159e14da..0c3f6498 100644 --- a/crates/deps-go/src/types.rs +++ b/crates/deps-go/src/types.rs @@ -5,7 +5,7 @@ use std::any::Any; use tower_lsp_server::ls_types::Range; /// A dependency from a go.mod file. -#[derive(Debug, Clone, PartialEq, serde::Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] pub struct GoDependency { /// Module path (e.g., "github.com/gin-gonic/gin") pub module_path: String, diff --git a/crates/deps-go/src/version.rs b/crates/deps-go/src/version.rs index 113749e6..24ca15aa 100644 --- a/crates/deps-go/src/version.rs +++ b/crates/deps-go/src/version.rs @@ -1,7 +1,6 @@ //! Version parsing and module path utilities for Go modules. use crate::error::{GoError, Result}; -use once_cell::sync::Lazy; use regex::Regex; use std::cmp::Ordering; @@ -41,7 +40,7 @@ pub fn escape_module_path(path: &str) -> String { let mut buf = [0u8; 4]; let encoded = c.encode_utf8(&mut buf); for &byte in encoded.as_bytes() { - result.push_str(&format!("%{:02X}", byte)); + result.push_str(&format!("%{byte:02X}")); } } } @@ -62,7 +61,7 @@ pub fn escape_module_path(path: &str) -> String { /// assert!(!is_pseudo_version("v1.2.3")); /// ``` pub fn is_pseudo_version(version: &str) -> bool { - static PSEUDO_REGEX: Lazy = Lazy::new(|| { + static PSEUDO_REGEX: std::sync::LazyLock = std::sync::LazyLock::new(|| { Regex::new(r"^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+.*)?$").unwrap() }); @@ -141,15 +140,15 @@ pub fn compare_versions(v1: &str, v2: &str) -> Ordering { let clean2 = v2.trim_start_matches('v').replace("+incompatible", ""); let cmp1 = if is_pseudo_version(v1) { - base_version_from_pseudo(v1).unwrap_or(clean1.clone()) + base_version_from_pseudo(v1).unwrap_or(clean1) } else { - clean1.clone() + clean1 }; let cmp2 = if is_pseudo_version(v2) { - base_version_from_pseudo(v2).unwrap_or(clean2.clone()) + base_version_from_pseudo(v2).unwrap_or(clean2) } else { - clean2.clone() + clean2 }; match (parse_semver(&cmp1), parse_semver(&cmp2)) { diff --git a/crates/deps-lsp/Cargo.toml b/crates/deps-lsp/Cargo.toml index 4dae83ed..5774075e 100644 --- a/crates/deps-lsp/Cargo.toml +++ b/crates/deps-lsp/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Language Server Protocol implementation for dependency management" publish = true +[lints] +workspace = true + [[bin]] name = "deps-lsp" path = "src/main.rs" diff --git a/crates/deps-lsp/benches/lsp_handlers.rs b/crates/deps-lsp/benches/lsp_handlers.rs index eee7e84c..9738f68f 100644 --- a/crates/deps-lsp/benches/lsp_handlers.rs +++ b/crates/deps-lsp/benches/lsp_handlers.rs @@ -117,7 +117,7 @@ fn bench_completion_handler(c: &mut Criterion) { ) .await }) - }) + }); }); // Setup: Pre-load document with medium manifest @@ -147,7 +147,7 @@ fn bench_completion_handler(c: &mut Criterion) { ) .await }) - }) + }); }); group.finish(); @@ -202,7 +202,7 @@ fn bench_inlay_hints_handler(c: &mut Criterion) { ) .await }) - }) + }); }); } @@ -236,7 +236,7 @@ fn bench_inlay_hints_handler(c: &mut Criterion) { ) .await }) - }) + }); }); group.finish(); @@ -282,7 +282,7 @@ fn bench_hover_handler(c: &mut Criterion) { ) .await }) - }) + }); }); group.bench_function("hover_on_version_string", |b| { @@ -304,7 +304,7 @@ fn bench_hover_handler(c: &mut Criterion) { ) .await }) - }) + }); }); group.finish(); @@ -321,7 +321,7 @@ fn bench_document_state_access(c: &mut Criterion) { // Pre-populate with 10 documents for i in 0..10 { - let uri = Uri::from_file_path(format!("/bench/doc{}/Cargo.toml", i)).unwrap(); + let uri = Uri::from_file_path(format!("/bench/doc{i}/Cargo.toml")).unwrap(); rt.block_on(setup_document(&state, &uri, SMALL_CARGO)); } @@ -330,7 +330,7 @@ fn bench_document_state_access(c: &mut Criterion) { b.iter(|| { let _doc = state.get_document(black_box(&uri)); - }) + }); }); group.bench_function("concurrent_reads_10_documents", |b| { @@ -341,8 +341,7 @@ fn bench_document_state_access(c: &mut Criterion) { for i in 0..10 { let state = Arc::clone(&state); let handle = tokio::spawn(async move { - let uri = - Uri::from_file_path(format!("/bench/doc{}/Cargo.toml", i)).unwrap(); + let uri = Uri::from_file_path(format!("/bench/doc{i}/Cargo.toml")).unwrap(); let _doc = state.get_document(&uri); }); handles.push(handle); @@ -351,8 +350,8 @@ fn bench_document_state_access(c: &mut Criterion) { for handle in handles { let _ = handle.await; } - }) - }) + }); + }); }); group.finish(); @@ -392,8 +391,8 @@ fn bench_cold_start_loading(c: &mut Criterion) { content.to_string(), parse_result, ); - }) - }) + }); + }); }); } diff --git a/crates/deps-lsp/src/config.rs b/crates/deps-lsp/src/config.rs index d4b43391..f02918f1 100644 --- a/crates/deps-lsp/src/config.rs +++ b/crates/deps-lsp/src/config.rs @@ -163,7 +163,7 @@ impl Default for CacheConfig { } // Default value functions -fn default_true() -> bool { +const fn default_true() -> bool { true } @@ -175,19 +175,19 @@ fn default_needs_update() -> String { "❌ {}".to_string() } -fn default_outdated_severity() -> DiagnosticSeverity { +const fn default_outdated_severity() -> DiagnosticSeverity { DiagnosticSeverity::HINT } -fn default_unknown_severity() -> DiagnosticSeverity { +const fn default_unknown_severity() -> DiagnosticSeverity { DiagnosticSeverity::WARNING } -fn default_yanked_severity() -> DiagnosticSeverity { +const fn default_yanked_severity() -> DiagnosticSeverity { DiagnosticSeverity::WARNING } -fn default_refresh_interval() -> u64 { +const fn default_refresh_interval() -> u64 { 300 // 5 minutes } @@ -235,7 +235,7 @@ impl Default for ColdStartConfig { } } -fn default_rate_limit_ms() -> u64 { +const fn default_rate_limit_ms() -> u64 { 100 // 10 req/sec per URI } @@ -339,7 +339,7 @@ mod tests { #[test] fn test_empty_config_deserialization() { - let json = r#"{}"#; + let json = r"{}"; let config: DepsConfig = serde_json::from_str(json).unwrap(); // All fields should use defaults assert!(config.inlay_hints.enabled); diff --git a/crates/deps-lsp/src/document/lifecycle.rs b/crates/deps-lsp/src/document/lifecycle.rs index 0a63cffa..931208c6 100644 --- a/crates/deps-lsp/src/document/lifecycle.rs +++ b/crates/deps-lsp/src/document/lifecycle.rs @@ -73,8 +73,7 @@ pub async fn handle_document_open( None => { tracing::debug!("No ecosystem handler for {:?}", uri); return Err(deps_core::error::DepsError::UnsupportedEcosystem(format!( - "{:?}", - uri + "{uri:?}" ))); } }; @@ -178,8 +177,7 @@ pub async fn handle_document_change( None => { tracing::debug!("No ecosystem handler for {:?}", uri); return Err(deps_core::error::DepsError::UnsupportedEcosystem(format!( - "{:?}", - uri + "{uri:?}" ))); } }; @@ -398,7 +396,7 @@ pub async fn ensure_document_loaded( Err(e) => { tracing::warn!("Failed to load document {:?}: {}", uri, e); client - .log_message(MessageType::WARNING, format!("Could not load file: {}", e)) + .log_message(MessageType::WARNING, format!("Could not load file: {e}")) .await; return false; } @@ -756,12 +754,12 @@ dependencies = ["requests>=2.0.0"] async fn test_document_parsing() { let state = Arc::new(ServerState::new()); let uri = tower_lsp_server::ls_types::Uri::from_file_path("/test/go.mod").unwrap(); - let content = r#"module example.com/mymodule + let content = r"module example.com/mymodule go 1.21 require github.com/gorilla/mux v1.8.0 -"#; +"; let ecosystem = state .ecosystem_registry diff --git a/crates/deps-lsp/src/document/loader.rs b/crates/deps-lsp/src/document/loader.rs index dc45d0f0..910e0d25 100644 --- a/crates/deps-lsp/src/document/loader.rs +++ b/crates/deps-lsp/src/document/loader.rs @@ -79,7 +79,7 @@ pub async fn load_document_from_disk(uri: &Uri) -> Result { Some(p) => p, None => { tracing::debug!("Cannot load non-file URI: {:?}", uri); - return Err(DepsError::InvalidUri(format!("{:?}", uri))); + return Err(DepsError::InvalidUri(format!("{uri:?}"))); } }; @@ -98,8 +98,7 @@ pub async fn load_document_from_disk(uri: &Uri) -> Result { MAX_FILE_SIZE ); return Err(DepsError::CacheError(format!( - "file too large: {} bytes (max: {} bytes)", - size, MAX_FILE_SIZE + "file too large: {size} bytes (max: {MAX_FILE_SIZE} bytes)" ))); } @@ -383,14 +382,12 @@ serde = "1.0" Err(DepsError::CacheError(msg)) => { assert!( msg.contains("file too large"), - "Error message should indicate file size issue: {}", - msg + "Error message should indicate file size issue: {msg}" ); assert!( msg.contains(&beyond_limit.to_string()) || msg.contains(&(beyond_limit + 1).to_string()), - "Error should mention actual file size: {}", - msg + "Error should mention actual file size: {msg}" ); } _ => panic!("Expected CacheError for oversized file"), diff --git a/crates/deps-lsp/src/document/state.rs b/crates/deps-lsp/src/document/state.rs index b84d0078..cca52af5 100644 --- a/crates/deps-lsp/src/document/state.rs +++ b/crates/deps-lsp/src/document/state.rs @@ -40,13 +40,13 @@ impl UnifiedDependency { pub fn name(&self) -> &str { match self { #[cfg(feature = "cargo")] - UnifiedDependency::Cargo(dep) => &dep.name, + Self::Cargo(dep) => &dep.name, #[cfg(feature = "npm")] - UnifiedDependency::Npm(dep) => &dep.name, + Self::Npm(dep) => &dep.name, #[cfg(feature = "pypi")] - UnifiedDependency::Pypi(dep) => &dep.name, + Self::Pypi(dep) => &dep.name, #[cfg(feature = "go")] - UnifiedDependency::Go(dep) => &dep.module_path, + Self::Go(dep) => &dep.module_path, _ => unreachable!("no ecosystem features enabled"), } } @@ -56,13 +56,13 @@ impl UnifiedDependency { pub fn name_range(&self) -> tower_lsp_server::ls_types::Range { match self { #[cfg(feature = "cargo")] - UnifiedDependency::Cargo(dep) => dep.name_range, + Self::Cargo(dep) => dep.name_range, #[cfg(feature = "npm")] - UnifiedDependency::Npm(dep) => dep.name_range, + Self::Npm(dep) => dep.name_range, #[cfg(feature = "pypi")] - UnifiedDependency::Pypi(dep) => dep.name_range, + Self::Pypi(dep) => dep.name_range, #[cfg(feature = "go")] - UnifiedDependency::Go(dep) => dep.module_path_range, + Self::Go(dep) => dep.module_path_range, _ => unreachable!("no ecosystem features enabled"), } } @@ -72,13 +72,13 @@ impl UnifiedDependency { pub fn version_req(&self) -> Option<&str> { match self { #[cfg(feature = "cargo")] - UnifiedDependency::Cargo(dep) => dep.version_req.as_deref(), + Self::Cargo(dep) => dep.version_req.as_deref(), #[cfg(feature = "npm")] - UnifiedDependency::Npm(dep) => dep.version_req.as_deref(), + Self::Npm(dep) => dep.version_req.as_deref(), #[cfg(feature = "pypi")] - UnifiedDependency::Pypi(dep) => dep.version_req.as_deref(), + Self::Pypi(dep) => dep.version_req.as_deref(), #[cfg(feature = "go")] - UnifiedDependency::Go(dep) => dep.version.as_deref(), + Self::Go(dep) => dep.version.as_deref(), _ => unreachable!("no ecosystem features enabled"), } } @@ -88,13 +88,13 @@ impl UnifiedDependency { pub fn version_range(&self) -> Option { match self { #[cfg(feature = "cargo")] - UnifiedDependency::Cargo(dep) => dep.version_range, + Self::Cargo(dep) => dep.version_range, #[cfg(feature = "npm")] - UnifiedDependency::Npm(dep) => dep.version_range, + Self::Npm(dep) => dep.version_range, #[cfg(feature = "pypi")] - UnifiedDependency::Pypi(dep) => dep.version_range, + Self::Pypi(dep) => dep.version_range, #[cfg(feature = "go")] - UnifiedDependency::Go(dep) => dep.version_range, + Self::Go(dep) => dep.version_range, _ => unreachable!("no ecosystem features enabled"), } } @@ -104,17 +104,17 @@ impl UnifiedDependency { pub fn is_registry(&self) -> bool { match self { #[cfg(feature = "cargo")] - UnifiedDependency::Cargo(dep) => { + Self::Cargo(dep) => { matches!(dep.source, deps_cargo::DependencySource::Registry) } #[cfg(feature = "npm")] - UnifiedDependency::Npm(_) => true, + Self::Npm(_) => true, #[cfg(feature = "pypi")] - UnifiedDependency::Pypi(dep) => { + Self::Pypi(dep) => { matches!(dep.source, deps_pypi::PypiDependencySource::PyPI) } #[cfg(feature = "go")] - UnifiedDependency::Go(_) => true, + Self::Go(_) => true, _ => unreachable!("no ecosystem features enabled"), } } @@ -142,13 +142,13 @@ impl UnifiedVersion { pub fn version_string(&self) -> &str { match self { #[cfg(feature = "cargo")] - UnifiedVersion::Cargo(v) => &v.num, + Self::Cargo(v) => &v.num, #[cfg(feature = "npm")] - UnifiedVersion::Npm(v) => &v.version, + Self::Npm(v) => &v.version, #[cfg(feature = "pypi")] - UnifiedVersion::Pypi(v) => &v.version, + Self::Pypi(v) => &v.version, #[cfg(feature = "go")] - UnifiedVersion::Go(v) => &v.version, + Self::Go(v) => &v.version, _ => unreachable!("no ecosystem features enabled"), } } @@ -158,13 +158,13 @@ impl UnifiedVersion { pub fn is_yanked(&self) -> bool { match self { #[cfg(feature = "cargo")] - UnifiedVersion::Cargo(v) => v.yanked, + Self::Cargo(v) => v.yanked, #[cfg(feature = "npm")] - UnifiedVersion::Npm(v) => v.deprecated, + Self::Npm(v) => v.deprecated, #[cfg(feature = "pypi")] - UnifiedVersion::Pypi(v) => v.yanked, + Self::Pypi(v) => v.yanked, #[cfg(feature = "go")] - UnifiedVersion::Go(v) => v.retracted, + Self::Go(v) => v.retracted, _ => unreachable!("no ecosystem features enabled"), } } @@ -368,7 +368,7 @@ impl ColdStartLimiter { if let Some(mut entry) = self.last_attempts.get_mut(uri) { let elapsed = now.duration_since(*entry); if elapsed < self.min_interval { - let retry_after = self.min_interval - elapsed; + let retry_after = self.min_interval.checked_sub(elapsed).unwrap(); tracing::warn!( "Cold start rate limited for {:?} (retry after {:?})", uri, @@ -504,7 +504,7 @@ impl DocumentState { /// Gets a reference to the parse result if available. pub fn parse_result(&self) -> Option<&dyn ParseResult> { - self.parse_result.as_ref().map(|b| b.as_ref()) + self.parse_result.as_ref().map(std::convert::AsRef::as_ref) } /// Updates the cached latest version information for dependencies. @@ -968,7 +968,7 @@ mod tests { let deps = vec![create_test_dependency()]; let doc_state = DocumentState::new(Ecosystem::Cargo, "test".into(), deps); - state.update_document(uri.clone(), doc_state.clone()); + state.update_document(uri.clone(), doc_state); assert_eq!(state.document_count(), 1); let retrieved = state.get_document(&uri); @@ -1041,7 +1041,7 @@ mod tests { #[test] fn test_document_state_new_without_parse_result() { let content = "[dependencies]\nserde = \"1.0\"\n".to_string(); - let doc_state = DocumentState::new_without_parse_result("cargo", content.clone()); + let doc_state = DocumentState::new_without_parse_result("cargo", content); assert_eq!(doc_state.ecosystem_id, "cargo"); assert_eq!(doc_state.ecosystem, Ecosystem::Cargo); @@ -1100,7 +1100,7 @@ mod tests { fn test_document_state_debug() { let deps = vec![create_test_dependency()]; let state = DocumentState::new(Ecosystem::Cargo, "test".into(), deps); - let debug_str = format!("{:?}", state); + let debug_str = format!("{state:?}"); assert!(debug_str.contains("DocumentState")); } } @@ -1143,7 +1143,7 @@ mod tests { #[test] fn test_document_state_new_without_parse_result() { let content = r#"{"dependencies": {"express": "^4.18.0"}}"#.to_string(); - let doc_state = DocumentState::new_without_parse_result("npm", content.clone()); + let doc_state = DocumentState::new_without_parse_result("npm", content); assert_eq!(doc_state.ecosystem_id, "npm"); assert_eq!(doc_state.ecosystem, Ecosystem::Npm); @@ -1194,7 +1194,7 @@ mod tests { #[test] fn test_document_state_new_without_parse_result() { let content = "[project]\ndependencies = [\"requests>=2.0.0\"]\n".to_string(); - let doc_state = DocumentState::new_without_parse_result("pypi", content.clone()); + let doc_state = DocumentState::new_without_parse_result("pypi", content); assert_eq!(doc_state.ecosystem_id, "pypi"); assert_eq!(doc_state.ecosystem, Ecosystem::Pypi); @@ -1327,7 +1327,7 @@ mod tests { let content = "module example.com/myapp\n\ngo 1.21\n\nrequire github.com/gin-gonic/gin v1.9.1\n" .to_string(); - let doc_state = DocumentState::new_without_parse_result("go", content.clone()); + let doc_state = DocumentState::new_without_parse_result("go", content); assert_eq!(doc_state.ecosystem_id, "go"); assert_eq!(doc_state.ecosystem, Ecosystem::Go); diff --git a/crates/deps-lsp/src/file_watcher.rs b/crates/deps-lsp/src/file_watcher.rs index fb40d257..a13400e2 100644 --- a/crates/deps-lsp/src/file_watcher.rs +++ b/crates/deps-lsp/src/file_watcher.rs @@ -52,7 +52,7 @@ pub async fn register_lock_file_watchers( client .register_capability(vec![registration]) .await - .map_err(|e| format!("Failed to register file watchers: {}", e))?; + .map_err(|e| format!("Failed to register file watchers: {e}"))?; tracing::info!("Registered {} lock file watchers", patterns.len()); Ok(()) diff --git a/crates/deps-lsp/src/handlers/code_actions.rs b/crates/deps-lsp/src/handlers/code_actions.rs index 3bb829e4..4ccbfc30 100644 --- a/crates/deps-lsp/src/handlers/code_actions.rs +++ b/crates/deps-lsp/src/handlers/code_actions.rs @@ -120,7 +120,7 @@ serde = "1.0.0" let state = Arc::new(ServerState::new()); let uri = Uri::from_file_path("/test/Cargo.toml").unwrap(); - let doc_state = DocumentState::new(Ecosystem::Cargo, "".to_string(), vec![]); + let doc_state = DocumentState::new(Ecosystem::Cargo, String::new(), vec![]); state.update_document(uri.clone(), doc_state); let params = CodeActionParams { diff --git a/crates/deps-lsp/src/handlers/completion.rs b/crates/deps-lsp/src/handlers/completion.rs index fee1cfad..672f4bad 100644 --- a/crates/deps-lsp/src/handlers/completion.rs +++ b/crates/deps-lsp/src/handlers/completion.rs @@ -299,13 +299,13 @@ fn create_package_completion_item( // Create insert text based on ecosystem let insert_text = match ecosystem_id { - "cargo" | "pypi" => format!("{} = \"{}\"", name, latest), - "npm" => format!("\"{}\": \"^{}\"", name, latest), - _ => format!("{} = \"{}\"", name, latest), + "cargo" | "pypi" => format!("{name} = \"{latest}\""), + "npm" => format!("\"{name}\": \"^{latest}\""), + _ => format!("{name} = \"{latest}\""), }; // Build detail text - let detail = format!("Latest: {}", latest); + let detail = format!("Latest: {latest}"); CompletionItem { label: name.to_string(), @@ -396,28 +396,28 @@ serde #[test] fn test_is_in_toml_dependencies_dev_deps() { - let content = r#" + let content = r" [dev-dependencies] tokio -"#; +"; assert!(is_in_toml_dependencies(content, 2)); } #[test] fn test_is_in_toml_dependencies_build_deps() { - let content = r#" + let content = r" [build-dependencies] cc -"#; +"; assert!(is_in_toml_dependencies(content, 2)); } #[test] fn test_is_in_toml_dependencies_project_deps() { - let content = r#" + let content = r" [project.dependencies] requests -"#; +"; assert!(is_in_toml_dependencies(content, 2)); } @@ -432,10 +432,10 @@ serde = "1.0" #[test] fn test_is_in_toml_dependencies_target_specific() { - let content = r#" + let content = r" [target.'cfg(windows)'.dependencies] winapi -"#; +"; assert!(is_in_toml_dependencies(content, 2)); } @@ -534,20 +534,20 @@ tokio #[test] fn test_is_in_dependencies_section_cargo() { - let content = r#" + let content = r" [dependencies] serde -"#; +"; assert!(is_in_dependencies_section(content, 2, "cargo")); assert!(!is_in_dependencies_section(content, 0, "cargo")); } #[test] fn test_is_in_dependencies_section_pypi() { - let content = r#" + let content = r" [project.dependencies] requests -"#; +"; assert!(is_in_dependencies_section(content, 2, "pypi")); } @@ -563,10 +563,10 @@ requests #[test] fn test_is_in_dependencies_section_unknown_ecosystem() { - let content = r#" + let content = r" [dependencies] something -"#; +"; assert!(!is_in_dependencies_section(content, 2, "unknown")); } @@ -574,7 +574,7 @@ something fn test_create_package_completion_item_cargo() { struct MockMetadata; impl deps_core::Metadata for MockMetadata { - fn name(&self) -> &str { + fn name(&self) -> &'static str { "serde" } fn description(&self) -> Option<&str> { @@ -586,7 +586,7 @@ something fn documentation(&self) -> Option<&str> { None } - fn latest_version(&self) -> &str { + fn latest_version(&self) -> &'static str { "1.0.214" } fn as_any(&self) -> &dyn std::any::Any { @@ -608,7 +608,7 @@ something fn test_create_package_completion_item_npm() { struct MockMetadata; impl deps_core::Metadata for MockMetadata { - fn name(&self) -> &str { + fn name(&self) -> &'static str { "express" } fn description(&self) -> Option<&str> { @@ -620,7 +620,7 @@ something fn documentation(&self) -> Option<&str> { None } - fn latest_version(&self) -> &str { + fn latest_version(&self) -> &'static str { "4.18.2" } fn as_any(&self) -> &dyn std::any::Any { @@ -642,7 +642,7 @@ something fn test_create_package_completion_item_pypi() { struct MockMetadata; impl deps_core::Metadata for MockMetadata { - fn name(&self) -> &str { + fn name(&self) -> &'static str { "requests" } fn description(&self) -> Option<&str> { @@ -654,7 +654,7 @@ something fn documentation(&self) -> Option<&str> { None } - fn latest_version(&self) -> &str { + fn latest_version(&self) -> &'static str { "2.31.0" } fn as_any(&self) -> &dyn std::any::Any { @@ -677,9 +677,9 @@ something let uri = Uri::from_file_path("/test/Cargo.toml").unwrap(); // Malformed content that will fail to parse - let content = r#"[dependencies] -ser"# - .to_string(); + let content = r"[dependencies] +ser" + .to_string(); // Create document without parse result (simulating parse failure) let doc = DocumentState::new(Ecosystem::Cargo, content.clone(), vec![]); @@ -705,10 +705,10 @@ ser"# #[test] fn test_fallback_rejects_single_char_prefix() { - let content = r#" + let content = r" [dependencies] s -"#; +"; // Extract prefix at position (1 char) let line = content.lines().nth(2).unwrap(); @@ -740,10 +740,10 @@ serde = "1.0" #[test] fn test_prefix_extraction_cursor_beyond_line() { - let content = r#" + let content = r" [dependencies] serde -"#; +"; // Try to extract prefix with cursor beyond line length let line = content.lines().nth(2).unwrap(); diff --git a/crates/deps-lsp/src/handlers/diagnostics.rs b/crates/deps-lsp/src/handlers/diagnostics.rs index 390367b1..96071d53 100644 --- a/crates/deps-lsp/src/handlers/diagnostics.rs +++ b/crates/deps-lsp/src/handlers/diagnostics.rs @@ -116,7 +116,7 @@ serde = "1.0.0" let uri = Uri::from_file_path("/test/Cargo.toml").unwrap(); let config = DiagnosticsConfig::default(); - let doc_state = DocumentState::new(Ecosystem::Cargo, "".to_string(), vec![]); + let doc_state = DocumentState::new(Ecosystem::Cargo, String::new(), vec![]); state.update_document(uri.clone(), doc_state); let (client, full_config) = create_test_client_and_config(); diff --git a/crates/deps-lsp/src/handlers/hover.rs b/crates/deps-lsp/src/handlers/hover.rs index 620fb0f8..43c6fe29 100644 --- a/crates/deps-lsp/src/handlers/hover.rs +++ b/crates/deps-lsp/src/handlers/hover.rs @@ -111,7 +111,7 @@ serde = "1.0.0" let state = Arc::new(ServerState::new()); let uri = Uri::from_file_path("/test/Cargo.toml").unwrap(); - let doc_state = DocumentState::new(Ecosystem::Cargo, "".to_string(), vec![]); + let doc_state = DocumentState::new(Ecosystem::Cargo, String::new(), vec![]); state.update_document(uri.clone(), doc_state); let params = HoverParams { diff --git a/crates/deps-lsp/src/handlers/inlay_hints.rs b/crates/deps-lsp/src/handlers/inlay_hints.rs index 3ba2905a..8fef9d72 100644 --- a/crates/deps-lsp/src/handlers/inlay_hints.rs +++ b/crates/deps-lsp/src/handlers/inlay_hints.rs @@ -195,7 +195,7 @@ serde = "1.0.0" needs_update_text: "❌ {}".to_string(), }; - let doc_state = DocumentState::new(Ecosystem::Cargo, "".to_string(), vec![]); + let doc_state = DocumentState::new(Ecosystem::Cargo, String::new(), vec![]); state.update_document(uri.clone(), doc_state); let params = InlayHintParams { diff --git a/crates/deps-lsp/src/server.rs b/crates/deps-lsp/src/server.rs index cfb0a700..9af59188 100644 --- a/crates/deps-lsp/src/server.rs +++ b/crates/deps-lsp/src/server.rs @@ -21,7 +21,7 @@ use tower_lsp_server::{Client, LanguageServer, jsonrpc::Result}; /// LSP command identifiers. mod commands { /// Command to update a dependency version. - pub const UPDATE_VERSION: &str = "deps-lsp.updateVersion"; + pub(super) const UPDATE_VERSION: &str = "deps-lsp.updateVersion"; } pub struct Backend { @@ -41,7 +41,7 @@ impl Backend { /// Get a reference to the LSP client (primarily for testing/benchmarking). #[doc(hidden)] - pub fn client(&self) -> &Client { + pub const fn client(&self) -> &Client { &self.client } @@ -62,7 +62,7 @@ impl Backend { Err(e) => { tracing::error!("failed to open document {:?}: {}", uri, e); self.client - .log_message(MessageType::ERROR, format!("Parse error: {}", e)) + .log_message(MessageType::ERROR, format!("Parse error: {e}")) .await; } } @@ -148,7 +148,7 @@ impl Backend { self.client .log_message( MessageType::ERROR, - format!("Failed to reload lock file: {}", e), + format!("Failed to reload lock file: {e}"), ) .await; HashMap::new() @@ -241,10 +241,7 @@ impl LanguageServer for Backend { if let Err(e) = file_watcher::register_lock_file_watchers(&self.client, &patterns).await { tracing::warn!("Failed to register file watchers: {}", e); self.client - .log_message( - MessageType::WARNING, - format!("File watching disabled: {}", e), - ) + .log_message(MessageType::WARNING, format!("File watching disabled: {e}")) .await; } diff --git a/crates/deps-lsp/src/test_utils.rs b/crates/deps-lsp/src/test_utils.rs index f77cfe4d..0dbe3681 100644 --- a/crates/deps-lsp/src/test_utils.rs +++ b/crates/deps-lsp/src/test_utils.rs @@ -13,7 +13,7 @@ pub(crate) mod test_helpers { /// Since handler tests pre-populate documents in state, the cold start /// logic is never triggered. These are just dummy values to satisfy /// the function signatures. - pub fn create_test_client_and_config() -> (Client, Arc>) { + pub(crate) fn create_test_client_and_config() -> (Client, Arc>) { let (service, _socket) = tower_lsp_server::LspService::build(Backend::new).finish(); let client = service.inner().client.clone(); let config = Arc::new(RwLock::new(DepsConfig::default())); diff --git a/crates/deps-lsp/tests/common/mod.rs b/crates/deps-lsp/tests/common/mod.rs index fca0d144..f1bb03ed 100644 --- a/crates/deps-lsp/tests/common/mod.rs +++ b/crates/deps-lsp/tests/common/mod.rs @@ -8,13 +8,13 @@ use std::io::{BufRead, BufReader, Read, Write}; use std::process::{Child, Command, Stdio}; /// LSP test client for communicating with the server binary. -pub struct LspClient { +pub(crate) struct LspClient { process: Child, } impl LspClient { /// Spawn the deps-lsp binary. - pub fn spawn() -> Self { + pub(crate) fn spawn() -> Self { let process = Command::new(env!("CARGO_BIN_EXE_deps-lsp")) .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -26,7 +26,7 @@ impl LspClient { } /// Send a JSON-RPC message to the server. - pub fn send(&mut self, message: &Value) { + pub(crate) fn send(&mut self, message: &Value) { let body = serde_json::to_string(message).unwrap(); let header = format!("Content-Length: {}\r\n\r\n", body.len()); @@ -40,7 +40,7 @@ impl LspClient { /// /// Skips notifications and returns the first response with matching id, /// or any response/error if no id filter is provided. - pub fn read_response(&mut self, expected_id: Option) -> Value { + pub(crate) fn read_response(&mut self, expected_id: Option) -> Value { let stdout = self.process.stdout.as_mut().expect("stdout not captured"); let mut reader = BufReader::new(stdout); @@ -52,9 +52,7 @@ impl LspClient { let bytes_read = reader.read_line(&mut line).expect("Failed to read header"); // EOF - server closed connection - if bytes_read == 0 { - panic!("Server closed connection unexpectedly"); - } + assert!(bytes_read != 0, "Server closed connection unexpectedly"); if line == "\r\n" || line == "\n" { break; @@ -104,7 +102,7 @@ impl LspClient { } /// Initialize the LSP session. - pub fn initialize(&mut self) -> Value { + pub(crate) fn initialize(&mut self) -> Value { self.send(&json!({ "jsonrpc": "2.0", "id": 1, @@ -141,7 +139,7 @@ impl LspClient { } /// Open a text document. - pub fn did_open(&mut self, uri: &str, language_id: &str, text: &str) { + pub(crate) fn did_open(&mut self, uri: &str, language_id: &str, text: &str) { self.send(&json!({ "jsonrpc": "2.0", "method": "textDocument/didOpen", @@ -157,7 +155,7 @@ impl LspClient { } /// Request hover information. - pub fn hover(&mut self, id: i64, uri: &str, line: u32, character: u32) -> Value { + pub(crate) fn hover(&mut self, id: i64, uri: &str, line: u32, character: u32) -> Value { self.send(&json!({ "jsonrpc": "2.0", "id": id, @@ -171,7 +169,7 @@ impl LspClient { } /// Request inlay hints. - pub fn inlay_hints(&mut self, id: i64, uri: &str) -> Value { + pub(crate) fn inlay_hints(&mut self, id: i64, uri: &str) -> Value { self.send(&json!({ "jsonrpc": "2.0", "id": id, @@ -188,7 +186,7 @@ impl LspClient { } /// Request completions. - pub fn completion(&mut self, id: i64, uri: &str, line: u32, character: u32) -> Value { + pub(crate) fn completion(&mut self, id: i64, uri: &str, line: u32, character: u32) -> Value { self.send(&json!({ "jsonrpc": "2.0", "id": id, @@ -202,7 +200,7 @@ impl LspClient { } /// Shutdown the server. - pub fn shutdown(&mut self) -> Value { + pub(crate) fn shutdown(&mut self) -> Value { self.send(&json!({ "jsonrpc": "2.0", "id": 999, diff --git a/crates/deps-lsp/tests/lsp_integration.rs b/crates/deps-lsp/tests/lsp_integration.rs index 22af65ce..b62c6a25 100644 --- a/crates/deps-lsp/tests/lsp_integration.rs +++ b/crates/deps-lsp/tests/lsp_integration.rs @@ -81,8 +81,7 @@ serde = "1.0" let hints = client.inlay_hints(10, "file:///test/Cargo.toml"); assert!( hints.get("error").is_none(), - "Inlay hints request should not error: {:?}", - hints + "Inlay hints request should not error: {hints:?}" ); assert!( hints.get("result").is_some(), @@ -170,8 +169,7 @@ serde = "1.0" // Should return a result (may be null if no hover info available yet) assert!( hover.get("error").is_none(), - "Hover should not error: {:?}", - hover + "Hover should not error: {hover:?}" ); } @@ -200,8 +198,7 @@ serde = "" // Should not error assert!( completion.get("error").is_none(), - "Completion should not error: {:?}", - completion + "Completion should not error: {completion:?}" ); } @@ -331,8 +328,7 @@ serde = "" // Should not error assert!( completion.get("error").is_none(), - "Cold start completion should not error: {:?}", - completion + "Cold start completion should not error: {completion:?}" ); // Should return some response (may be empty if network fails) diff --git a/crates/deps-npm/Cargo.toml b/crates/deps-npm/Cargo.toml index e5480d4a..e5e5cd40 100644 --- a/crates/deps-npm/Cargo.toml +++ b/crates/deps-npm/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "npm/package.json support for deps-lsp" publish = true +[lints] +workspace = true + [dependencies] deps-core = { workspace = true } async-trait = { workspace = true } diff --git a/crates/deps-npm/benches/npm_benchmarks.rs b/crates/deps-npm/benches/npm_benchmarks.rs index b4db440d..2a231e3c 100644 --- a/crates/deps-npm/benches/npm_benchmarks.rs +++ b/crates/deps-npm/benches/npm_benchmarks.rs @@ -175,20 +175,20 @@ fn bench_npm_parsing(c: &mut Criterion) { let uri = bench_uri(); group.bench_function("small_5_deps", |b| { - b.iter(|| parse_package_json(black_box(SMALL_PACKAGE_JSON), &uri)) + b.iter(|| parse_package_json(black_box(SMALL_PACKAGE_JSON), &uri)); }); group.bench_function("medium_25_deps", |b| { - b.iter(|| parse_package_json(black_box(MEDIUM_PACKAGE_JSON), &uri)) + b.iter(|| parse_package_json(black_box(MEDIUM_PACKAGE_JSON), &uri)); }); let large_json = generate_large_package_json(); group.bench_function("large_100_deps", |b| { - b.iter(|| parse_package_json(black_box(&large_json), &uri)) + b.iter(|| parse_package_json(black_box(&large_json), &uri)); }); group.bench_function("monorepo_all_sections", |b| { - b.iter(|| parse_package_json(black_box(MONOREPO_PACKAGE_JSON), &uri)) + b.iter(|| parse_package_json(black_box(MONOREPO_PACKAGE_JSON), &uri)); }); group.finish(); @@ -217,11 +217,11 @@ fn bench_position_tracking(c: &mut Criterion) { }"#; group.bench_function("single_dependency", |b| { - b.iter(|| parse_package_json(black_box(single), &uri)) + b.iter(|| parse_package_json(black_box(single), &uri)); }); group.bench_function("scoped_packages", |b| { - b.iter(|| parse_package_json(black_box(scoped), &uri)) + b.iter(|| parse_package_json(black_box(scoped), &uri)); }); group.finish(); @@ -236,7 +236,7 @@ fn bench_registry_parsing(c: &mut Criterion) { group.bench_function("npm_registry_response", |b| { b.iter(|| { let _value: Value = serde_json::from_str(black_box(NPM_REGISTRY_RESPONSE)).unwrap(); - }) + }); }); // Large registry response with 100 versions @@ -261,7 +261,7 @@ fn bench_registry_parsing(c: &mut Criterion) { group.bench_function("large_registry_100_versions", |b| { b.iter(|| { let _value: Value = serde_json::from_str(black_box(&large_response)).unwrap(); - }) + }); }); group.finish(); @@ -278,24 +278,24 @@ fn bench_version_matching(c: &mut Criterion) { // Caret range (most common) let caret_range = Range::parse("^4.18.0").unwrap(); group.bench_function("caret_range", |b| { - b.iter(|| caret_range.satisfies(black_box(&latest))) + b.iter(|| caret_range.satisfies(black_box(&latest))); }); // Tilde range let tilde_range = Range::parse("~4.18.0").unwrap(); group.bench_function("tilde_range", |b| { - b.iter(|| tilde_range.satisfies(black_box(&latest))) + b.iter(|| tilde_range.satisfies(black_box(&latest))); }); // Complex range let complex_range = Range::parse(">=4.17.0 <5.0.0").unwrap(); group.bench_function("complex_range", |b| { - b.iter(|| complex_range.satisfies(black_box(&latest))) + b.iter(|| complex_range.satisfies(black_box(&latest))); }); // Find latest matching version let versions: Vec = (0..20) - .map(|i| Version::parse(format!("4.18.{}", i)).unwrap()) + .map(|i| Version::parse(format!("4.18.{i}")).unwrap()) .collect(); group.bench_function("find_latest_matching", |b| { @@ -305,7 +305,7 @@ fn bench_version_matching(c: &mut Criterion) { .filter(|v| caret_range.satisfies(v)) .max() .cloned() - }) + }); }); group.finish(); @@ -335,7 +335,7 @@ fn bench_version_specifiers(c: &mut Criterion) { let uri = bench_uri(); for (name, content) in specifiers { group.bench_with_input(BenchmarkId::from_parameter(name), &content, |b, content| { - b.iter(|| parse_package_json(black_box(content), &uri)) + b.iter(|| parse_package_json(black_box(content), &uri)); }); } @@ -360,7 +360,7 @@ fn bench_name_collision(c: &mut Criterion) { }"#; c.bench_function("name_collision_in_scripts", |b| { - b.iter(|| parse_package_json(black_box(collision_json), &uri)) + b.iter(|| parse_package_json(black_box(collision_json), &uri)); }); } @@ -377,7 +377,7 @@ fn bench_unicode_parsing(c: &mut Criterion) { }"#; c.bench_function("unicode_parsing", |b| { - b.iter(|| parse_package_json(black_box(unicode_json), &uri)) + b.iter(|| parse_package_json(black_box(unicode_json), &uri)); }); } diff --git a/crates/deps-npm/src/error.rs b/crates/deps-npm/src/error.rs index 9bbc6a64..4a0eb7da 100644 --- a/crates/deps-npm/src/error.rs +++ b/crates/deps-npm/src/error.rs @@ -117,17 +117,15 @@ impl NpmError { impl From for NpmError { fn from(err: deps_core::DepsError) -> Self { match err { - deps_core::DepsError::ParseError { source, .. } => { - NpmError::CacheError(source.to_string()) - } - deps_core::DepsError::CacheError(msg) => NpmError::CacheError(msg), - deps_core::DepsError::InvalidVersionReq(msg) => NpmError::InvalidVersionSpecifier { + deps_core::DepsError::ParseError { source, .. } => Self::CacheError(source.to_string()), + deps_core::DepsError::CacheError(msg) => Self::CacheError(msg), + deps_core::DepsError::InvalidVersionReq(msg) => Self::InvalidVersionSpecifier { specifier: String::new(), message: msg, }, - deps_core::DepsError::Io(e) => NpmError::Io(e), - deps_core::DepsError::Json(e) => NpmError::JsonParseError { source: e }, - other => NpmError::CacheError(other.to_string()), + deps_core::DepsError::Io(e) => Self::Io(e), + deps_core::DepsError::Json(e) => Self::JsonParseError { source: e }, + other => Self::CacheError(other.to_string()), } } } @@ -136,25 +134,23 @@ impl From for NpmError { impl From for deps_core::DepsError { fn from(err: NpmError) -> Self { match err { - NpmError::JsonParseError { source } => deps_core::DepsError::Json(source), - NpmError::InvalidVersionSpecifier { message, .. } => { - deps_core::DepsError::InvalidVersionReq(message) - } + NpmError::JsonParseError { source } => Self::Json(source), + NpmError::InvalidVersionSpecifier { message, .. } => Self::InvalidVersionReq(message), NpmError::PackageNotFound { package } => { - deps_core::DepsError::CacheError(format!("Package '{}' not found", package)) + Self::CacheError(format!("Package '{package}' not found")) } - NpmError::RegistryError { package, source } => deps_core::DepsError::ParseError { - file_type: format!("npm registry for {}", package), + NpmError::RegistryError { package, source } => Self::ParseError { + file_type: format!("npm registry for {package}"), source, }, - NpmError::ApiResponseError { source, .. } => deps_core::DepsError::Json(source), - NpmError::InvalidStructure { message } => deps_core::DepsError::CacheError(message), + NpmError::ApiResponseError { source, .. } => Self::Json(source), + NpmError::InvalidStructure { message } => Self::CacheError(message), NpmError::MissingField { section, field } => { - deps_core::DepsError::CacheError(format!("Missing '{}' in {}", field, section)) + Self::CacheError(format!("Missing '{field}' in {section}")) } - NpmError::CacheError(msg) => deps_core::DepsError::CacheError(msg), - NpmError::Io(e) => deps_core::DepsError::Io(e), - NpmError::Other(e) => deps_core::DepsError::CacheError(e.to_string()), + NpmError::CacheError(msg) => Self::CacheError(msg), + NpmError::Io(e) => Self::Io(e), + NpmError::Other(e) => Self::CacheError(e.to_string()), } } } diff --git a/crates/deps-npm/src/formatter.rs b/crates/deps-npm/src/formatter.rs index 2ac65ef8..1c4acfd8 100644 --- a/crates/deps-npm/src/formatter.rs +++ b/crates/deps-npm/src/formatter.rs @@ -9,7 +9,7 @@ impl EcosystemFormatter for NpmFormatter { } fn package_url(&self, name: &str) -> String { - format!("https://www.npmjs.com/package/{}", name) + format!("https://www.npmjs.com/package/{name}") } fn yanked_message(&self) -> &'static str { diff --git a/crates/deps-npm/src/parser.rs b/crates/deps-npm/src/parser.rs index 7f739c05..9fb496c2 100644 --- a/crates/deps-npm/src/parser.rs +++ b/crates/deps-npm/src/parser.rs @@ -176,7 +176,7 @@ fn parse_dependency_section( // Calculate positions for name and version let (name_range, version_range) = - find_dependency_positions(content, name, &version_req, line_table); + find_dependency_positions(content, name, version_req.as_ref(), line_table); result.push(NpmDependency { name: name.clone(), @@ -197,13 +197,13 @@ fn parse_dependency_section( fn find_dependency_positions( content: &str, name: &str, - version_req: &Option, + version_req: Option<&String>, line_table: &LineOffsetTable, ) -> (Range, Option) { let mut name_range = Range::default(); let mut version_range = None; - let name_pattern = format!("\"{}\"", name); + let name_pattern = format!("\"{name}\""); // Find all occurrences of the name pattern and check which one is a dependency key let mut search_start = 0; @@ -226,7 +226,7 @@ fn find_dependency_positions( // Find version position (after the colon) if let Some(version) = version_req { - let version_search = format!("\"{}\"", version); + let version_search = format!("\"{version}\""); // Search for version only in the portion after the colon let colon_offset = name_start_idx + name_pattern.len() + (after_name.len() - trimmed.len()); diff --git a/crates/deps-npm/src/registry.rs b/crates/deps-npm/src/registry.rs index 534ad96b..1d58b408 100644 --- a/crates/deps-npm/src/registry.rs +++ b/crates/deps-npm/src/registry.rs @@ -35,7 +35,7 @@ pub struct NpmRegistry { impl NpmRegistry { /// Creates a new npm registry client with the given HTTP cache. - pub fn new(cache: Arc) -> Self { + pub const fn new(cache: Arc) -> Self { Self { cache } } @@ -67,7 +67,7 @@ impl NpmRegistry { /// # } /// ``` pub async fn get_versions(&self, name: &str) -> Result> { - let url = format!("{}/{}", REGISTRY_BASE, name); + let url = format!("{REGISTRY_BASE}/{name}"); let data = self.cache.get_cached(&url).await?; parse_package_metadata(&data) diff --git a/crates/deps-npm/src/types.rs b/crates/deps-npm/src/types.rs index 491b05e4..c3664133 100644 --- a/crates/deps-npm/src/types.rs +++ b/crates/deps-npm/src/types.rs @@ -23,7 +23,7 @@ use tower_lsp_server::ls_types::Range; /// assert_eq!(dep.name, "express"); /// assert!(matches!(dep.section, NpmDependencySection::Dependencies)); /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct NpmDependency { pub name: String, pub name_range: Range, @@ -56,7 +56,7 @@ deps_core::impl_dependency!(NpmDependency { /// let section = NpmDependencySection::Dependencies; /// assert!(matches!(section, NpmDependencySection::Dependencies)); /// ``` -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NpmDependencySection { /// Production dependencies (`dependencies`) Dependencies, diff --git a/crates/deps-pypi/Cargo.toml b/crates/deps-pypi/Cargo.toml index d322106e..3177d5a4 100644 --- a/crates/deps-pypi/Cargo.toml +++ b/crates/deps-pypi/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "PyPI/Python support for deps-lsp" publish = true +[lints] +workspace = true + [dependencies] deps-core = { workspace = true } async-trait = { workspace = true } diff --git a/crates/deps-pypi/benches/pypi_benchmarks.rs b/crates/deps-pypi/benches/pypi_benchmarks.rs index bd38a834..9713daae 100644 --- a/crates/deps-pypi/benches/pypi_benchmarks.rs +++ b/crates/deps-pypi/benches/pypi_benchmarks.rs @@ -161,24 +161,24 @@ fn bench_pypi_parsing(c: &mut Criterion) { let uri = bench_uri(); group.bench_function("pep621_small_5_deps", |b| { - b.iter(|| parser.parse_content(black_box(SMALL_PEP621), &uri)) + b.iter(|| parser.parse_content(black_box(SMALL_PEP621), &uri)); }); group.bench_function("pep621_medium_25_deps", |b| { - b.iter(|| parser.parse_content(black_box(MEDIUM_PEP621), &uri)) + b.iter(|| parser.parse_content(black_box(MEDIUM_PEP621), &uri)); }); let large_poetry = generate_large_poetry(); group.bench_function("poetry_large_100_deps", |b| { - b.iter(|| parser.parse_content(black_box(&large_poetry), &uri)) + b.iter(|| parser.parse_content(black_box(&large_poetry), &uri)); }); group.bench_function("pep735_format", |b| { - b.iter(|| parser.parse_content(black_box(PEP735_FORMAT), &uri)) + b.iter(|| parser.parse_content(black_box(PEP735_FORMAT), &uri)); }); group.bench_function("mixed_format", |b| { - b.iter(|| parser.parse_content(black_box(MIXED_FORMAT), &uri)) + b.iter(|| parser.parse_content(black_box(MIXED_FORMAT), &uri)); }); group.finish(); @@ -213,7 +213,7 @@ fn bench_pep508_parsing(c: &mut Criterion) { group.bench_with_input(BenchmarkId::from_parameter(name), &req_str, |b, req_str| { b.iter(|| { let _: Result = Requirement::from_str(black_box(req_str)); - }) + }); }); } @@ -232,20 +232,20 @@ fn bench_pep440_version_matching(c: &mut Criterion) { // Simple version specifier let simple = VersionSpecifiers::from_str(">=2.28.0").unwrap(); group.bench_function("simple_specifier", |b| { - b.iter(|| simple.contains(black_box(&latest))) + b.iter(|| simple.contains(black_box(&latest))); }); // Complex version specifier let complex = VersionSpecifiers::from_str(">=2.0,<3.0,!=2.28.1").unwrap(); group.bench_function("complex_specifier", |b| { - b.iter(|| complex.contains(black_box(&latest))) + b.iter(|| complex.contains(black_box(&latest))); }); // Pre-release handling let prerelease_version = Version::from_str("3.0.0b1").unwrap(); let prerelease_spec = VersionSpecifiers::from_str(">=3.0.0").unwrap(); group.bench_function("prerelease_check", |b| { - b.iter(|| prerelease_spec.contains(black_box(&prerelease_version))) + b.iter(|| prerelease_spec.contains(black_box(&prerelease_version))); }); // Find latest matching version @@ -261,7 +261,7 @@ fn bench_pep440_version_matching(c: &mut Criterion) { .filter(|v| simple.contains(v)) .max() .cloned() - }) + }); }); group.finish(); @@ -292,15 +292,15 @@ dependencies = ["numpy>=1.24; python_version>='3.9'"] "#; group.bench_function("simple_dependency", |b| { - b.iter(|| parser.parse_content(black_box(simple), &uri)) + b.iter(|| parser.parse_content(black_box(simple), &uri)); }); group.bench_function("with_extras", |b| { - b.iter(|| parser.parse_content(black_box(with_extras), &uri)) + b.iter(|| parser.parse_content(black_box(with_extras), &uri)); }); group.bench_function("with_markers", |b| { - b.iter(|| parser.parse_content(black_box(with_markers), &uri)) + b.iter(|| parser.parse_content(black_box(with_markers), &uri)); }); group.finish(); @@ -347,7 +347,7 @@ package = { url = "https://example.com/package.whl" }"#, for (name, content) in sources { group.bench_with_input(BenchmarkId::from_parameter(name), &content, |b, content| { - b.iter(|| parser.parse_content(black_box(content), &uri)) + b.iter(|| parser.parse_content(black_box(content), &uri)); }); } @@ -378,7 +378,7 @@ dev = [ "#; c.bench_function("parsing_with_comments", |b| { - b.iter(|| parser.parse_content(black_box(with_comments), &uri)) + b.iter(|| parser.parse_content(black_box(with_comments), &uri)); }); } @@ -398,7 +398,7 @@ dependencies = [ "#; c.bench_function("unicode_parsing", |b| { - b.iter(|| parser.parse_content(black_box(unicode_toml), &uri)) + b.iter(|| parser.parse_content(black_box(unicode_toml), &uri)); }); } @@ -438,7 +438,7 @@ django = ">=4.0,<5.0""#, for (name, content) in constraints { group.bench_with_input(BenchmarkId::from_parameter(name), &content, |b, content| { - b.iter(|| parser.parse_content(black_box(content), &uri)) + b.iter(|| parser.parse_content(black_box(content), &uri)); }); } diff --git a/crates/deps-pypi/src/ecosystem.rs b/crates/deps-pypi/src/ecosystem.rs index 50541f7c..17f85ef3 100644 --- a/crates/deps-pypi/src/ecosystem.rs +++ b/crates/deps-pypi/src/ecosystem.rs @@ -480,9 +480,9 @@ dependencies = [] let ecosystem = PypiEcosystem::new(cache); let uri = Uri::from_file_path("/test/pyproject.toml").unwrap(); - let content = r#"[project] + let content = r"[project] dependencies = [] -"#; +"; let parse_result = ecosystem.parse_manifest(content, &uri).await.unwrap(); let cached_versions = HashMap::new(); diff --git a/crates/deps-pypi/src/formatter.rs b/crates/deps-pypi/src/formatter.rs index 5b00e36f..a970eb2f 100644 --- a/crates/deps-pypi/src/formatter.rs +++ b/crates/deps-pypi/src/formatter.rs @@ -22,7 +22,7 @@ impl EcosystemFormatter for PypiFormatter { .and_then(|v| v.checked_add(1)) .unwrap_or(1); - format!(">={},<{}", version, next_major) + format!(">={version},<{next_major}") } fn version_satisfies_requirement(&self, version: &str, requirement: &str) -> bool { @@ -38,7 +38,7 @@ impl EcosystemFormatter for PypiFormatter { } fn package_url(&self, name: &str) -> String { - format!("https://pypi.org/project/{}", name) + format!("https://pypi.org/project/{name}") } fn is_position_on_dependency(&self, dep: &dyn Dependency, position: Position) -> bool { @@ -50,8 +50,7 @@ impl EcosystemFormatter for PypiFormatter { let end_char = dep .version_range() - .map(|r| r.end.character) - .unwrap_or(name_range.end.character); + .map_or(name_range.end.character, |r| r.end.character); let start_char = name_range.start.character.saturating_sub(2); let end_char = end_char.saturating_add(2); @@ -168,7 +167,7 @@ mod tests { } impl deps_core::Dependency for MockDep { - fn name(&self) -> &str { + fn name(&self) -> &'static str { "test-package" } fn name_range(&self) -> Range { diff --git a/crates/deps-pypi/src/lockfile.rs b/crates/deps-pypi/src/lockfile.rs index 7e234d7a..7990382d 100644 --- a/crates/deps-pypi/src/lockfile.rs +++ b/crates/deps-pypi/src/lockfile.rs @@ -117,7 +117,7 @@ impl LockFileProvider for PypiLockParser { return Ok(packages); }; - for table in package_array.iter() { + for table in package_array { // Extract required fields let Some(name) = table.get("name").and_then(|v: &toml_edit::Item| v.as_str()) else { tracing::warn!("Package missing name field"); @@ -542,9 +542,9 @@ source = { path = "../local-package" } #[tokio::test] async fn test_parse_empty_lock_file() { - let lockfile_content = r#" + let lockfile_content = r" version = 1 -"#; +"; let temp_dir = tempfile::tempdir().unwrap(); let lockfile_path = temp_dir.path().join("poetry.lock"); diff --git a/crates/deps-pypi/src/parser.rs b/crates/deps-pypi/src/parser.rs index dc666bbf..1c495ac5 100644 --- a/crates/deps-pypi/src/parser.rs +++ b/crates/deps-pypi/src/parser.rs @@ -65,7 +65,7 @@ pub struct PypiParser; impl PypiParser { /// Create a new PyPI parser. - pub fn new() -> Self { + pub const fn new() -> Self { Self } @@ -162,7 +162,7 @@ impl PypiParser { let mut dependencies = Vec::new(); - for value in requires_array.iter() { + for value in requires_array { if let Some(dep_str) = value.as_str() { // Find exact position of this dependency string in content let position = self @@ -201,7 +201,7 @@ impl PypiParser { let mut dependencies = Vec::new(); - for value in deps_array.iter() { + for value in deps_array { if let Some(dep_str) = value.as_str() { // Find exact position of this dependency string in content let position = self @@ -240,9 +240,9 @@ impl PypiParser { let mut dependencies = Vec::new(); - for (group_name, group_item) in opt_deps_table.iter() { + for (group_name, group_item) in opt_deps_table { if let Some(group_array) = group_item.as_array() { - for value in group_array.iter() { + for value in group_array { if let Some(dep_str) = value.as_str() { // Find exact position of this dependency string in content let position = self @@ -285,9 +285,9 @@ impl PypiParser { ) -> Result> { let mut dependencies = Vec::new(); - for (group_name, group_item) in dep_groups.iter() { + for (group_name, group_item) in dep_groups { if let Some(group_array) = group_item.as_array() { - for value in group_array.iter() { + for value in group_array { if let Some(dep_str) = value.as_str() { // Find exact position of this dependency string in content let position = self @@ -334,7 +334,7 @@ impl PypiParser { let mut dependencies = Vec::new(); - for (name, value) in deps_table.iter() { + for (name, value) in deps_table { // Skip Python version constraint if name == "python" { continue; @@ -368,13 +368,13 @@ impl PypiParser { let mut dependencies = Vec::new(); - for (group_name, group_item) in groups_table.iter() { + for (group_name, group_item) in groups_table { if let Some(group_table) = group_item.as_table() && let Some(deps_item) = group_table.get("dependencies") && let Some(deps_table) = deps_item.as_table() { - for (name, value) in deps_table.iter() { - let section_path = format!("tool.poetry.group.{}.dependencies", group_name); + for (name, value) in deps_table { + let section_path = format!("tool.poetry.group.{group_name}.dependencies"); let position = self.find_table_key_position(content, §ion_path, name); match self.parse_poetry_dependency(name, value, position) { @@ -429,7 +429,7 @@ impl PypiParser { let extras_joined = requirement .extras .iter() - .map(|e| e.to_string()) + .map(std::string::ToString::to_string) .collect::>() .join(","); extras_joined.len() + 2 // +2 for [ and ] @@ -459,7 +459,7 @@ impl PypiParser { None, None, PypiDependencySource::Git { - url: url_str.clone(), + url: url_str, rev: None, }, ) @@ -606,8 +606,7 @@ impl PypiParser { } Err(PypiError::unsupported_format(format!( - "Unsupported Poetry dependency format for '{}'", - name + "Unsupported Poetry dependency format for '{name}'" ))) } @@ -648,7 +647,7 @@ impl PypiParser { // pos is now at the start of dep_str (after opening quote) let before = &content[..pos]; let line = before.chars().filter(|&c| c == '\n').count() as u32; - let last_newline = before.rfind('\n').map(|p| p + 1).unwrap_or(0); + let last_newline = before.rfind('\n').map_or(0, |p| p + 1); let character = (pos - last_newline) as u32; used_positions.insert(pos); @@ -664,18 +663,18 @@ impl PypiParser { /// Find position of table key in source content. fn find_table_key_position(&self, content: &str, section: &str, key: &str) -> Option { // Find section first - let section_marker = format!("[{}]", section); + let section_marker = format!("[{section}]"); let section_start = content.find(§ion_marker)?; // Find the key after the section let after_section = &content[section_start..]; - let key_pattern = format!("{} = ", key); + let key_pattern = format!("{key} = "); let key_pos = after_section.find(&key_pattern)?; let total_offset = section_start + key_pos; let before_key = &content[..total_offset]; let line = before_key.chars().filter(|&c| c == '\n').count() as u32; - let last_newline = before_key.rfind('\n').map(|p| p + 1).unwrap_or(0); + let last_newline = before_key.rfind('\n').map_or(0, |p| p + 1); let character = (total_offset - last_newline) as u32; Some(Position::new(line, character)) diff --git a/crates/deps-pypi/src/registry.rs b/crates/deps-pypi/src/registry.rs index 59249b63..4a512227 100644 --- a/crates/deps-pypi/src/registry.rs +++ b/crates/deps-pypi/src/registry.rs @@ -80,7 +80,7 @@ pub struct PypiRegistry { impl PypiRegistry { /// Creates a new PyPI registry client with the given HTTP cache. - pub fn new(cache: Arc) -> Self { + pub const fn new(cache: Arc) -> Self { Self { cache } } @@ -113,7 +113,7 @@ impl PypiRegistry { /// ``` pub async fn get_versions(&self, name: &str) -> Result> { let normalized = normalize_package_name(name); - let url = format!("{}/{}/json", PYPI_BASE, normalized); + let url = format!("{PYPI_BASE}/{normalized}/json"); let data = self.cache.get_cached(&url).await.map_err(|e| { if e.to_string().contains("404") { PypiError::PackageNotFound { @@ -218,7 +218,7 @@ impl PypiRegistry { /// - JSON parsing fails pub async fn get_package_metadata(&self, name: &str) -> Result { let normalized = normalize_package_name(name); - let url = format!("{}/{}/json", PYPI_BASE, normalized); + let url = format!("{PYPI_BASE}/{normalized}/json"); let data = self.cache.get_cached(&url).await.map_err(|e| { if e.to_string().contains("404") { PypiError::PackageNotFound { @@ -240,7 +240,7 @@ impl PackageRegistry for PypiRegistry { type VersionReq = String; async fn get_versions(&self, name: &str) -> deps_core::error::Result> { - PypiRegistry::get_versions(self, name) + Self::get_versions(self, name) .await .map_err(|e| deps_core::error::DepsError::CacheError(e.to_string())) } @@ -250,7 +250,7 @@ impl PackageRegistry for PypiRegistry { name: &str, req: &Self::VersionReq, ) -> deps_core::error::Result> { - PypiRegistry::get_latest_matching(self, name, req) + Self::get_latest_matching(self, name, req) .await .map_err(|e| deps_core::error::DepsError::CacheError(e.to_string())) } @@ -260,7 +260,7 @@ impl PackageRegistry for PypiRegistry { query: &str, limit: usize, ) -> deps_core::error::Result> { - PypiRegistry::search(self, query, limit) + Self::search(self, query, limit) .await .map_err(|e| deps_core::error::DepsError::CacheError(e.to_string())) } @@ -273,7 +273,7 @@ impl deps_core::Registry for PypiRegistry { &self, name: &str, ) -> deps_core::error::Result>> { - let versions = PypiRegistry::get_versions(self, name) + let versions = Self::get_versions(self, name) .await .map_err(|e| deps_core::error::DepsError::CacheError(e.to_string()))?; Ok(versions @@ -287,7 +287,7 @@ impl deps_core::Registry for PypiRegistry { name: &str, req: &str, ) -> deps_core::error::Result>> { - let version = PypiRegistry::get_latest_matching(self, name, req) + let version = Self::get_latest_matching(self, name, req) .await .map_err(|e| deps_core::error::DepsError::CacheError(e.to_string()))?; Ok(version.map(|v| Box::new(v) as Box)) @@ -298,7 +298,7 @@ impl deps_core::Registry for PypiRegistry { query: &str, limit: usize, ) -> deps_core::error::Result>> { - let packages = PypiRegistry::search(self, query, limit) + let packages = Self::search(self, query, limit) .await .map_err(|e| deps_core::error::DepsError::CacheError(e.to_string()))?; Ok(packages diff --git a/crates/deps-pypi/src/types.rs b/crates/deps-pypi/src/types.rs index bebae615..00b9355e 100644 --- a/crates/deps-pypi/src/types.rs +++ b/crates/deps-pypi/src/types.rs @@ -108,7 +108,7 @@ pub enum PypiDependencySection { /// url: "https://example.com/package.whl".into(), /// }; /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum PypiDependencySource { /// Dependency from PyPI registry PyPI,