use derive_builder::Builder;
use git_checks_core::impl_prelude::*;
use itertools::Itertools;
const CR_LF_ENDING: &str = "\r\n";
const CARRIAGE_RETURN_SYMBOL: &str = "\u{23ce}";
#[derive(Builder, Debug, Default, Clone, Copy)]
#[builder(field(private))]
pub struct CheckWhitespace {}
impl CheckWhitespace {
pub fn builder() -> CheckWhitespaceBuilder {
CheckWhitespaceBuilder::default()
}
fn diff_tree(
ctx: &CheckGitContext,
args: &[&str],
content: &dyn Content,
) -> Result<CheckResult, Box<dyn Error>> {
let mut result = CheckResult::new();
let diff_tree = ctx
.git()
.arg("diff-tree")
.arg("--no-commit-id")
.arg("--root")
.arg("-c")
.arg("--check")
.args(args)
.output()
.map_err(|err| GitError::subcommand("diff-tree", err))?;
if !diff_tree.status.success() {
Self::add_error(&mut result, &diff_tree.stdout, content);
}
Ok(result)
}
fn add_error(result: &mut CheckResult, output: &[u8], content: &dyn Content) {
let output = String::from_utf8_lossy(output);
let crlf_msg = if output.contains(CR_LF_ENDING) {
" including CR/LF line endings"
} else {
""
};
let formatted_output = output
.split('\n')
.dropping_back(1)
.map(|line| format!(" {}\n", line))
.join("")
.replace('\r', CARRIAGE_RETURN_SYMBOL);
result.add_error(format!(
"{}adds bad whitespace{}:\n\n{}",
commit_prefix(content),
crlf_msg,
formatted_output,
));
}
}
impl Check for CheckWhitespace {
fn name(&self) -> &str {
"check-whitespace"
}
fn check(&self, ctx: &CheckGitContext, commit: &Commit) -> Result<CheckResult, Box<dyn Error>> {
Self::diff_tree(ctx, &[commit.sha1.as_str()], commit)
}
}
impl TopicCheck for CheckWhitespace {
fn name(&self) -> &str {
"check-whitespace"
}
fn check(&self, ctx: &CheckGitContext, topic: &Topic) -> Result<CheckResult, Box<dyn Error>> {
Self::diff_tree(ctx, &[topic.base.as_str(), topic.sha1.as_str()], topic)
}
}
#[cfg(feature = "config")]
pub(crate) mod config {
use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
use serde::Deserialize;
#[cfg(test)]
use serde_json::json;
use crate::CheckWhitespace;
#[derive(Deserialize, Debug)]
pub struct CheckWhitespaceConfig {}
impl IntoCheck for CheckWhitespaceConfig {
type Check = CheckWhitespace;
fn into_check(self) -> Self::Check {
CheckWhitespace::default()
}
}
register_checks! {
CheckWhitespaceConfig {
"check_whitespace" => CommitCheckConfig,
"check_whitespace/topic" => TopicCheckConfig,
},
}
#[test]
fn test_check_whitespace_config_empty() {
let json = json!({});
let check: CheckWhitespaceConfig = serde_json::from_value(json).unwrap();
let _ = check.into_check();
}
}
#[cfg(test)]
mod tests {
use git_checks_core::{Check, TopicCheck};
use crate::test::*;
use crate::CheckWhitespace;
const DEFAULT_TOPIC: &str = "829cdf8cb069b8f8a634a034d3f85089271601cf";
const NOCR_TOPIC: &str = "5db0c24d032d972ba5bf50eca99016adbfdd3e87";
const ALL_IGNORED_TOPIC: &str = "3a87e0f3f7430bbb81ebbd8ae8764b7f26384f1c";
const ALL_IGNORED_BLANKET_TOPIC: &str = "92cac7579a26f7d8449512476bd64b3000688fd5";
#[test]
fn test_check_whitespace_builder_default() {
assert!(CheckWhitespace::builder().build().is_ok());
}
#[test]
fn test_check_whitespace_name_commit() {
let check = CheckWhitespace::default();
assert_eq!(Check::name(&check), "check-whitespace");
}
#[test]
fn test_check_whitespace_name_topic() {
let check = CheckWhitespace::default();
assert_eq!(TopicCheck::name(&check), "check-whitespace");
}
#[test]
fn test_check_whitespace_defaults() {
let check = CheckWhitespace::default();
let result = run_check("test_check_whitespace_defaults", DEFAULT_TOPIC, check);
test_result_errors(
result,
&[
"commit 829cdf8cb069b8f8a634a034d3f85089271601cf adds bad whitespace including \
CR/LF line endings:\n\
\n \
crlf-file:1: trailing whitespace.\n \
+This file contains CRLF lines.\u{23ce}\n \
crlf-file:2: trailing whitespace.\n \
+\u{23ce}\n \
crlf-file:3: trailing whitespace.\n \
+line1\u{23ce}\n \
crlf-file:4: trailing whitespace.\n \
+line2\u{23ce}\n \
crlf-mixed-file:3: trailing whitespace.\n \
+crlf\u{23ce}\n \
extra-newlines:2: new blank line at EOF.\n \
mixed-tabs-spaces:3: space before tab in indent.\n \
+ \tmixed indent\n \
trailing-spaces:3: trailing whitespace.\n \
+trailing \n \
trailing-tab:3: trailing whitespace.\n \
+trailing\t\n",
],
);
}
#[test]
fn test_check_whitespace_nocr() {
let check = CheckWhitespace::default();
let result = run_check("test_check_whitespace_nocr", NOCR_TOPIC, check);
test_result_errors(
result,
&[
"commit 5db0c24d032d972ba5bf50eca99016adbfdd3e87 adds bad whitespace:\n\
\n \
extra-newlines:2: new blank line at EOF.\n \
mixed-tabs-spaces:3: space before tab in indent.\n \
+ \tmixed indent\n \
trailing-spaces:3: trailing whitespace.\n \
+trailing \n \
trailing-tab:3: trailing whitespace.\n \
+trailing\t\n",
],
);
}
#[test]
fn test_check_whitespace_all_ignored() {
let check = CheckWhitespace::default();
run_check_ok(
"test_check_whitespace_all_ignored",
ALL_IGNORED_TOPIC,
check,
);
}
#[test]
fn test_check_whitespace_all_ignored_blanket() {
let check = CheckWhitespace::default();
run_check_ok(
"test_check_whitespace_all_ignored_blanket",
ALL_IGNORED_BLANKET_TOPIC,
check,
);
}
#[test]
fn test_check_whitespace_defaults_topic() {
let check = CheckWhitespace::default();
let result = run_topic_check("test_check_whitespace_defaults_topic", DEFAULT_TOPIC, check);
test_result_errors(
result,
&["adds bad whitespace including CR/LF line endings:\n\
\n \
crlf-file:1: trailing whitespace.\n \
+This file contains CRLF lines.\u{23ce}\n \
crlf-file:2: trailing whitespace.\n \
+\u{23ce}\n \
crlf-file:3: trailing whitespace.\n \
+line1\u{23ce}\n \
crlf-file:4: trailing whitespace.\n \
+line2\u{23ce}\n \
crlf-mixed-file:3: trailing whitespace.\n \
+crlf\u{23ce}\n \
extra-newlines:2: new blank line at EOF.\n \
mixed-tabs-spaces:3: space before tab in indent.\n \
+ \tmixed indent\n \
trailing-spaces:3: trailing whitespace.\n \
+trailing \n \
trailing-tab:3: trailing whitespace.\n \
+trailing\t\n"],
);
}
#[test]
fn test_check_whitespace_nocr_topic() {
let check = CheckWhitespace::default();
let result = run_topic_check("test_check_whitespace_nocr_topic", NOCR_TOPIC, check);
test_result_errors(
result,
&["adds bad whitespace:\n\
\n \
extra-newlines:2: new blank line at EOF.\n \
mixed-tabs-spaces:3: space before tab in indent.\n \
+ \tmixed indent\n \
trailing-spaces:3: trailing whitespace.\n \
+trailing \n \
trailing-tab:3: trailing whitespace.\n \
+trailing\t\n"],
);
}
#[test]
fn test_check_whitespace_all_ignored_topic() {
let check = CheckWhitespace::default();
run_topic_check_ok(
"test_check_whitespace_all_ignored_topic",
ALL_IGNORED_TOPIC,
check,
);
}
#[test]
fn test_check_whitespace_all_ignored_blanket_topic() {
let check = CheckWhitespace::default();
run_topic_check_ok(
"test_check_whitespace_all_ignored_blanket_topic",
ALL_IGNORED_BLANKET_TOPIC,
check,
);
}
}