You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Comprehensive architectural review of the deps-lsp codebase (9 crates, 8 ecosystem implementations). This issue documents identified problems grouped by theme, with prioritized improvement items.
P0: Remove dead legacy trait system
The codebase carries two parallel trait hierarchies in deps-core/src/registry.rs:
New: Registry, Version, Metadata (trait object-based, used by all ecosystems)
Legacy: PackageRegistry, VersionInfo, PackageMetadata (GAT-based with associated types, used only by handler.rs test mocks)
The legacy traits are explicitly marked deprecated but still exported from deps-core/src/lib.rs. The EcosystemHandler trait in handler.rs is also deprecated (line 8: "being phased out in favor of the new Ecosystem trait") but contains ~1000 lines of generic handler code plus ~900 lines of tests that duplicate what lsp_helpers.rs already provides.
Action items:
Delete PackageRegistry, VersionInfo, PackageMetadata traits from registry.rs
Delete EcosystemHandler trait and all generic functions from handler.rs
Remove InlayHintsConfig, DiagnosticsConfig from handler.rs (duplicated in ecosystem.rs as EcosystemConfig)
Remove VersionStringGetter, YankedChecker helper traits (only used by deprecated handler)
Clean up re-exports in deps-core/src/lib.rs
Impact: ~2000 lines removed. Eliminates confusion about which trait system to use.
P0: Eliminate ecosystem boilerplate via default trait methods or macro
Every ecosystem implementation (CargoEcosystem, NpmEcosystem, PypiEcosystem, GoEcosystem, BundlerEcosystem, DartEcosystem, MavenEcosystem, GradleEcosystem) has identical implementations for 4 of the 6 LSP methods on the Ecosystem trait:
// This exact pattern is copy-pasted in ALL 8 ecosystems:asyncfngenerate_inlay_hints(...) -> Vec<InlayHint>{
lsp_helpers::generate_inlay_hints(parse_result, cached_versions, resolved_versions, loading_state, config,&self.formatter)}asyncfngenerate_hover(...) -> Option<Hover>{
lsp_helpers::generate_hover(parse_result, position, cached_versions, resolved_versions,self.registry.as_ref(),&self.formatter).await}asyncfngenerate_code_actions(...) -> Vec<CodeAction>{
lsp_helpers::generate_code_actions(parse_result, position, uri,self.registry.as_ref(),&self.formatter).await}asyncfngenerate_diagnostics(...) -> Vec<Diagnostic>{
lsp_helpers::generate_diagnostics_from_cache(parse_result, cached_versions, resolved_versions,&self.formatter)}
Action items:
Add fn formatter(&self) -> &dyn EcosystemFormatter to Ecosystem trait
Move generate_inlay_hints, generate_hover, generate_code_actions, generate_diagnostics to default methods
Remove identical implementations from all 8 ecosystem crates
Impact: ~400 lines of duplicated code removed across 8 crates.
P0: Deduplicate complete_package_names across ecosystems
The complete_package_names method is copy-pasted identically in 6 ecosystem crates (Cargo, npm, Bundler, Dart, PyPI, Go). The pattern:
Note: Go ecosystem retains custom implementation due to different min_prefix (3 chars vs 2).
Impact: ~120 lines removed.
P1: Replace async_trait with native async fn in traits
Since Rust 1.75, async fn in traits is stable. The project MSRV is 1.89 which is well above the threshold. All uses of #[async_trait] can be replaced with native async trait methods.
Current affected traits:
Ecosystem trait (deps-core/src/ecosystem.rs)
Registry trait (deps-core/src/registry.rs)
All ecosystem impl Ecosystem for ... blocks (8 crates)
All registry impl Registry for ... blocks (8 crates)
Action items:
Remove #[async_trait] annotations — replaced with explicit BoxFuture pattern
Eliminates async-trait proc-macro overhead (14% build time improvement)
Note: Used BoxFuture instead of native async fn in traits because the Ecosystem and Registry traits are used as trait objects (dyn Ecosystem), which requires Send + Sync bounds that native async fn in traits does not yet support ergonomically with dyn. Follow-up: revisit when async fn in dyn Trait stabilizes.
deps-core/src/handler.rs:648 — takes (Range, Range), checks if two ranges overlap
deps-core/src/lsp_helpers.rs:64 — takes (Range, Position), checks if position is in range
Both are public. The handler version will be deleted with P0, but the naming conflict is confusing. The lsp_helpers version is actually position_in_range semantics but named ranges_overlap.
The Ecosystem trait is the central extension point but should only be implemented within the deps-lsp workspace. External implementations would break invariants assumed by the LSP server.
EcosystemFormatter::format_version_for_code_action returns different formats:
Cargo: "1.0.0" (with quotes)
npm: 1.0.0 (without quotes)
PyPI: ecosystem-specific
Go: ecosystem-specific
The inconsistency is intentional (different manifest formats), but the method name doesn't communicate that it includes quoting. Consider renaming to format_version_for_text_edit to make the TextEdit context clear.
DependencySource is an enum (Registry, Git, Path, Workspace) but is stringly-typed in many places. Consider phantom type markers for compile-time differentiation when the source matters for logic branching.
P3: Duplicate is_prerelease logic
Version::is_prerelease() and VersionInfo::is_prerelease() have identical default implementations (substring matching on -alpha, -beta, etc.). After removing legacy traits (P0), only one copy remains.
Resolved by P0: VersionInfo deleted, single Version::is_prerelease() remains.
Summary
Comprehensive architectural review of the deps-lsp codebase (9 crates, 8 ecosystem implementations). This issue documents identified problems grouped by theme, with prioritized improvement items.
P0: Remove dead legacy trait system
The codebase carries two parallel trait hierarchies in
deps-core/src/registry.rs:Registry,Version,Metadata(trait object-based, used by all ecosystems)PackageRegistry,VersionInfo,PackageMetadata(GAT-based with associated types, used only byhandler.rstest mocks)The legacy traits are explicitly marked deprecated but still exported from
deps-core/src/lib.rs. TheEcosystemHandlertrait inhandler.rsis also deprecated (line 8: "being phased out in favor of the new Ecosystem trait") but contains ~1000 lines of generic handler code plus ~900 lines of tests that duplicate whatlsp_helpers.rsalready provides.Action items:
PackageRegistry,VersionInfo,PackageMetadatatraits fromregistry.rsEcosystemHandlertrait and all generic functions fromhandler.rsInlayHintsConfig,DiagnosticsConfigfromhandler.rs(duplicated inecosystem.rsasEcosystemConfig)VersionStringGetter,YankedCheckerhelper traits (only used by deprecated handler)deps-core/src/lib.rsImpact: ~2000 lines removed. Eliminates confusion about which trait system to use.
P0: Eliminate ecosystem boilerplate via default trait methods or macro
Every ecosystem implementation (
CargoEcosystem,NpmEcosystem,PypiEcosystem,GoEcosystem,BundlerEcosystem,DartEcosystem,MavenEcosystem,GradleEcosystem) has identical implementations for 4 of the 6 LSP methods on theEcosystemtrait:Action items:
fn formatter(&self) -> &dyn EcosystemFormattertoEcosystemtraitgenerate_inlay_hints,generate_hover,generate_code_actions,generate_diagnosticsto default methodsImpact: ~400 lines of duplicated code removed across 8 crates.
P0: Deduplicate
complete_package_namesacross ecosystemsThe
complete_package_namesmethod is copy-pasted identically in 6 ecosystem crates (Cargo, npm, Bundler, Dart, PyPI, Go). The pattern:Maven and Gradle already use
deps_core::completion::complete_package_names_genericwhich does the same thing.Action items:
complete_package_names_generic— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71Impact: ~120 lines removed.
P1: Replace
async_traitwith native async fn in traitsSince Rust 1.75,
async fnin traits is stable. The project MSRV is 1.89 which is well above the threshold. All uses of#[async_trait]can be replaced with native async trait methods.Current affected traits:
Ecosystemtrait (deps-core/src/ecosystem.rs)Registrytrait (deps-core/src/registry.rs)impl Ecosystem for ...blocks (8 crates)impl Registry for ...blocks (8 crates)Action items:
#[async_trait]annotations — replaced with explicitBoxFuturepatternasync-traitproc-macro overhead (14% build time improvement)Impact: 14% faster build times, proc-macro eliminated.
P1: Consolidate duplicate
ranges_overlapfunctionsThere are two different
ranges_overlapfunctions:deps-core/src/handler.rs:648— takes(Range, Range), checks if two ranges overlapdeps-core/src/lsp_helpers.rs:64— takes(Range, Position), checks if position is in rangeBoth are public. The handler version will be deleted with P0, but the naming conflict is confusing. The
lsp_helpersversion is actuallyposition_in_rangesemantics but namedranges_overlap.Action items:
handler.rsdeleted — removes the(Range, Range)variant — done in refactor(core): remove legacy traits, add Ecosystem defaults, replace async_trait #70lsp_helpers::ranges_overlaptoposition_in_rangeand audit callers — done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71P1: Test boilerplate — extract shared MockDep/MockParseResult
The
lsp_helpers.rstest module has 7 copies of identicalMockDep+MockParseResultstructs. Each test redefines the same mock types.Action items:
MockDepandMockParseResultindeps-core::lsp_helperstest module — done in refactor(core): remove legacy traits, add Ecosystem defaults, replace async_trait #70Migrate remaining local mock copies in ecosystem crates— not applicable: ecosystem crates use concrete types (ParsedDependency, GoDependency) that require ecosystem-specific mock impls. Core mocks centralized in refactor(core): remove legacy traits, add Ecosystem defaults, replace async_trait #70.Impact: ~500 lines of test boilerplate removed from
lsp_helpers.rs.P2: Leverage Edition 2024 and modern Rust features
The project already uses Edition 2024 and
let chains. Additional opportunities:LazyLockvsonce_cell—std::sync::LazyLockis stable since 1.80. The project uses bothonce_cellandLazyLock. Removeonce_celldependency.Action items:
once_cell::sync::Lazywithstd::sync::LazyLockeverywhere — done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71once_cellfrom workspace dependencies — done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71P2: Seal the Ecosystem trait
The
Ecosystemtrait is the central extension point but should only be implemented within the deps-lsp workspace. External implementations would break invariants assumed by the LSP server.Action items:
mod private { pub trait Sealed {} }todeps-core— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71Ecosystem: private::Sealed— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71Sealedfor each ecosystem type — done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71P2: Version formatting inconsistency
EcosystemFormatter::format_version_for_code_actionreturns different formats:"1.0.0"(with quotes)1.0.0(without quotes)The inconsistency is intentional (different manifest formats), but the method name doesn't communicate that it includes quoting. Consider renaming to
format_version_for_text_editto make the TextEdit context clear.Action items:
format_version_for_code_actiontoformat_version_for_text_edit— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71P3: Type-safe dependency source (DONE)
DependencySourceis an enum (Registry,Git,Path,Workspace) but is stringly-typed in many places. Consider phantom type markers for compile-time differentiation when the source matters for logic branching.P3: Duplicate
is_prereleaselogicVersion::is_prerelease()andVersionInfo::is_prerelease()have identical default implementations (substring matching on-alpha,-beta, etc.). After removing legacy traits (P0), only one copy remains.VersionInfodeleted, singleVersion::is_prerelease()remains.Implementation order
P0: Remove legacy traits (handler.rs, PackageRegistry, VersionInfo, PackageMetadata)— done in refactor(core): remove legacy traits, add Ecosystem defaults, replace async_trait #70P0: Default methods on Ecosystem trait (deduplicate LSP handlers)— done in refactor(core): remove legacy traits, add Ecosystem defaults, replace async_trait #70P0: Consolidate— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71complete_package_namesP1: Replace— done in refactor(core): remove legacy traits, add Ecosystem defaults, replace async_trait #70async_traitwith BoxFuture patternP1: Extract test helpers— centralized in refactor(core): remove legacy traits, add Ecosystem defaults, replace async_trait #70, ecosystem crates deferredP1: Clean up— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71ranges_overlapnamingP2: Remove— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71once_cell, seal Ecosystem, rename formatter methodP3: Type-safe improvements— done in refactor(core): consolidate complete_package_names, seal Ecosystem, remove once_cell #71 (unified DependencySource enum)