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::diagnostics::{CheckId, DiagCtx, RunningCheck};
8use crate::walk::{filter_dirs, walk};
9
10#[rustfmt::skip]
11const ALLOWED_CAPITALIZED_WORDS: &[&str] = &[
12    // tidy-alphabetical-start
13    "ABI",
14    "ABIs",
15    "ADT",
16    "C",
17    "CGU",
18    "Ferris",
19    "MIR",
20    "OK",
21    "Rust",
22    "VS", // VS Code
23    // tidy-alphabetical-end
24];
25
26fn filter_fluent(path: &Path) -> bool {
27    if let Some(ext) = path.extension() { ext.to_str() != Some("ftl") } else { true }
28}
29
30fn is_allowed_capitalized_word(msg: &str) -> bool {
31    ALLOWED_CAPITALIZED_WORDS.iter().any(|word| {
32        msg.strip_prefix(word)
33            .map(|tail| tail.chars().next().map(|c| c == '-' || c.is_whitespace()).unwrap_or(true))
34            .unwrap_or_default()
35    })
36}
37
38fn check_lowercase(filename: &str, contents: &str, check: &mut RunningCheck) {
39    let (Ok(parse) | Err((parse, _))) = fluent_syntax::parser::parse(contents);
40
41    for entry in &parse.body {
42        if let Entry::Message(msg) = entry
43            && let Message { value: Some(pattern), .. } = msg
44            && let [first_pattern, ..] = &pattern.elements[..]
45            && let PatternElement::TextElement { value } = first_pattern
46            && value.chars().next().is_some_and(char::is_uppercase)
47            && !is_allowed_capitalized_word(value)
48        {
49            check.error(format!(
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, diag_ctx: DiagCtx) {
57    let mut check = diag_ctx.start_check(CheckId::new("fluent_lowercase").path(path));
58    walk(
59        path,
60        |path, is_dir| filter_dirs(path) || (!is_dir && filter_fluent(path)),
61        &mut |ent, contents| {
62            check_lowercase(ent.path().to_str().unwrap(), contents, &mut check);
63        },
64    );
65}