Skip to main content

layer_conform_core/
matcher.rs

1//! Pure rule × file matching.
2
3use std::path::Path;
4
5use crate::rule::Rule;
6
7/// Returns the subset of `rules` that match `path`.
8///
9/// A rule matches when its `applyTo` glob accepts the path and its `ignore`
10/// glob does not. Disabled rules are skipped.
11pub fn matching_rules<'r>(path: &Path, rules: &'r [Rule]) -> Vec<&'r Rule> {
12    rules.iter().filter(|r| r.matches(path)).collect()
13}
14
15#[cfg(test)]
16mod tests {
17    use super::*;
18    use globset::{Glob, GlobSet, GlobSetBuilder};
19
20    fn glob_set(patterns: &[&str]) -> GlobSet {
21        let mut b = GlobSetBuilder::new();
22        for p in patterns {
23            b.add(Glob::new(p).expect("valid glob"));
24        }
25        b.build().expect("build glob set")
26    }
27
28    fn rule(id: &str, apply: &[&str], ignore: &[&str], disabled: bool) -> Rule {
29        Rule {
30            id: id.into(),
31            goldens: vec![],
32            apply_to: glob_set(apply),
33            ignore: glob_set(ignore),
34            threshold: None,
35            disabled,
36        }
37    }
38
39    #[test]
40    fn returns_rule_when_apply_to_matches() {
41        let rules = vec![rule("repos", &["src/repos/**/*.ts"], &[], false)];
42        let m = matching_rules(Path::new("src/repos/user.ts"), &rules);
43        assert_eq!(m.len(), 1);
44        assert_eq!(m[0].id, "repos");
45    }
46
47    #[test]
48    fn empty_when_no_apply_to_matches() {
49        let rules = vec![rule("repos", &["src/repos/**/*.ts"], &[], false)];
50        let m = matching_rules(Path::new("src/components/Button.tsx"), &rules);
51        assert!(m.is_empty());
52    }
53
54    #[test]
55    fn ignore_overrides_apply_to() {
56        let rules = vec![rule(
57            "repos",
58            &["src/repos/**/*.ts"],
59            &["src/repos/legacy/**"],
60            false,
61        )];
62        let m = matching_rules(Path::new("src/repos/legacy/old.ts"), &rules);
63        assert!(m.is_empty(), "ignore should suppress");
64        let m = matching_rules(Path::new("src/repos/user.ts"), &rules);
65        assert_eq!(m.len(), 1);
66    }
67
68    #[test]
69    fn multiple_rules_can_match_same_path() {
70        let rules = vec![
71            rule("ts", &["**/*.ts"], &[], false),
72            rule("repos", &["src/repos/**/*.ts"], &[], false),
73        ];
74        let m = matching_rules(Path::new("src/repos/user.ts"), &rules);
75        assert_eq!(m.len(), 2);
76    }
77
78    #[test]
79    fn disabled_rule_never_matches() {
80        let rules = vec![rule("repos", &["**/*.ts"], &[], true)];
81        let m = matching_rules(Path::new("src/foo.ts"), &rules);
82        assert!(m.is_empty());
83    }
84
85    #[test]
86    fn multiple_apply_to_patterns_are_or() {
87        let rules = vec![rule(
88            "many",
89            &["src/a/**/*.ts", "src/b/**/*.ts"],
90            &[],
91            false,
92        )];
93        assert_eq!(matching_rules(Path::new("src/a/x.ts"), &rules).len(), 1);
94        assert_eq!(matching_rules(Path::new("src/b/y.ts"), &rules).len(), 1);
95        assert!(matching_rules(Path::new("src/c/z.ts"), &rules).is_empty());
96    }
97}