harper_core/linting/
hyphenate_number_day.rs

1use crate::{
2    Token,
3    patterns::{EitherPattern, NominalPhrase, Pattern, SequencePattern},
4};
5
6use super::{Lint, LintKind, PatternLinter, Suggestion};
7
8pub struct HyphenateNumberDay {
9    pattern: Box<dyn Pattern>,
10}
11
12impl Default for HyphenateNumberDay {
13    fn default() -> Self {
14        let pattern = SequencePattern::default()
15            .then_number()
16            .then_whitespace()
17            .t_aco("day")
18            .then(EitherPattern::new(vec![
19                Box::new(
20                    SequencePattern::default()
21                        .then_whitespace()
22                        .then(NominalPhrase),
23                ),
24                Box::new(
25                    SequencePattern::default()
26                        .then_hyphen()
27                        .then_adjective()
28                        .then_whitespace()
29                        .then(NominalPhrase),
30                ),
31            ]));
32
33        Self {
34            pattern: Box::new(pattern),
35        }
36    }
37}
38
39impl PatternLinter for HyphenateNumberDay {
40    fn pattern(&self) -> &dyn Pattern {
41        self.pattern.as_ref()
42    }
43
44    fn match_to_lint(&self, matched_tokens: &[Token], _source: &[char]) -> Option<Lint> {
45        let number = matched_tokens[0].kind.as_number()?;
46        let space = &matched_tokens[1];
47
48        Some(Lint {
49            span: space.span,
50            lint_kind: LintKind::Miscellaneous,
51            suggestions: vec![Suggestion::ReplaceWith(vec!['-'])],
52            message: format!(
53                "Use a hyphen in `{}-day` when forming an adjectival compound.",
54                number
55            ),
56            priority: 31,
57        })
58    }
59
60    fn description(&self) -> &'static str {
61        "Ensures a hyphen is used in `X-day` when it is part of a compound adjective, such as `4-day work week`."
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::HyphenateNumberDay;
68    use crate::linting::tests::assert_suggestion_result;
69
70    #[test]
71    fn corrects_three_day_training() {
72        assert_suggestion_result(
73            "The company offers a 3 day training program.",
74            HyphenateNumberDay::default(),
75            "The company offers a 3-day training program.",
76        );
77    }
78
79    #[test]
80    fn corrects_five_day_challenge() {
81        assert_suggestion_result(
82            "Join the 5 day challenge to improve your skills.",
83            HyphenateNumberDay::default(),
84            "Join the 5-day challenge to improve your skills.",
85        );
86    }
87
88    #[test]
89    fn corrects_seven_day_plan() {
90        assert_suggestion_result(
91            "She followed a strict 7 day meal plan.",
92            HyphenateNumberDay::default(),
93            "She followed a strict 7-day meal plan.",
94        );
95    }
96
97    #[test]
98    fn does_not_correct_when_not_adjective() {
99        assert_suggestion_result(
100            "The seminar lasts for 2 days.",
101            HyphenateNumberDay::default(),
102            "The seminar lasts for 2 days.",
103        );
104    }
105
106    #[test]
107    fn corrects_varied_phrases() {
108        assert_suggestion_result(
109            "They implemented a new 6 day work schedule.",
110            HyphenateNumberDay::default(),
111            "They implemented a new 6-day work schedule.",
112        );
113
114        assert_suggestion_result(
115            "Enroll in our 10 day fitness bootcamp!",
116            HyphenateNumberDay::default(),
117            "Enroll in our 10-day fitness bootcamp!",
118        );
119    }
120
121    #[test]
122    fn edge_case_day_long() {
123        assert_suggestion_result(
124            "The 4 day-long seminar was insightful.",
125            HyphenateNumberDay::default(),
126            "The 4-day-long seminar was insightful.",
127        );
128    }
129
130    #[test]
131    fn edge_case_plural_days() {
132        assert_suggestion_result(
133            "The trip was a fun 5 day experience.",
134            HyphenateNumberDay::default(),
135            "The trip was a fun 5-day experience.",
136        );
137    }
138
139    #[test]
140    fn ignores_spelled_out_numbers() {
141        assert_suggestion_result(
142            "We had a three day holiday.",
143            HyphenateNumberDay::default(),
144            "We had a three day holiday.",
145        );
146    }
147}