deps_core/
macros.rs

1//! Macro utilities for reducing boilerplate in ecosystem implementations.
2//!
3//! Provides macros for implementing common traits with minimal code duplication.
4
5/// Implement `Dependency` and `DependencyInfo` traits for a struct.
6///
7/// # Arguments
8///
9/// * `$type` - The struct type name
10/// * `name` - Field name for the dependency name (`String`)
11/// * `name_range` - Field name for the name range (`Range`)
12/// * `version` - Field name for version requirement (`Option<String>`)
13/// * `version_range` - Field name for version range (`Option<Range>`)
14/// * `source` - Optional: expression for dependency source (defaults to `Registry`)
15///
16/// # Examples
17///
18/// ```ignore
19/// use deps_core::impl_dependency;
20///
21/// pub struct MyDependency {
22///     pub name: String,
23///     pub name_range: Range,
24///     pub version_req: Option<String>,
25///     pub version_range: Option<Range>,
26/// }
27///
28/// impl_dependency!(MyDependency {
29///     name: name,
30///     name_range: name_range,
31///     version: version_req,
32///     version_range: version_range,
33/// });
34/// ```
35#[macro_export]
36macro_rules! impl_dependency {
37    ($type:ty {
38        name: $name:ident,
39        name_range: $name_range:ident,
40        version: $version:ident,
41        version_range: $version_range:ident $(,)?
42    }) => {
43        $crate::impl_dependency!($type {
44            name: $name,
45            name_range: $name_range,
46            version: $version,
47            version_range: $version_range,
48            source: $crate::parser::DependencySource::Registry,
49        });
50    };
51    ($type:ty {
52        name: $name:ident,
53        name_range: $name_range:ident,
54        version: $version:ident,
55        version_range: $version_range:ident,
56        source: $source:expr $(,)?
57    }) => {
58        impl $crate::parser::DependencyInfo for $type {
59            fn name(&self) -> &str {
60                &self.$name
61            }
62
63            fn name_range(&self) -> ::tower_lsp_server::ls_types::Range {
64                self.$name_range
65            }
66
67            fn version_requirement(&self) -> Option<&str> {
68                self.$version.as_deref()
69            }
70
71            fn version_range(&self) -> Option<::tower_lsp_server::ls_types::Range> {
72                self.$version_range
73            }
74
75            fn source(&self) -> $crate::parser::DependencySource {
76                $source
77            }
78        }
79
80        impl $crate::ecosystem::Dependency for $type {
81            fn name(&self) -> &str {
82                &self.$name
83            }
84
85            fn name_range(&self) -> ::tower_lsp_server::ls_types::Range {
86                self.$name_range
87            }
88
89            fn version_requirement(&self) -> Option<&str> {
90                self.$version.as_deref()
91            }
92
93            fn version_range(&self) -> Option<::tower_lsp_server::ls_types::Range> {
94                self.$version_range
95            }
96
97            fn source(&self) -> $crate::parser::DependencySource {
98                $source
99            }
100
101            fn as_any(&self) -> &dyn ::std::any::Any {
102                self
103            }
104        }
105    };
106}
107
108/// Implement `Version` and `VersionInfo` traits for a struct.
109///
110/// # Arguments
111///
112/// * `$type` - The struct type name
113/// * `version` - Field name for version string (`String`)
114/// * `yanked` - Field name for yanked/deprecated status (`bool`)
115///
116/// # Examples
117///
118/// ```ignore
119/// use deps_core::impl_version;
120///
121/// pub struct MyVersion {
122///     pub version: String,
123///     pub deprecated: bool,
124/// }
125///
126/// impl_version!(MyVersion {
127///     version: version,
128///     yanked: deprecated,
129/// });
130/// ```
131#[macro_export]
132macro_rules! impl_version {
133    ($type:ty {
134        version: $version:ident,
135        yanked: $yanked:ident $(,)?
136    }) => {
137        impl $crate::registry::VersionInfo for $type {
138            fn version_string(&self) -> &str {
139                &self.$version
140            }
141
142            fn is_yanked(&self) -> bool {
143                self.$yanked
144            }
145        }
146
147        impl $crate::registry::Version for $type {
148            fn version_string(&self) -> &str {
149                &self.$version
150            }
151
152            fn is_yanked(&self) -> bool {
153                self.$yanked
154            }
155
156            fn as_any(&self) -> &dyn ::std::any::Any {
157                self
158            }
159        }
160    };
161}
162
163/// Implement `Metadata` and `PackageMetadata` traits for a struct.
164///
165/// # Arguments
166///
167/// * `$type` - The struct type name
168/// * `name` - Field name for package name (`String`)
169/// * `description` - Field name for description (`Option<String>`)
170/// * `repository` - Field name for repository (`Option<String>`)
171/// * `documentation` - Field name for documentation URL (`Option<String>`)
172/// * `latest_version` - Field name for latest version (`String`)
173///
174/// # Examples
175///
176/// ```ignore
177/// use deps_core::impl_metadata;
178///
179/// pub struct MyPackage {
180///     pub name: String,
181///     pub description: Option<String>,
182///     pub repository: Option<String>,
183///     pub homepage: Option<String>,
184///     pub latest_version: String,
185/// }
186///
187/// impl_metadata!(MyPackage {
188///     name: name,
189///     description: description,
190///     repository: repository,
191///     documentation: homepage,
192///     latest_version: latest_version,
193/// });
194/// ```
195#[macro_export]
196macro_rules! impl_metadata {
197    ($type:ty {
198        name: $name:ident,
199        description: $description:ident,
200        repository: $repository:ident,
201        documentation: $documentation:ident,
202        latest_version: $latest_version:ident $(,)?
203    }) => {
204        impl $crate::registry::PackageMetadata for $type {
205            fn name(&self) -> &str {
206                &self.$name
207            }
208
209            fn description(&self) -> Option<&str> {
210                self.$description.as_deref()
211            }
212
213            fn repository(&self) -> Option<&str> {
214                self.$repository.as_deref()
215            }
216
217            fn documentation(&self) -> Option<&str> {
218                self.$documentation.as_deref()
219            }
220
221            fn latest_version(&self) -> &str {
222                &self.$latest_version
223            }
224        }
225
226        impl $crate::registry::Metadata for $type {
227            fn name(&self) -> &str {
228                &self.$name
229            }
230
231            fn description(&self) -> Option<&str> {
232                self.$description.as_deref()
233            }
234
235            fn repository(&self) -> Option<&str> {
236                self.$repository.as_deref()
237            }
238
239            fn documentation(&self) -> Option<&str> {
240                self.$documentation.as_deref()
241            }
242
243            fn latest_version(&self) -> &str {
244                &self.$latest_version
245            }
246
247            fn as_any(&self) -> &dyn ::std::any::Any {
248                self
249            }
250        }
251    };
252}
253
254/// Implement `ParseResult` trait for a struct.
255///
256/// # Arguments
257///
258/// * `$type` - The struct type name
259/// * `$dep_type` - The dependency type that implements `Dependency`
260/// * `dependencies` - Field name for dependencies vec (`Vec<DepType>`)
261/// * `uri` - Field name for document URI (`Url`)
262/// * `workspace_root` - Optional: field name for workspace root (`Option<PathBuf>`)
263///
264/// # Examples
265///
266/// ```ignore
267/// use deps_core::impl_parse_result;
268///
269/// pub struct MyParseResult {
270///     pub dependencies: Vec<MyDependency>,
271///     pub uri: Uri,
272/// }
273///
274/// impl_parse_result!(MyParseResult, MyDependency {
275///     dependencies: dependencies,
276///     uri: uri,
277/// });
278///
279/// // With workspace root:
280/// impl_parse_result!(MyParseResult, MyDependency {
281///     dependencies: dependencies,
282///     uri: uri,
283///     workspace_root: workspace_root,
284/// });
285/// ```
286#[macro_export]
287macro_rules! impl_parse_result {
288    ($type:ty, $dep_type:ty {
289        dependencies: $dependencies:ident,
290        uri: $uri:ident $(,)?
291    }) => {
292        impl $crate::ecosystem::ParseResult for $type {
293            fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
294                self.$dependencies
295                    .iter()
296                    .map(|d| d as &dyn $crate::ecosystem::Dependency)
297                    .collect()
298            }
299
300            fn workspace_root(&self) -> Option<&::std::path::Path> {
301                None
302            }
303
304            fn uri(&self) -> &::tower_lsp_server::ls_types::Uri {
305                &self.$uri
306            }
307
308            fn as_any(&self) -> &dyn ::std::any::Any {
309                self
310            }
311        }
312    };
313    ($type:ty, $dep_type:ty {
314        dependencies: $dependencies:ident,
315        uri: $uri:ident,
316        workspace_root: $workspace_root:ident $(,)?
317    }) => {
318        impl $crate::ecosystem::ParseResult for $type {
319            fn dependencies(&self) -> Vec<&dyn $crate::ecosystem::Dependency> {
320                self.$dependencies
321                    .iter()
322                    .map(|d| d as &dyn $crate::ecosystem::Dependency)
323                    .collect()
324            }
325
326            fn workspace_root(&self) -> Option<&::std::path::Path> {
327                self.$workspace_root.as_deref()
328            }
329
330            fn uri(&self) -> &::tower_lsp_server::ls_types::Uri {
331                &self.$uri
332            }
333
334            fn as_any(&self) -> &dyn ::std::any::Any {
335                self
336            }
337        }
338    };
339}
340
341/// Delegate a method call to all enum variants.
342///
343/// This macro generates a match expression that delegates to the same
344/// method on each enum variant, eliminating boilerplate.
345///
346/// # Examples
347///
348/// ```ignore
349/// impl UnifiedDependency {
350///     pub fn name(&self) -> &str {
351///         delegate_to_variants!(self, name)
352///     }
353/// }
354/// ```
355#[macro_export]
356macro_rules! delegate_to_variants {
357    ($self:ident, $method:ident $(, $arg:expr)*) => {
358        match $self {
359            Self::Cargo(dep) => dep.$method($($arg),*),
360            Self::Npm(dep) => dep.$method($($arg),*),
361            Self::Pypi(dep) => dep.$method($($arg),*),
362        }
363    };
364}
365
366#[cfg(test)]
367mod tests {
368    use tower_lsp_server::ls_types::{Position, Range, Uri};
369
370    // Test structs
371    #[derive(Debug, Clone)]
372    struct TestDependency {
373        name: String,
374        name_range: Range,
375        version_req: Option<String>,
376        version_range: Option<Range>,
377    }
378
379    #[derive(Debug, Clone)]
380    struct TestVersion {
381        version: String,
382        yanked: bool,
383    }
384
385    #[derive(Debug, Clone)]
386    struct TestPackage {
387        name: String,
388        description: Option<String>,
389        repository: Option<String>,
390        homepage: Option<String>,
391        latest_version: String,
392    }
393
394    #[derive(Debug)]
395    struct TestParseResult {
396        dependencies: Vec<TestDependency>,
397        uri: Uri,
398    }
399
400    // Apply macros
401    impl_dependency!(TestDependency {
402        name: name,
403        name_range: name_range,
404        version: version_req,
405        version_range: version_range,
406    });
407
408    impl_version!(TestVersion {
409        version: version,
410        yanked: yanked,
411    });
412
413    impl_metadata!(TestPackage {
414        name: name,
415        description: description,
416        repository: repository,
417        documentation: homepage,
418        latest_version: latest_version,
419    });
420
421    impl_parse_result!(
422        TestParseResult,
423        TestDependency {
424            dependencies: dependencies,
425            uri: uri,
426        }
427    );
428
429    #[test]
430    fn test_impl_dependency_macro() {
431        use crate::ecosystem::Dependency;
432
433        let dep = TestDependency {
434            name: "test-pkg".into(),
435            name_range: Range::new(Position::new(0, 0), Position::new(0, 8)),
436            version_req: Some("1.0.0".into()),
437            version_range: Some(Range::new(Position::new(0, 10), Position::new(0, 15))),
438        };
439
440        assert_eq!(dep.name(), "test-pkg");
441        assert_eq!(dep.version_requirement(), Some("1.0.0"));
442        assert!(dep.as_any().is::<TestDependency>());
443    }
444
445    #[test]
446    fn test_impl_version_macro() {
447        use crate::registry::Version;
448
449        let version = TestVersion {
450            version: "2.0.0".into(),
451            yanked: true,
452        };
453
454        assert_eq!(version.version_string(), "2.0.0");
455        assert!(version.is_yanked());
456        assert!(version.as_any().is::<TestVersion>());
457    }
458
459    #[test]
460    fn test_impl_metadata_macro() {
461        use crate::registry::Metadata;
462
463        let pkg = TestPackage {
464            name: "my-pkg".into(),
465            description: Some("A test package".into()),
466            repository: Some("user/repo".into()),
467            homepage: Some("https://example.com".into()),
468            latest_version: "3.0.0".into(),
469        };
470
471        assert_eq!(pkg.name(), "my-pkg");
472        assert_eq!(pkg.description(), Some("A test package"));
473        assert_eq!(pkg.documentation(), Some("https://example.com"));
474        assert!(pkg.as_any().is::<TestPackage>());
475    }
476
477    #[test]
478    fn test_impl_parse_result_macro() {
479        use crate::ecosystem::ParseResult;
480
481        let result = TestParseResult {
482            dependencies: vec![TestDependency {
483                name: "dep1".into(),
484                name_range: Range::default(),
485                version_req: None,
486                version_range: None,
487            }],
488            uri: Uri::from_file_path("/test").unwrap(),
489        };
490
491        assert_eq!(result.dependencies().len(), 1);
492        assert!(result.workspace_root().is_none());
493        assert!(result.as_any().is::<TestParseResult>());
494    }
495}