Skip to main content

alint_rules/
lib.rs

1//! Built-in rule implementations for alint.
2//!
3//! Rules are registered into an [`alint_core::RuleRegistry`] via
4//! [`register_builtin`]. Each kind has its own submodule.
5
6use alint_core::RuleRegistry;
7
8pub mod case;
9pub mod command;
10pub mod commented_out_code;
11pub mod dir_absent;
12pub mod dir_contains;
13pub mod dir_exists;
14pub mod dir_only_contains;
15pub mod every_matching_has;
16pub mod executable_bit;
17pub mod executable_has_shebang;
18pub mod file_absent;
19pub mod file_content_forbidden;
20pub mod file_content_matches;
21pub mod file_ends_with;
22pub mod file_exists;
23pub mod file_footer;
24pub mod file_hash;
25pub mod file_header;
26pub mod file_is_ascii;
27pub mod file_is_text;
28pub mod file_max_lines;
29pub mod file_max_size;
30pub mod file_min_lines;
31pub mod file_min_size;
32pub mod file_shebang;
33pub mod file_starts_with;
34pub mod filename_case;
35pub mod filename_regex;
36pub mod final_newline;
37pub mod fixers;
38pub mod for_each_dir;
39pub mod for_each_file;
40pub mod git_blame_age;
41pub mod git_commit_message;
42pub mod git_no_denied_paths;
43pub mod indent_style;
44pub mod io;
45pub mod json_schema_passes;
46pub mod line_endings;
47pub mod line_max_width;
48pub mod markdown_paths_resolve;
49pub mod max_consecutive_blank_lines;
50pub mod max_directory_depth;
51pub mod max_files_per_directory;
52pub mod no_bidi_controls;
53pub mod no_bom;
54pub mod no_case_conflicts;
55pub mod no_empty_files;
56pub mod no_illegal_windows_names;
57pub mod no_merge_conflict_markers;
58pub mod no_submodules;
59pub mod no_symlinks;
60pub mod no_trailing_whitespace;
61pub mod no_zero_width_chars;
62pub mod pair;
63pub mod shebang_has_executable;
64pub mod structured_path;
65pub mod unique_by;
66
67/// Register every built-in rule kind into the given registry.
68///
69/// Naming convention: rules that have a `dir_*` sibling keep
70/// their `file_*` prefix (`file_exists` vs `dir_exists`); rules
71/// with no such parallel also register a short alias without the
72/// prefix — `content_matches`, `content_forbidden`, `header`,
73/// `is_text`, `max_size`. Both forms resolve to the same
74/// builder; new rules land under short names only.
75pub fn register_builtin(registry: &mut RuleRegistry) {
76    registry.register("file_exists", file_exists::build);
77    registry.register("file_absent", file_absent::build);
78    registry.register("dir_exists", dir_exists::build);
79    registry.register("dir_absent", dir_absent::build);
80
81    registry.register("file_content_matches", file_content_matches::build);
82    registry.register("content_matches", file_content_matches::build);
83    registry.register("file_content_forbidden", file_content_forbidden::build);
84    registry.register("content_forbidden", file_content_forbidden::build);
85    registry.register("file_header", file_header::build);
86    registry.register("header", file_header::build);
87    registry.register("file_max_size", file_max_size::build);
88    registry.register("max_size", file_max_size::build);
89    registry.register("file_min_size", file_min_size::build);
90    registry.register("min_size", file_min_size::build);
91    registry.register("file_min_lines", file_min_lines::build);
92    registry.register("min_lines", file_min_lines::build);
93    registry.register("file_max_lines", file_max_lines::build);
94    registry.register("max_lines", file_max_lines::build);
95    registry.register("file_footer", file_footer::build);
96    registry.register("footer", file_footer::build);
97    registry.register("file_shebang", file_shebang::build);
98    registry.register("shebang", file_shebang::build);
99
100    // Structured-query family — JSONPath queries over
101    // JSON / YAML / TOML documents.
102    registry.register("json_path_equals", structured_path::json_path_equals_build);
103    registry.register(
104        "json_path_matches",
105        structured_path::json_path_matches_build,
106    );
107    registry.register("yaml_path_equals", structured_path::yaml_path_equals_build);
108    registry.register(
109        "yaml_path_matches",
110        structured_path::yaml_path_matches_build,
111    );
112    registry.register("toml_path_equals", structured_path::toml_path_equals_build);
113    registry.register(
114        "toml_path_matches",
115        structured_path::toml_path_matches_build,
116    );
117    registry.register("json_schema_passes", json_schema_passes::build);
118    registry.register("markdown_paths_resolve", markdown_paths_resolve::build);
119    registry.register("commented_out_code", commented_out_code::build);
120    registry.register("git_no_denied_paths", git_no_denied_paths::build);
121    registry.register("git_commit_message", git_commit_message::build);
122    registry.register("git_blame_age", git_blame_age::build);
123    registry.register("file_is_text", file_is_text::build);
124    registry.register("is_text", file_is_text::build);
125
126    registry.register("filename_case", filename_case::build);
127    registry.register("filename_regex", filename_regex::build);
128    registry.register("pair", pair::build);
129    registry.register("for_each_dir", for_each_dir::build);
130    registry.register("for_each_file", for_each_file::build);
131    registry.register("dir_only_contains", dir_only_contains::build);
132    registry.register("unique_by", unique_by::build);
133    registry.register("dir_contains", dir_contains::build);
134    registry.register("every_matching_has", every_matching_has::build);
135
136    // Text-hygiene family (short names — no `file_` prefix).
137    registry.register("no_trailing_whitespace", no_trailing_whitespace::build);
138    registry.register("final_newline", final_newline::build);
139    registry.register("line_endings", line_endings::build);
140    registry.register("line_max_width", line_max_width::build);
141
142    // Security / Unicode sanity.
143    registry.register(
144        "no_merge_conflict_markers",
145        no_merge_conflict_markers::build,
146    );
147    registry.register("no_bidi_controls", no_bidi_controls::build);
148    registry.register("no_zero_width_chars", no_zero_width_chars::build);
149
150    // Encoding + content fingerprint.
151    registry.register("file_is_ascii", file_is_ascii::build);
152    registry.register("no_bom", no_bom::build);
153    registry.register("file_hash", file_hash::build);
154
155    // Structure / layout.
156    registry.register("max_directory_depth", max_directory_depth::build);
157    registry.register("max_files_per_directory", max_files_per_directory::build);
158    registry.register("no_empty_files", no_empty_files::build);
159
160    // Cross-platform / portable metadata.
161    registry.register("no_case_conflicts", no_case_conflicts::build);
162    registry.register("no_illegal_windows_names", no_illegal_windows_names::build);
163
164    // Unix metadata + git.
165    registry.register("no_symlinks", no_symlinks::build);
166    registry.register("executable_bit", executable_bit::build);
167    registry.register("executable_has_shebang", executable_has_shebang::build);
168    registry.register("shebang_has_executable", shebang_has_executable::build);
169    registry.register("no_submodules", no_submodules::build);
170
171    // Hygiene + byte fingerprint.
172    registry.register("indent_style", indent_style::build);
173    registry.register(
174        "max_consecutive_blank_lines",
175        max_consecutive_blank_lines::build,
176    );
177    registry.register("file_starts_with", file_starts_with::build);
178    registry.register("file_ends_with", file_ends_with::build);
179
180    // Plugin tier 1 — shell out to an external CLI per matched
181    // file. Trust-gated at config-load: only the user's own
182    // top-level config can declare these.
183    registry.register("command", command::build);
184}
185
186/// Convenience constructor that returns a fresh registry pre-populated with
187/// every built-in rule.
188pub fn builtin_registry() -> RuleRegistry {
189    let mut r = RuleRegistry::new();
190    register_builtin(&mut r);
191    r
192}
193
194#[cfg(test)]
195mod registry_tests {
196    use super::*;
197
198    #[test]
199    fn every_documented_kind_is_registered() {
200        let r = builtin_registry();
201        let known: Vec<&str> = r.known_kinds().collect();
202        for kind in [
203            // Prefixed kinds (parallel with dir_*).
204            "file_exists",
205            "file_absent",
206            "dir_exists",
207            "dir_absent",
208            // Prefixed + short alias pairs.
209            "file_content_matches",
210            "content_matches",
211            "file_content_forbidden",
212            "content_forbidden",
213            "file_header",
214            "header",
215            "file_max_size",
216            "max_size",
217            "file_min_size",
218            "min_size",
219            "file_min_lines",
220            "min_lines",
221            "file_max_lines",
222            "max_lines",
223            "file_footer",
224            "footer",
225            "file_shebang",
226            "shebang",
227            // Structured-query family.
228            "json_path_equals",
229            "json_path_matches",
230            "yaml_path_equals",
231            "yaml_path_matches",
232            "toml_path_equals",
233            "toml_path_matches",
234            "json_schema_passes",
235            "git_no_denied_paths",
236            "git_commit_message",
237            "git_blame_age",
238            "file_is_text",
239            "is_text",
240            // Short-only.
241            "filename_case",
242            "filename_regex",
243            "pair",
244            "for_each_dir",
245            "for_each_file",
246            "dir_only_contains",
247            "unique_by",
248            "dir_contains",
249            "every_matching_has",
250            // Text-hygiene family.
251            "no_trailing_whitespace",
252            "final_newline",
253            "line_endings",
254            "line_max_width",
255            // Security / Unicode sanity.
256            "no_merge_conflict_markers",
257            "no_bidi_controls",
258            "no_zero_width_chars",
259            // Encoding + fingerprint.
260            "file_is_ascii",
261            "no_bom",
262            "file_hash",
263            // Structure / layout.
264            "max_directory_depth",
265            "max_files_per_directory",
266            "no_empty_files",
267            // Portable metadata.
268            "no_case_conflicts",
269            "no_illegal_windows_names",
270            // Unix metadata + git.
271            "no_symlinks",
272            "executable_bit",
273            "executable_has_shebang",
274            "shebang_has_executable",
275            "no_submodules",
276            // Hygiene + byte fingerprint.
277            "indent_style",
278            "max_consecutive_blank_lines",
279            "file_starts_with",
280            "file_ends_with",
281            // Plugin (tier 1).
282            "command",
283        ] {
284            assert!(
285                known.contains(&kind),
286                "{kind} missing from builtin registry"
287            );
288        }
289    }
290}