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 => {
1056 if cst.node(node_id).parent == Some(cst.root) {
1058 derive_top_level_field(cst, node_id, file);
1059 }
1060 }
1061 CstNodeKind::Section => {
1062 let source = cst.source.as_str();
1064 let is_top_level_section = if let Some(ref kw_span) = node.section_keyword {
1065 let kw = kw_span.slice(source).to_ascii_lowercase();
1066 matches!(
1067 kw.as_str(),
1068 "library"
1069 | "executable"
1070 | "test-suite"
1071 | "benchmark"
1072 | "common"
1073 | "flag"
1074 | "source-repository"
1075 )
1076 } else {
1077 false
1078 };
1079
1080 if is_top_level_section {
1081 derive_section(cst, node_id, file);
1082 let children: Vec<NodeId> = cst.node(node_id).children.clone();
1085 for child_id in children {
1086 let child = cst.node(child_id);
1087 if child.kind == CstNodeKind::Section {
1088 collect_ast_nodes(cst, child_id, file);
1089 }
1090 }
1091 }
1092 }
1093 _ => {}
1094 }
1095}
1096
1097fn field_full_value(cst: &CabalCst, node_id: NodeId) -> String {
1100 let node = cst.node(node_id);
1101 let source = cst.source.as_str();
1102
1103 let mut value = String::new();
1104
1105 if let Some(ref val_span) = node.field_value {
1107 value.push_str(val_span.slice(source).trim());
1108 }
1109
1110 for &child_id in &node.children {
1112 let child = cst.node(child_id);
1113 if child.kind == CstNodeKind::ValueLine {
1114 let line_text = child.content_span.slice(source).trim();
1115 if !line_text.is_empty() {
1116 if !value.is_empty() {
1117 value.push('\n');
1118 }
1119 value.push_str(line_text);
1120 }
1121 }
1122 }
1123
1124 value
1125}
1126
1127fn field_first_line_value(cst: &CabalCst, node_id: NodeId) -> Option<&str> {
1131 let node = cst.node(node_id);
1132 let source = cst.source.as_str();
1133
1134 if let Some(ref val_span) = node.field_value {
1136 let v = val_span.slice(source).trim();
1137 if !v.is_empty() {
1138 return Some(v);
1139 }
1140 }
1141
1142 for &child_id in &node.children {
1144 let child = cst.node(child_id);
1145 if child.kind == CstNodeKind::ValueLine {
1146 let v = child.content_span.slice(source).trim();
1147 if !v.is_empty() {
1148 return Some(v);
1149 }
1150 }
1151 }
1152
1153 None
1154}
1155
1156fn parse_list_field(cst: &CabalCst, node_id: NodeId) -> Vec<&str> {
1160 let node = cst.node(node_id);
1161 let source = cst.source.as_str();
1162 let mut items = Vec::new();
1163
1164 if let Some(ref val_span) = node.field_value {
1166 let text = val_span.slice(source).trim();
1167 for item in split_list_items(text) {
1168 if !item.is_empty() {
1169 items.push(item);
1170 }
1171 }
1172 }
1173
1174 for &child_id in &node.children {
1176 let child = cst.node(child_id);
1177 if child.kind == CstNodeKind::ValueLine {
1178 let text = child.content_span.slice(source).trim();
1179 for item in split_list_items(text) {
1180 if !item.is_empty() {
1181 items.push(item);
1182 }
1183 }
1184 }
1185 }
1186
1187 items
1188}
1189
1190fn split_list_items(text: &str) -> Vec<&str> {
1193 let mut items = Vec::new();
1194 if text.contains(',') {
1195 for part in text.split(',') {
1196 let trimmed = part.trim();
1197 if !trimmed.is_empty() {
1198 items.push(trimmed);
1199 }
1200 }
1201 } else {
1202 for part in text.split_whitespace() {
1204 items.push(part);
1205 }
1206 }
1207 items
1208}
1209
1210fn parse_ghc_options(cst: &CabalCst, node_id: NodeId) -> Vec<&str> {
1212 let node = cst.node(node_id);
1213 let source = cst.source.as_str();
1214 let mut opts = Vec::new();
1215
1216 if let Some(ref val_span) = node.field_value {
1217 for opt in val_span.slice(source).split_whitespace() {
1218 opts.push(opt);
1219 }
1220 }
1221
1222 for &child_id in &node.children {
1223 let child = cst.node(child_id);
1224 if child.kind == CstNodeKind::ValueLine {
1225 for opt in child.content_span.slice(source).split_whitespace() {
1226 opts.push(opt);
1227 }
1228 }
1229 }
1230
1231 opts
1232}
1233
1234fn parse_build_depends<'a>(cst: &'a CabalCst, node_id: NodeId) -> Vec<Dependency<'a>> {
1236 let node = cst.node(node_id);
1237 let source = cst.source.as_str();
1238 let mut deps = Vec::new();
1239
1240 if let Some(ref val_span) = node.field_value {
1242 let text = val_span.slice(source).trim();
1243 deps.extend(parse_dependencies_from_text(text, node_id));
1244 }
1245
1246 for &child_id in &node.children {
1248 let child = cst.node(child_id);
1249 if child.kind == CstNodeKind::ValueLine {
1250 let text = child.content_span.slice(source).trim();
1251 if !text.is_empty() {
1252 deps.extend(parse_dependencies_from_text(text, child_id));
1255 }
1256 }
1257 }
1258
1259 deps
1260}
1261
1262fn derive_top_level_field<'a>(cst: &'a CabalCst, node_id: NodeId, file: &mut CabalFile<'a>) {
1264 let node = cst.node(node_id);
1265 let source = cst.source.as_str();
1266
1267 let raw_name = match node.field_name {
1268 Some(ref span) => span.slice(source),
1269 None => return,
1270 };
1271 let canon = canonicalize_field_name(raw_name);
1272
1273 match canon.as_str() {
1274 "cabal-version" => {
1275 let raw = field_first_line_value(cst, node_id).unwrap_or("");
1276 let version_str = raw.strip_prefix(">=").unwrap_or(raw).trim();
1278 file.cabal_version = Some(CabalVersion {
1279 raw,
1280 version: Version::parse(version_str),
1281 cst_node: node_id,
1282 });
1283 }
1284 "name" => {
1285 file.name = field_first_line_value(cst, node_id);
1286 }
1287 "version" => {
1288 let raw = field_first_line_value(cst, node_id).unwrap_or("");
1289 file.version = Version::parse(raw);
1290 }
1291 "license" => {
1292 file.license = field_first_line_value(cst, node_id);
1293 }
1294 "synopsis" => {
1295 file.synopsis = field_first_line_value(cst, node_id);
1296 }
1297 "description" => {
1298 file.description = field_first_line_value(cst, node_id);
1302 }
1303 "author" => {
1304 file.author = field_first_line_value(cst, node_id);
1305 }
1306 "maintainer" => {
1307 file.maintainer = field_first_line_value(cst, node_id);
1308 }
1309 "homepage" => {
1310 file.homepage = field_first_line_value(cst, node_id);
1311 }
1312 "bug-reports" => {
1313 file.bug_reports = field_first_line_value(cst, node_id);
1314 }
1315 "category" => {
1316 file.category = field_first_line_value(cst, node_id);
1317 }
1318 "build-type" => {
1319 file.build_type = field_first_line_value(cst, node_id);
1320 }
1321 "tested-with" => {
1322 file.tested_with = field_first_line_value(cst, node_id);
1323 }
1324 "extra-source-files" | "extra-doc-files" => {
1325 file.extra_source_files
1326 .extend(parse_list_field(cst, node_id));
1327 }
1328 _ => {
1329 let value = field_full_value(cst, node_id);
1330 file.other_fields.push(Field {
1331 name: canon,
1332 raw_name,
1333 value,
1334 cst_node: node_id,
1335 });
1336 }
1337 }
1338}
1339
1340fn derive_section<'a>(cst: &'a CabalCst, node_id: NodeId, file: &mut CabalFile<'a>) {
1342 let node = cst.node(node_id);
1343 let source = cst.source.as_str();
1344
1345 let keyword = match node.section_keyword {
1346 Some(ref span) => span.slice(source),
1347 None => return,
1348 };
1349 let section_arg = node.section_arg.map(|span| span.slice(source));
1350 let keyword_lower = keyword.to_ascii_lowercase();
1351
1352 match keyword_lower.as_str() {
1353 "library" => {
1354 let lib = derive_library(cst, node_id, section_arg);
1355 if section_arg.is_some() {
1356 file.named_libraries.push(lib);
1357 } else {
1358 file.library = Some(lib);
1359 }
1360 }
1361 "executable" => {
1362 let exe = derive_executable(cst, node_id, section_arg);
1363 file.executables.push(exe);
1364 }
1365 "test-suite" => {
1366 let ts = derive_test_suite(cst, node_id, section_arg);
1367 file.test_suites.push(ts);
1368 }
1369 "benchmark" => {
1370 let bm = derive_benchmark(cst, node_id, section_arg);
1371 file.benchmarks.push(bm);
1372 }
1373 "common" => {
1374 if let Some(name) = section_arg {
1375 let cs = derive_common_stanza(cst, node_id, name);
1376 file.common_stanzas.push(cs);
1377 }
1378 }
1379 "flag" => {
1380 if let Some(name) = section_arg {
1381 let flag = derive_flag(cst, node_id, name);
1382 file.flags.push(flag);
1383 }
1384 }
1385 "source-repository" => {
1386 let sr = derive_source_repository(cst, node_id, section_arg);
1387 file.source_repositories.push(sr);
1388 }
1389 _ => {
1390 }
1392 }
1393}
1394
1395fn empty_component_fields<'a>(name: Option<&'a str>, cst_node: NodeId) -> ComponentFields<'a> {
1397 ComponentFields {
1398 name,
1399 cst_node,
1400 imports: Vec::new(),
1401 build_depends: Vec::new(),
1402 other_modules: Vec::new(),
1403 hs_source_dirs: Vec::new(),
1404 default_language: None,
1405 default_extensions: Vec::new(),
1406 ghc_options: Vec::new(),
1407 other_fields: Vec::new(),
1408 conditionals: Vec::new(),
1409 }
1410}
1411
1412fn populate_component_fields<'a>(
1414 cst: &'a CabalCst,
1415 section_id: NodeId,
1416 fields: &mut ComponentFields<'a>,
1417) {
1418 let section = cst.node(section_id);
1419 let source = cst.source.as_str();
1420
1421 for &child_id in §ion.children {
1422 let child = cst.node(child_id);
1423 match child.kind {
1424 CstNodeKind::Field => {
1425 let raw_name = match child.field_name {
1426 Some(ref span) => span.slice(source),
1427 None => continue,
1428 };
1429 let canon = canonicalize_field_name(raw_name);
1430
1431 match canon.as_str() {
1432 "build-depends" => {
1433 fields
1434 .build_depends
1435 .extend(parse_build_depends(cst, child_id));
1436 }
1437 "exposed-modules" => {
1438 }
1441 "other-modules" => {
1442 fields.other_modules.extend(parse_list_field(cst, child_id));
1443 }
1444 "hs-source-dirs" => {
1445 fields
1446 .hs_source_dirs
1447 .extend(parse_list_field(cst, child_id));
1448 }
1449 "default-language" => {
1450 fields.default_language = field_first_line_value(cst, child_id);
1451 }
1452 "default-extensions" | "extensions" => {
1453 fields
1454 .default_extensions
1455 .extend(parse_list_field(cst, child_id));
1456 }
1457 "ghc-options" => {
1458 fields.ghc_options.extend(parse_ghc_options(cst, child_id));
1459 }
1460 _ => {
1461 let value = field_full_value(cst, child_id);
1462 fields.other_fields.push(Field {
1463 name: canon,
1464 raw_name,
1465 value,
1466 cst_node: child_id,
1467 });
1468 }
1469 }
1470 }
1471 CstNodeKind::Import => {
1472 if let Some(ref val_span) = child.field_value {
1473 let val = val_span.slice(source).trim();
1474 if !val.is_empty() {
1475 for item in val.split(',') {
1477 let item = item.trim();
1478 if !item.is_empty() {
1479 fields.imports.push(item);
1480 }
1481 }
1482 }
1483 }
1484 }
1485 CstNodeKind::Conditional => {
1486 let cond = derive_conditional(cst, child_id);
1487 fields.conditionals.push(cond);
1488 }
1489 _ => {}
1491 }
1492 }
1493}
1494
1495fn derive_conditional<'a>(cst: &'a CabalCst, node_id: NodeId) -> Conditional<'a> {
1497 let node = cst.node(node_id);
1498 let source = cst.source.as_str();
1499
1500 let condition = match node.condition_expr {
1502 Some(ref span) => parse_condition(span.slice(source)),
1503 None => Condition::Raw(""),
1504 };
1505
1506 let mut cond = Conditional {
1507 condition,
1508 then_fields: Vec::new(),
1509 then_deps: Vec::new(),
1510 else_fields: Vec::new(),
1511 else_deps: Vec::new(),
1512 then_conditionals: Vec::new(),
1513 else_conditionals: Vec::new(),
1514 cst_node: node_id,
1515 };
1516
1517 for &child_id in &node.children {
1519 let child = cst.node(child_id);
1520 match child.kind {
1521 CstNodeKind::Field => {
1522 let raw_name = match child.field_name {
1523 Some(ref span) => span.slice(source),
1524 None => continue,
1525 };
1526 let canon = canonicalize_field_name(raw_name);
1527
1528 if canon == "build-depends" {
1529 cond.then_deps.extend(parse_build_depends(cst, child_id));
1530 } else {
1531 let value = field_full_value(cst, child_id);
1532 cond.then_fields.push(Field {
1533 name: canon,
1534 raw_name,
1535 value,
1536 cst_node: child_id,
1537 });
1538 }
1539 }
1540 CstNodeKind::Conditional => {
1541 cond.then_conditionals
1542 .push(derive_conditional(cst, child_id));
1543 }
1544 CstNodeKind::ElseBlock => {
1545 for &else_child_id in &child.children {
1547 let else_child = cst.node(else_child_id);
1548 match else_child.kind {
1549 CstNodeKind::Field => {
1550 let raw_name = match else_child.field_name {
1551 Some(ref span) => span.slice(source),
1552 None => continue,
1553 };
1554 let canon = canonicalize_field_name(raw_name);
1555
1556 if canon == "build-depends" {
1557 cond.else_deps
1558 .extend(parse_build_depends(cst, else_child_id));
1559 } else {
1560 let value = field_full_value(cst, else_child_id);
1561 cond.else_fields.push(Field {
1562 name: canon,
1563 raw_name,
1564 value,
1565 cst_node: else_child_id,
1566 });
1567 }
1568 }
1569 CstNodeKind::Conditional => {
1570 cond.else_conditionals
1571 .push(derive_conditional(cst, else_child_id));
1572 }
1573 _ => {}
1574 }
1575 }
1576 }
1577 _ => {}
1578 }
1579 }
1580
1581 cond
1582}
1583
1584fn derive_library<'a>(cst: &'a CabalCst, node_id: NodeId, name: Option<&'a str>) -> Library<'a> {
1586 let mut fields = empty_component_fields(name, node_id);
1587 populate_component_fields(cst, node_id, &mut fields);
1588
1589 let exposed_modules = extract_exposed_modules(cst, node_id);
1592
1593 Library {
1594 fields,
1595 exposed_modules,
1596 }
1597}
1598
1599fn extract_exposed_modules(cst: &CabalCst, section_id: NodeId) -> Vec<&str> {
1601 let section = cst.node(section_id);
1602 let source = cst.source.as_str();
1603 let mut modules = Vec::new();
1604
1605 for &child_id in §ion.children {
1606 let child = cst.node(child_id);
1607 if child.kind == CstNodeKind::Field {
1608 if let Some(ref name_span) = child.field_name {
1609 let canon = canonicalize_field_name(name_span.slice(source));
1610 if canon == "exposed-modules" {
1611 modules.extend(parse_list_field(cst, child_id));
1612 }
1613 }
1614 }
1615 }
1616
1617 modules
1618}
1619
1620fn derive_executable<'a>(
1622 cst: &'a CabalCst,
1623 node_id: NodeId,
1624 name: Option<&'a str>,
1625) -> Executable<'a> {
1626 let main_is = find_field_value_in_section(cst, node_id, "main-is");
1627
1628 let mut fields = empty_component_fields(name, node_id);
1629 populate_component_fields(cst, node_id, &mut fields);
1630 remove_field_by_name(&mut fields.other_fields, "main-is");
1631
1632 Executable { fields, main_is }
1633}
1634
1635fn derive_test_suite<'a>(
1637 cst: &'a CabalCst,
1638 node_id: NodeId,
1639 name: Option<&'a str>,
1640) -> TestSuite<'a> {
1641 let test_type = find_field_value_in_section(cst, node_id, "type");
1642 let main_is = find_field_value_in_section(cst, node_id, "main-is");
1643
1644 let mut fields = empty_component_fields(name, node_id);
1645 populate_component_fields(cst, node_id, &mut fields);
1646 remove_field_by_name(&mut fields.other_fields, "type");
1647 remove_field_by_name(&mut fields.other_fields, "main-is");
1648
1649 TestSuite {
1650 fields,
1651 test_type,
1652 main_is,
1653 }
1654}
1655
1656fn derive_benchmark<'a>(
1658 cst: &'a CabalCst,
1659 node_id: NodeId,
1660 name: Option<&'a str>,
1661) -> Benchmark<'a> {
1662 let bench_type = find_field_value_in_section(cst, node_id, "type");
1663 let main_is = find_field_value_in_section(cst, node_id, "main-is");
1664
1665 let mut fields = empty_component_fields(name, node_id);
1666 populate_component_fields(cst, node_id, &mut fields);
1667 remove_field_by_name(&mut fields.other_fields, "type");
1668 remove_field_by_name(&mut fields.other_fields, "main-is");
1669
1670 Benchmark {
1671 fields,
1672 bench_type,
1673 main_is,
1674 }
1675}
1676
1677fn derive_common_stanza<'a>(cst: &'a CabalCst, node_id: NodeId, name: &'a str) -> CommonStanza<'a> {
1679 let mut fields = empty_component_fields(Some(name), node_id);
1680 populate_component_fields(cst, node_id, &mut fields);
1681
1682 CommonStanza { name, fields }
1683}
1684
1685fn derive_flag<'a>(cst: &'a CabalCst, node_id: NodeId, name: &'a str) -> Flag<'a> {
1687 let section = cst.node(node_id);
1688 let source = cst.source.as_str();
1689
1690 let mut description = None;
1691 let mut default = None;
1692 let mut manual = None;
1693 let mut other_fields = Vec::new();
1694
1695 for &child_id in §ion.children {
1696 let child = cst.node(child_id);
1697 if child.kind == CstNodeKind::Field {
1698 let raw_name = match child.field_name {
1699 Some(ref span) => span.slice(source),
1700 None => continue,
1701 };
1702 let canon = canonicalize_field_name(raw_name);
1703
1704 match canon.as_str() {
1705 "description" => {
1706 description = field_first_line_value(cst, child_id);
1707 }
1708 "default" => {
1709 if let Some(val) = field_first_line_value(cst, child_id) {
1710 let lower = val.to_ascii_lowercase();
1711 default = Some(lower == "true");
1712 }
1713 }
1714 "manual" => {
1715 if let Some(val) = field_first_line_value(cst, child_id) {
1716 let lower = val.to_ascii_lowercase();
1717 manual = Some(lower == "true");
1718 }
1719 }
1720 _ => {
1721 let value = field_full_value(cst, child_id);
1722 other_fields.push(Field {
1723 name: canon,
1724 raw_name,
1725 value,
1726 cst_node: child_id,
1727 });
1728 }
1729 }
1730 }
1731 }
1732
1733 Flag {
1734 name,
1735 description,
1736 default,
1737 manual,
1738 other_fields,
1739 cst_node: node_id,
1740 }
1741}
1742
1743fn derive_source_repository<'a>(
1745 cst: &'a CabalCst,
1746 node_id: NodeId,
1747 kind: Option<&'a str>,
1748) -> SourceRepository<'a> {
1749 let section = cst.node(node_id);
1750 let source = cst.source.as_str();
1751
1752 let mut repo_type = None;
1753 let mut location = None;
1754 let mut tag = None;
1755 let mut branch = None;
1756 let mut subdir = None;
1757 let mut other_fields = Vec::new();
1758
1759 for &child_id in §ion.children {
1760 let child = cst.node(child_id);
1761 if child.kind == CstNodeKind::Field {
1762 let raw_name = match child.field_name {
1763 Some(ref span) => span.slice(source),
1764 None => continue,
1765 };
1766 let canon = canonicalize_field_name(raw_name);
1767
1768 match canon.as_str() {
1769 "type" => {
1770 repo_type = field_first_line_value(cst, child_id);
1771 }
1772 "location" => {
1773 location = field_first_line_value(cst, child_id);
1774 }
1775 "tag" => {
1776 tag = field_first_line_value(cst, child_id);
1777 }
1778 "branch" => {
1779 branch = field_first_line_value(cst, child_id);
1780 }
1781 "subdir" => {
1782 subdir = field_first_line_value(cst, child_id);
1783 }
1784 _ => {
1785 let value = field_full_value(cst, child_id);
1786 other_fields.push(Field {
1787 name: canon,
1788 raw_name,
1789 value,
1790 cst_node: child_id,
1791 });
1792 }
1793 }
1794 }
1795 }
1796
1797 SourceRepository {
1798 kind,
1799 repo_type,
1800 location,
1801 tag,
1802 branch,
1803 subdir,
1804 other_fields,
1805 cst_node: node_id,
1806 }
1807}
1808
1809fn remove_field_by_name(fields: &mut Vec<Field<'_>>, canonical_name: &str) {
1811 fields.retain(|f| f.name != canonical_name);
1812}
1813
1814fn find_field_value_in_section<'a>(
1817 cst: &'a CabalCst,
1818 section_id: NodeId,
1819 target_canon: &str,
1820) -> Option<&'a str> {
1821 let section = cst.node(section_id);
1822 let source = cst.source.as_str();
1823
1824 for &child_id in §ion.children {
1825 let child = cst.node(child_id);
1826 if child.kind == CstNodeKind::Field {
1827 if let Some(ref name_span) = child.field_name {
1828 let canon = canonicalize_field_name(name_span.slice(source));
1829 if canon == target_canon {
1830 return field_first_line_value(cst, child_id);
1831 }
1832 }
1833 }
1834 }
1835 None
1836}
1837
1838#[cfg(test)]
1843mod tests {
1844 use super::*;
1845
1846 fn do_parse(source: &str) -> crate::parse::ParseResult {
1848 crate::parse::parse(source)
1849 }
1850
1851 #[test]
1854 fn version_parse_simple() {
1855 let v = Version::parse("0.1.0.0").unwrap();
1856 assert_eq!(v.components, vec![0, 1, 0, 0]);
1857 }
1858
1859 #[test]
1860 fn version_parse_two_components() {
1861 let v = Version::parse("4.14").unwrap();
1862 assert_eq!(v.components, vec![4, 14]);
1863 }
1864
1865 #[test]
1866 fn version_parse_single() {
1867 let v = Version::parse("5").unwrap();
1868 assert_eq!(v.components, vec![5]);
1869 }
1870
1871 #[test]
1872 fn version_parse_empty() {
1873 assert!(Version::parse("").is_none());
1874 }
1875
1876 #[test]
1877 fn version_parse_invalid() {
1878 assert!(Version::parse("abc").is_none());
1879 assert!(Version::parse("1.2.abc").is_none());
1880 }
1881
1882 #[test]
1883 fn version_display() {
1884 let v = Version {
1885 components: vec![1, 2, 3, 0],
1886 };
1887 assert_eq!(v.to_string(), "1.2.3.0");
1888 }
1889
1890 #[test]
1893 fn version_range_gte() {
1894 let vr = parse_version_range(">=4.14").unwrap();
1895 assert_eq!(
1896 vr,
1897 VersionRange::Gte(Version {
1898 components: vec![4, 14]
1899 })
1900 );
1901 }
1902
1903 #[test]
1904 fn version_range_lt() {
1905 let vr = parse_version_range("<5").unwrap();
1906 assert_eq!(
1907 vr,
1908 VersionRange::Lt(Version {
1909 components: vec![5]
1910 })
1911 );
1912 }
1913
1914 #[test]
1915 fn version_range_major_bound() {
1916 let vr = parse_version_range("^>=2.2").unwrap();
1917 assert_eq!(
1918 vr,
1919 VersionRange::MajorBound(Version {
1920 components: vec![2, 2]
1921 })
1922 );
1923 }
1924
1925 #[test]
1926 fn version_range_eq() {
1927 let vr = parse_version_range("==1.0").unwrap();
1928 assert_eq!(
1929 vr,
1930 VersionRange::Eq(Version {
1931 components: vec![1, 0]
1932 })
1933 );
1934 }
1935
1936 #[test]
1937 fn version_range_and() {
1938 let vr = parse_version_range(">=4.14 && <5").unwrap();
1939 assert_eq!(
1940 vr,
1941 VersionRange::And(
1942 Box::new(VersionRange::Gte(Version {
1943 components: vec![4, 14]
1944 })),
1945 Box::new(VersionRange::Lt(Version {
1946 components: vec![5]
1947 })),
1948 )
1949 );
1950 }
1951
1952 #[test]
1953 fn version_range_or() {
1954 let vr = parse_version_range(">=2.0 || ==1.9").unwrap();
1955 assert_eq!(
1956 vr,
1957 VersionRange::Or(
1958 Box::new(VersionRange::Gte(Version {
1959 components: vec![2, 0]
1960 })),
1961 Box::new(VersionRange::Eq(Version {
1962 components: vec![1, 9]
1963 })),
1964 )
1965 );
1966 }
1967
1968 #[test]
1969 fn version_range_complex_and() {
1970 let vr = parse_version_range(">=2.0 && <2.2").unwrap();
1971 assert_eq!(
1972 vr,
1973 VersionRange::And(
1974 Box::new(VersionRange::Gte(Version {
1975 components: vec![2, 0]
1976 })),
1977 Box::new(VersionRange::Lt(Version {
1978 components: vec![2, 2]
1979 })),
1980 )
1981 );
1982 }
1983
1984 #[test]
1985 fn version_range_empty() {
1986 assert!(parse_version_range("").is_none());
1987 }
1988
1989 #[test]
1992 fn canonicalize_mixed_case() {
1993 assert_eq!(canonicalize_field_name("Build-Depends"), "build-depends");
1994 }
1995
1996 #[test]
1997 fn canonicalize_underscore() {
1998 assert_eq!(canonicalize_field_name("build_depends"), "build-depends");
1999 }
2000
2001 #[test]
2002 fn canonicalize_already_canonical() {
2003 assert_eq!(canonicalize_field_name("build-depends"), "build-depends");
2004 }
2005
2006 #[test]
2009 fn parse_dep_no_version() {
2010 let dep = parse_single_dependency("base", NodeId(0)).unwrap();
2011 assert_eq!(dep.package, "base");
2012 assert!(dep.version_range.is_none());
2013 }
2014
2015 #[test]
2016 fn parse_dep_with_version() {
2017 let dep = parse_single_dependency("aeson ^>=2.2", NodeId(0)).unwrap();
2018 assert_eq!(dep.package, "aeson");
2019 assert_eq!(
2020 dep.version_range,
2021 Some(VersionRange::MajorBound(Version {
2022 components: vec![2, 2]
2023 }))
2024 );
2025 }
2026
2027 #[test]
2028 fn parse_dep_with_range() {
2029 let dep = parse_single_dependency("base >=4.14 && <5", NodeId(0)).unwrap();
2030 assert_eq!(dep.package, "base");
2031 assert_eq!(
2032 dep.version_range,
2033 Some(VersionRange::And(
2034 Box::new(VersionRange::Gte(Version {
2035 components: vec![4, 14]
2036 })),
2037 Box::new(VersionRange::Lt(Version {
2038 components: vec![5]
2039 })),
2040 ))
2041 );
2042 }
2043
2044 #[test]
2045 fn parse_deps_comma_separated() {
2046 let deps = parse_dependencies_from_text("base >=4.14, text >=2.0, aeson ^>=2.2", NodeId(0));
2047 assert_eq!(deps.len(), 3);
2048 assert_eq!(deps[0].package, "base");
2049 assert_eq!(deps[1].package, "text");
2050 assert_eq!(deps[2].package, "aeson");
2051 }
2052
2053 #[test]
2054 fn parse_deps_empty() {
2055 let deps = parse_dependencies_from_text("", NodeId(0));
2056 assert!(deps.is_empty());
2057 }
2058
2059 #[test]
2062 fn parse_condition_flag() {
2063 let c = parse_condition("flag(dev)");
2064 assert_eq!(c, Condition::Flag("dev"));
2065 }
2066
2067 #[test]
2068 fn parse_condition_os() {
2069 let c = parse_condition("os(windows)");
2070 assert_eq!(c, Condition::OS("windows"));
2071 }
2072
2073 #[test]
2074 fn parse_condition_arch() {
2075 let c = parse_condition("arch(x86_64)");
2076 assert_eq!(c, Condition::Arch("x86_64"));
2077 }
2078
2079 #[test]
2080 fn parse_condition_impl() {
2081 let c = parse_condition("impl(ghc >= 9.6)");
2082 assert_eq!(
2083 c,
2084 Condition::Impl(
2085 "ghc",
2086 Some(VersionRange::Gte(Version {
2087 components: vec![9, 6]
2088 }))
2089 )
2090 );
2091 }
2092
2093 #[test]
2094 fn parse_condition_not() {
2095 let c = parse_condition("!os(windows)");
2096 assert_eq!(c, Condition::Not(Box::new(Condition::OS("windows"))));
2097 }
2098
2099 #[test]
2100 fn parse_condition_and() {
2101 let c = parse_condition("flag(dev) && !os(windows)");
2102 assert_eq!(
2103 c,
2104 Condition::And(
2105 Box::new(Condition::Flag("dev")),
2106 Box::new(Condition::Not(Box::new(Condition::OS("windows")))),
2107 )
2108 );
2109 }
2110
2111 #[test]
2112 fn parse_condition_or() {
2113 let c = parse_condition("flag(a) || flag(b)");
2114 assert_eq!(
2115 c,
2116 Condition::Or(
2117 Box::new(Condition::Flag("a")),
2118 Box::new(Condition::Flag("b")),
2119 )
2120 );
2121 }
2122
2123 #[test]
2124 fn parse_condition_empty() {
2125 let c = parse_condition("");
2126 assert_eq!(c, Condition::Raw(""));
2127 }
2128
2129 #[test]
2132 fn derive_minimal_file() {
2133 let src = "cabal-version: 3.0\nname: my-pkg\nversion: 0.1.0.0\n";
2134 let result = do_parse(src);
2135 let ast = derive_ast(&result.cst);
2136
2137 assert_eq!(ast.name, Some("my-pkg"));
2138 assert_eq!(
2139 ast.version,
2140 Some(Version {
2141 components: vec![0, 1, 0, 0]
2142 })
2143 );
2144 assert!(ast.cabal_version.is_some());
2145 let cv = ast.cabal_version.as_ref().unwrap();
2146 assert_eq!(cv.raw, "3.0");
2147 assert_eq!(
2148 cv.version,
2149 Some(Version {
2150 components: vec![3, 0]
2151 })
2152 );
2153 }
2154
2155 #[test]
2156 fn derive_with_library() {
2157 let src = "\
2158cabal-version: 3.0
2159name: my-pkg
2160version: 0.1.0.0
2161
2162library
2163 exposed-modules:
2164 Foo
2165 Bar
2166 build-depends:
2167 base >=4.14
2168 default-language: GHC2021
2169";
2170 let result = do_parse(src);
2171 let ast = derive_ast(&result.cst);
2172
2173 assert!(ast.library.is_some());
2174 let lib = ast.library.as_ref().unwrap();
2175 assert_eq!(lib.exposed_modules, vec!["Foo", "Bar"]);
2176 assert_eq!(lib.fields.build_depends.len(), 1);
2177 assert_eq!(lib.fields.build_depends[0].package, "base");
2178 assert_eq!(lib.fields.default_language, Some("GHC2021"));
2179 }
2180
2181 #[test]
2182 fn derive_with_executable() {
2183 let src = "\
2184cabal-version: 3.0
2185name: my-pkg
2186version: 0.1.0.0
2187
2188executable my-exe
2189 main-is: Main.hs
2190 build-depends: base
2191 hs-source-dirs: app
2192";
2193 let result = do_parse(src);
2194 let ast = derive_ast(&result.cst);
2195
2196 assert_eq!(ast.executables.len(), 1);
2197 let exe = &ast.executables[0];
2198 assert_eq!(exe.fields.name, Some("my-exe"));
2199 assert_eq!(exe.main_is, Some("Main.hs"));
2200 assert_eq!(exe.fields.build_depends.len(), 1);
2201 assert_eq!(exe.fields.hs_source_dirs, vec!["app"]);
2202 }
2203
2204 #[test]
2205 fn derive_with_test_suite() {
2206 let src = "\
2207cabal-version: 3.0
2208name: my-pkg
2209version: 0.1.0.0
2210
2211test-suite my-tests
2212 type: exitcode-stdio-1.0
2213 main-is: Main.hs
2214 build-depends: base, tasty
2215";
2216 let result = do_parse(src);
2217 let ast = derive_ast(&result.cst);
2218
2219 assert_eq!(ast.test_suites.len(), 1);
2220 let ts = &ast.test_suites[0];
2221 assert_eq!(ts.fields.name, Some("my-tests"));
2222 assert_eq!(ts.test_type, Some("exitcode-stdio-1.0"));
2223 assert_eq!(ts.main_is, Some("Main.hs"));
2224 assert_eq!(ts.fields.build_depends.len(), 2);
2225 }
2226
2227 #[test]
2228 fn derive_with_common_stanza() {
2229 let src = "\
2230cabal-version: 3.0
2231name: my-pkg
2232version: 0.1.0.0
2233
2234common warnings
2235 ghc-options: -Wall -Wcompat
2236
2237library
2238 import: warnings
2239 exposed-modules: Foo
2240";
2241 let result = do_parse(src);
2242 let ast = derive_ast(&result.cst);
2243
2244 assert_eq!(ast.common_stanzas.len(), 1);
2245 assert_eq!(ast.common_stanzas[0].name, "warnings");
2246 assert_eq!(
2247 ast.common_stanzas[0].fields.ghc_options,
2248 vec!["-Wall", "-Wcompat"]
2249 );
2250
2251 let lib = ast.library.as_ref().unwrap();
2252 assert_eq!(lib.fields.imports, vec!["warnings"]);
2253 }
2254
2255 #[test]
2256 fn derive_with_flag() {
2257 let src = "\
2258cabal-version: 3.0
2259name: my-pkg
2260version: 0.1.0.0
2261
2262flag dev
2263 description: Development mode
2264 default: False
2265 manual: True
2266";
2267 let result = do_parse(src);
2268 let ast = derive_ast(&result.cst);
2269
2270 assert_eq!(ast.flags.len(), 1);
2271 let flag = &ast.flags[0];
2272 assert_eq!(flag.name, "dev");
2273 assert_eq!(flag.description, Some("Development mode"));
2274 assert_eq!(flag.default, Some(false));
2275 assert_eq!(flag.manual, Some(true));
2276 }
2277
2278 #[test]
2279 fn derive_with_source_repository() {
2280 let src = "\
2281cabal-version: 3.0
2282name: my-pkg
2283version: 0.1.0.0
2284
2285source-repository head
2286 type: git
2287 location: https://github.com/example/my-pkg
2288";
2289 let result = do_parse(src);
2290 let ast = derive_ast(&result.cst);
2291
2292 assert_eq!(ast.source_repositories.len(), 1);
2293 let sr = &ast.source_repositories[0];
2294 assert_eq!(sr.kind, Some("head"));
2295 assert_eq!(sr.repo_type, Some("git"));
2296 assert_eq!(sr.location, Some("https://github.com/example/my-pkg"));
2297 }
2298
2299 #[test]
2300 fn derive_conditional() {
2301 let src = "\
2302cabal-version: 3.0
2303name: my-pkg
2304version: 0.1.0.0
2305
2306library
2307 build-depends: base
2308 if flag(dev)
2309 ghc-options: -O0
2310 else
2311 ghc-options: -O2
2312";
2313 let result = do_parse(src);
2314 let ast = derive_ast(&result.cst);
2315
2316 let lib = ast.library.as_ref().unwrap();
2317 assert_eq!(lib.fields.conditionals.len(), 1);
2318 let cond = &lib.fields.conditionals[0];
2319 assert_eq!(cond.condition, Condition::Flag("dev"));
2320 assert_eq!(cond.then_fields.len(), 1);
2321 assert_eq!(cond.then_fields[0].name, "ghc-options");
2322 assert_eq!(cond.then_fields[0].value, "-O0");
2323 assert_eq!(cond.else_fields.len(), 1);
2324 assert_eq!(cond.else_fields[0].name, "ghc-options");
2325 assert_eq!(cond.else_fields[0].value, "-O2");
2326 }
2327
2328 #[test]
2329 fn derive_all_dependencies() {
2330 let src = "\
2331cabal-version: 3.0
2332name: my-pkg
2333version: 0.1.0.0
2334
2335library
2336 build-depends: base, text
2337
2338executable my-exe
2339 build-depends: base, my-pkg
2340";
2341 let result = do_parse(src);
2342 let ast = derive_ast(&result.cst);
2343
2344 let all_deps = ast.all_dependencies();
2345 assert_eq!(all_deps.len(), 4);
2346 let names: Vec<&str> = all_deps.iter().map(|d| d.package).collect();
2347 assert!(names.contains(&"base"));
2348 assert!(names.contains(&"text"));
2349 assert!(names.contains(&"my-pkg"));
2350 }
2351
2352 #[test]
2353 fn derive_all_components() {
2354 let src = "\
2355cabal-version: 3.0
2356name: my-pkg
2357version: 0.1.0.0
2358
2359library
2360 exposed-modules: Foo
2361
2362executable my-exe
2363 main-is: Main.hs
2364
2365test-suite my-tests
2366 type: exitcode-stdio-1.0
2367 main-is: Main.hs
2368
2369benchmark my-bench
2370 type: exitcode-stdio-1.0
2371 main-is: Main.hs
2372";
2373 let result = do_parse(src);
2374 let ast = derive_ast(&result.cst);
2375
2376 let comps = ast.all_components();
2377 assert_eq!(comps.len(), 4);
2378 }
2379
2380 #[test]
2381 fn derive_find_component() {
2382 let src = "\
2383cabal-version: 3.0
2384name: my-pkg
2385version: 0.1.0.0
2386
2387library
2388 exposed-modules: Foo
2389
2390executable my-exe
2391 main-is: Main.hs
2392";
2393 let result = do_parse(src);
2394 let ast = derive_ast(&result.cst);
2395
2396 assert!(ast.find_component("library").is_some());
2397 assert!(ast.find_component("my-exe").is_some());
2398 assert!(ast.find_component("nonexistent").is_none());
2399 }
2400
2401 #[test]
2402 fn derive_cst_node_back_references_valid() {
2403 let src = "\
2404cabal-version: 3.0
2405name: my-pkg
2406version: 0.1.0.0
2407
2408library
2409 build-depends: base >=4.14
2410";
2411 let result = do_parse(src);
2412 let ast = derive_ast(&result.cst);
2413
2414 assert_eq!(ast.cst_root, result.cst.root);
2416
2417 let lib = ast.library.as_ref().unwrap();
2419 let node = result.cst.node(lib.fields.cst_node);
2420 assert_eq!(node.kind, CstNodeKind::Section);
2421
2422 assert!(!lib.fields.build_depends.is_empty());
2424 let dep_node_id = lib.fields.build_depends[0].cst_node;
2425 assert!(dep_node_id.0 < result.cst.node_count());
2426 }
2427
2428 #[test]
2429 fn derive_deps_leading_comma_style() {
2430 let src = "\
2431cabal-version: 3.0
2432name: my-pkg
2433version: 0.1.0.0
2434
2435library
2436 build-depends:
2437 base >=4.14
2438 , text >=2.0
2439 , aeson ^>=2.2
2440";
2441 let result = do_parse(src);
2442 let ast = derive_ast(&result.cst);
2443
2444 let lib = ast.library.as_ref().unwrap();
2445 assert_eq!(lib.fields.build_depends.len(), 3);
2446 assert_eq!(lib.fields.build_depends[0].package, "base");
2447 assert_eq!(lib.fields.build_depends[1].package, "text");
2448 assert_eq!(lib.fields.build_depends[2].package, "aeson");
2449 }
2450
2451 #[test]
2452 fn derive_deps_trailing_comma_style() {
2453 let src = "\
2454cabal-version: 3.0
2455name: my-pkg
2456version: 0.1.0.0
2457
2458library
2459 build-depends:
2460 base >=4.14,
2461 text >=2.0,
2462 aeson ^>=2.2
2463";
2464 let result = do_parse(src);
2465 let ast = derive_ast(&result.cst);
2466
2467 let lib = ast.library.as_ref().unwrap();
2468 assert_eq!(lib.fields.build_depends.len(), 3);
2469 assert_eq!(lib.fields.build_depends[0].package, "base");
2470 assert_eq!(lib.fields.build_depends[1].package, "text");
2471 assert_eq!(lib.fields.build_depends[2].package, "aeson");
2472 }
2473
2474 #[test]
2475 fn derive_deps_single_line() {
2476 let src = "\
2477cabal-version: 3.0
2478name: my-pkg
2479version: 0.1.0.0
2480
2481library
2482 build-depends: base >=4.14, text >=2.0, aeson ^>=2.2
2483";
2484 let result = do_parse(src);
2485 let ast = derive_ast(&result.cst);
2486
2487 let lib = ast.library.as_ref().unwrap();
2488 assert_eq!(lib.fields.build_depends.len(), 3);
2489 }
2490
2491 #[test]
2492 fn derive_default_extensions() {
2493 let src = "\
2494cabal-version: 3.0
2495name: my-pkg
2496version: 0.1.0.0
2497
2498library
2499 default-extensions:
2500 OverloadedStrings
2501 DerivingStrategies
2502";
2503 let result = do_parse(src);
2504 let ast = derive_ast(&result.cst);
2505
2506 let lib = ast.library.as_ref().unwrap();
2507 assert_eq!(
2508 lib.fields.default_extensions,
2509 vec!["OverloadedStrings", "DerivingStrategies"]
2510 );
2511 }
2512
2513 #[test]
2514 fn derive_metadata_fields() {
2515 let src = "\
2516cabal-version: 3.0
2517name: my-pkg
2518version: 0.1.0.0
2519license: MIT
2520synopsis: A test package
2521author: Test Author
2522maintainer: test@example.com
2523homepage: https://example.com
2524bug-reports: https://example.com/issues
2525category: Development
2526build-type: Simple
2527";
2528 let result = do_parse(src);
2529 let ast = derive_ast(&result.cst);
2530
2531 assert_eq!(ast.license, Some("MIT"));
2532 assert_eq!(ast.synopsis, Some("A test package"));
2533 assert_eq!(ast.author, Some("Test Author"));
2534 assert_eq!(ast.maintainer, Some("test@example.com"));
2535 assert_eq!(ast.homepage, Some("https://example.com"));
2536 assert_eq!(ast.bug_reports, Some("https://example.com/issues"));
2537 assert_eq!(ast.category, Some("Development"));
2538 assert_eq!(ast.build_type, Some("Simple"));
2539 }
2540
2541 #[test]
2542 fn derive_conditional_deps() {
2543 let src = "\
2544cabal-version: 3.0
2545name: my-pkg
2546version: 0.1.0.0
2547
2548library
2549 build-depends: base
2550 if os(windows)
2551 build-depends: Win32
2552 else
2553 build-depends: unix
2554";
2555 let result = do_parse(src);
2556 let ast = derive_ast(&result.cst);
2557
2558 let all_deps = ast.all_dependencies();
2559 let names: Vec<&str> = all_deps.iter().map(|d| d.package).collect();
2560 assert!(names.contains(&"base"));
2561 assert!(names.contains(&"Win32"));
2562 assert!(names.contains(&"unix"));
2563 assert_eq!(all_deps.len(), 3);
2564 }
2565
2566 #[test]
2569 fn parse_condition_true() {
2570 assert_eq!(parse_condition("true"), Condition::Lit(true));
2571 }
2572
2573 #[test]
2574 fn parse_condition_false() {
2575 assert_eq!(parse_condition("false"), Condition::Lit(false));
2576 }
2577
2578 #[test]
2579 fn parse_condition_true_case_insensitive() {
2580 assert_eq!(parse_condition("True"), Condition::Lit(true));
2581 assert_eq!(parse_condition("FALSE"), Condition::Lit(false));
2582 }
2583
2584 #[test]
2587 fn version_range_wildcard() {
2588 let r = parse_version_range("==1.2.*").unwrap();
2589 match r {
2590 VersionRange::And(a, b) => {
2591 assert_eq!(
2592 *a,
2593 VersionRange::Gte(Version {
2594 components: vec![1, 2]
2595 })
2596 );
2597 assert_eq!(
2598 *b,
2599 VersionRange::Lt(Version {
2600 components: vec![1, 3]
2601 })
2602 );
2603 }
2604 _ => panic!("Expected And range, got {:?}", r),
2605 }
2606 }
2607
2608 #[test]
2611 fn version_range_any_keyword() {
2612 assert_eq!(parse_version_range("-any").unwrap(), VersionRange::Any);
2613 }
2614
2615 #[test]
2616 fn version_range_none_keyword() {
2617 assert_eq!(
2618 parse_version_range("-none").unwrap(),
2619 VersionRange::NoVersion
2620 );
2621 }
2622
2623 #[test]
2626 fn version_range_set_major_bound() {
2627 let r = parse_version_range("^>= { 2.6, 2.7, 2.8 }").unwrap();
2628 match r {
2629 VersionRange::Or(_, _) => {} _ => panic!("Expected Or range for set notation, got {:?}", r),
2631 }
2632 }
2633
2634 #[test]
2635 fn version_range_set_eq() {
2636 let r = parse_version_range("== { 1.0, 2.0 }").unwrap();
2637 match r {
2638 VersionRange::Or(_, _) => {}
2639 _ => panic!("Expected Or range for set notation, got {:?}", r),
2640 }
2641 }
2642
2643 #[test]
2646 fn version_range_display_any() {
2647 assert_eq!(VersionRange::Any.to_string(), "-any");
2648 }
2649
2650 #[test]
2651 fn version_range_display_none() {
2652 assert_eq!(VersionRange::NoVersion.to_string(), "-none");
2653 }
2654
2655 #[test]
2656 fn derive_benchmark() {
2657 let src = "\
2658cabal-version: 3.0
2659name: my-pkg
2660version: 0.1.0.0
2661
2662benchmark my-bench
2663 type: exitcode-stdio-1.0
2664 main-is: Main.hs
2665 build-depends: base, criterion
2666 hs-source-dirs: bench
2667";
2668 let result = do_parse(src);
2669 let ast = derive_ast(&result.cst);
2670
2671 assert_eq!(ast.benchmarks.len(), 1);
2672 let bm = &ast.benchmarks[0];
2673 assert_eq!(bm.fields.name, Some("my-bench"));
2674 assert_eq!(bm.bench_type, Some("exitcode-stdio-1.0"));
2675 assert_eq!(bm.main_is, Some("Main.hs"));
2676 assert_eq!(bm.fields.build_depends.len(), 2);
2677 }
2678}