1use std::path::Path;
4
5use fluent_syntax::ast::{Entry, PatternElement};
6
7use crate::walk::{filter_dirs, walk};
8
9fn filter_fluent(path: &Path) -> bool {
10 if let Some(ext) = path.extension() { ext.to_str() != Some("ftl") } else { true }
11}
12
13const ALLOWLIST: &[&str] = &[
17 "const_eval_long_running",
18 "const_eval_validation_failure_note",
19 "driver_impl_ice",
20 "incremental_corrupt_file",
21];
22
23fn check_period(filename: &str, contents: &str, bad: &mut bool) {
24 if filename.contains("codegen") {
25 return;
27 }
28
29 let (Ok(parse) | Err((parse, _))) = fluent_syntax::parser::parse(contents);
30 for entry in &parse.body {
31 if let Entry::Message(m) = entry {
32 if ALLOWLIST.contains(&m.id.name) {
33 continue;
34 }
35
36 if let Some(pat) = &m.value
37 && let Some(PatternElement::TextElement { value }) = pat.elements.last()
38 {
39 if value.ends_with(".") && !value.ends_with("...") {
41 let ll = find_line(contents, value);
42 let name = m.id.name;
43 tidy_error!(bad, "{filename}:{ll}: message `{name}` ends in a period");
44 }
45 }
46
47 for attr in &m.attributes {
48 if attr.id.name == "teach_note" {
50 continue;
51 }
52
53 if let Some(PatternElement::TextElement { value }) = attr.value.elements.last()
54 && value.ends_with(".")
55 && !value.ends_with("...")
56 {
57 let ll = find_line(contents, value);
58 let name = attr.id.name;
59 tidy_error!(bad, "{filename}:{ll}: attr `{name}` ends in a period");
60 }
61 }
62 }
63 }
64}
65
66fn find_line(haystack: &str, needle: &str) -> usize {
68 for (ll, line) in haystack.lines().enumerate() {
69 if line.as_ptr() > needle.as_ptr() {
70 return ll;
71 }
72 }
73
74 1
75}
76
77pub fn check(path: &Path, bad: &mut bool) {
78 walk(
79 path,
80 |path, is_dir| filter_dirs(path) || (!is_dir && filter_fluent(path)),
81 &mut |ent, contents| {
82 check_period(ent.path().to_str().unwrap(), contents, bad);
83 },
84 );
85}