1use std::collections::HashMap;
4
5use super::ResolvedCrateConfig;
6use crate::config::extras::{AdapterConfig, Language};
7use crate::config::output::{BuildCommandConfig, CleanConfig, LintConfig, SetupConfig, TestConfig, UpdateConfig};
8use crate::config::tools::LangContext;
9use crate::config::{build_defaults, clean_defaults, lint_defaults, setup_defaults, test_defaults, update_defaults};
10
11impl ResolvedCrateConfig {
12 pub fn adapter_for_function(&self, fn_name: &str) -> Option<&AdapterConfig> {
19 self.adapters.iter().find(|a| a.name == fn_name)
20 }
21 pub fn package_dir(&self, lang: Language) -> String {
24 let override_path = match lang {
25 Language::Python => self.python.as_ref().and_then(|c| c.scaffold_output.as_ref()),
26 Language::Node => self.node.as_ref().and_then(|c| c.scaffold_output.as_ref()),
27 Language::Ruby => self.ruby.as_ref().and_then(|c| c.scaffold_output.as_ref()),
28 Language::Php => self.php.as_ref().and_then(|c| c.scaffold_output.as_ref()),
29 Language::Elixir => self.elixir.as_ref().and_then(|c| c.scaffold_output.as_ref()),
30 _ => None,
31 };
32 if let Some(p) = override_path {
33 p.to_string_lossy().to_string()
34 } else {
35 match lang {
36 Language::Python => "packages/python".to_string(),
37 Language::Node => format!("crates/{}-node", self.name),
44 Language::Wasm => format!("crates/{}-wasm", self.name),
45 Language::Ruby => "packages/ruby".to_string(),
46 Language::Php => "packages/php".to_string(),
47 Language::Elixir => "packages/elixir".to_string(),
48 Language::KotlinAndroid => "packages/kotlin-android".to_string(),
49 _ => format!("packages/{lang}"),
50 }
51 }
52 }
53
54 pub fn run_wrapper_for_language(&self, lang: Language) -> Option<&str> {
56 match lang {
57 Language::Python => self.python.as_ref().and_then(|c| c.run_wrapper.as_deref()),
58 Language::Node => self.node.as_ref().and_then(|c| c.run_wrapper.as_deref()),
59 Language::Ruby => self.ruby.as_ref().and_then(|c| c.run_wrapper.as_deref()),
60 Language::Php => self.php.as_ref().and_then(|c| c.run_wrapper.as_deref()),
61 Language::Elixir => self.elixir.as_ref().and_then(|c| c.run_wrapper.as_deref()),
62 Language::Wasm => self.wasm.as_ref().and_then(|c| c.run_wrapper.as_deref()),
63 Language::Go => self.go.as_ref().and_then(|c| c.run_wrapper.as_deref()),
64 Language::Java => self.java.as_ref().and_then(|c| c.run_wrapper.as_deref()),
65 Language::Csharp => self.csharp.as_ref().and_then(|c| c.run_wrapper.as_deref()),
66 Language::R => self.r.as_ref().and_then(|c| c.run_wrapper.as_deref()),
67 Language::Kotlin => self.kotlin.as_ref().and_then(|c| c.run_wrapper.as_deref()),
68 Language::KotlinAndroid => self.kotlin_android.as_ref().and_then(|c| c.run_wrapper.as_deref()),
69 Language::Dart => self.dart.as_ref().and_then(|c| c.run_wrapper.as_deref()),
70 Language::Swift => self.swift.as_ref().and_then(|c| c.run_wrapper.as_deref()),
71 Language::Gleam => self.gleam.as_ref().and_then(|c| c.run_wrapper.as_deref()),
72 Language::Zig => self.zig.as_ref().and_then(|c| c.run_wrapper.as_deref()),
73 Language::Ffi | Language::Rust | Language::C | Language::Jni => None,
74 }
75 }
76
77 pub fn extra_lint_paths_for_language(&self, lang: Language) -> &[String] {
79 match lang {
80 Language::Python => self
81 .python
82 .as_ref()
83 .map(|c| c.extra_lint_paths.as_slice())
84 .unwrap_or(&[]),
85 Language::Node => self.node.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
86 Language::Ruby => self.ruby.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
87 Language::Php => self.php.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
88 Language::Elixir => self
89 .elixir
90 .as_ref()
91 .map(|c| c.extra_lint_paths.as_slice())
92 .unwrap_or(&[]),
93 Language::Wasm => self.wasm.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
94 Language::Go => self.go.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
95 Language::Java => self.java.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
96 Language::Csharp => self
97 .csharp
98 .as_ref()
99 .map(|c| c.extra_lint_paths.as_slice())
100 .unwrap_or(&[]),
101 Language::R => self.r.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
102 Language::Kotlin => self
103 .kotlin
104 .as_ref()
105 .map(|c| c.extra_lint_paths.as_slice())
106 .unwrap_or(&[]),
107 Language::KotlinAndroid => self
108 .kotlin_android
109 .as_ref()
110 .map(|c| c.extra_lint_paths.as_slice())
111 .unwrap_or(&[]),
112 Language::Dart => self.dart.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
113 Language::Swift => self
114 .swift
115 .as_ref()
116 .map(|c| c.extra_lint_paths.as_slice())
117 .unwrap_or(&[]),
118 Language::Gleam => self
119 .gleam
120 .as_ref()
121 .map(|c| c.extra_lint_paths.as_slice())
122 .unwrap_or(&[]),
123 Language::Zig => self.zig.as_ref().map(|c| c.extra_lint_paths.as_slice()).unwrap_or(&[]),
124 Language::Ffi | Language::Rust | Language::C | Language::Jni => &[],
125 }
126 }
127
128 pub fn project_file_for_language(&self, lang: Language) -> Option<&str> {
130 match lang {
131 Language::Java => self.java.as_ref().and_then(|c| c.project_file.as_deref()),
132 Language::Csharp => self.csharp.as_ref().and_then(|c| c.project_file.as_deref()),
133 _ => None,
134 }
135 }
136
137 pub fn lint_config_for_language(&self, lang: Language) -> LintConfig {
139 let lang_str = lang.to_string();
140 if let Some(explicit) = self.lint.get(&lang_str) {
141 return explicit.clone();
142 }
143 let output_dir = self.package_dir(lang);
144 let run_wrapper = self.run_wrapper_for_language(lang);
145 let extra_lint_paths = self.extra_lint_paths_for_language(lang);
146 let project_file = self.project_file_for_language(lang);
147 let ctx = LangContext {
148 tools: &self.tools,
149 run_wrapper,
150 extra_lint_paths,
151 project_file,
152 };
153 lint_defaults::default_lint_config(lang, &output_dir, &ctx)
154 }
155
156 pub fn update_config_for_language(&self, lang: Language) -> UpdateConfig {
158 let lang_str = lang.to_string();
159 if let Some(explicit) = self.update.get(&lang_str) {
160 return explicit.clone();
161 }
162 let output_dir = self.package_dir(lang);
163 let ctx = LangContext {
164 tools: &self.tools,
165 run_wrapper: None,
166 extra_lint_paths: &[],
167 project_file: None,
168 };
169 update_defaults::default_update_config(lang, &output_dir, &ctx)
170 }
171
172 pub fn test_config_for_language(&self, lang: Language) -> TestConfig {
174 let lang_str = lang.to_string();
175 if let Some(explicit) = self.test.get(&lang_str) {
176 return explicit.clone();
177 }
178 let output_dir = self.package_dir(lang);
179 let run_wrapper = self.run_wrapper_for_language(lang);
180 let project_file = self.project_file_for_language(lang);
181 let ctx = LangContext {
182 tools: &self.tools,
183 run_wrapper,
184 extra_lint_paths: &[],
185 project_file,
186 };
187 test_defaults::default_test_config(lang, &output_dir, &ctx)
188 }
189
190 pub fn setup_config_for_language(&self, lang: Language) -> SetupConfig {
192 let lang_str = lang.to_string();
193 if let Some(explicit) = self.setup.get(&lang_str) {
194 return explicit.clone();
195 }
196 let output_dir = self.package_dir(lang);
197 let ctx = LangContext {
198 tools: &self.tools,
199 run_wrapper: None,
200 extra_lint_paths: &[],
201 project_file: None,
202 };
203 setup_defaults::default_setup_config(lang, &output_dir, &ctx)
204 }
205
206 pub fn clean_config_for_language(&self, lang: Language) -> CleanConfig {
208 let lang_str = lang.to_string();
209 if let Some(explicit) = self.clean.get(&lang_str) {
210 return explicit.clone();
211 }
212 let output_dir = self.package_dir(lang);
213 let ctx = LangContext {
214 tools: &self.tools,
215 run_wrapper: None,
216 extra_lint_paths: &[],
217 project_file: None,
218 };
219 clean_defaults::default_clean_config(lang, &output_dir, &ctx)
220 }
221
222 pub fn build_command_config_for_language(&self, lang: Language) -> BuildCommandConfig {
224 let lang_str = lang.to_string();
225 let output_dir = self.package_dir(lang);
226 let run_wrapper = self.run_wrapper_for_language(lang);
227 let project_file = self.project_file_for_language(lang);
228 let ctx = LangContext {
229 tools: &self.tools,
230 run_wrapper,
231 extra_lint_paths: &[],
232 project_file,
233 };
234 let default = build_defaults::default_build_config(lang, &output_dir, &self.name, &ctx);
235 if let Some(explicit) = self.build_commands.get(&lang_str) {
236 default.merge_overlay(explicit)
237 } else {
238 default
239 }
240 }
241
242 pub fn features_for_language(&self, lang: Language) -> &[String] {
244 let override_features = match lang {
245 Language::Python => self.python.as_ref().and_then(|c| c.features.as_deref()),
246 Language::Node => self.node.as_ref().and_then(|c| c.features.as_deref()),
247 Language::Ruby => self.ruby.as_ref().and_then(|c| c.features.as_deref()),
248 Language::Php => self.php.as_ref().and_then(|c| c.features.as_deref()),
249 Language::Elixir => self.elixir.as_ref().and_then(|c| c.features.as_deref()),
250 Language::Wasm => self.wasm.as_ref().and_then(|c| c.features.as_deref()),
251 Language::Ffi => self.ffi.as_ref().and_then(|c| c.features.as_deref()),
252 Language::Go => self.go.as_ref().and_then(|c| c.features.as_deref()),
253 Language::Java => self.java.as_ref().and_then(|c| c.features.as_deref()),
254 Language::Kotlin => self.kotlin.as_ref().and_then(|c| c.features.as_deref()),
255 Language::KotlinAndroid => self.kotlin_android.as_ref().and_then(|c| c.features.as_deref()),
256 Language::Csharp => self.csharp.as_ref().and_then(|c| c.features.as_deref()),
257 Language::R => self.r.as_ref().and_then(|c| c.features.as_deref()),
258 Language::Zig => self.zig.as_ref().and_then(|c| c.features.as_deref()),
259 Language::Dart => self.dart.as_ref().and_then(|c| c.features.as_deref()),
260 Language::Swift => self.swift.as_ref().and_then(|c| c.features.as_deref()),
261 Language::Gleam => self.gleam.as_ref().and_then(|c| c.features.as_deref()),
262 Language::Rust | Language::C | Language::Jni => None,
263 };
264 override_features.unwrap_or(&self.features)
265 }
266
267 pub fn extra_deps_for_language(&self, lang: Language) -> HashMap<String, toml::Value> {
269 let mut deps = self.extra_dependencies.clone();
270 let lang_deps = match lang {
271 Language::Python => self.python.as_ref().map(|c| &c.extra_dependencies),
272 Language::Node => self.node.as_ref().map(|c| &c.extra_dependencies),
273 Language::Ruby => self.ruby.as_ref().map(|c| &c.extra_dependencies),
274 Language::Php => self.php.as_ref().map(|c| &c.extra_dependencies),
275 Language::Elixir => self.elixir.as_ref().map(|c| &c.extra_dependencies),
276 Language::Wasm => self.wasm.as_ref().map(|c| &c.extra_dependencies),
277 Language::Dart => self.dart.as_ref().map(|c| &c.extra_dependencies),
278 Language::Swift => self.swift.as_ref().map(|c| &c.extra_dependencies),
279 _ => None,
280 };
281 if let Some(lang_deps) = lang_deps {
282 deps.extend(lang_deps.iter().map(|(k, v)| (k.clone(), v.clone())));
283 }
284 let exclude: &[String] = match lang {
285 Language::Wasm => self
286 .wasm
287 .as_ref()
288 .map(|c| c.exclude_extra_dependencies.as_slice())
289 .unwrap_or(&[]),
290 Language::Dart => self
291 .dart
292 .as_ref()
293 .map(|c| c.exclude_extra_dependencies.as_slice())
294 .unwrap_or(&[]),
295 Language::Swift => self
296 .swift
297 .as_ref()
298 .map(|c| c.exclude_extra_dependencies.as_slice())
299 .unwrap_or(&[]),
300 _ => &[],
301 };
302 for key in exclude {
303 deps.remove(key);
304 }
305 deps
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use crate::config::extras::Language;
312 use crate::config::new_config::NewAlefConfig;
313
314 fn resolved_one(toml: &str) -> super::super::ResolvedCrateConfig {
315 let cfg: NewAlefConfig = toml::from_str(toml).unwrap();
316 cfg.resolve().unwrap().remove(0)
317 }
318
319 fn minimal() -> super::super::ResolvedCrateConfig {
320 resolved_one(
321 r#"
322[workspace]
323languages = ["python", "node"]
324
325[[crates]]
326name = "test-lib"
327sources = ["src/lib.rs"]
328"#,
329 )
330 }
331
332 #[test]
333 fn resolved_lint_config_inherits_workspace_when_crate_unset() {
334 let r = resolved_one(
335 r#"
336[workspace]
337languages = ["python"]
338
339[workspace.lint.python]
340check = "ruff check ."
341
342[[crates]]
343name = "test-lib"
344sources = ["src/lib.rs"]
345"#,
346 );
347 let lint = r.lint_config_for_language(Language::Python);
348 assert_eq!(lint.check.unwrap().commands(), vec!["ruff check ."]);
349 }
350
351 #[test]
352 fn resolved_lint_config_crate_overrides_workspace_field_wholesale() {
353 let r = resolved_one(
354 r#"
355[workspace]
356languages = ["python"]
357
358[workspace.lint.python]
359check = "ruff check ."
360
361[[crates]]
362name = "test-lib"
363sources = ["src/lib.rs"]
364
365[crates.lint.python]
366check = "ruff check crates/test-lib-py/"
367"#,
368 );
369 let lint = r.lint_config_for_language(Language::Python);
370 assert_eq!(lint.check.unwrap().commands(), vec!["ruff check crates/test-lib-py/"]);
371 }
372
373 #[test]
374 fn resolved_features_per_language_overrides_crate_default() {
375 let r = resolved_one(
376 r#"
377[workspace]
378languages = ["python"]
379
380[[crates]]
381name = "test-lib"
382sources = ["src/lib.rs"]
383features = ["base"]
384
385[crates.python]
386features = ["python-extra"]
387"#,
388 );
389 assert_eq!(r.features_for_language(Language::Python), &["python-extra"]);
390 assert_eq!(r.features_for_language(Language::Node), &["base"]);
391 }
392
393 #[test]
394 fn resolved_extra_deps_crate_value_wins_on_key_collision() {
395 let r = resolved_one(
396 r#"
397[workspace]
398languages = ["python"]
399
400[[crates]]
401name = "test-lib"
402sources = ["src/lib.rs"]
403
404[crates.extra_dependencies]
405tokio = "1"
406
407[crates.python]
408extra_dependencies = { tokio = "2" }
409"#,
410 );
411 let deps = r.extra_deps_for_language(Language::Python);
412 let tokio = deps.get("tokio").unwrap().as_str().unwrap();
413 assert_eq!(tokio, "2", "per-language extra_dep should win on collision");
414 }
415
416 #[test]
417 fn resolved_extra_deps_excludes_apply_after_merge() {
418 let r = resolved_one(
419 r#"
420[workspace]
421languages = ["wasm"]
422
423[[crates]]
424name = "test-lib"
425sources = ["src/lib.rs"]
426
427[crates.extra_dependencies]
428tokio = "1"
429serde = "1"
430
431[crates.wasm]
432exclude_extra_dependencies = ["tokio"]
433"#,
434 );
435 let deps = r.extra_deps_for_language(Language::Wasm);
436 assert!(!deps.contains_key("tokio"), "excluded dep should be absent");
437 assert!(deps.contains_key("serde"), "non-excluded dep should be present");
438 }
439
440 #[test]
441 fn resolved_extra_deps_includes_swift_overrides() {
442 let r = resolved_one(
443 r#"
444[workspace]
445languages = ["swift"]
446
447[[crates]]
448name = "test-lib"
449sources = ["src/lib.rs"]
450
451[crates.extra_dependencies]
452serde = "1"
453
454[crates.swift.extra_dependencies]
455tokio = "1"
456"#,
457 );
458 let deps = r.extra_deps_for_language(Language::Swift);
459 assert!(deps.contains_key("serde"), "crate-level dep should be present");
460 assert!(deps.contains_key("tokio"), "Swift dep should be present");
461 }
462
463 #[test]
464 fn package_dir_defaults_are_correct() {
465 let r = minimal();
466 assert_eq!(r.package_dir(Language::Python), "packages/python");
467 assert_eq!(r.package_dir(Language::Node), format!("crates/{}-node", r.name));
470 assert_eq!(r.package_dir(Language::Wasm), format!("crates/{}-wasm", r.name));
471 assert_eq!(r.package_dir(Language::Ruby), "packages/ruby");
472 assert_eq!(r.package_dir(Language::Go), "packages/go");
473 assert_eq!(r.package_dir(Language::Java), "packages/java");
474 assert_eq!(r.package_dir(Language::KotlinAndroid), "packages/kotlin-android");
475 }
476}