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