harper_core/linting/
quote_spacing.rs1use crate::expr::{ExprExt, SequenceExpr};
2use crate::linting::LintKind;
3use crate::{Document, TokenStringExt};
4
5use super::{Lint, Linter};
6
7pub struct QuoteSpacing {
8 expr: SequenceExpr,
9}
10
11impl QuoteSpacing {
12 pub fn new() -> Self {
13 Self {
14 expr: SequenceExpr::default()
15 .then_any_word()
16 .then_quote()
17 .then_any_word(),
18 }
19 }
20}
21
22impl Default for QuoteSpacing {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl Linter for QuoteSpacing {
29 fn lint(&mut self, document: &Document) -> Vec<Lint> {
30 let mut lints = Vec::new();
31
32 for m in self.expr.iter_matches_in_doc(document) {
33 let matched_tokens = m.get_content(document.get_tokens());
34
35 let Some(span) = matched_tokens.span() else {
36 continue;
37 };
38
39 lints.push(Lint {
40 span,
41 lint_kind: LintKind::Formatting,
42 suggestions: vec![],
43 message: "A quote must be preceded or succeeded by a space.".to_owned(),
44 priority: 31,
45 })
46 }
47
48 lints
49 }
50
51 fn description(&self) -> &str {
52 "Checks that quotation marks are preceded or succeeded by whitespace."
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::QuoteSpacing;
59 use crate::linting::tests::{assert_lint_count, assert_no_lints};
60
61 #[test]
62 fn flags_missing_space_before_quote() {
63 assert_lint_count("He said\"hello\" to me.", QuoteSpacing::default(), 1);
64 }
65
66 #[test]
67 fn flags_missing_space_after_quote() {
68 assert_lint_count(
69 "She whispered \"hurry\"and left.",
70 QuoteSpacing::default(),
71 1,
72 );
73 }
74
75 #[test]
76 fn allows_quotes_with_spacing() {
77 assert_no_lints("They shouted \"charge\" together.", QuoteSpacing::default());
78 }
79
80 #[test]
81 fn allows_quotes_at_end_of_sentence() {
82 assert_no_lints("They shouted \"charge.\"", QuoteSpacing::default());
83 }
84}