1use std::any::Any;
4use std::sync::Arc;
5use tower_lsp_server::ls_types::{CompletionItem, Position, Range, Uri};
6
7use deps_core::{
8 Ecosystem, ParseResult as ParseResultTrait, Registry, Result, completion::Completions,
9 lsp_helpers::EcosystemFormatter,
10};
11
12use crate::formatter::DartFormatter;
13use crate::registry::PubDevRegistry;
14
15pub struct DartEcosystem {
16 registry: Arc<PubDevRegistry>,
17 formatter: DartFormatter,
18}
19
20impl DartEcosystem {
21 pub fn new(cache: Arc<deps_core::HttpCache>) -> Self {
22 Self {
23 registry: Arc::new(PubDevRegistry::new(cache)),
24 formatter: DartFormatter,
25 }
26 }
27
28 async fn complete_package_names(&self, prefix: &str, range: Range) -> Vec<CompletionItem> {
29 deps_core::completion::complete_package_names_generic(
30 self.registry.as_ref(),
31 prefix,
32 20,
33 range,
34 )
35 .await
36 }
37
38 async fn complete_versions(
39 &self,
40 package_name: &deps_core::PackageName,
41 prefix: &str,
42 freshness: deps_core::FreshnessSettings,
43 ) -> Vec<CompletionItem> {
44 deps_core::completion::complete_versions_generic(
45 self.registry.as_ref(),
46 package_name,
47 prefix,
48 &['^', '>', '<', '='],
49 freshness,
50 )
51 .await
52 }
53}
54
55impl deps_core::ecosystem::private::Sealed for DartEcosystem {}
56
57impl Ecosystem for DartEcosystem {
58 fn id(&self) -> &'static str {
59 "dart"
60 }
61
62 fn display_name(&self) -> &'static str {
63 "Dart (Pub)"
64 }
65
66 fn manifest_filenames(&self) -> &[&'static str] {
67 &["pubspec.yaml"]
68 }
69
70 fn lockfile_filenames(&self) -> &[&'static str] {
71 &["pubspec.lock"]
72 }
73
74 fn parse_manifest<'a>(
75 &'a self,
76 content: &'a str,
77 uri: &'a Uri,
78 ) -> deps_core::ecosystem::BoxFuture<'a, Result<Box<dyn ParseResultTrait>>> {
79 Box::pin(async move {
80 let result = crate::parser::parse_pubspec_yaml(content, uri)?;
81 Ok(Box::new(result) as Box<dyn ParseResultTrait>)
82 })
83 }
84
85 fn registry(&self) -> Arc<dyn Registry> {
86 self.registry.clone() as Arc<dyn Registry>
87 }
88
89 fn lockfile_provider(&self) -> Option<Arc<dyn deps_core::lockfile::LockFileProvider>> {
90 Some(Arc::new(crate::lockfile::PubspecLockParser))
91 }
92
93 fn formatter(&self) -> &dyn EcosystemFormatter {
94 &self.formatter
95 }
96
97 fn generate_completions<'a>(
98 &'a self,
99 parse_result: &'a dyn ParseResultTrait,
100 position: Position,
101 content: &'a str,
102 freshness: deps_core::FreshnessSettings,
103 ) -> deps_core::ecosystem::BoxFuture<'a, Completions> {
104 Box::pin(async move {
105 use deps_core::completion::{CompletionContext, detect_completion_context};
106
107 let context = detect_completion_context(parse_result, position, content);
108
109 match context {
110 CompletionContext::PackageName { prefix, range } => {
111 self.complete_package_names(&prefix, range).await
112 }
113 CompletionContext::Version {
114 package_name,
115 prefix,
116 } => {
117 self.complete_versions(&package_name, &prefix, freshness)
118 .await
119 }
120 CompletionContext::Feature { .. } | CompletionContext::None => vec![],
121 }
122 .into()
123 })
124 }
125
126 fn as_any(&self) -> &dyn Any {
127 self
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_ecosystem_id() {
137 let cache = Arc::new(deps_core::HttpCache::new());
138 let eco = DartEcosystem::new(cache);
139 assert_eq!(eco.id(), "dart");
140 }
141
142 #[test]
143 fn test_ecosystem_display_name() {
144 let cache = Arc::new(deps_core::HttpCache::new());
145 let eco = DartEcosystem::new(cache);
146 assert_eq!(eco.display_name(), "Dart (Pub)");
147 }
148
149 #[test]
150 fn test_ecosystem_manifest_filenames() {
151 let cache = Arc::new(deps_core::HttpCache::new());
152 let eco = DartEcosystem::new(cache);
153 assert_eq!(eco.manifest_filenames(), &["pubspec.yaml"]);
154 }
155
156 #[test]
157 fn test_ecosystem_lockfile_filenames() {
158 let cache = Arc::new(deps_core::HttpCache::new());
159 let eco = DartEcosystem::new(cache);
160 assert_eq!(eco.lockfile_filenames(), &["pubspec.lock"]);
161 }
162
163 #[test]
164 fn test_as_any() {
165 let cache = Arc::new(deps_core::HttpCache::new());
166 let eco = DartEcosystem::new(cache);
167 assert!(eco.as_any().is::<DartEcosystem>());
168 }
169
170 #[tokio::test]
171 async fn test_package_name_completion_context_has_real_range() {
172 let cache = Arc::new(deps_core::HttpCache::new());
175 let eco = DartEcosystem::new(cache);
176 let content = "name: my_app\ndependencies:\n http: ^1.0.0\n";
177 let uri = deps_core::test_util::test_uri("/test/pubspec.yaml");
178
179 let parse_result = eco.parse_manifest(content, &uri).await.unwrap();
180 let position = Position::new(2, 4); let context = deps_core::completion::detect_completion_context(
183 parse_result.as_ref(),
184 position,
185 content,
186 );
187
188 match context {
189 deps_core::completion::CompletionContext::PackageName { prefix, range } => {
190 assert_eq!(prefix, "ht");
191 assert_ne!(range, Range::default());
192 assert_eq!(range, Range::new(Position::new(2, 2), Position::new(2, 6)));
193 }
194 other => panic!("Expected PackageName context, got {other:?}"),
195 }
196 }
197
198 #[tokio::test]
199 async fn test_complete_package_names_min_prefix() {
200 let cache = Arc::new(deps_core::HttpCache::new());
201 let eco = DartEcosystem::new(cache);
202 assert!(
203 eco.complete_package_names("h", Range::default())
204 .await
205 .is_empty()
206 );
207 assert!(
208 eco.complete_package_names("", Range::default())
209 .await
210 .is_empty()
211 );
212 }
213
214 #[tokio::test]
215 async fn test_complete_package_names_max_length() {
216 let cache = Arc::new(deps_core::HttpCache::new());
217 let eco = DartEcosystem::new(cache);
218 let long = "a".repeat(201);
219 assert!(
220 eco.complete_package_names(&long, Range::default())
221 .await
222 .is_empty()
223 );
224 }
225
226 #[tokio::test]
227 async fn test_lockfile_provider() {
228 let cache = Arc::new(deps_core::HttpCache::new());
229 let eco = DartEcosystem::new(cache);
230 assert!(eco.lockfile_provider().is_some());
231 }
232
233 #[tokio::test]
234 async fn test_parse_manifest() {
235 let cache = Arc::new(deps_core::HttpCache::new());
236 let eco = DartEcosystem::new(cache);
237
238 let yaml = "name: app\ndependencies:\n http: ^1.0.0\n";
239 #[cfg(windows)]
240 let path = "C:/test/pubspec.yaml";
241 #[cfg(not(windows))]
242 let path = "/test/pubspec.yaml";
243 let uri = Uri::from_file_path(path).unwrap();
244
245 let result = eco.parse_manifest(yaml, &uri).await.unwrap();
246 assert_eq!(result.dependencies().len(), 1);
247 }
248}