1use std::any::Any;
9use std::sync::Arc;
10use tower_lsp_server::ls_types::{CompletionItem, Position, Range, Uri};
11
12use deps_core::{
13 Ecosystem, ParseResult as ParseResultTrait, Registry, Result, completion::Completions,
14 lsp_helpers::EcosystemFormatter,
15};
16
17use crate::formatter::DenoFormatter;
18use crate::registry::DenoRegistry;
19use deps_npm::NpmRegistry;
20
21pub struct DenoEcosystem {
33 registry: Arc<DenoRegistry>,
34 formatter: DenoFormatter,
35}
36
37impl DenoEcosystem {
38 pub fn new(cache: Arc<deps_core::HttpCache>) -> Self {
47 Self {
48 registry: Arc::new(DenoRegistry::new(cache)),
49 formatter: DenoFormatter,
50 }
51 }
52
53 #[must_use]
62 pub fn with_npm(cache: Arc<deps_core::HttpCache>, npm: NpmRegistry) -> Self {
63 Self {
64 registry: Arc::new(DenoRegistry::with_npm(cache, npm)),
65 formatter: DenoFormatter,
66 }
67 }
68
69 async fn complete_package_names(&self, prefix: &str, range: Range) -> Vec<CompletionItem> {
72 deps_core::completion::complete_package_names_generic(
73 self.registry.as_ref(),
74 prefix,
75 20,
76 range,
77 )
78 .await
79 }
80
81 async fn complete_versions(
82 &self,
83 package_name: &deps_core::PackageName,
84 prefix: &str,
85 freshness: deps_core::FreshnessSettings,
86 ) -> Vec<CompletionItem> {
87 deps_core::completion::complete_versions_generic(
88 self.registry.as_ref(),
89 package_name,
90 prefix,
91 &['^', '~', '=', '<', '>', '*'],
92 freshness,
93 )
94 .await
95 }
96}
97
98impl deps_core::ecosystem::private::Sealed for DenoEcosystem {}
99
100impl Ecosystem for DenoEcosystem {
101 fn id(&self) -> &'static str {
102 "deno"
103 }
104
105 fn display_name(&self) -> &'static str {
106 "Deno (JSR/npm)"
107 }
108
109 fn manifest_filenames(&self) -> &[&'static str] {
110 &["deno.json", "deno.jsonc"]
111 }
112
113 fn parse_manifest<'a>(
114 &'a self,
115 content: &'a str,
116 uri: &'a Uri,
117 ) -> deps_core::ecosystem::BoxFuture<'a, Result<Box<dyn ParseResultTrait>>> {
118 Box::pin(async move {
119 let result = crate::parser::parse_deno_json(content, uri)?;
120 Ok(Box::new(result) as Box<dyn ParseResultTrait>)
121 })
122 }
123
124 fn registry(&self) -> Arc<dyn Registry> {
125 self.registry.clone() as Arc<dyn Registry>
126 }
127
128 fn formatter(&self) -> &dyn EcosystemFormatter {
129 &self.formatter
130 }
131
132 fn generate_completions<'a>(
133 &'a self,
134 parse_result: &'a dyn ParseResultTrait,
135 position: Position,
136 content: &'a str,
137 freshness: deps_core::FreshnessSettings,
138 ) -> deps_core::ecosystem::BoxFuture<'a, Completions> {
139 Box::pin(async move {
140 use deps_core::completion::{CompletionContext, detect_completion_context};
141
142 let context = detect_completion_context(parse_result, position, content);
143
144 match context {
145 CompletionContext::PackageName { prefix, range } => {
146 self.complete_package_names(&prefix, range).await
147 }
148 CompletionContext::Version {
149 package_name,
150 prefix,
151 } => {
152 self.complete_versions(&package_name, &prefix, freshness)
153 .await
154 }
155 CompletionContext::Feature { .. } => vec![],
156 CompletionContext::None => vec![],
157 }
158 .into()
159 })
160 }
161
162 fn as_any(&self) -> &dyn Any {
163 self
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_ecosystem_id() {
173 let cache = Arc::new(deps_core::HttpCache::new());
174 let ecosystem = DenoEcosystem::new(cache);
175 assert_eq!(ecosystem.id(), "deno");
176 }
177
178 #[test]
179 fn test_ecosystem_display_name() {
180 let cache = Arc::new(deps_core::HttpCache::new());
181 let ecosystem = DenoEcosystem::new(cache);
182 assert_eq!(ecosystem.display_name(), "Deno (JSR/npm)");
183 }
184
185 #[test]
186 fn test_ecosystem_manifest_filenames() {
187 let cache = Arc::new(deps_core::HttpCache::new());
188 let ecosystem = DenoEcosystem::new(cache);
189 assert_eq!(ecosystem.manifest_filenames(), &["deno.json", "deno.jsonc"]);
190 }
191
192 #[test]
193 fn test_ecosystem_no_lockfile_support() {
194 let cache = Arc::new(deps_core::HttpCache::new());
195 let ecosystem = DenoEcosystem::new(cache);
196 assert!(ecosystem.lockfile_filenames().is_empty());
197 assert!(ecosystem.lockfile_provider().is_none());
198 }
199
200 #[test]
201 fn test_as_any() {
202 let cache = Arc::new(deps_core::HttpCache::new());
203 let ecosystem = DenoEcosystem::new(cache);
204 assert!(ecosystem.as_any().is::<DenoEcosystem>());
205 }
206
207 #[tokio::test]
208 async fn test_parse_manifest_valid_json() {
209 let cache = Arc::new(deps_core::HttpCache::new());
210 let ecosystem = DenoEcosystem::new(cache);
211 let uri = deps_core::test_util::test_uri("/test/deno.json");
212
213 let content = r#"{"imports": {"@std/fs": "jsr:@std/fs@^1.0"}}"#;
214
215 let result = ecosystem.parse_manifest(content, &uri).await;
216 assert!(result.is_ok());
217 assert!(!result.unwrap().dependencies().is_empty());
218 }
219
220 #[tokio::test]
221 async fn test_parse_manifest_invalid_json() {
222 let cache = Arc::new(deps_core::HttpCache::new());
223 let ecosystem = DenoEcosystem::new(cache);
224 let uri = deps_core::test_util::test_uri("/test/deno.json");
225
226 let result = ecosystem.parse_manifest("{ not valid !!", &uri).await;
227 assert!(result.is_err());
228 }
229
230 #[tokio::test]
231 async fn test_registry_returns_arc() {
232 let cache = Arc::new(deps_core::HttpCache::new());
233 let ecosystem = DenoEcosystem::new(cache);
234 let registry = ecosystem.registry();
235 assert!(Arc::strong_count(®istry) >= 1);
236 }
237
238 #[tokio::test]
239 async fn test_generate_completions_no_context() {
240 let cache = Arc::new(deps_core::HttpCache::new());
241 let ecosystem = DenoEcosystem::new(cache);
242 let uri = deps_core::test_util::test_uri("/test/deno.json");
243
244 let content = r#"{"name": "test"}"#;
245 let parse_result = ecosystem.parse_manifest(content, &uri).await.unwrap();
246 let position = Position::new(0, 0);
247
248 let completions = ecosystem
249 .generate_completions(
250 parse_result.as_ref(),
251 position,
252 content,
253 deps_core::FreshnessSettings::default(),
254 )
255 .await;
256
257 assert!(completions.items.is_empty());
258 }
259
260 #[tokio::test]
261 async fn test_complete_package_names_minimum_prefix() {
262 let cache = Arc::new(deps_core::HttpCache::new());
263 let ecosystem = DenoEcosystem::new(cache);
264
265 let results = ecosystem
266 .complete_package_names("j", Range::default())
267 .await;
268 assert!(results.is_empty());
269 }
270
271 #[tokio::test]
272 async fn test_complete_versions_unknown_package() {
273 let cache = Arc::new(deps_core::HttpCache::new());
274 let ecosystem = DenoEcosystem::new(cache);
275
276 let results = ecosystem
277 .complete_versions(
278 &deps_core::PackageName::new("jsr:@this-scope/does-not-exist-12345"),
279 "1.0",
280 deps_core::FreshnessSettings::default(),
281 )
282 .await;
283 assert!(results.is_empty());
284 }
285}