Skip to main content

cratestack_parser/
directive_targets.rs

1//! A lenient scan for the files a `.cstack` source points at.
2//!
3//! Tooling (the LSP's rename guard) must learn "who imports me" from files that
4//! may be half-typed or invalid, where a full parse would fail and say nothing.
5//! The line grammar lives in `parse::directives`; this only walks lines and
6//! reuses it, so there is one definition of what a directive line is.
7
8use crate::line_helpers::collect_lines_at;
9use crate::parse::{directive_keyword, parse_path_directive};
10
11/// Which directive a target was written in.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum DirectiveKind {
14    /// `part "…"`.
15    Part,
16    /// `part of "…"`.
17    PartOf,
18    /// `import "…"`.
19    Import,
20}
21
22/// Every well-formed `part`, `part of` and `import` target in `source`, in
23/// order, paths as written. Malformed directive lines are skipped, and nothing
24/// else in the file is looked at, so this never fails.
25pub fn directive_targets(source: &str) -> Vec<(DirectiveKind, String)> {
26    collect_lines_at(source, 0)
27        .iter()
28        .filter_map(|line| {
29            let keyword = directive_keyword(line.trimmed)?;
30            let directive = parse_path_directive(line, keyword).ok()?;
31            let kind = match keyword {
32                "part of" => DirectiveKind::PartOf,
33                "part" => DirectiveKind::Part,
34                _ => DirectiveKind::Import,
35            };
36            Some((kind, directive.path))
37        })
38        .collect()
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn collects_targets_in_order_and_ignores_comments_and_malformed_lines() {
47        let source = "// import \"no.cstack\"\npart of \"main.cstack\"\n  import \"a/b.cstack\"\n\
48                      part \"p.cstack\"\nimport nothing\npartial \"x.cstack\"\nmodel M {\n}\n";
49        assert_eq!(
50            directive_targets(source),
51            vec![
52                (DirectiveKind::PartOf, "main.cstack".to_owned()),
53                (DirectiveKind::Import, "a/b.cstack".to_owned()),
54                (DirectiveKind::Part, "p.cstack".to_owned()),
55            ]
56        );
57    }
58}