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 ctx: &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 diff_attr = ctx.check_attr("diff", diff.name.as_path())?;
48 if let AttributeState::Unset = diff_attr {
49 continue;
51 }
52
53 let text_attr = ctx.check_attr("text", diff.name.as_path())?;
54 if let AttributeState::Unset = text_attr {
55 continue;
57 }
58
59 let patch = match content.path_diff(&diff.name) {
60 Ok(s) => s,
61 Err(err) => {
62 result.add_alert(
63 format!(
64 "{}failed to get the diff for file `{}`: {err}",
65 commit_prefix(content),
66 diff.name,
67 ),
68 true,
69 );
70 continue;
71 },
72 };
73
74 let has_missing_newline = patch.lines().last() == Some("\\ No newline at end of file");
75
76 if has_missing_newline {
77 result.add_error(format!(
78 "{}missing newline at the end of file in `{}`.",
79 commit_prefix_str(content, "is not allowed;"),
80 diff.name,
81 ));
82 }
83 }
84
85 Ok(result)
86 }
87}
88
89#[cfg(feature = "config")]
90pub(crate) mod config {
91 use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
92 use serde::Deserialize;
93 #[cfg(test)]
94 use serde_json::json;
95
96 use crate::CheckEndOfLine;
97
98 #[derive(Deserialize, Debug)]
105 pub struct CheckEndOfLineConfig {}
106
107 impl IntoCheck for CheckEndOfLineConfig {
108 type Check = CheckEndOfLine;
109
110 fn into_check(self) -> Self::Check {
111 Default::default()
112 }
113 }
114
115 register_checks! {
116 CheckEndOfLineConfig {
117 "check_end_of_line" => CommitCheckConfig,
118 "check_end_of_line/topic" => TopicCheckConfig,
119 },
120 }
121
122 #[test]
123 fn test_check_end_of_line_config_empty() {
124 let json = json!({});
125 let check: CheckEndOfLineConfig = serde_json::from_value(json).unwrap();
126
127 let _ = check.into_check();
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use git_checks_core::{Check, TopicCheck};
134
135 use crate::test::*;
136 use crate::CheckEndOfLine;
137
138 const BAD_COMMIT: &str = "829cdf8cb069b8f8a634a034d3f85089271601cf";
139 const SYMLINK_COMMIT: &str = "00ffdf352196c16a453970de022a8b4343610ccf";
140 const FIX_COMMIT: &str = "767dd1c173175d85e0f7de23dcd286f5a83617b1";
141 const DELETE_COMMIT: &str = "74828dc2e957f883cc520f0c0fc5a73efc4c0fca";
142 const NO_DIFF_COMMIT: &str = "665ee8fbdd0e260e0416f1c3b199aa2e34a1ab92";
143 const NO_TEXT_COMMIT: &str = "86e09d587be3d1d760a67d16eead0ccce716b5a1";
144 const BINARY_COMMIT: &str = "83cd07b3ca3f3e616ed69fbe01f3cc5731467823";
145
146 #[test]
147 fn test_check_end_of_line_builder_default() {
148 assert!(CheckEndOfLine::builder().build().is_ok());
149 }
150
151 #[test]
152 fn test_check_end_of_line_name_commit() {
153 let check = CheckEndOfLine::default();
154 assert_eq!(Check::name(&check), "check-end-of-line");
155 }
156
157 #[test]
158 fn test_check_end_of_line_name_topic() {
159 let check = CheckEndOfLine::default();
160 assert_eq!(TopicCheck::name(&check), "check-end-of-line");
161 }
162
163 #[test]
164 fn test_check_end_of_line() {
165 let check = CheckEndOfLine::default();
166 let result = run_check("test_check_end_of_line", BAD_COMMIT, check);
167 test_result_errors(result, &[
168 "commit 829cdf8cb069b8f8a634a034d3f85089271601cf is not allowed; missing newline at \
169 the end of file in `missing-newline-eof`.",
170 ]);
171 }
172
173 #[test]
174 fn test_check_end_of_line_topic() {
175 let check = CheckEndOfLine::default();
176 let result = run_topic_check("test_check_end_of_line_topic", BAD_COMMIT, check);
177 test_result_errors(
178 result,
179 &["missing newline at the end of file in `missing-newline-eof`."],
180 );
181 }
182
183 #[test]
184 fn test_check_end_of_line_removal() {
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_removal",
190 FIX_COMMIT,
191 BAD_COMMIT,
192 &conf,
193 );
194 test_result_ok(result);
195 }
196
197 #[test]
198 fn test_check_end_of_line_delete_file() {
199 let check = CheckEndOfLine::default();
200 let conf = make_check_conf(&check);
201
202 let result = test_check_base(
203 "test_check_end_of_line_delete_file",
204 DELETE_COMMIT,
205 BAD_COMMIT,
206 &conf,
207 );
208 test_result_ok(result);
209 }
210
211 #[test]
212 fn test_check_end_of_line_topic_fixed() {
213 let check = CheckEndOfLine::default();
214 run_topic_check_ok("test_check_end_of_line_topic_fixed", FIX_COMMIT, check);
215 }
216
217 #[test]
218 fn test_check_end_of_line_topic_delete_file() {
219 let check = CheckEndOfLine::default();
220 run_topic_check_ok(
221 "test_check_end_of_line_topic_delete_file",
222 DELETE_COMMIT,
223 check,
224 );
225 }
226
227 #[test]
228 fn test_check_end_of_line_ignore_symlinks() {
229 let check = CheckEndOfLine::default();
230 run_check_ok(
231 "test_check_end_of_line_ignore_symlinks",
232 SYMLINK_COMMIT,
233 check,
234 );
235 }
236
237 #[test]
238 fn test_check_end_of_line_ignore_symlinks_topic() {
239 let check = CheckEndOfLine::default();
240 run_topic_check_ok(
241 "test_check_end_of_line_ignore_symlinks_topic",
242 SYMLINK_COMMIT,
243 check,
244 );
245 }
246
247 #[test]
248 fn test_check_end_of_line_ignore_via_diff() {
249 let check = CheckEndOfLine::default();
250 run_check_ok(
251 "test_check_end_of_line_ignore_via_diff",
252 NO_DIFF_COMMIT,
253 check,
254 );
255 }
256
257 #[test]
258 fn test_check_end_of_line_ignore_via_diff_topic() {
259 let check = CheckEndOfLine::default();
260 run_topic_check_ok(
261 "test_check_end_of_line_ignore_via_diff_topic",
262 NO_DIFF_COMMIT,
263 check,
264 );
265 }
266
267 #[test]
268 fn test_check_end_of_line_ignore_via_text() {
269 let check = CheckEndOfLine::default();
270 run_check_ok(
271 "test_check_end_of_line_ignore_via_text",
272 NO_TEXT_COMMIT,
273 check,
274 );
275 }
276
277 #[test]
278 fn test_check_end_of_line_ignore_via_text_topic() {
279 let check = CheckEndOfLine::default();
280 run_topic_check_ok(
281 "test_check_end_of_line_ignore_via_text_topic",
282 NO_TEXT_COMMIT,
283 check,
284 );
285 }
286
287 #[test]
288 fn test_check_end_of_line_ignore_via_binary() {
289 let check = CheckEndOfLine::default();
290 run_check_ok(
291 "test_check_end_of_line_ignore_via_binary",
292 BINARY_COMMIT,
293 check,
294 );
295 }
296
297 #[test]
298 fn test_check_end_of_line_ignore_via_binary_topic() {
299 let check = CheckEndOfLine::default();
300 run_topic_check_ok(
301 "test_check_end_of_line_ignore_via_binary_topic",
302 BINARY_COMMIT,
303 check,
304 );
305 }
306}