1use crate::cst::{CabalCst, CstNodeKind};
23use crate::span::NodeId;
24
25pub fn canonicalize_field_name(name: &str) -> String {
35 name.to_ascii_lowercase().replace('_', "-")
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub struct Version {
45 pub components: Vec<u64>,
46}
47
48impl Version {
49 pub fn parse(s: &str) -> Option<Self> {
54 let s = s.trim();
55 if s.is_empty() {
56 return None;
57 }
58 let mut components = Vec::new();
59 for part in s.split('.') {
60 let part = part.trim();
61 if part.is_empty() {
62 return None;
63 }
64 match part.parse::<u64>() {
65 Ok(n) => components.push(n),
66 Err(_) => return None,
67 }
68 }
69 if components.is_empty() {
70 return None;
71 }
72 Some(Version { components })
73 }
74}
75
76impl std::fmt::Display for Version {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 let mut first = true;
79 for c in &self.components {
80 if !first {
81 write!(f, ".")?;
82 }
83 write!(f, "{c}")?;
84 first = false;
85 }
86 Ok(())
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum VersionRange {
97 Any,
99 NoVersion,
101 Eq(Version),
103 Gt(Version),
105 Gte(Version),
107 Lt(Version),
109 Lte(Version),
111 MajorBound(Version),
113 And(Box<VersionRange>, Box<VersionRange>),
115 Or(Box<VersionRange>, Box<VersionRange>),
117}
118
119impl std::fmt::Display for VersionRange {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 match self {
122 VersionRange::Any => write!(f, "-any"),
123 VersionRange::NoVersion => write!(f, "-none"),
124 VersionRange::Eq(v) => write!(f, "=={v}"),
125 VersionRange::Gt(v) => write!(f, ">{v}"),
126 VersionRange::Gte(v) => write!(f, ">={v}"),
127 VersionRange::Lt(v) => write!(f, "<{v}"),
128 VersionRange::Lte(v) => write!(f, "<={v}"),
129 VersionRange::MajorBound(v) => write!(f, "^>={v}"),
130 VersionRange::And(a, b) => write!(f, "{a} && {b}"),
131 VersionRange::Or(a, b) => write!(f, "{a} || {b}"),
132 }
133 }
134}
135
136impl VersionRange {
137 pub fn satisfies(&self, version: &Version) -> bool {
152 version_satisfies(version, self)
153 }
154}
155
156pub fn version_satisfies(version: &Version, vr: &VersionRange) -> bool {
160 use std::cmp::Ordering;
161
162 let cmp_versions = |a: &Version, b: &Version| -> Ordering {
163 let max_len = a.components.len().max(b.components.len());
164 for i in 0..max_len {
165 let ac = a.components.get(i).copied().unwrap_or(0);
166 let bc = b.components.get(i).copied().unwrap_or(0);
167 match ac.cmp(&bc) {
168 Ordering::Equal => continue,
169 other => return other,
170 }
171 }
172 Ordering::Equal
173 };
174
175 match vr {
176 VersionRange::Any => true,
177 VersionRange::NoVersion => false,
178 VersionRange::Eq(v) => cmp_versions(version, v) == Ordering::Equal,
179 VersionRange::Gt(v) => cmp_versions(version, v) == Ordering::Greater,
180 VersionRange::Gte(v) => cmp_versions(version, v) != Ordering::Less,
181 VersionRange::Lt(v) => cmp_versions(version, v) == Ordering::Less,
182 VersionRange::Lte(v) => cmp_versions(version, v) != Ordering::Greater,
183 VersionRange::MajorBound(v) => {
184 if cmp_versions(version, v) == Ordering::Less {
186 return false;
187 }
188 let mut upper = v.clone();
189 if upper.components.len() >= 2 {
190 upper.components[1] += 1;
191 upper.components.truncate(2);
192 } else if upper.components.len() == 1 {
193 upper.components[0] += 1;
194 }
195 cmp_versions(version, &upper) == Ordering::Less
196 }
197 VersionRange::And(a, b) => version_satisfies(version, a) && version_satisfies(version, b),
198 VersionRange::Or(a, b) => version_satisfies(version, a) || version_satisfies(version, b),
199 }
200}
201
202pub fn parse_version_range(s: &str) -> Option<VersionRange> {
213 let s = s.trim();
214 if s.is_empty() {
215 return None;
216 }
217
218 if let Some(range) = parse_or_range(s) {
220 return Some(range);
221 }
222
223 None
224}
225
226fn parse_or_range(s: &str) -> Option<VersionRange> {
228 let parts = split_respecting_parens(s, "||");
230 if parts.len() > 1 {
231 let mut ranges: Vec<VersionRange> = Vec::new();
232 for part in &parts {
233 ranges.push(parse_and_range(part.trim())?);
234 }
235 let mut result = ranges.remove(0);
236 for r in ranges {
237 result = VersionRange::Or(Box::new(result), Box::new(r));
238 }
239 return Some(result);
240 }
241 parse_and_range(s)
242}
243
244fn parse_and_range(s: &str) -> Option<VersionRange> {
246 let parts = split_respecting_parens(s, "&&");
247 if parts.len() > 1 {
248 let mut ranges: Vec<VersionRange> = Vec::new();
249 for part in &parts {
250 ranges.push(parse_atom_range(part.trim())?);
251 }
252 let mut result = ranges.remove(0);
253 for r in ranges {
254 result = VersionRange::And(Box::new(result), Box::new(r));
255 }
256 return Some(result);
257 }
258 parse_atom_range(s)
259}
260
261fn parse_atom_range(s: &str) -> Option<VersionRange> {
264 let s = s.trim();
265 if s.is_empty() {
266 return None;
267 }
268
269 if s.eq_ignore_ascii_case("-any") {
271 return Some(VersionRange::Any);
272 }
273 if s.eq_ignore_ascii_case("-none") {
274 return Some(VersionRange::NoVersion);
275 }
276
277 if s.starts_with('(') && s.ends_with(')') {
279 return parse_or_range(&s[1..s.len() - 1]);
280 }
281
282 if let Some(rest) = s.strip_prefix("^>=") {
284 let rest = rest.trim();
285 if rest.starts_with('{') && rest.ends_with('}') {
286 let inner = &rest[1..rest.len() - 1];
287 let versions: Vec<&str> = inner.split(',').map(|v| v.trim()).collect();
288 let mut ranges: Vec<VersionRange> = Vec::new();
289 for v_str in versions {
290 if !v_str.is_empty() {
291 if let Some(v) = Version::parse(v_str) {
292 ranges.push(VersionRange::MajorBound(v));
293 }
294 }
295 }
296 if ranges.is_empty() {
297 return None;
298 }
299 let mut result = ranges.remove(0);
300 for r in ranges {
301 result = VersionRange::Or(Box::new(result), Box::new(r));
302 }
303 return Some(result);
304 }
305 let v = Version::parse(rest)?;
306 return Some(VersionRange::MajorBound(v));
307 }
308
309 if let Some(rest) = s.strip_prefix(">=") {
311 let v = Version::parse(rest.trim())?;
312 return Some(VersionRange::Gte(v));
313 }
314
315 if let Some(rest) = s.strip_prefix("<=") {
317 let v = Version::parse(rest.trim())?;
318 return Some(VersionRange::Lte(v));
319 }
320
321 if let Some(rest) = s.strip_prefix("==") {
323 let rest = rest.trim();
324 if rest.starts_with('{') && rest.ends_with('}') {
326 let inner = &rest[1..rest.len() - 1];
327 let versions: Vec<&str> = inner.split(',').map(|v| v.trim()).collect();
328 let mut ranges: Vec<VersionRange> = Vec::new();
329 for v_str in versions {
330 if !v_str.is_empty() {
331 if let Some(v) = Version::parse(v_str) {
332 ranges.push(VersionRange::Eq(v));
333 }
334 }
335 }
336 if ranges.is_empty() {
337 return None;
338 }
339 let mut result = ranges.remove(0);
340 for r in ranges {
341 result = VersionRange::Or(Box::new(result), Box::new(r));
342 }
343 return Some(result);
344 }
345 if let Some(prefix) = rest.strip_suffix(".*") {
347 let v = Version::parse(prefix)?;
348 let mut upper = v.clone();
349 if let Some(last) = upper.components.last_mut() {
350 *last += 1;
351 }
352 return Some(VersionRange::And(
353 Box::new(VersionRange::Gte(v)),
354 Box::new(VersionRange::Lt(upper)),
355 ));
356 }
357 let v = Version::parse(rest)?;
358 return Some(VersionRange::Eq(v));
359 }
360
361 if let Some(rest) = s.strip_prefix('>') {
363 let v = Version::parse(rest.trim())?;
364 return Some(VersionRange::Gt(v));
365 }
366
367 if let Some(rest) = s.strip_prefix('<') {
369 let v = Version::parse(rest.trim())?;
370 return Some(VersionRange::Lt(v));
371 }
372
373 None
374}
375
376fn split_respecting_parens<'a>(s: &'a str, delim: &str) -> Vec<&'a str> {
378 let mut parts = Vec::new();
379 let mut depth = 0usize;
380 let mut last = 0;
381 let bytes = s.as_bytes();
382 let delim_bytes = delim.as_bytes();
383 let delim_len = delim_bytes.len();
384
385 let mut i = 0;
386 while i < bytes.len() {
387 if bytes[i] == b'(' {
388 depth += 1;
389 i += 1;
390 } else if bytes[i] == b')' {
391 depth = depth.saturating_sub(1);
392 i += 1;
393 } else if depth == 0
394 && i + delim_len <= bytes.len()
395 && &bytes[i..i + delim_len] == delim_bytes
396 {
397 parts.push(&s[last..i]);
398 i += delim_len;
399 last = i;
400 } else {
401 i += 1;
402 }
403 }
404 parts.push(&s[last..]);
405 parts
406}
407
408#[derive(Debug, Clone, PartialEq, Eq)]
414pub struct Dependency<'a> {
415 pub package: &'a str,
417 pub version_range: Option<VersionRange>,
419 pub cst_node: NodeId,
422}
423
424fn parse_single_dependency<'a>(s: &'a str, cst_node: NodeId) -> Option<Dependency<'a>> {
428 let s = s.trim();
429 if s.is_empty() {
430 return None;
431 }
432
433 let name_end = s
435 .find(|c: char| !c.is_alphanumeric() && c != '-' && c != '_')
436 .unwrap_or(s.len());
437 let package = s[..name_end].trim();
438 if package.is_empty() {
439 return None;
440 }
441
442 let rest = s[name_end..].trim();
443 let version_range = if rest.is_empty() {
444 None
445 } else {
446 parse_version_range(rest)
447 };
448
449 Some(Dependency {
450 package,
451 version_range,
452 cst_node,
453 })
454}
455
456fn parse_dependencies_from_text<'a>(text: &'a str, cst_node: NodeId) -> Vec<Dependency<'a>> {
463 let mut deps = Vec::new();
464 for part in text.split(',') {
466 let part = part.trim();
467 if part.is_empty() {
468 continue;
469 }
470 if let Some(dep) = parse_single_dependency(part, cst_node) {
471 deps.push(dep);
472 }
473 }
474 deps
475}
476
477#[derive(Debug, Clone, PartialEq, Eq)]
483pub struct CabalVersion<'a> {
484 pub raw: &'a str,
486 pub version: Option<Version>,
488 pub cst_node: NodeId,
490}
491
492#[derive(Debug, Clone, PartialEq, Eq)]
498pub struct Field<'a> {
499 pub name: String,
501 pub raw_name: &'a str,
503 pub value: String,
506 pub cst_node: NodeId,
508}
509
510#[derive(Debug, Clone, PartialEq, Eq)]
516pub enum Condition<'a> {
517 Flag(&'a str),
519 OS(&'a str),
521 Arch(&'a str),
523 Impl(&'a str, Option<VersionRange>),
525 Not(Box<Condition<'a>>),
527 And(Box<Condition<'a>>, Box<Condition<'a>>),
529 Or(Box<Condition<'a>>, Box<Condition<'a>>),
531 Lit(bool),
533 Raw(&'a str),
535}
536
537pub fn parse_condition(s: &str) -> Condition<'_> {
546 let s = s.trim();
547 if s.is_empty() {
548 return Condition::Raw(s);
549 }
550 match parse_condition_or(s) {
551 Some(c) => c,
552 None => Condition::Raw(s),
553 }
554}
555
556fn parse_condition_or(s: &str) -> Option<Condition<'_>> {
558 let parts = split_respecting_parens(s, "||");
559 if parts.len() > 1 {
560 let mut conds: Vec<Condition<'_>> = Vec::new();
561 for part in &parts {
562 conds.push(parse_condition_and(part.trim())?);
563 }
564 let mut result = conds.remove(0);
565 for c in conds {
566 result = Condition::Or(Box::new(result), Box::new(c));
567 }
568 return Some(result);
569 }
570 parse_condition_and(s)
571}
572
573fn parse_condition_and(s: &str) -> Option<Condition<'_>> {
575 let parts = split_respecting_parens(s, "&&");
576 if parts.len() > 1 {
577 let mut conds: Vec<Condition<'_>> = Vec::new();
578 for part in &parts {
579 conds.push(parse_condition_atom(part.trim())?);
580 }
581 let mut result = conds.remove(0);
582 for c in conds {
583 result = Condition::And(Box::new(result), Box::new(c));
584 }
585 return Some(result);
586 }
587 parse_condition_atom(s)
588}
589
590fn parse_condition_atom(s: &str) -> Option<Condition<'_>> {
593 let s = s.trim();
594 if s.is_empty() {
595 return None;
596 }
597
598 if let Some(rest) = s.strip_prefix('!') {
600 let inner = parse_condition_atom(rest.trim())?;
601 return Some(Condition::Not(Box::new(inner)));
602 }
603
604 if s.starts_with('(') && s.ends_with(')') {
606 return parse_condition_or(&s[1..s.len() - 1]);
607 }
608
609 if let Some(paren_start) = s.find('(') {
611 if s.ends_with(')') {
612 let func = s[..paren_start].trim();
613 let arg = s[paren_start + 1..s.len() - 1].trim();
614 let func_lower = func.to_ascii_lowercase();
615 match func_lower.as_str() {
616 "flag" => return Some(Condition::Flag(arg)),
617 "os" => return Some(Condition::OS(arg)),
618 "arch" => return Some(Condition::Arch(arg)),
619 "impl" => {
620 let parts: Vec<&str> = arg.splitn(2, char::is_whitespace).collect();
622 let compiler = parts[0];
623 let vr = if parts.len() > 1 {
624 parse_version_range(parts[1].trim())
625 } else {
626 None
627 };
628 return Some(Condition::Impl(compiler, vr));
629 }
630 _ => {}
631 }
632 }
633 }
634
635 match s.to_ascii_lowercase().as_str() {
637 "true" => return Some(Condition::Lit(true)),
638 "false" => return Some(Condition::Lit(false)),
639 _ => {}
640 }
641
642 Some(Condition::Raw(s))
644}
645
646#[derive(Debug, Clone, PartialEq, Eq)]
652pub struct Conditional<'a> {
653 pub condition: Condition<'a>,
655 pub then_fields: Vec<Field<'a>>,
657 pub then_deps: Vec<Dependency<'a>>,
659 pub else_fields: Vec<Field<'a>>,
661 pub else_deps: Vec<Dependency<'a>>,
663 pub then_conditionals: Vec<Conditional<'a>>,
665 pub else_conditionals: Vec<Conditional<'a>>,
667 pub cst_node: NodeId,
669}
670
671#[derive(Debug, Clone, PartialEq, Eq)]
677pub struct ComponentFields<'a> {
678 pub name: Option<&'a str>,
680 pub cst_node: NodeId,
682 pub imports: Vec<&'a str>,
684 pub build_depends: Vec<Dependency<'a>>,
686 pub other_modules: Vec<&'a str>,
688 pub hs_source_dirs: Vec<&'a str>,
690 pub default_language: Option<&'a str>,
692 pub default_extensions: Vec<&'a str>,
694 pub ghc_options: Vec<&'a str>,
696 pub other_fields: Vec<Field<'a>>,
698 pub conditionals: Vec<Conditional<'a>>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
704pub struct Library<'a> {
705 pub fields: ComponentFields<'a>,
707 pub exposed_modules: Vec<&'a str>,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq)]
713pub struct Executable<'a> {
714 pub fields: ComponentFields<'a>,
716 pub main_is: Option<&'a str>,
718}
719
720#[derive(Debug, Clone, PartialEq, Eq)]
722pub struct TestSuite<'a> {
723 pub fields: ComponentFields<'a>,
725 pub test_type: Option<&'a str>,
727 pub main_is: Option<&'a str>,
729}
730
731#[derive(Debug, Clone, PartialEq, Eq)]
733pub struct Benchmark<'a> {
734 pub fields: ComponentFields<'a>,
736 pub bench_type: Option<&'a str>,
738 pub main_is: Option<&'a str>,
740}
741
742#[derive(Debug, Clone, PartialEq, Eq)]
744pub struct CommonStanza<'a> {
745 pub name: &'a str,
747 pub fields: ComponentFields<'a>,
749}
750
751#[derive(Debug, Clone, PartialEq, Eq)]
753pub struct Flag<'a> {
754 pub name: &'a str,
756 pub description: Option<&'a str>,
758 pub default: Option<bool>,
760 pub manual: Option<bool>,
762 pub other_fields: Vec<Field<'a>>,
764 pub cst_node: NodeId,
766}
767
768#[derive(Debug, Clone, PartialEq, Eq)]
770pub struct SourceRepository<'a> {
771 pub kind: Option<&'a str>,
773 pub repo_type: Option<&'a str>,
775 pub location: Option<&'a str>,
777 pub tag: Option<&'a str>,
779 pub branch: Option<&'a str>,
781 pub subdir: Option<&'a str>,
783 pub other_fields: Vec<Field<'a>>,
785 pub cst_node: NodeId,
787}
788
789#[derive(Debug, Clone)]
795pub enum Component<'a, 'b> {
796 Library(&'b Library<'a>),
797 Executable(&'b Executable<'a>),
798 TestSuite(&'b TestSuite<'a>),
799 Benchmark(&'b Benchmark<'a>),
800}
801
802impl<'a, 'b> Component<'a, 'b> {
803 pub fn fields(&self) -> &ComponentFields<'a> {
805 match self {
806 Component::Library(l) => &l.fields,
807 Component::Executable(e) => &e.fields,
808 Component::TestSuite(t) => &t.fields,
809 Component::Benchmark(b) => &b.fields,
810 }
811 }
812
813 pub fn name(&self) -> Option<&'a str> {
815 self.fields().name
816 }
817
818 pub fn cst_node(&self) -> NodeId {
820 self.fields().cst_node
821 }
822}
823
824#[derive(Debug, Clone, PartialEq, Eq)]
830pub struct CabalFile<'a> {
831 pub source: &'a str,
833 pub cabal_version: Option<CabalVersion<'a>>,
835 pub name: Option<&'a str>,
837 pub version: Option<Version>,
839 pub license: Option<&'a str>,
841 pub synopsis: Option<&'a str>,
843 pub description: Option<&'a str>,
845 pub author: Option<&'a str>,
847 pub maintainer: Option<&'a str>,
849 pub homepage: Option<&'a str>,
851 pub bug_reports: Option<&'a str>,
853 pub category: Option<&'a str>,
855 pub build_type: Option<&'a str>,
857 pub tested_with: Option<&'a str>,
859 pub extra_source_files: Vec<&'a str>,
861 pub other_fields: Vec<Field<'a>>,
863 pub common_stanzas: Vec<CommonStanza<'a>>,
865 pub flags: Vec<Flag<'a>>,
867 pub library: Option<Library<'a>>,
869 pub named_libraries: Vec<Library<'a>>,
871 pub executables: Vec<Executable<'a>>,
873 pub test_suites: Vec<TestSuite<'a>>,
875 pub benchmarks: Vec<Benchmark<'a>>,
877 pub source_repositories: Vec<SourceRepository<'a>>,
879 pub cst_root: NodeId,
881}
882
883impl<'a> CabalFile<'a> {
884 pub fn all_dependencies(&self) -> Vec<&Dependency<'a>> {
887 let mut deps = Vec::new();
888
889 if let Some(ref lib) = self.library {
890 collect_component_deps(&lib.fields, &mut deps);
891 }
892 for lib in &self.named_libraries {
893 collect_component_deps(&lib.fields, &mut deps);
894 }
895 for exe in &self.executables {
896 collect_component_deps(&exe.fields, &mut deps);
897 }
898 for ts in &self.test_suites {
899 collect_component_deps(&ts.fields, &mut deps);
900 }
901 for bm in &self.benchmarks {
902 collect_component_deps(&bm.fields, &mut deps);
903 }
904 for cs in &self.common_stanzas {
905 collect_component_deps(&cs.fields, &mut deps);
906 }
907
908 deps
909 }
910
911 pub fn all_components(&self) -> Vec<Component<'a, '_>> {
914 let mut comps = Vec::new();
915 if let Some(ref lib) = self.library {
916 comps.push(Component::Library(lib));
917 }
918 for lib in &self.named_libraries {
919 comps.push(Component::Library(lib));
920 }
921 for exe in &self.executables {
922 comps.push(Component::Executable(exe));
923 }
924 for ts in &self.test_suites {
925 comps.push(Component::TestSuite(ts));
926 }
927 for bm in &self.benchmarks {
928 comps.push(Component::Benchmark(bm));
929 }
930 comps
931 }
932
933 pub fn find_component(&self, name: &str) -> Option<Component<'a, '_>> {
937 if let Some(ref lib) = self.library {
938 if name == "library" || lib.fields.name == Some(name) {
939 return Some(Component::Library(lib));
940 }
941 }
942 for lib in &self.named_libraries {
943 if lib.fields.name == Some(name) {
944 return Some(Component::Library(lib));
945 }
946 }
947 for exe in &self.executables {
948 if exe.fields.name == Some(name) {
949 return Some(Component::Executable(exe));
950 }
951 }
952 for ts in &self.test_suites {
953 if ts.fields.name == Some(name) {
954 return Some(Component::TestSuite(ts));
955 }
956 }
957 for bm in &self.benchmarks {
958 if bm.fields.name == Some(name) {
959 return Some(Component::Benchmark(bm));
960 }
961 }
962 None
963 }
964}
965
966fn collect_component_deps<'a, 'b>(
968 fields: &'b ComponentFields<'a>,
969 deps: &mut Vec<&'b Dependency<'a>>,
970) {
971 for d in &fields.build_depends {
972 deps.push(d);
973 }
974 collect_conditional_deps(&fields.conditionals, deps);
975}
976
977fn collect_conditional_deps<'a, 'b>(
978 conditionals: &'b [Conditional<'a>],
979 deps: &mut Vec<&'b Dependency<'a>>,
980) {
981 for cond in conditionals {
982 for d in &cond.then_deps {
983 deps.push(d);
984 }
985 for d in &cond.else_deps {
986 deps.push(d);
987 }
988 collect_conditional_deps(&cond.then_conditionals, deps);
989 collect_conditional_deps(&cond.else_conditionals, deps);
990 }
991}
992
993pub fn derive_ast<'a>(cst: &'a CabalCst) -> CabalFile<'a> {
1003 let source = cst.source.as_str();
1004 let mut file = CabalFile {
1005 source,
1006 cabal_version: None,
1007 name: None,
1008 version: None,
1009 license: None,
1010 synopsis: None,
1011 description: None,
1012 author: None,
1013 maintainer: None,
1014 homepage: None,
1015 bug_reports: None,
1016 category: None,
1017 build_type: None,
1018 tested_with: None,
1019 extra_source_files: Vec::new(),
1020 other_fields: Vec::new(),
1021 common_stanzas: Vec::new(),
1022 flags: Vec::new(),
1023 library: None,
1024 named_libraries: Vec::new(),
1025 executables: Vec::new(),
1026 test_suites: Vec::new(),
1027 benchmarks: Vec::new(),
1028 source_repositories: Vec::new(),
1029 cst_root: cst.root,
1030 };
1031
1032 collect_ast_nodes(cst, cst.root, &mut file);
1036
1037 file
1038}
1039
1040fn collect_ast_nodes<'a>(cst: &'a CabalCst, node_id: NodeId, file: &mut CabalFile<'a>) {
1045 let node = cst.node(node_id);
1046
1047 match node.kind {
1048 CstNodeKind::Root => {
1049 let children: Vec<NodeId> = node.children.clone();
1051 for child_id in children {
1052 collect_ast_nodes(cst, child_id, file);
1053 }
1054 }
1055 CstNodeKind::Field if cst.node(node_id).parent == Some(cst.root) => {
1056 derive_top_level_field(cst, node_id, file);
1057 }
1058 CstNodeKind::Field => {}
1059 CstNodeKind::Section => {
1060 let source = cst.source.as_str();
1062 let is_top_level_section = if let Some(ref kw_span) = node.section_keyword {
1063 let kw = kw_span.slice(source).to_ascii_lowercase();
1064 matches!(
1065 kw.as_str(),
1066 "library"
1067 | "executable"
1068 | "test-suite"
1069 | "benchmark"
1070 | "common"
1071 | "flag"
1072 | "source-repository"
1073 )
1074 } else {
1075 false
1076 };
1077
1078 if is_top_level_section {
1079 derive_section(cst, node_id, file);
1080 let children: Vec<NodeId> = cst.node(node_id).children.clone();
1083 for child_id in children {
1084 let child = cst.node(child_id);
1085 if child.kind == CstNodeKind::Section {
1086 collect_ast_nodes(cst, child_id, file);
1087 }
1088 }
1089 }
1090 }
1091 _ => {}
1092 }
1093}
1094
1095fn field_full_value(cst: &CabalCst, node_id: NodeId) -> String {
1098 let node = cst.node(node_id);
1099 let source = cst.source.as_str();
1100
1101 let mut value = String::new();
1102
1103 if let Some(ref val_span) = node.field_value {
1105 value.push_str(val_span.slice(source).trim());
1106 }
1107
1108 for &child_id in &node.children {
1110 let child = cst.node(child_id);
1111 if child.kind == CstNodeKind::ValueLine {
1112 let line_text = child.content_span.slice(source).trim();
1113 if !line_text.is_empty() {
1114 if !value.is_empty() {
1115 value.push('\n');
1116 }
1117 value.push_str(line_text);
1118 }
1119 }
1120 }
1121
1122 value
1123}
1124
1125fn field_first_line_value(cst: &CabalCst, node_id: NodeId) -> Option<&str> {
1129 let node = cst.node(node_id);
1130 let source = cst.source.as_str();
1131
1132 if let Some(ref val_span) = node.field_value {
1134 let v = val_span.slice(source).trim();
1135 if !v.is_empty() {
1136 return Some(v);
1137 }
1138 }
1139
1140 for &child_id in &node.children {
1142 let child = cst.node(child_id);
1143 if child.kind == CstNodeKind::ValueLine {
1144 let v = child.content_span.slice(source).trim();
1145 if !v.is_empty() {
1146 return Some(v);
1147 }
1148 }
1149 }
1150
1151 None
1152}
1153
1154fn parse_list_field(cst: &CabalCst, node_id: NodeId) -> Vec<&str> {
1158 let node = cst.node(node_id);
1159 let source = cst.source.as_str();
1160 let mut items = Vec::new();
1161
1162 if let Some(ref val_span) = node.field_value {
1164 let text = val_span.slice(source).trim();
1165 for item in split_list_items(text) {
1166 if !item.is_empty() {
1167 items.push(item);
1168 }
1169 }
1170 }
1171
1172 for &child_id in &node.children {
1174 let child = cst.node(child_id);
1175 if child.kind == CstNodeKind::ValueLine {
1176 let text = child.content_span.slice(source).trim();
1177 for item in split_list_items(text) {
1178 if !item.is_empty() {
1179 items.push(item);
1180 }
1181 }
1182 }
1183 }
1184
1185 items
1186}
1187
1188fn split_list_items(text: &str) -> Vec<&str> {
1191 let mut items = Vec::new();
1192 if text.contains(',') {
1193 for part in text.split(',') {
1194 let trimmed = part.trim();
1195 if !trimmed.is_empty() {
1196 items.push(trimmed);
1197 }
1198 }
1199 } else {
1200 for part in text.split_whitespace() {
1202 items.push(part);
1203 }
1204 }
1205 items
1206}
1207
1208fn parse_ghc_options(cst: &CabalCst, node_id: NodeId) -> Vec<&str> {
1210 let node = cst.node(node_id);
1211 let source = cst.source.as_str();
1212 let mut opts = Vec::new();
1213
1214 if let Some(ref val_span) = node.field_value {
1215 for opt in val_span.slice(source).split_whitespace() {
1216 opts.push(opt);
1217 }
1218 }
1219
1220 for &child_id in &node.children {
1221 let child = cst.node(child_id);
1222 if child.kind == CstNodeKind::ValueLine {
1223 for opt in child.content_span.slice(source).split_whitespace() {
1224 opts.push(opt);
1225 }
1226 }
1227 }
1228
1229 opts
1230}
1231
1232fn parse_build_depends<'a>(cst: &'a CabalCst, node_id: NodeId) -> Vec<Dependency<'a>> {
1234 let node = cst.node(node_id);
1235 let source = cst.source.as_str();
1236 let mut deps = Vec::new();
1237
1238 if let Some(ref val_span) = node.field_value {
1240 let text = val_span.slice(source).trim();
1241 deps.extend(parse_dependencies_from_text(text, node_id));
1242 }
1243
1244 for &child_id in &node.children {
1246 let child = cst.node(child_id);
1247 if child.kind == CstNodeKind::ValueLine {
1248 let text = child.content_span.slice(source).trim();
1249 if !text.is_empty() {
1250 deps.extend(parse_dependencies_from_text(text, child_id));
1253 }
1254 }
1255 }
1256
1257 deps
1258}
1259
1260fn derive_top_level_field<'a>(cst: &'a CabalCst, node_id: NodeId, file: &mut CabalFile<'a>) {
1262 let node = cst.node(node_id);
1263 let source = cst.source.as_str();
1264
1265 let raw_name = match node.field_name {
1266 Some(ref span) => span.slice(source),
1267 None => return,
1268 };
1269 let canon = canonicalize_field_name(raw_name);
1270
1271 match canon.as_str() {
1272 "cabal-version" => {
1273 let raw = field_first_line_value(cst, node_id).unwrap_or("");
1274 let version_str = raw.strip_prefix(">=").unwrap_or(raw).trim();
1276 file.cabal_version = Some(CabalVersion {
1277 raw,
1278 version: Version::parse(version_str),
1279 cst_node: node_id,
1280 });
1281 }
1282 "name" => {
1283 file.name = field_first_line_value(cst, node_id);
1284 }
1285 "version" => {
1286 let raw = field_first_line_value(cst, node_id).unwrap_or("");
1287 file.version = Version::parse(raw);
1288 }
1289 "license" => {
1290 file.license = field_first_line_value(cst, node_id);
1291 }
1292 "synopsis" => {
1293 file.synopsis = field_first_line_value(cst, node_id);
1294 }
1295 "description" => {
1296 file.description = field_first_line_value(cst, node_id);
1300 }
1301 "author" => {
1302 file.author = field_first_line_value(cst, node_id);
1303 }
1304 "maintainer" => {
1305 file.maintainer = field_first_line_value(cst, node_id);
1306 }
1307 "homepage" => {
1308 file.homepage = field_first_line_value(cst, node_id);
1309 }
1310 "bug-reports" => {
1311 file.bug_reports = field_first_line_value(cst, node_id);
1312 }
1313 "category" => {
1314 file.category = field_first_line_value(cst, node_id);
1315 }
1316 "build-type" => {
1317 file.build_type = field_first_line_value(cst, node_id);
1318 }
1319 "tested-with" => {
1320 file.tested_with = field_first_line_value(cst, node_id);
1321 }
1322 "extra-source-files" | "extra-doc-files" => {
1323 file.extra_source_files
1324 .extend(parse_list_field(cst, node_id));
1325 }
1326 _ => {
1327 let value = field_full_value(cst, node_id);
1328 file.other_fields.push(Field {
1329 name: canon,
1330 raw_name,
1331 value,
1332 cst_node: node_id,
1333 });
1334 }
1335 }
1336}
1337
1338fn derive_section<'a>(cst: &'a CabalCst, node_id: NodeId, file: &mut CabalFile<'a>) {
1340 let node = cst.node(node_id);
1341 let source = cst.source.as_str();
1342
1343 let keyword = match node.section_keyword {
1344 Some(ref span) => span.slice(source),
1345 None => return,
1346 };
1347 let section_arg = node.section_arg.map(|span| span.slice(source));
1348 let keyword_lower = keyword.to_ascii_lowercase();
1349
1350 match keyword_lower.as_str() {
1351 "library" => {
1352 let lib = derive_library(cst, node_id, section_arg);
1353 if section_arg.is_some() {
1354 file.named_libraries.push(lib);
1355 } else {
1356 file.library = Some(lib);
1357 }
1358 }
1359 "executable" => {
1360 let exe = derive_executable(cst, node_id, section_arg);
1361 file.executables.push(exe);
1362 }
1363 "test-suite" => {
1364 let ts = derive_test_suite(cst, node_id, section_arg);
1365 file.test_suites.push(ts);
1366 }
1367 "benchmark" => {
1368 let bm = derive_benchmark(cst, node_id, section_arg);
1369 file.benchmarks.push(bm);
1370 }
1371 "common" => {
1372 if let Some(name) = section_arg {
1373 let cs = derive_common_stanza(cst, node_id, name);
1374 file.common_stanzas.push(cs);
1375 }
1376 }
1377 "flag" => {
1378 if let Some(name) = section_arg {
1379 let flag = derive_flag(cst, node_id, name);
1380 file.flags.push(flag);
1381 }
1382 }
1383 "source-repository" => {
1384 let sr = derive_source_repository(cst, node_id, section_arg);
1385 file.source_repositories.push(sr);
1386 }
1387 _ => {
1388 }
1390 }
1391}
1392
1393fn empty_component_fields<'a>(name: Option<&'a str>, cst_node: NodeId) -> ComponentFields<'a> {
1395 ComponentFields {
1396 name,
1397 cst_node,
1398 imports: Vec::new(),
1399 build_depends: Vec::new(),
1400 other_modules: Vec::new(),
1401 hs_source_dirs: Vec::new(),
1402 default_language: None,
1403 default_extensions: Vec::new(),
1404 ghc_options: Vec::new(),
1405 other_fields: Vec::new(),
1406 conditionals: Vec::new(),
1407 }
1408}
1409
1410fn populate_component_fields<'a>(
1412 cst: &'a CabalCst,
1413 section_id: NodeId,
1414 fields: &mut ComponentFields<'a>,
1415) {
1416 let section = cst.node(section_id);
1417 let source = cst.source.as_str();
1418
1419 for &child_id in §ion.children {
1420 let child = cst.node(child_id);
1421 match child.kind {
1422 CstNodeKind::Field => {
1423 let raw_name = match child.field_name {
1424 Some(ref span) => span.slice(source),
1425 None => continue,
1426 };
1427 let canon = canonicalize_field_name(raw_name);
1428
1429 match canon.as_str() {
1430 "build-depends" => {
1431 fields
1432 .build_depends
1433 .extend(parse_build_depends(cst, child_id));
1434 }
1435 "exposed-modules" => {
1436 }
1439 "other-modules" => {
1440 fields.other_modules.extend(parse_list_field(cst, child_id));
1441 }
1442 "hs-source-dirs" => {
1443 fields
1444 .hs_source_dirs
1445 .extend(parse_list_field(cst, child_id));
1446 }
1447 "default-language" => {
1448 fields.default_language = field_first_line_value(cst, child_id);
1449 }
1450 "default-extensions" | "extensions" => {
1451 fields
1452 .default_extensions
1453 .extend(parse_list_field(cst, child_id));
1454 }
1455 "ghc-options" => {
1456 fields.ghc_options.extend(parse_ghc_options(cst, child_id));
1457 }
1458 _ => {
1459 let value = field_full_value(cst, child_id);
1460 fields.other_fields.push(Field {
1461 name: canon,
1462 raw_name,
1463 value,
1464 cst_node: child_id,
1465 });
1466 }
1467 }
1468 }
1469 CstNodeKind::Import => {
1470 if let Some(ref val_span) = child.field_value {
1471 let val = val_span.slice(source).trim();
1472 if !val.is_empty() {
1473 for item in val.split(',') {
1475 let item = item.trim();
1476 if !item.is_empty() {
1477 fields.imports.push(item);
1478 }
1479 }
1480 }
1481 }
1482 }
1483 CstNodeKind::Conditional => {
1484 let cond = derive_conditional(cst, child_id);
1485 fields.conditionals.push(cond);
1486 }
1487 _ => {}
1489 }
1490 }
1491}
1492
1493fn derive_conditional<'a>(cst: &'a CabalCst, node_id: NodeId) -> Conditional<'a> {
1495 let node = cst.node(node_id);
1496 let source = cst.source.as_str();
1497
1498 let condition = match node.condition_expr {
1500 Some(ref span) => parse_condition(span.slice(source)),
1501 None => Condition::Raw(""),
1502 };
1503
1504 let mut cond = Conditional {
1505 condition,
1506 then_fields: Vec::new(),
1507 then_deps: Vec::new(),
1508 else_fields: Vec::new(),
1509 else_deps: Vec::new(),
1510 then_conditionals: Vec::new(),
1511 else_conditionals: Vec::new(),
1512 cst_node: node_id,
1513 };
1514
1515 for &child_id in &node.children {
1517 let child = cst.node(child_id);
1518 match child.kind {
1519 CstNodeKind::Field => {
1520 let raw_name = match child.field_name {
1521 Some(ref span) => span.slice(source),
1522 None => continue,
1523 };
1524 let canon = canonicalize_field_name(raw_name);
1525
1526 if canon == "build-depends" {
1527 cond.then_deps.extend(parse_build_depends(cst, child_id));
1528 } else {
1529 let value = field_full_value(cst, child_id);
1530 cond.then_fields.push(Field {
1531 name: canon,
1532 raw_name,
1533 value,
1534 cst_node: child_id,
1535 });
1536 }
1537 }
1538 CstNodeKind::Conditional => {
1539 cond.then_conditionals
1540 .push(derive_conditional(cst, child_id));
1541 }
1542 CstNodeKind::ElseBlock => {
1543 for &else_child_id in &child.children {
1545 let else_child = cst.node(else_child_id);
1546 match else_child.kind {
1547 CstNodeKind::Field => {
1548 let raw_name = match else_child.field_name {
1549 Some(ref span) => span.slice(source),
1550 None => continue,
1551 };
1552 let canon = canonicalize_field_name(raw_name);
1553
1554 if canon == "build-depends" {
1555 cond.else_deps
1556 .extend(parse_build_depends(cst, else_child_id));
1557 } else {
1558 let value = field_full_value(cst, else_child_id);
1559 cond.else_fields.push(Field {
1560 name: canon,
1561 raw_name,
1562 value,
1563 cst_node: else_child_id,
1564 });
1565 }
1566 }
1567 CstNodeKind::Conditional => {
1568 cond.else_conditionals
1569 .push(derive_conditional(cst, else_child_id));
1570 }
1571 _ => {}
1572 }
1573 }
1574 }
1575 _ => {}
1576 }
1577 }
1578
1579 cond
1580}
1581
1582fn derive_library<'a>(cst: &'a CabalCst, node_id: NodeId, name: Option<&'a str>) -> Library<'a> {
1584 let mut fields = empty_component_fields(name, node_id);
1585 populate_component_fields(cst, node_id, &mut fields);
1586
1587 let exposed_modules = extract_exposed_modules(cst, node_id);
1590
1591 Library {
1592 fields,
1593 exposed_modules,
1594 }
1595}
1596
1597fn extract_exposed_modules(cst: &CabalCst, section_id: NodeId) -> Vec<&str> {
1599 let section = cst.node(section_id);
1600 let source = cst.source.as_str();
1601 let mut modules = Vec::new();
1602
1603 for &child_id in §ion.children {
1604 let child = cst.node(child_id);
1605 if child.kind == CstNodeKind::Field {
1606 if let Some(ref name_span) = child.field_name {
1607 let canon = canonicalize_field_name(name_span.slice(source));
1608 if canon == "exposed-modules" {
1609 modules.extend(parse_list_field(cst, child_id));
1610 }
1611 }
1612 }
1613 }
1614
1615 modules
1616}
1617
1618fn derive_executable<'a>(
1620 cst: &'a CabalCst,
1621 node_id: NodeId,
1622 name: Option<&'a str>,
1623) -> Executable<'a> {
1624 let main_is = find_field_value_in_section(cst, node_id, "main-is");
1625
1626 let mut fields = empty_component_fields(name, node_id);
1627 populate_component_fields(cst, node_id, &mut fields);
1628 remove_field_by_name(&mut fields.other_fields, "main-is");
1629
1630 Executable { fields, main_is }
1631}
1632
1633fn derive_test_suite<'a>(
1635 cst: &'a CabalCst,
1636 node_id: NodeId,
1637 name: Option<&'a str>,
1638) -> TestSuite<'a> {
1639 let test_type = find_field_value_in_section(cst, node_id, "type");
1640 let main_is = find_field_value_in_section(cst, node_id, "main-is");
1641
1642 let mut fields = empty_component_fields(name, node_id);
1643 populate_component_fields(cst, node_id, &mut fields);
1644 remove_field_by_name(&mut fields.other_fields, "type");
1645 remove_field_by_name(&mut fields.other_fields, "main-is");
1646
1647 TestSuite {
1648 fields,
1649 test_type,
1650 main_is,
1651 }
1652}
1653
1654fn derive_benchmark<'a>(
1656 cst: &'a CabalCst,
1657 node_id: NodeId,
1658 name: Option<&'a str>,
1659) -> Benchmark<'a> {
1660 let bench_type = find_field_value_in_section(cst, node_id, "type");
1661 let main_is = find_field_value_in_section(cst, node_id, "main-is");
1662
1663 let mut fields = empty_component_fields(name, node_id);
1664 populate_component_fields(cst, node_id, &mut fields);
1665 remove_field_by_name(&mut fields.other_fields, "type");
1666 remove_field_by_name(&mut fields.other_fields, "main-is");
1667
1668 Benchmark {
1669 fields,
1670 bench_type,
1671 main_is,
1672 }
1673}
1674
1675fn derive_common_stanza<'a>(cst: &'a CabalCst, node_id: NodeId, name: &'a str) -> CommonStanza<'a> {
1677 let mut fields = empty_component_fields(Some(name), node_id);
1678 populate_component_fields(cst, node_id, &mut fields);
1679
1680 CommonStanza { name, fields }
1681}
1682
1683fn derive_flag<'a>(cst: &'a CabalCst, node_id: NodeId, name: &'a str) -> Flag<'a> {
1685 let section = cst.node(node_id);
1686 let source = cst.source.as_str();
1687
1688 let mut description = None;
1689 let mut default = None;
1690 let mut manual = None;
1691 let mut other_fields = Vec::new();
1692
1693 for &child_id in §ion.children {
1694 let child = cst.node(child_id);
1695 if child.kind == CstNodeKind::Field {
1696 let raw_name = match child.field_name {
1697 Some(ref span) => span.slice(source),
1698 None => continue,
1699 };
1700 let canon = canonicalize_field_name(raw_name);
1701
1702 match canon.as_str() {
1703 "description" => {
1704 description = field_first_line_value(cst, child_id);
1705 }
1706 "default" => {
1707 if let Some(val) = field_first_line_value(cst, child_id) {
1708 let lower = val.to_ascii_lowercase();
1709 default = Some(lower == "true");
1710 }
1711 }
1712 "manual" => {
1713 if let Some(val) = field_first_line_value(cst, child_id) {
1714 let lower = val.to_ascii_lowercase();
1715 manual = Some(lower == "true");
1716 }
1717 }
1718 _ => {
1719 let value = field_full_value(cst, child_id);
1720 other_fields.push(Field {
1721 name: canon,
1722 raw_name,
1723 value,
1724 cst_node: child_id,
1725 });
1726 }
1727 }
1728 }
1729 }
1730
1731 Flag {
1732 name,
1733 description,
1734 default,
1735 manual,
1736 other_fields,
1737 cst_node: node_id,
1738 }
1739}
1740
1741fn derive_source_repository<'a>(
1743 cst: &'a CabalCst,
1744 node_id: NodeId,
1745 kind: Option<&'a str>,
1746) -> SourceRepository<'a> {
1747 let section = cst.node(node_id);
1748 let source = cst.source.as_str();
1749
1750 let mut repo_type = None;
1751 let mut location = None;
1752 let mut tag = None;
1753 let mut branch = None;
1754 let mut subdir = None;
1755 let mut other_fields = Vec::new();
1756
1757 for &child_id in §ion.children {
1758 let child = cst.node(child_id);
1759 if child.kind == CstNodeKind::Field {
1760 let raw_name = match child.field_name {
1761 Some(ref span) => span.slice(source),
1762 None => continue,
1763 };
1764 let canon = canonicalize_field_name(raw_name);
1765
1766 match canon.as_str() {
1767 "type" => {
1768 repo_type = field_first_line_value(cst, child_id);
1769 }
1770 "location" => {
1771 location = field_first_line_value(cst, child_id);
1772 }
1773 "tag" => {
1774 tag = field_first_line_value(cst, child_id);
1775 }
1776 "branch" => {
1777 branch = field_first_line_value(cst, child_id);
1778 }
1779 "subdir" => {
1780 subdir = field_first_line_value(cst, child_id);
1781 }
1782 _ => {
1783 let value = field_full_value(cst, child_id);
1784 other_fields.push(Field {
1785 name: canon,
1786 raw_name,
1787 value,
1788 cst_node: child_id,
1789 });
1790 }
1791 }
1792 }
1793 }
1794
1795 SourceRepository {
1796 kind,
1797 repo_type,
1798 location,
1799 tag,
1800 branch,
1801 subdir,
1802 other_fields,
1803 cst_node: node_id,
1804 }
1805}
1806
1807fn remove_field_by_name(fields: &mut Vec<Field<'_>>, canonical_name: &str) {
1809 fields.retain(|f| f.name != canonical_name);
1810}
1811
1812fn find_field_value_in_section<'a>(
1815 cst: &'a CabalCst,
1816 section_id: NodeId,
1817 target_canon: &str,
1818) -> Option<&'a str> {
1819 let section = cst.node(section_id);
1820 let source = cst.source.as_str();
1821
1822 for &child_id in §ion.children {
1823 let child = cst.node(child_id);
1824 if child.kind == CstNodeKind::Field {
1825 if let Some(ref name_span) = child.field_name {
1826 let canon = canonicalize_field_name(name_span.slice(source));
1827 if canon == target_canon {
1828 return field_first_line_value(cst, child_id);
1829 }
1830 }
1831 }
1832 }
1833 None
1834}
1835
1836#[cfg(test)]
1841mod tests {
1842 use super::*;
1843
1844 fn do_parse(source: &str) -> crate::parse::ParseResult {
1846 crate::parse::parse(source)
1847 }
1848
1849 #[test]
1852 fn version_parse_simple() {
1853 let v = Version::parse("0.1.0.0").unwrap();
1854 assert_eq!(v.components, vec![0, 1, 0, 0]);
1855 }
1856
1857 #[test]
1858 fn version_parse_two_components() {
1859 let v = Version::parse("4.14").unwrap();
1860 assert_eq!(v.components, vec![4, 14]);
1861 }
1862
1863 #[test]
1864 fn version_parse_single() {
1865 let v = Version::parse("5").unwrap();
1866 assert_eq!(v.components, vec![5]);
1867 }
1868
1869 #[test]
1870 fn version_parse_empty() {
1871 assert!(Version::parse("").is_none());
1872 }
1873
1874 #[test]
1875 fn version_parse_invalid() {
1876 assert!(Version::parse("abc").is_none());
1877 assert!(Version::parse("1.2.abc").is_none());
1878 }
1879
1880 #[test]
1881 fn version_display() {
1882 let v = Version {
1883 components: vec![1, 2, 3, 0],
1884 };
1885 assert_eq!(v.to_string(), "1.2.3.0");
1886 }
1887
1888 #[test]
1891 fn version_range_gte() {
1892 let vr = parse_version_range(">=4.14").unwrap();
1893 assert_eq!(
1894 vr,
1895 VersionRange::Gte(Version {
1896 components: vec![4, 14]
1897 })
1898 );
1899 }
1900
1901 #[test]
1902 fn version_range_lt() {
1903 let vr = parse_version_range("<5").unwrap();
1904 assert_eq!(
1905 vr,
1906 VersionRange::Lt(Version {
1907 components: vec![5]
1908 })
1909 );
1910 }
1911
1912 #[test]
1913 fn version_range_major_bound() {
1914 let vr = parse_version_range("^>=2.2").unwrap();
1915 assert_eq!(
1916 vr,
1917 VersionRange::MajorBound(Version {
1918 components: vec![2, 2]
1919 })
1920 );
1921 }
1922
1923 #[test]
1924 fn version_range_eq() {
1925 let vr = parse_version_range("==1.0").unwrap();
1926 assert_eq!(
1927 vr,
1928 VersionRange::Eq(Version {
1929 components: vec![1, 0]
1930 })
1931 );
1932 }
1933
1934 #[test]
1935 fn version_range_and() {
1936 let vr = parse_version_range(">=4.14 && <5").unwrap();
1937 assert_eq!(
1938 vr,
1939 VersionRange::And(
1940 Box::new(VersionRange::Gte(Version {
1941 components: vec![4, 14]
1942 })),
1943 Box::new(VersionRange::Lt(Version {
1944 components: vec![5]
1945 })),
1946 )
1947 );
1948 }
1949
1950 #[test]
1951 fn version_range_or() {
1952 let vr = parse_version_range(">=2.0 || ==1.9").unwrap();
1953 assert_eq!(
1954 vr,
1955 VersionRange::Or(
1956 Box::new(VersionRange::Gte(Version {
1957 components: vec![2, 0]
1958 })),
1959 Box::new(VersionRange::Eq(Version {
1960 components: vec![1, 9]
1961 })),
1962 )
1963 );
1964 }
1965
1966 #[test]
1967 fn version_range_complex_and() {
1968 let vr = parse_version_range(">=2.0 && <2.2").unwrap();
1969 assert_eq!(
1970 vr,
1971 VersionRange::And(
1972 Box::new(VersionRange::Gte(Version {
1973 components: vec![2, 0]
1974 })),
1975 Box::new(VersionRange::Lt(Version {
1976 components: vec![2, 2]
1977 })),
1978 )
1979 );
1980 }
1981
1982 #[test]
1983 fn version_range_empty() {
1984 assert!(parse_version_range("").is_none());
1985 }
1986
1987 #[test]
1990 fn canonicalize_mixed_case() {
1991 assert_eq!(canonicalize_field_name("Build-Depends"), "build-depends");
1992 }
1993
1994 #[test]
1995 fn canonicalize_underscore() {
1996 assert_eq!(canonicalize_field_name("build_depends"), "build-depends");
1997 }
1998
1999 #[test]
2000 fn canonicalize_already_canonical() {
2001 assert_eq!(canonicalize_field_name("build-depends"), "build-depends");
2002 }
2003
2004 #[test]
2007 fn parse_dep_no_version() {
2008 let dep = parse_single_dependency("base", NodeId(0)).unwrap();
2009 assert_eq!(dep.package, "base");
2010 assert!(dep.version_range.is_none());
2011 }
2012
2013 #[test]
2014 fn parse_dep_with_version() {
2015 let dep = parse_single_dependency("aeson ^>=2.2", NodeId(0)).unwrap();
2016 assert_eq!(dep.package, "aeson");
2017 assert_eq!(
2018 dep.version_range,
2019 Some(VersionRange::MajorBound(Version {
2020 components: vec![2, 2]
2021 }))
2022 );
2023 }
2024
2025 #[test]
2026 fn parse_dep_with_range() {
2027 let dep = parse_single_dependency("base >=4.14 && <5", NodeId(0)).unwrap();
2028 assert_eq!(dep.package, "base");
2029 assert_eq!(
2030 dep.version_range,
2031 Some(VersionRange::And(
2032 Box::new(VersionRange::Gte(Version {
2033 components: vec![4, 14]
2034 })),
2035 Box::new(VersionRange::Lt(Version {
2036 components: vec![5]
2037 })),
2038 ))
2039 );
2040 }
2041
2042 #[test]
2043 fn parse_deps_comma_separated() {
2044 let deps = parse_dependencies_from_text("base >=4.14, text >=2.0, aeson ^>=2.2", NodeId(0));
2045 assert_eq!(deps.len(), 3);
2046 assert_eq!(deps[0].package, "base");
2047 assert_eq!(deps[1].package, "text");
2048 assert_eq!(deps[2].package, "aeson");
2049 }
2050
2051 #[test]
2052 fn parse_deps_empty() {
2053 let deps = parse_dependencies_from_text("", NodeId(0));
2054 assert!(deps.is_empty());
2055 }
2056
2057 #[test]
2060 fn parse_condition_flag() {
2061 let c = parse_condition("flag(dev)");
2062 assert_eq!(c, Condition::Flag("dev"));
2063 }
2064
2065 #[test]
2066 fn parse_condition_os() {
2067 let c = parse_condition("os(windows)");
2068 assert_eq!(c, Condition::OS("windows"));
2069 }
2070
2071 #[test]
2072 fn parse_condition_arch() {
2073 let c = parse_condition("arch(x86_64)");
2074 assert_eq!(c, Condition::Arch("x86_64"));
2075 }
2076
2077 #[test]
2078 fn parse_condition_impl() {
2079 let c = parse_condition("impl(ghc >= 9.6)");
2080 assert_eq!(
2081 c,
2082 Condition::Impl(
2083 "ghc",
2084 Some(VersionRange::Gte(Version {
2085 components: vec![9, 6]
2086 }))
2087 )
2088 );
2089 }
2090
2091 #[test]
2092 fn parse_condition_not() {
2093 let c = parse_condition("!os(windows)");
2094 assert_eq!(c, Condition::Not(Box::new(Condition::OS("windows"))));
2095 }
2096
2097 #[test]
2098 fn parse_condition_and() {
2099 let c = parse_condition("flag(dev) && !os(windows)");
2100 assert_eq!(
2101 c,
2102 Condition::And(
2103 Box::new(Condition::Flag("dev")),
2104 Box::new(Condition::Not(Box::new(Condition::OS("windows")))),
2105 )
2106 );
2107 }
2108
2109 #[test]
2110 fn parse_condition_or() {
2111 let c = parse_condition("flag(a) || flag(b)");
2112 assert_eq!(
2113 c,
2114 Condition::Or(
2115 Box::new(Condition::Flag("a")),
2116 Box::new(Condition::Flag("b")),
2117 )
2118 );
2119 }
2120
2121 #[test]
2122 fn parse_condition_empty() {
2123 let c = parse_condition("");
2124 assert_eq!(c, Condition::Raw(""));
2125 }
2126
2127 #[test]
2130 fn derive_minimal_file() {
2131 let src = "cabal-version: 3.0\nname: my-pkg\nversion: 0.1.0.0\n";
2132 let result = do_parse(src);
2133 let ast = derive_ast(&result.cst);
2134
2135 assert_eq!(ast.name, Some("my-pkg"));
2136 assert_eq!(
2137 ast.version,
2138 Some(Version {
2139 components: vec![0, 1, 0, 0]
2140 })
2141 );
2142 assert!(ast.cabal_version.is_some());
2143 let cv = ast.cabal_version.as_ref().unwrap();
2144 assert_eq!(cv.raw, "3.0");
2145 assert_eq!(
2146 cv.version,
2147 Some(Version {
2148 components: vec![3, 0]
2149 })
2150 );
2151 }
2152
2153 #[test]
2154 fn derive_with_library() {
2155 let src = "\
2156cabal-version: 3.0
2157name: my-pkg
2158version: 0.1.0.0
2159
2160library
2161 exposed-modules:
2162 Foo
2163 Bar
2164 build-depends:
2165 base >=4.14
2166 default-language: GHC2021
2167";
2168 let result = do_parse(src);
2169 let ast = derive_ast(&result.cst);
2170
2171 assert!(ast.library.is_some());
2172 let lib = ast.library.as_ref().unwrap();
2173 assert_eq!(lib.exposed_modules, vec!["Foo", "Bar"]);
2174 assert_eq!(lib.fields.build_depends.len(), 1);
2175 assert_eq!(lib.fields.build_depends[0].package, "base");
2176 assert_eq!(lib.fields.default_language, Some("GHC2021"));
2177 }
2178
2179 #[test]
2180 fn derive_with_executable() {
2181 let src = "\
2182cabal-version: 3.0
2183name: my-pkg
2184version: 0.1.0.0
2185
2186executable my-exe
2187 main-is: Main.hs
2188 build-depends: base
2189 hs-source-dirs: app
2190";
2191 let result = do_parse(src);
2192 let ast = derive_ast(&result.cst);
2193
2194 assert_eq!(ast.executables.len(), 1);
2195 let exe = &ast.executables[0];
2196 assert_eq!(exe.fields.name, Some("my-exe"));
2197 assert_eq!(exe.main_is, Some("Main.hs"));
2198 assert_eq!(exe.fields.build_depends.len(), 1);
2199 assert_eq!(exe.fields.hs_source_dirs, vec!["app"]);
2200 }
2201
2202 #[test]
2203 fn derive_with_test_suite() {
2204 let src = "\
2205cabal-version: 3.0
2206name: my-pkg
2207version: 0.1.0.0
2208
2209test-suite my-tests
2210 type: exitcode-stdio-1.0
2211 main-is: Main.hs
2212 build-depends: base, tasty
2213";
2214 let result = do_parse(src);
2215 let ast = derive_ast(&result.cst);
2216
2217 assert_eq!(ast.test_suites.len(), 1);
2218 let ts = &ast.test_suites[0];
2219 assert_eq!(ts.fields.name, Some("my-tests"));
2220 assert_eq!(ts.test_type, Some("exitcode-stdio-1.0"));
2221 assert_eq!(ts.main_is, Some("Main.hs"));
2222 assert_eq!(ts.fields.build_depends.len(), 2);
2223 }
2224
2225 #[test]
2226 fn derive_with_common_stanza() {
2227 let src = "\
2228cabal-version: 3.0
2229name: my-pkg
2230version: 0.1.0.0
2231
2232common warnings
2233 ghc-options: -Wall -Wcompat
2234
2235library
2236 import: warnings
2237 exposed-modules: Foo
2238";
2239 let result = do_parse(src);
2240 let ast = derive_ast(&result.cst);
2241
2242 assert_eq!(ast.common_stanzas.len(), 1);
2243 assert_eq!(ast.common_stanzas[0].name, "warnings");
2244 assert_eq!(
2245 ast.common_stanzas[0].fields.ghc_options,
2246 vec!["-Wall", "-Wcompat"]
2247 );
2248
2249 let lib = ast.library.as_ref().unwrap();
2250 assert_eq!(lib.fields.imports, vec!["warnings"]);
2251 }
2252
2253 #[test]
2254 fn derive_with_flag() {
2255 let src = "\
2256cabal-version: 3.0
2257name: my-pkg
2258version: 0.1.0.0
2259
2260flag dev
2261 description: Development mode
2262 default: False
2263 manual: True
2264";
2265 let result = do_parse(src);
2266 let ast = derive_ast(&result.cst);
2267
2268 assert_eq!(ast.flags.len(), 1);
2269 let flag = &ast.flags[0];
2270 assert_eq!(flag.name, "dev");
2271 assert_eq!(flag.description, Some("Development mode"));
2272 assert_eq!(flag.default, Some(false));
2273 assert_eq!(flag.manual, Some(true));
2274 }
2275
2276 #[test]
2277 fn derive_with_source_repository() {
2278 let src = "\
2279cabal-version: 3.0
2280name: my-pkg
2281version: 0.1.0.0
2282
2283source-repository head
2284 type: git
2285 location: https://github.com/example/my-pkg
2286";
2287 let result = do_parse(src);
2288 let ast = derive_ast(&result.cst);
2289
2290 assert_eq!(ast.source_repositories.len(), 1);
2291 let sr = &ast.source_repositories[0];
2292 assert_eq!(sr.kind, Some("head"));
2293 assert_eq!(sr.repo_type, Some("git"));
2294 assert_eq!(sr.location, Some("https://github.com/example/my-pkg"));
2295 }
2296
2297 #[test]
2298 fn derive_conditional() {
2299 let src = "\
2300cabal-version: 3.0
2301name: my-pkg
2302version: 0.1.0.0
2303
2304library
2305 build-depends: base
2306 if flag(dev)
2307 ghc-options: -O0
2308 else
2309 ghc-options: -O2
2310";
2311 let result = do_parse(src);
2312 let ast = derive_ast(&result.cst);
2313
2314 let lib = ast.library.as_ref().unwrap();
2315 assert_eq!(lib.fields.conditionals.len(), 1);
2316 let cond = &lib.fields.conditionals[0];
2317 assert_eq!(cond.condition, Condition::Flag("dev"));
2318 assert_eq!(cond.then_fields.len(), 1);
2319 assert_eq!(cond.then_fields[0].name, "ghc-options");
2320 assert_eq!(cond.then_fields[0].value, "-O0");
2321 assert_eq!(cond.else_fields.len(), 1);
2322 assert_eq!(cond.else_fields[0].name, "ghc-options");
2323 assert_eq!(cond.else_fields[0].value, "-O2");
2324 }
2325
2326 #[test]
2327 fn derive_all_dependencies() {
2328 let src = "\
2329cabal-version: 3.0
2330name: my-pkg
2331version: 0.1.0.0
2332
2333library
2334 build-depends: base, text
2335
2336executable my-exe
2337 build-depends: base, my-pkg
2338";
2339 let result = do_parse(src);
2340 let ast = derive_ast(&result.cst);
2341
2342 let all_deps = ast.all_dependencies();
2343 assert_eq!(all_deps.len(), 4);
2344 let names: Vec<&str> = all_deps.iter().map(|d| d.package).collect();
2345 assert!(names.contains(&"base"));
2346 assert!(names.contains(&"text"));
2347 assert!(names.contains(&"my-pkg"));
2348 }
2349
2350 #[test]
2351 fn derive_all_components() {
2352 let src = "\
2353cabal-version: 3.0
2354name: my-pkg
2355version: 0.1.0.0
2356
2357library
2358 exposed-modules: Foo
2359
2360executable my-exe
2361 main-is: Main.hs
2362
2363test-suite my-tests
2364 type: exitcode-stdio-1.0
2365 main-is: Main.hs
2366
2367benchmark my-bench
2368 type: exitcode-stdio-1.0
2369 main-is: Main.hs
2370";
2371 let result = do_parse(src);
2372 let ast = derive_ast(&result.cst);
2373
2374 let comps = ast.all_components();
2375 assert_eq!(comps.len(), 4);
2376 }
2377
2378 #[test]
2379 fn derive_find_component() {
2380 let src = "\
2381cabal-version: 3.0
2382name: my-pkg
2383version: 0.1.0.0
2384
2385library
2386 exposed-modules: Foo
2387
2388executable my-exe
2389 main-is: Main.hs
2390";
2391 let result = do_parse(src);
2392 let ast = derive_ast(&result.cst);
2393
2394 assert!(ast.find_component("library").is_some());
2395 assert!(ast.find_component("my-exe").is_some());
2396 assert!(ast.find_component("nonexistent").is_none());
2397 }
2398
2399 #[test]
2400 fn derive_cst_node_back_references_valid() {
2401 let src = "\
2402cabal-version: 3.0
2403name: my-pkg
2404version: 0.1.0.0
2405
2406library
2407 build-depends: base >=4.14
2408";
2409 let result = do_parse(src);
2410 let ast = derive_ast(&result.cst);
2411
2412 assert_eq!(ast.cst_root, result.cst.root);
2414
2415 let lib = ast.library.as_ref().unwrap();
2417 let node = result.cst.node(lib.fields.cst_node);
2418 assert_eq!(node.kind, CstNodeKind::Section);
2419
2420 assert!(!lib.fields.build_depends.is_empty());
2422 let dep_node_id = lib.fields.build_depends[0].cst_node;
2423 assert!(dep_node_id.0 < result.cst.node_count());
2424 }
2425
2426 #[test]
2427 fn derive_deps_leading_comma_style() {
2428 let src = "\
2429cabal-version: 3.0
2430name: my-pkg
2431version: 0.1.0.0
2432
2433library
2434 build-depends:
2435 base >=4.14
2436 , text >=2.0
2437 , aeson ^>=2.2
2438";
2439 let result = do_parse(src);
2440 let ast = derive_ast(&result.cst);
2441
2442 let lib = ast.library.as_ref().unwrap();
2443 assert_eq!(lib.fields.build_depends.len(), 3);
2444 assert_eq!(lib.fields.build_depends[0].package, "base");
2445 assert_eq!(lib.fields.build_depends[1].package, "text");
2446 assert_eq!(lib.fields.build_depends[2].package, "aeson");
2447 }
2448
2449 #[test]
2450 fn derive_deps_trailing_comma_style() {
2451 let src = "\
2452cabal-version: 3.0
2453name: my-pkg
2454version: 0.1.0.0
2455
2456library
2457 build-depends:
2458 base >=4.14,
2459 text >=2.0,
2460 aeson ^>=2.2
2461";
2462 let result = do_parse(src);
2463 let ast = derive_ast(&result.cst);
2464
2465 let lib = ast.library.as_ref().unwrap();
2466 assert_eq!(lib.fields.build_depends.len(), 3);
2467 assert_eq!(lib.fields.build_depends[0].package, "base");
2468 assert_eq!(lib.fields.build_depends[1].package, "text");
2469 assert_eq!(lib.fields.build_depends[2].package, "aeson");
2470 }
2471
2472 #[test]
2473 fn derive_deps_single_line() {
2474 let src = "\
2475cabal-version: 3.0
2476name: my-pkg
2477version: 0.1.0.0
2478
2479library
2480 build-depends: base >=4.14, text >=2.0, aeson ^>=2.2
2481";
2482 let result = do_parse(src);
2483 let ast = derive_ast(&result.cst);
2484
2485 let lib = ast.library.as_ref().unwrap();
2486 assert_eq!(lib.fields.build_depends.len(), 3);
2487 }
2488
2489 #[test]
2490 fn derive_default_extensions() {
2491 let src = "\
2492cabal-version: 3.0
2493name: my-pkg
2494version: 0.1.0.0
2495
2496library
2497 default-extensions:
2498 OverloadedStrings
2499 DerivingStrategies
2500";
2501 let result = do_parse(src);
2502 let ast = derive_ast(&result.cst);
2503
2504 let lib = ast.library.as_ref().unwrap();
2505 assert_eq!(
2506 lib.fields.default_extensions,
2507 vec!["OverloadedStrings", "DerivingStrategies"]
2508 );
2509 }
2510
2511 #[test]
2512 fn derive_metadata_fields() {
2513 let src = "\
2514cabal-version: 3.0
2515name: my-pkg
2516version: 0.1.0.0
2517license: MIT
2518synopsis: A test package
2519author: Test Author
2520maintainer: test@example.com
2521homepage: https://example.com
2522bug-reports: https://example.com/issues
2523category: Development
2524build-type: Simple
2525";
2526 let result = do_parse(src);
2527 let ast = derive_ast(&result.cst);
2528
2529 assert_eq!(ast.license, Some("MIT"));
2530 assert_eq!(ast.synopsis, Some("A test package"));
2531 assert_eq!(ast.author, Some("Test Author"));
2532 assert_eq!(ast.maintainer, Some("test@example.com"));
2533 assert_eq!(ast.homepage, Some("https://example.com"));
2534 assert_eq!(ast.bug_reports, Some("https://example.com/issues"));
2535 assert_eq!(ast.category, Some("Development"));
2536 assert_eq!(ast.build_type, Some("Simple"));
2537 }
2538
2539 #[test]
2540 fn derive_conditional_deps() {
2541 let src = "\
2542cabal-version: 3.0
2543name: my-pkg
2544version: 0.1.0.0
2545
2546library
2547 build-depends: base
2548 if os(windows)
2549 build-depends: Win32
2550 else
2551 build-depends: unix
2552";
2553 let result = do_parse(src);
2554 let ast = derive_ast(&result.cst);
2555
2556 let all_deps = ast.all_dependencies();
2557 let names: Vec<&str> = all_deps.iter().map(|d| d.package).collect();
2558 assert!(names.contains(&"base"));
2559 assert!(names.contains(&"Win32"));
2560 assert!(names.contains(&"unix"));
2561 assert_eq!(all_deps.len(), 3);
2562 }
2563
2564 #[test]
2567 fn parse_condition_true() {
2568 assert_eq!(parse_condition("true"), Condition::Lit(true));
2569 }
2570
2571 #[test]
2572 fn parse_condition_false() {
2573 assert_eq!(parse_condition("false"), Condition::Lit(false));
2574 }
2575
2576 #[test]
2577 fn parse_condition_true_case_insensitive() {
2578 assert_eq!(parse_condition("True"), Condition::Lit(true));
2579 assert_eq!(parse_condition("FALSE"), Condition::Lit(false));
2580 }
2581
2582 #[test]
2585 fn version_range_wildcard() {
2586 let r = parse_version_range("==1.2.*").unwrap();
2587 match r {
2588 VersionRange::And(a, b) => {
2589 assert_eq!(
2590 *a,
2591 VersionRange::Gte(Version {
2592 components: vec![1, 2]
2593 })
2594 );
2595 assert_eq!(
2596 *b,
2597 VersionRange::Lt(Version {
2598 components: vec![1, 3]
2599 })
2600 );
2601 }
2602 _ => panic!("Expected And range, got {:?}", r),
2603 }
2604 }
2605
2606 #[test]
2609 fn version_range_any_keyword() {
2610 assert_eq!(parse_version_range("-any").unwrap(), VersionRange::Any);
2611 }
2612
2613 #[test]
2614 fn version_range_none_keyword() {
2615 assert_eq!(
2616 parse_version_range("-none").unwrap(),
2617 VersionRange::NoVersion
2618 );
2619 }
2620
2621 #[test]
2624 fn version_range_set_major_bound() {
2625 let r = parse_version_range("^>= { 2.6, 2.7, 2.8 }").unwrap();
2626 match r {
2627 VersionRange::Or(_, _) => {} _ => panic!("Expected Or range for set notation, got {:?}", r),
2629 }
2630 }
2631
2632 #[test]
2633 fn version_range_set_eq() {
2634 let r = parse_version_range("== { 1.0, 2.0 }").unwrap();
2635 match r {
2636 VersionRange::Or(_, _) => {}
2637 _ => panic!("Expected Or range for set notation, got {:?}", r),
2638 }
2639 }
2640
2641 #[test]
2644 fn version_range_display_any() {
2645 assert_eq!(VersionRange::Any.to_string(), "-any");
2646 }
2647
2648 #[test]
2649 fn version_range_display_none() {
2650 assert_eq!(VersionRange::NoVersion.to_string(), "-none");
2651 }
2652
2653 #[test]
2654 fn derive_benchmark() {
2655 let src = "\
2656cabal-version: 3.0
2657name: my-pkg
2658version: 0.1.0.0
2659
2660benchmark my-bench
2661 type: exitcode-stdio-1.0
2662 main-is: Main.hs
2663 build-depends: base, criterion
2664 hs-source-dirs: bench
2665";
2666 let result = do_parse(src);
2667 let ast = derive_ast(&result.cst);
2668
2669 assert_eq!(ast.benchmarks.len(), 1);
2670 let bm = &ast.benchmarks[0];
2671 assert_eq!(bm.fields.name, Some("my-bench"));
2672 assert_eq!(bm.bench_type, Some("exitcode-stdio-1.0"));
2673 assert_eq!(bm.main_is, Some("Main.hs"));
2674 assert_eq!(bm.fields.build_depends.len(), 2);
2675 }
2676}