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