git_checks/
check_end_of_line.rs1use derive_builder::Builder;
10use git_checks_core::impl_prelude::*;
11
12#[derive(Builder, Debug, Default, Clone, Copy)]
14pub struct CheckEndOfLine {}
15
16impl CheckEndOfLine {
17 pub fn builder() -> CheckEndOfLineBuilder {
19 Default::default()
20 }
21}
22
23impl ContentCheck for CheckEndOfLine {
24 fn name(&self) -> &str {
25 "check-end-of-line"
26 }
27
28 fn check(
29 &self,
30 _: &CheckGitContext,
31 content: &dyn Content,
32 ) -> Result<CheckResult, Box<dyn Error>> {
33 let mut result = CheckResult::new();
34
35 for diff in content.diffs() {
36 match diff.status {
37 StatusChange::Added | StatusChange::Modified(_) => (),
38 _ => continue,
39 }
40
41 if diff.new_mode == "120000" {
44 continue;
45 }
46
47 let patch = match content.path_diff(&diff.name) {
48 Ok(s) => s,
49 Err(err) => {
50 result.add_alert(
51 format!(
52 "{}failed to get the diff for file `{}`: {}",
53 commit_prefix(content),
54 diff.name,
55 err,
56 ),
57 true,
58 );
59 continue;
60 },
61 };
62
63 let has_missing_newline = patch.lines().last() == Some("\\ No newline at end of file");
64
65 if has_missing_newline {
66 result.add_error(format!(
67 "{}missing newline at the end of file in `{}`.",
68 commit_prefix_str(content, "is not allowed;"),
69 diff.name,
70 ));
71 }
72 }
73
74 Ok(result)
75 }
76}
77
78#[cfg(feature = "config")]
79pub(crate) mod config {
80 use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
81 use serde::Deserialize;
82 #[cfg(test)]
83 use serde_json::json;
84
85 use crate::CheckEndOfLine;
86
87 #[derive(Deserialize, Debug)]
94 pub struct CheckEndOfLineConfig {}
95
96 impl IntoCheck for CheckEndOfLineConfig {
97 type Check = CheckEndOfLine;
98
99 fn into_check(self) -> Self::Check {
100 Default::default()
101 }
102 }
103
104 register_checks! {
105 CheckEndOfLineConfig {
106 "check_end_of_line" => CommitCheckConfig,
107 "check_end_of_line/topic" => TopicCheckConfig,
108 },
109 }
110
111 #[test]
112 fn test_check_end_of_line_config_empty() {
113 let json = json!({});
114 let check: CheckEndOfLineConfig = serde_json::from_value(json).unwrap();
115
116 let _ = check.into_check();
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use git_checks_core::{Check, TopicCheck};
123
124 use crate::test::*;
125 use crate::CheckEndOfLine;
126
127 const BAD_COMMIT: &str = "829cdf8cb069b8f8a634a034d3f85089271601cf";
128 const SYMLINK_COMMIT: &str = "00ffdf352196c16a453970de022a8b4343610ccf";
129 const FIX_COMMIT: &str = "767dd1c173175d85e0f7de23dcd286f5a83617b1";
130 const DELETE_COMMIT: &str = "74828dc2e957f883cc520f0c0fc5a73efc4c0fca";
131
132 #[test]
133 fn test_check_end_of_line_builder_default() {
134 assert!(CheckEndOfLine::builder().build().is_ok());
135 }
136
137 #[test]
138 fn test_check_end_of_line_name_commit() {
139 let check = CheckEndOfLine::default();
140 assert_eq!(Check::name(&check), "check-end-of-line");
141 }
142
143 #[test]
144 fn test_check_end_of_line_name_topic() {
145 let check = CheckEndOfLine::default();
146 assert_eq!(TopicCheck::name(&check), "check-end-of-line");
147 }
148
149 #[test]
150 fn test_check_end_of_line() {
151 let check = CheckEndOfLine::default();
152 let result = run_check("test_check_end_of_line", BAD_COMMIT, check);
153 test_result_errors(result, &[
154 "commit 829cdf8cb069b8f8a634a034d3f85089271601cf is not allowed; missing newline at \
155 the end of file in `missing-newline-eof`.",
156 ]);
157 }
158
159 #[test]
160 fn test_check_end_of_line_topic() {
161 let check = CheckEndOfLine::default();
162 let result = run_topic_check("test_check_end_of_line_topic", BAD_COMMIT, check);
163 test_result_errors(
164 result,
165 &["missing newline at the end of file in `missing-newline-eof`."],
166 );
167 }
168
169 #[test]
170 fn test_check_end_of_line_removal() {
171 let check = CheckEndOfLine::default();
172 let conf = make_check_conf(&check);
173
174 let result = test_check_base(
175 "test_check_end_of_line_removal",
176 FIX_COMMIT,
177 BAD_COMMIT,
178 &conf,
179 );
180 test_result_ok(result);
181 }
182
183 #[test]
184 fn test_check_end_of_line_delete_file() {
185 let check = CheckEndOfLine::default();
186 let conf = make_check_conf(&check);
187
188 let result = test_check_base(
189 "test_check_end_of_line_delete_file",
190 DELETE_COMMIT,
191 BAD_COMMIT,
192 &conf,
193 );
194 test_result_ok(result);
195 }
196
197 #[test]
198 fn test_check_end_of_line_topic_fixed() {
199 let check = CheckEndOfLine::default();
200 run_topic_check_ok("test_check_end_of_line_topic_fixed", FIX_COMMIT, check);
201 }
202
203 #[test]
204 fn test_check_end_of_line_topic_delete_file() {
205 let check = CheckEndOfLine::default();
206 run_topic_check_ok(
207 "test_check_end_of_line_topic_delete_file",
208 DELETE_COMMIT,
209 check,
210 );
211 }
212
213 #[test]
214 fn test_check_end_of_line_ignore_symlinks() {
215 let check = CheckEndOfLine::default();
216 run_check_ok(
217 "test_check_end_of_line_ignore_symlinks",
218 SYMLINK_COMMIT,
219 check,
220 );
221 }
222
223 #[test]
224 fn test_check_end_of_line_ignore_symlinks_topic() {
225 let check = CheckEndOfLine::default();
226 run_topic_check_ok(
227 "test_check_end_of_line_ignore_symlinks_topic",
228 SYMLINK_COMMIT,
229 check,
230 );
231 }
232}