use crate::expr::Expr;
use crate::expr::FirstMatchOf;
use crate::expr::FixedPhrase;
use crate::linting::expr_linter::Chunk;
use crate::{
Token, TokenStringExt,
linting::{ExprLinter, Lint, LintKind, Suggestion},
};
pub struct APart {
expr: FirstMatchOf,
}
impl Default for APart {
fn default() -> Self {
let pattern = FirstMatchOf::new(vec![
Box::new(FixedPhrase::from_phrase("a part from")),
Box::new(FixedPhrase::from_phrase("apart of")),
Box::new(FixedPhrase::from_phrase("fall a part")),
Box::new(FixedPhrase::from_phrase("far a part")),
]);
Self { expr: pattern }
}
}
impl ExprLinter for APart {
type Unit = Chunk;
fn expr(&self) -> &dyn Expr {
&self.expr
}
fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
let span = matched_tokens.span()?;
let text: String = span.get_content(source).iter().collect();
let text_lower = text.to_lowercase();
let (suggestions, message) = match text_lower.as_str() {
"a part from" => (
vec![
Suggestion::ReplaceWith("apart from".chars().collect()),
Suggestion::ReplaceWith("a part of".chars().collect()),
],
"If you mean 'except for', use 'apart from'. If you mean 'a piece belonging to', use 'a part of'. Keep it this way if referring to the origin of a piece.",
),
"apart of" => (
vec![
Suggestion::ReplaceWith("a part of".chars().collect()),
Suggestion::ReplaceWith("apart from".chars().collect()),
],
"Did you mean 'a part of' (a piece belonging to) or 'apart from' (except for)?",
),
"far a part" => (
vec![Suggestion::ReplaceWith("far apart".chars().collect())],
"If you mean 'separated by a distance', use 'far apart'. If referring to the distance of a piece, keep it this way.",
),
"fall a part" => (
vec![Suggestion::ReplaceWith("fall apart".chars().collect())],
"'Fall apart' meaning 'collapse into pieces' or 'stop functioning' is written as two words.",
),
_ => return None,
};
Some(Lint {
span,
lint_kind: LintKind::WordChoice,
suggestions,
message: message.to_owned(),
priority: 50,
})
}
fn description(&self) -> &'static str {
"Finds and corrects common mistakes between 'a part' and 'apart'"
}
}
#[cfg(test)]
mod tests {
use super::APart;
use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
#[test]
fn allow_normal_use_of_a_part() {
assert_lint_count(
"That's not the whole truth, it's just a part.",
APart::default(),
0,
);
}
#[test]
fn allow_normal_use_of_apart() {
assert_lint_count("You shouldn't have taken it apart.", APart::default(), 0);
}
#[test]
fn allow_normal_use_of_a_part_of() {
assert_lint_count("The elbow is a part of the arm.", APart::default(), 0);
}
#[test]
fn allow_normal_use_of_apart_from() {
assert_lint_count("Apart from one error, the code works.", APart::default(), 0);
}
#[test]
fn allow_normal_us_of_fall_apart() {
assert_lint_count("The roof fell apart.", APart::default(), 0);
}
#[test]
fn allow_normal_use_of_far_apart() {
assert_lint_count("Okinawa and Hokkaido are far apart.", APart::default(), 0);
}
#[test]
fn corrects_a_part_from_to_apart_from_format() {
assert_suggestion_result(
"Is it correct that the output file seems to be the same of the input file a part from the format (input: jpg, output: png)?",
APart::default(),
"Is it correct that the output file seems to be the same of the input file apart from the format (input: jpg, output: png)?",
);
}
#[test]
fn corrects_a_part_from_to_apart_from_english() {
assert_suggestion_result(
"Do you know there are more languages out there a part from English right?",
APart::default(),
"Do you know there are more languages out there apart from English right?",
)
}
#[test]
fn corrects_a_part_from_to_a_part_of() {
assert_suggestion_result(
"An easy tool to generate backdoor with msfvenom (a part from metasploit framework).",
APart::default(),
"An easy tool to generate backdoor with msfvenom (a part of metasploit framework).",
)
}
#[test]
fn corrects_apart_of_to_apart_from_cflinuxfs() {
assert_suggestion_result(
"Doesn't work with any stacks apart of cflinuxfs2 and cflinuxfs3",
APart::default(),
"Doesn't work with any stacks apart from cflinuxfs2 and cflinuxfs3",
)
}
#[test]
fn corrects_apart_of_to_apart_from_using() {
assert_suggestion_result(
"apart of using filter, i can't find it in the documentation",
APart::default(),
"apart from using filter, i can't find it in the documentation",
)
}
#[test]
fn corrects_apart_of_to_a_part_of_openai() {
assert_suggestion_result(
"export 'Usage' class as apart of openai.types",
APart::default(),
"export 'Usage' class as a part of openai.types",
)
}
#[test]
fn corrects_apart_of_to_a_part_of_formly() {
assert_suggestion_result(
"FormlyDatepickerTypeComponent is not listed as apart of the Formly Public API",
APart::default(),
"FormlyDatepickerTypeComponent is not listed as a part of the Formly Public API",
)
}
#[test]
fn corrects_far_a_part() {
assert_suggestion_result(
"That leaves you only the other hand on the keyboard and you don't want the keys to be that far a part.",
APart::default(),
"That leaves you only the other hand on the keyboard and you don't want the keys to be that far apart.",
)
}
#[test]
fn corrects_so_far_a_part_from_being_taken() {
assert_suggestion_result(
"I can't see in the code what is done really with this session_timeout so far a part from being taken from the conf if defined there or setup ...",
APart::default(),
"I can't see in the code what is done really with this session_timeout so far apart from being taken from the conf if defined there or setup ...",
)
}
#[test]
fn corrects_so_far_a_part_from_version_upgrade() {
assert_suggestion_result(
"Any workaround so far a part from the version upgrade?",
APart::default(),
"Any workaround so far apart from the version upgrade?",
)
}
#[test]
fn corrects_fall_a_part() {
assert_suggestion_result(
"When I set up a script I set up card priority based on my frontline but sometimes a servant dies which sometimes causes things to fall a part.",
APart::default(),
"When I set up a script I set up card priority based on my frontline but sometimes a servant dies which sometimes causes things to fall apart.",
)
}
}