harper_core/linting/
hyphenate_number_day.rs1use 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}