tidy/
fluent_lowercase.rs

1//! Checks that the error messages start with a lowercased letter (except when allowed to).
2
3use std::path::Path;
4
5use fluent_syntax::ast::{Entry, Message, PatternElement};
6
7use crate::walk::{filter_dirs, walk};
8
9#[rustfmt::skip]
10const ALLOWED_CAPITALIZED_WORDS: &[&str] = &[
11    // tidy-alphabetical-start
12    "ABI",
13    "ABIs",
14    "ADT",
15    "C",
16    "CGU",
17    "Ferris",
18    "MIR",
19    "OK",
20    "Rust",
21    "VS", // VS Code
22    // tidy-alphabetical-end
23];
24
25fn filter_fluent(path: &Path) -> bool {
26    if let Some(ext) = path.extension() { ext.to_str() != Some("ftl") } else { true }
27}
28
29fn is_allowed_capitalized_word(msg: &str) -> bool {
30    ALLOWED_CAPITALIZED_WORDS.iter().any(|word| {
31        msg.strip_prefix(word)
32            .map(|tail| tail.chars().next().map(|c| c == '-' || c.is_whitespace()).unwrap_or(true))
33            .unwrap_or_default()
34    })
35}
36
37fn check_lowercase(filename: &str, contents: &str, bad: &mut bool) {
38    let (Ok(parse) | Err((parse, _))) = fluent_syntax::parser::parse(contents);
39
40    for entry in &parse.body {
41        if let Entry::Message(msg) = entry
42            && let Message { value: Some(pattern), .. } = msg
43            && let [first_pattern, ..] = &pattern.elements[..]
44            && let PatternElement::TextElement { value } = first_pattern
45            && value.chars().next().is_some_and(char::is_uppercase)
46            && !is_allowed_capitalized_word(value)
47        {
48            tidy_error!(
49                bad,
50                "{filename}: message `{value}` starts with an uppercase letter. Fix it or add it to `ALLOWED_CAPITALIZED_WORDS`"
51            );
52        }
53    }
54}
55
56pub fn check(path: &Path, bad: &mut bool) {
57    walk(
58        path,
59        |path, is_dir| filter_dirs(path) || (!is_dir && filter_fluent(path)),
60        &mut |ent, contents| {
61            check_lowercase(ent.path().to_str().unwrap(), contents, bad);
62        },
63    );
64}