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