1use 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 "ABI",
14 "ABIs",
15 "ADT",
16 "C",
17 "CGU",
18 "Ferris",
19 "MIR",
20 "OK",
21 "Rust",
22 "VS", ];
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}