1use alint_core::RuleRegistry;
7
8pub mod case;
9pub mod command;
10pub mod command_idempotent;
11pub mod commented_out_code;
12pub mod cross_file_value_equals;
13pub mod dir_absent;
14pub mod dir_contains;
15pub mod dir_exists;
16pub mod dir_only_contains;
17pub mod every_matching_has;
18pub mod executable_bit;
19pub mod executable_has_shebang;
20mod extract;
21pub mod file_absent;
22pub mod file_content_forbidden;
23pub mod file_content_matches;
24pub mod file_ends_with;
25pub mod file_exists;
26pub mod file_footer;
27pub mod file_hash;
28pub mod file_header;
29pub mod file_is_ascii;
30pub mod file_is_text;
31pub mod file_max_lines;
32pub mod file_max_size;
33pub mod file_min_lines;
34pub mod file_min_size;
35pub mod file_shebang;
36pub mod file_starts_with;
37pub mod filename_case;
38pub mod filename_regex;
39pub mod final_newline;
40pub mod fixers;
41pub mod for_each_dir;
42pub mod for_each_file;
43pub mod generated_file_fresh;
44pub mod git_blame_age;
45pub mod git_commit_message;
46pub mod git_no_denied_paths;
47pub mod import_gate;
48pub mod indent_style;
49pub mod io;
50pub mod json_schema_passes;
51pub mod line_endings;
52pub mod line_max_width;
53pub mod markdown_paths_resolve;
54pub mod max_consecutive_blank_lines;
55pub mod max_directory_depth;
56pub mod max_files_per_directory;
57pub mod no_bidi_controls;
58pub mod no_bom;
59pub mod no_case_conflicts;
60pub mod no_empty_files;
61pub mod no_illegal_windows_names;
62pub mod no_merge_conflict_markers;
63pub mod no_submodules;
64pub mod no_symlinks;
65pub mod no_trailing_whitespace;
66pub mod no_zero_width_chars;
67pub mod ordered_block;
68pub mod pair;
69pub mod pair_hash;
70pub mod registry_paths_resolve;
71pub mod shebang_has_executable;
72mod spawn;
73pub mod structured_path;
74#[cfg(test)]
75mod test_support;
76pub mod unique_by;
77
78pub fn register_builtin(registry: &mut RuleRegistry) {
87 registry.register("file_exists", file_exists::build);
88 registry.register("file_absent", file_absent::build);
89 registry.register("dir_exists", dir_exists::build);
90 registry.register("dir_absent", dir_absent::build);
91
92 registry.register("file_content_matches", file_content_matches::build);
93 registry.register("content_matches", file_content_matches::build);
94 registry.register("file_content_forbidden", file_content_forbidden::build);
95 registry.register("content_forbidden", file_content_forbidden::build);
96 registry.register("file_header", file_header::build);
97 registry.register("header", file_header::build);
98 registry.register("file_max_size", file_max_size::build);
99 registry.register("max_size", file_max_size::build);
100 registry.register("file_min_size", file_min_size::build);
101 registry.register("min_size", file_min_size::build);
102 registry.register("file_min_lines", file_min_lines::build);
103 registry.register("min_lines", file_min_lines::build);
104 registry.register("file_max_lines", file_max_lines::build);
105 registry.register("max_lines", file_max_lines::build);
106 registry.register("file_footer", file_footer::build);
107 registry.register("footer", file_footer::build);
108 registry.register("file_shebang", file_shebang::build);
109 registry.register("shebang", file_shebang::build);
110
111 registry.register("json_path_equals", structured_path::json_path_equals_build);
114 registry.register(
115 "json_path_matches",
116 structured_path::json_path_matches_build,
117 );
118 registry.register("yaml_path_equals", structured_path::yaml_path_equals_build);
119 registry.register(
120 "yaml_path_matches",
121 structured_path::yaml_path_matches_build,
122 );
123 registry.register("toml_path_equals", structured_path::toml_path_equals_build);
124 registry.register(
125 "toml_path_matches",
126 structured_path::toml_path_matches_build,
127 );
128 registry.register("xml_path_equals", structured_path::xml_path_equals_build);
129 registry.register("xml_path_matches", structured_path::xml_path_matches_build);
130 registry.register("json_schema_passes", json_schema_passes::build);
131 registry.register("markdown_paths_resolve", markdown_paths_resolve::build);
132 registry.register("commented_out_code", commented_out_code::build);
133 registry.register("git_no_denied_paths", git_no_denied_paths::build);
134 registry.register("git_commit_message", git_commit_message::build);
135 registry.register("git_blame_age", git_blame_age::build);
136 registry.register("file_is_text", file_is_text::build);
137 registry.register("is_text", file_is_text::build);
138
139 registry.register("filename_case", filename_case::build);
140 registry.register("filename_regex", filename_regex::build);
141 registry.register("pair", pair::build);
142 registry.register("pair_hash", pair_hash::build);
143 registry.register("for_each_dir", for_each_dir::build);
144 registry.register("for_each_file", for_each_file::build);
145 registry.register("dir_only_contains", dir_only_contains::build);
146 registry.register("unique_by", unique_by::build);
147 registry.register("dir_contains", dir_contains::build);
148 registry.register("every_matching_has", every_matching_has::build);
149 registry.register("registry_paths_resolve", registry_paths_resolve::build);
150 registry.register("cross_file_value_equals", cross_file_value_equals::build);
151 registry.register("ordered_block", ordered_block::build);
152 registry.register("generated_file_fresh", generated_file_fresh::build);
153 registry.register("import_gate", import_gate::build);
154 registry.register("command_idempotent", command_idempotent::build);
155
156 registry.register("no_trailing_whitespace", no_trailing_whitespace::build);
158 registry.register("final_newline", final_newline::build);
159 registry.register("line_endings", line_endings::build);
160 registry.register("line_max_width", line_max_width::build);
161
162 registry.register(
164 "no_merge_conflict_markers",
165 no_merge_conflict_markers::build,
166 );
167 registry.register("no_bidi_controls", no_bidi_controls::build);
168 registry.register("no_zero_width_chars", no_zero_width_chars::build);
169
170 registry.register("file_is_ascii", file_is_ascii::build);
172 registry.register("no_bom", no_bom::build);
173 registry.register("file_hash", file_hash::build);
174
175 registry.register("max_directory_depth", max_directory_depth::build);
177 registry.register("max_files_per_directory", max_files_per_directory::build);
178 registry.register("no_empty_files", no_empty_files::build);
179
180 registry.register("no_case_conflicts", no_case_conflicts::build);
182 registry.register("no_illegal_windows_names", no_illegal_windows_names::build);
183
184 registry.register("no_symlinks", no_symlinks::build);
186 registry.register("executable_bit", executable_bit::build);
187 registry.register("executable_has_shebang", executable_has_shebang::build);
188 registry.register("shebang_has_executable", shebang_has_executable::build);
189 registry.register("no_submodules", no_submodules::build);
190
191 registry.register("indent_style", indent_style::build);
193 registry.register(
194 "max_consecutive_blank_lines",
195 max_consecutive_blank_lines::build,
196 );
197 registry.register("file_starts_with", file_starts_with::build);
198 registry.register("file_ends_with", file_ends_with::build);
199
200 registry.register("command", command::build);
204}
205
206pub fn builtin_registry() -> RuleRegistry {
209 let mut r = RuleRegistry::new();
210 register_builtin(&mut r);
211 r
212}
213
214#[cfg(test)]
215mod registry_tests {
216 use super::*;
217
218 #[test]
219 fn every_documented_kind_is_registered() {
220 let r = builtin_registry();
221 let known: Vec<&str> = r.known_kinds().collect();
222 for kind in [
223 "file_exists",
225 "file_absent",
226 "dir_exists",
227 "dir_absent",
228 "file_content_matches",
230 "content_matches",
231 "file_content_forbidden",
232 "content_forbidden",
233 "file_header",
234 "header",
235 "file_max_size",
236 "max_size",
237 "file_min_size",
238 "min_size",
239 "file_min_lines",
240 "min_lines",
241 "file_max_lines",
242 "max_lines",
243 "file_footer",
244 "footer",
245 "file_shebang",
246 "shebang",
247 "json_path_equals",
249 "json_path_matches",
250 "yaml_path_equals",
251 "yaml_path_matches",
252 "toml_path_equals",
253 "toml_path_matches",
254 "xml_path_equals",
255 "xml_path_matches",
256 "json_schema_passes",
257 "git_no_denied_paths",
258 "git_commit_message",
259 "git_blame_age",
260 "file_is_text",
261 "is_text",
262 "filename_case",
264 "filename_regex",
265 "pair",
266 "pair_hash",
267 "for_each_dir",
268 "for_each_file",
269 "dir_only_contains",
270 "unique_by",
271 "dir_contains",
272 "every_matching_has",
273 "registry_paths_resolve",
274 "cross_file_value_equals",
275 "ordered_block",
276 "generated_file_fresh",
277 "import_gate",
278 "command_idempotent",
279 "no_trailing_whitespace",
281 "final_newline",
282 "line_endings",
283 "line_max_width",
284 "no_merge_conflict_markers",
286 "no_bidi_controls",
287 "no_zero_width_chars",
288 "file_is_ascii",
290 "no_bom",
291 "file_hash",
292 "max_directory_depth",
294 "max_files_per_directory",
295 "no_empty_files",
296 "no_case_conflicts",
298 "no_illegal_windows_names",
299 "no_symlinks",
301 "executable_bit",
302 "executable_has_shebang",
303 "shebang_has_executable",
304 "no_submodules",
305 "indent_style",
307 "max_consecutive_blank_lines",
308 "file_starts_with",
309 "file_ends_with",
310 "command",
312 ] {
313 assert!(
314 known.contains(&kind),
315 "{kind} missing from builtin registry"
316 );
317 }
318 }
319
320 #[test]
327 fn v010_cross_file_kinds_require_full_index_and_no_path_scope() {
328 use crate::test_support::spec_yaml;
329 use alint_core::Rule;
330
331 let cases: &[(&str, &str)] = &[
332 (
333 "registry_paths_resolve",
334 "id: t\nkind: registry_paths_resolve\nsource: Cargo.toml\n\
335 extract:\n toml: \"$.x\"\nlevel: error\n",
336 ),
337 (
338 "cross_file_value_equals",
339 "id: t\nkind: cross_file_value_equals\nsource:\n file: a.toml\n \
340 extract:\n toml: \"$.x\"\ntargets:\n files: \"b/*.toml\"\n \
341 extract:\n toml: \"$.y\"\nlevel: error\n",
342 ),
343 (
344 "generated_file_fresh",
345 "id: t\nkind: generated_file_fresh\nfile: x\ncommand: [\"true\"]\n\
346 level: error\n",
347 ),
348 (
349 "command_idempotent",
350 "id: t\nkind: command_idempotent\ncommand: [\"true\"]\nlevel: error\n",
351 ),
352 (
353 "pair_hash",
354 "id: t\nkind: pair_hash\nsource: a\ntarget: b\nlevel: error\n",
355 ),
356 ];
357
358 for (kind, yaml) in cases {
359 let spec = spec_yaml(yaml);
360 let built: alint_core::Result<Box<dyn Rule>> = match *kind {
361 "registry_paths_resolve" => crate::registry_paths_resolve::build(&spec),
362 "cross_file_value_equals" => crate::cross_file_value_equals::build(&spec),
363 "generated_file_fresh" => crate::generated_file_fresh::build(&spec),
364 "command_idempotent" => crate::command_idempotent::build(&spec),
365 "pair_hash" => crate::pair_hash::build(&spec),
366 _ => unreachable!(),
367 };
368 let rule = built.unwrap_or_else(|e| panic!("{kind} build failed: {e}"));
369 assert!(
370 rule.requires_full_index(),
371 "{kind}: requires_full_index() must be true (cross-file; \
372 must not be --changed-filtered)"
373 );
374 assert!(
375 rule.path_scope().is_none(),
376 "{kind}: must declare no path_scope (cross-file dispatch)"
377 );
378 }
379 }
380}