deps_core/
parser.rs

1use crate::error::Result;
2use tower_lsp_server::ls_types::{Range, Uri};
3
4/// Generic manifest parser interface.
5///
6/// Implementors parse ecosystem-specific manifest files (Cargo.toml, package.json, etc.)
7/// and extract dependency information with precise LSP positions.
8///
9/// # Note
10///
11/// This trait is being phased out in favor of the `Ecosystem` trait.
12/// New implementations should use `Ecosystem::parse_manifest()` instead.
13pub trait ManifestParser: Send + Sync {
14    /// Parsed dependency type for this ecosystem.
15    type Dependency: DependencyInfo + Clone + Send + Sync;
16
17    /// Parse result containing dependencies and optional workspace information.
18    type ParseResult: ParseResultInfo<Dependency = Self::Dependency> + Send;
19
20    /// Parses a manifest file and extracts all dependencies with positions.
21    ///
22    /// # Errors
23    ///
24    /// Returns error if:
25    /// - Manifest syntax is invalid
26    /// - File path cannot be determined from URL
27    fn parse(&self, content: &str, doc_uri: &Uri) -> Result<Self::ParseResult>;
28}
29
30/// Dependency information trait.
31///
32/// All parsed dependencies must implement this for generic handler access.
33///
34/// # Note
35///
36/// The new `Ecosystem` trait uses `crate::ecosystem::Dependency` instead.
37/// This trait is kept for backward compatibility during migration.
38pub trait DependencyInfo {
39    /// Dependency name (package/crate name).
40    fn name(&self) -> &str;
41
42    /// LSP range of the dependency name in the source file.
43    fn name_range(&self) -> Range;
44
45    /// Version requirement string (e.g., "^1.0", "~2.3.4").
46    fn version_requirement(&self) -> Option<&str>;
47
48    /// LSP range of the version string (for inlay hints positioning).
49    fn version_range(&self) -> Option<Range>;
50
51    /// Dependency source (registry, git, path).
52    fn source(&self) -> DependencySource;
53
54    /// Feature flags requested (Cargo-specific, empty for npm).
55    fn features(&self) -> &[String] {
56        &[]
57    }
58}
59
60/// Parse result information trait.
61///
62/// # Note
63///
64/// The new `Ecosystem` trait uses `crate::ecosystem::ParseResult` instead.
65/// This trait is kept for backward compatibility during migration.
66pub trait ParseResultInfo {
67    type Dependency: DependencyInfo;
68
69    /// All dependencies found in the manifest.
70    fn dependencies(&self) -> &[Self::Dependency];
71
72    /// Workspace root path (for monorepo support).
73    fn workspace_root(&self) -> Option<&std::path::Path>;
74}
75
76/// Dependency source (shared across ecosystems).
77#[derive(Debug, Clone, PartialEq)]
78pub enum DependencySource {
79    /// Dependency from default registry (crates.io, npm, PyPI).
80    Registry,
81    /// Dependency from Git repository.
82    Git { url: String, rev: Option<String> },
83    /// Dependency from local filesystem path.
84    Path { path: String },
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_dependency_source_registry() {
93        let source = DependencySource::Registry;
94        assert_eq!(source, DependencySource::Registry);
95    }
96
97    #[test]
98    fn test_dependency_source_git() {
99        let source = DependencySource::Git {
100            url: "https://github.com/user/repo".into(),
101            rev: Some("main".into()),
102        };
103
104        match source {
105            DependencySource::Git { url, rev } => {
106                assert_eq!(url, "https://github.com/user/repo");
107                assert_eq!(rev, Some("main".into()));
108            }
109            _ => panic!("Expected Git source"),
110        }
111    }
112
113    #[test]
114    fn test_dependency_source_git_no_rev() {
115        let source = DependencySource::Git {
116            url: "https://github.com/user/repo".into(),
117            rev: None,
118        };
119
120        match source {
121            DependencySource::Git { url, rev } => {
122                assert_eq!(url, "https://github.com/user/repo");
123                assert!(rev.is_none());
124            }
125            _ => panic!("Expected Git source"),
126        }
127    }
128
129    #[test]
130    fn test_dependency_source_path() {
131        let source = DependencySource::Path {
132            path: "../local-crate".into(),
133        };
134
135        match source {
136            DependencySource::Path { path } => {
137                assert_eq!(path, "../local-crate");
138            }
139            _ => panic!("Expected Path source"),
140        }
141    }
142
143    #[test]
144    fn test_dependency_source_clone() {
145        let source1 = DependencySource::Git {
146            url: "https://example.com/repo".into(),
147            rev: Some("v1.0".into()),
148        };
149        let source2 = source1.clone();
150
151        assert_eq!(source1, source2);
152    }
153
154    #[test]
155    fn test_dependency_source_equality() {
156        let reg1 = DependencySource::Registry;
157        let reg2 = DependencySource::Registry;
158        assert_eq!(reg1, reg2);
159
160        let git1 = DependencySource::Git {
161            url: "https://example.com".into(),
162            rev: None,
163        };
164        let git2 = DependencySource::Git {
165            url: "https://example.com".into(),
166            rev: None,
167        };
168        assert_eq!(git1, git2);
169
170        let git3 = DependencySource::Git {
171            url: "https://different.com".into(),
172            rev: None,
173        };
174        assert_ne!(git1, git3);
175    }
176
177    #[test]
178    fn test_dependency_source_debug() {
179        let source = DependencySource::Registry;
180        let debug = format!("{:?}", source);
181        assert_eq!(debug, "Registry");
182
183        let git = DependencySource::Git {
184            url: "https://example.com".into(),
185            rev: Some("main".into()),
186        };
187        let git_debug = format!("{:?}", git);
188        assert!(git_debug.contains("https://example.com"));
189        assert!(git_debug.contains("main"));
190    }
191}