use crate::linting::{LintKind, Linter, Suggestion};
use crate::{Document, Lint, Number, TokenStringExt};
#[derive(Default, Clone, Copy)]
pub struct SpelledNumbers;
impl Linter for SpelledNumbers {
fn lint(&mut self, document: &Document) -> Vec<crate::Lint> {
let mut lints = Vec::new();
for number_tok in document.iter_numbers() {
let Number {
value,
suffix: None,
..
} = number_tok.kind.as_number().unwrap()
else {
continue;
};
let value: f64 = (*value).into();
if (value - value.floor()).abs() < f64::EPSILON && value < 10. {
lints.push(Lint {
span: number_tok.span,
lint_kind: LintKind::Readability,
suggestions: vec![Suggestion::ReplaceWith(
spell_out_number(value as u64).unwrap().chars().collect(),
)],
message: "Try to spell out numbers less than ten.".to_owned(),
priority: 63,
})
}
}
lints
}
fn description(&self) -> &'static str {
"Most style guides recommend that you spell out numbers less than ten."
}
}
fn spell_out_number(num: u64) -> Option<String> {
if num > 999 {
return None;
}
match num {
hundred if hundred % 100 == 0 && hundred > 0 => Some(format!(
"{} hundred",
spell_out_number(hundred / 100).unwrap()
)),
num if num > 100 || (num > 20 && num % 10 != 0) => {
let n = 10u64.pow((num as f32).log10() as u32);
let parent = (num / n) * n; let child = num % n;
Some(format!(
"{}{}{}",
spell_out_number(parent).unwrap(),
if num <= 99 { '-' } else { ' ' },
spell_out_number(child).unwrap()
))
}
base_num => Some(
match base_num {
0 => "zero",
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
6 => "six",
7 => "seven",
8 => "eight",
9 => "nine",
10 => "ten",
11 => "eleven",
12 => "twelve",
13 => "thirteen",
14 => "fourteen",
15 => "fifteen",
16 => "sixteen",
17 => "seventeen",
18 => "eighteen",
19 => "nineteen",
20 => "twenty",
30 => "thirty",
40 => "forty",
50 => "fifty",
60 => "sixty",
70 => "seventy",
80 => "eighty",
90 => "ninety",
_ => return None,
}
.to_owned(),
),
}
}
#[cfg(test)]
mod tests {
use crate::linting::tests::assert_suggestion_result;
use super::{SpelledNumbers, spell_out_number};
#[test]
fn produces_zero() {
assert_eq!(spell_out_number(0), Some("zero".to_owned()))
}
#[test]
fn produces_eighty_two() {
assert_eq!(spell_out_number(82), Some("eighty-two".to_owned()))
}
#[test]
fn produces_nine_hundred_ninety_nine() {
assert_eq!(
spell_out_number(999),
Some("nine hundred ninety-nine".to_owned())
)
}
#[test]
fn corrects_nine() {
assert_suggestion_result("There are 9 pigs.", SpelledNumbers, "There are nine pigs.");
}
#[test]
fn does_not_correct_ten() {
assert_suggestion_result("There are 10 pigs.", SpelledNumbers, "There are 10 pigs.");
}
#[test]
fn services_range() {
for i in 0..1000 {
spell_out_number(i).unwrap();
}
}
}