deps_bundler/
ecosystem.rs1use std::any::Any;
4use std::sync::Arc;
5use tower_lsp_server::ls_types::{CompletionItem, Position, Uri};
6
7use deps_core::{
8 Ecosystem, ParseResult as ParseResultTrait, Registry, Result, lsp_helpers::EcosystemFormatter,
9};
10
11use crate::formatter::BundlerFormatter;
12use crate::registry::RubyGemsRegistry;
13
14pub struct BundlerEcosystem {
24 registry: Arc<RubyGemsRegistry>,
25 formatter: BundlerFormatter,
26}
27
28impl BundlerEcosystem {
29 pub fn new(cache: Arc<deps_core::HttpCache>) -> Self {
31 Self {
32 registry: Arc::new(RubyGemsRegistry::new(cache)),
33 formatter: BundlerFormatter,
34 }
35 }
36
37 async fn complete_package_names(&self, prefix: &str) -> Vec<CompletionItem> {
38 deps_core::completion::complete_package_names_generic(self.registry.as_ref(), prefix, 20)
39 .await
40 }
41
42 async fn complete_versions(&self, package_name: &str, prefix: &str) -> Vec<CompletionItem> {
43 deps_core::completion::complete_versions_generic(
44 self.registry.as_ref(),
45 package_name,
46 prefix,
47 &['~', '>', '<', '=', '!'],
48 )
49 .await
50 }
51}
52
53impl deps_core::ecosystem::private::Sealed for BundlerEcosystem {}
54
55impl Ecosystem for BundlerEcosystem {
56 fn id(&self) -> &'static str {
57 "bundler"
58 }
59
60 fn display_name(&self) -> &'static str {
61 "Bundler (Ruby)"
62 }
63
64 fn manifest_filenames(&self) -> &[&'static str] {
65 &["Gemfile"]
66 }
67
68 fn lockfile_filenames(&self) -> &[&'static str] {
69 &["Gemfile.lock"]
70 }
71
72 fn parse_manifest<'a>(
73 &'a self,
74 content: &'a str,
75 uri: &'a Uri,
76 ) -> deps_core::ecosystem::BoxFuture<'a, Result<Box<dyn ParseResultTrait>>> {
77 Box::pin(async move {
78 let result = crate::parser::parse_gemfile(content, uri)?;
79 Ok(Box::new(result) as Box<dyn ParseResultTrait>)
80 })
81 }
82
83 fn registry(&self) -> Arc<dyn Registry> {
84 self.registry.clone() as Arc<dyn Registry>
85 }
86
87 fn lockfile_provider(&self) -> Option<Arc<dyn deps_core::lockfile::LockFileProvider>> {
88 Some(Arc::new(crate::lockfile::GemfileLockParser))
89 }
90
91 fn formatter(&self) -> &dyn EcosystemFormatter {
92 &self.formatter
93 }
94
95 fn generate_completions<'a>(
96 &'a self,
97 parse_result: &'a dyn ParseResultTrait,
98 position: Position,
99 content: &'a str,
100 ) -> deps_core::ecosystem::BoxFuture<'a, Vec<CompletionItem>> {
101 Box::pin(async move {
102 use deps_core::completion::{CompletionContext, detect_completion_context};
103
104 let context = detect_completion_context(parse_result, position, content);
105
106 match context {
107 CompletionContext::PackageName { prefix } => {
108 self.complete_package_names(&prefix).await
109 }
110 CompletionContext::Version {
111 package_name,
112 prefix,
113 } => self.complete_versions(&package_name, &prefix).await,
114 CompletionContext::Feature { .. } | CompletionContext::None => vec![],
115 }
116 })
117 }
118
119 fn as_any(&self) -> &dyn Any {
120 self
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_ecosystem_id() {
130 let cache = Arc::new(deps_core::HttpCache::new());
131 let ecosystem = BundlerEcosystem::new(cache);
132 assert_eq!(ecosystem.id(), "bundler");
133 }
134
135 #[test]
136 fn test_ecosystem_display_name() {
137 let cache = Arc::new(deps_core::HttpCache::new());
138 let ecosystem = BundlerEcosystem::new(cache);
139 assert_eq!(ecosystem.display_name(), "Bundler (Ruby)");
140 }
141
142 #[test]
143 fn test_ecosystem_manifest_filenames() {
144 let cache = Arc::new(deps_core::HttpCache::new());
145 let ecosystem = BundlerEcosystem::new(cache);
146 assert_eq!(ecosystem.manifest_filenames(), &["Gemfile"]);
147 }
148
149 #[test]
150 fn test_ecosystem_lockfile_filenames() {
151 let cache = Arc::new(deps_core::HttpCache::new());
152 let ecosystem = BundlerEcosystem::new(cache);
153 assert_eq!(ecosystem.lockfile_filenames(), &["Gemfile.lock"]);
154 }
155
156 #[test]
157 fn test_as_any() {
158 let cache = Arc::new(deps_core::HttpCache::new());
159 let ecosystem = BundlerEcosystem::new(cache);
160 let any = ecosystem.as_any();
161 assert!(any.is::<BundlerEcosystem>());
162 }
163
164 #[tokio::test]
165 async fn test_complete_package_names_minimum_prefix() {
166 let cache = Arc::new(deps_core::HttpCache::new());
167 let ecosystem = BundlerEcosystem::new(cache);
168
169 let results = ecosystem.complete_package_names("r").await;
171 assert!(results.is_empty());
172
173 let results = ecosystem.complete_package_names("").await;
175 assert!(results.is_empty());
176 }
177
178 #[tokio::test]
179 async fn test_complete_package_names_max_length() {
180 let cache = Arc::new(deps_core::HttpCache::new());
181 let ecosystem = BundlerEcosystem::new(cache);
182
183 let long_prefix = "a".repeat(201);
185 let results = ecosystem.complete_package_names(&long_prefix).await;
186 assert!(results.is_empty());
187 }
188
189 #[tokio::test]
190 async fn test_lockfile_provider() {
191 let cache = Arc::new(deps_core::HttpCache::new());
192 let ecosystem = BundlerEcosystem::new(cache);
193 assert!(ecosystem.lockfile_provider().is_some());
194 }
195
196 #[tokio::test]
197 async fn test_parse_manifest() {
198 let cache = Arc::new(deps_core::HttpCache::new());
199 let ecosystem = BundlerEcosystem::new(cache);
200
201 let gemfile = r"source 'https://rubygems.org'
202gem 'rails', '~> 7.0'";
203
204 #[cfg(windows)]
205 let path = "C:/test/Gemfile";
206 #[cfg(not(windows))]
207 let path = "/test/Gemfile";
208 let uri = Uri::from_file_path(path).unwrap();
209
210 let result = ecosystem.parse_manifest(gemfile, &uri).await.unwrap();
211 assert_eq!(result.dependencies().len(), 1);
212 }
213}