deps_go/
types.rs

1//! Types for Go module dependency management.
2
3use deps_core::parser::DependencySource;
4use std::any::Any;
5use tower_lsp_server::ls_types::Range;
6
7/// A dependency from a go.mod file.
8#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
9pub struct GoDependency {
10    /// Module path (e.g., "github.com/gin-gonic/gin")
11    pub module_path: String,
12    /// LSP range of the module path in source
13    pub module_path_range: Range,
14    /// Version requirement (e.g., "v1.9.1", "v0.0.0-20191109021931-daa7c04131f5")
15    pub version: Option<String>,
16    /// LSP range of version in source
17    pub version_range: Option<Range>,
18    /// Dependency directive type
19    pub directive: GoDirective,
20    /// Whether this is an indirect dependency (// indirect comment)
21    pub indirect: bool,
22}
23
24/// Go module directive types.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
26pub enum GoDirective {
27    /// Direct dependency in require block
28    Require,
29    /// Replacement directive
30    Replace,
31    /// Exclusion directive
32    Exclude,
33    /// Retraction directive
34    Retract,
35}
36
37/// Version information from proxy.golang.org.
38#[derive(Debug, Clone)]
39pub struct GoVersion {
40    /// Version string (e.g., "v1.9.1")
41    pub version: String,
42    /// Timestamp when version was published
43    pub time: Option<String>,
44    /// Whether this is a pseudo-version
45    pub is_pseudo: bool,
46    /// Whether this version is retracted
47    pub retracted: bool,
48}
49
50/// Package metadata from proxy.golang.org.
51#[derive(Debug, Clone)]
52pub struct GoMetadata {
53    /// Module path
54    pub module_path: String,
55    /// Latest stable version
56    pub latest_version: String,
57    /// Description (if available from go.mod or README)
58    pub description: Option<String>,
59    /// Repository URL (inferred from module path)
60    pub repository: Option<String>,
61    /// Documentation URL (pkg.go.dev)
62    pub documentation: Option<String>,
63}
64
65// NOTE: Cannot use deps_core::impl_dependency! macro because we need to provide custom
66// features() implementation (Go modules don't have features like Cargo).
67// The macro would provide features() but we need to override it anyway.
68impl deps_core::parser::DependencyInfo for GoDependency {
69    fn name(&self) -> &str {
70        &self.module_path
71    }
72
73    fn name_range(&self) -> Range {
74        self.module_path_range
75    }
76
77    fn version_requirement(&self) -> Option<&str> {
78        self.version.as_deref()
79    }
80
81    fn version_range(&self) -> Option<Range> {
82        self.version_range
83    }
84
85    fn source(&self) -> DependencySource {
86        DependencySource::Registry
87    }
88
89    fn features(&self) -> &[String] {
90        &[]
91    }
92}
93
94impl deps_core::ecosystem::Dependency for GoDependency {
95    fn name(&self) -> &str {
96        &self.module_path
97    }
98
99    fn name_range(&self) -> Range {
100        self.module_path_range
101    }
102
103    fn version_requirement(&self) -> Option<&str> {
104        self.version.as_deref()
105    }
106
107    fn version_range(&self) -> Option<Range> {
108        self.version_range
109    }
110
111    fn source(&self) -> DependencySource {
112        DependencySource::Registry
113    }
114
115    fn features(&self) -> &[String] {
116        &[]
117    }
118
119    fn as_any(&self) -> &dyn Any {
120        self
121    }
122}
123
124// NOTE: Cannot use impl_version! macro because GoVersion has custom is_prerelease() logic.
125// Go considers pseudo-versions as pre-releases, and has special handling for +incompatible suffix.
126impl deps_core::registry::Version for GoVersion {
127    fn version_string(&self) -> &str {
128        &self.version
129    }
130
131    fn is_yanked(&self) -> bool {
132        self.retracted
133    }
134
135    fn is_prerelease(&self) -> bool {
136        // Go considers pseudo-versions as pre-releases (they're commit-based).
137        // Regular pre-releases contain '-' (e.g., v1.0.0-beta.1).
138        // BUT: +incompatible suffix is NOT a pre-release indicator.
139        self.is_pseudo || (self.version.contains('-') && !self.version.contains("+incompatible"))
140    }
141
142    fn features(&self) -> Vec<String> {
143        vec![]
144    }
145
146    fn as_any(&self) -> &dyn Any {
147        self
148    }
149}
150
151deps_core::impl_metadata!(GoMetadata {
152    name: module_path,
153    description: description,
154    repository: repository,
155    documentation: documentation,
156    latest_version: latest_version,
157});
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use deps_core::parser::DependencyInfo;
163    use deps_core::registry::{Metadata, Version};
164    use tower_lsp_server::ls_types::Position;
165
166    #[test]
167    fn test_go_dependency_trait() {
168        let dep = GoDependency {
169            module_path: "github.com/gin-gonic/gin".to_string(),
170            module_path_range: Range::new(Position::new(0, 0), Position::new(0, 10)),
171            version: Some("v1.9.1".to_string()),
172            version_range: Some(Range::new(Position::new(0, 11), Position::new(0, 17))),
173            directive: GoDirective::Require,
174            indirect: false,
175        };
176
177        assert_eq!(dep.name(), "github.com/gin-gonic/gin");
178        assert_eq!(dep.version_requirement(), Some("v1.9.1"));
179        assert!(matches!(dep.source(), DependencySource::Registry));
180        assert_eq!(dep.features().len(), 0);
181    }
182
183    #[test]
184    fn test_go_version_trait() {
185        let version = GoVersion {
186            version: "v1.9.1".to_string(),
187            time: Some("2023-01-01T00:00:00Z".to_string()),
188            is_pseudo: false,
189            retracted: false,
190        };
191
192        assert_eq!(version.version_string(), "v1.9.1");
193        assert!(!version.is_yanked());
194        assert!(!version.is_prerelease());
195        assert!(version.is_stable());
196    }
197
198    #[test]
199    fn test_pseudo_version_is_prerelease() {
200        let version = GoVersion {
201            version: "v0.0.0-20191109021931-daa7c04131f5".to_string(),
202            time: None,
203            is_pseudo: true,
204            retracted: false,
205        };
206
207        assert!(version.is_prerelease());
208        assert!(!version.is_stable());
209    }
210
211    #[test]
212    fn test_retracted_version_is_yanked() {
213        let version = GoVersion {
214            version: "v1.0.0".to_string(),
215            time: None,
216            is_pseudo: false,
217            retracted: true,
218        };
219
220        assert!(version.is_yanked());
221        assert!(!version.is_stable());
222    }
223
224    #[test]
225    fn test_go_metadata_trait() {
226        let metadata = GoMetadata {
227            module_path: "github.com/gin-gonic/gin".to_string(),
228            latest_version: "v1.9.1".to_string(),
229            description: Some("Gin is a HTTP web framework".to_string()),
230            repository: Some("https://github.com/gin-gonic/gin".to_string()),
231            documentation: Some("https://pkg.go.dev/github.com/gin-gonic/gin".to_string()),
232        };
233
234        assert_eq!(metadata.name(), "github.com/gin-gonic/gin");
235        assert_eq!(metadata.latest_version(), "v1.9.1");
236        assert_eq!(metadata.description(), Some("Gin is a HTTP web framework"));
237        assert_eq!(
238            metadata.repository(),
239            Some("https://github.com/gin-gonic/gin")
240        );
241        assert_eq!(
242            metadata.documentation(),
243            Some("https://pkg.go.dev/github.com/gin-gonic/gin")
244        );
245    }
246
247    #[test]
248    fn test_go_directive_equality() {
249        assert_eq!(GoDirective::Require, GoDirective::Require);
250        assert_ne!(GoDirective::Require, GoDirective::Replace);
251    }
252}