1use std::sync::LazyLock;
2
3use crate::rule::Rule;
4
5use super::flavor::normalize_key;
6
7static DEFAULT_REGISTRY: LazyLock<RuleRegistry> = LazyLock::new(|| {
13 let default_config = super::types::Config::default();
14 let rules = crate::rules::all_rules(&default_config);
15 RuleRegistry::from_rules(&rules)
16});
17
18pub fn default_registry() -> &'static RuleRegistry {
24 &DEFAULT_REGISTRY
25}
26
27pub struct RuleRegistry {
29 pub rule_schemas: std::collections::BTreeMap<String, toml::map::Map<String, toml::Value>>,
31 pub rule_aliases: std::collections::BTreeMap<String, std::collections::HashMap<String, String>>,
33}
34
35impl RuleRegistry {
36 pub fn from_rules(rules: &[Box<dyn Rule>]) -> Self {
38 let mut rule_schemas = std::collections::BTreeMap::new();
39 let mut rule_aliases = std::collections::BTreeMap::new();
40
41 for rule in rules {
42 let norm_name = if let Some((name, toml::Value::Table(table))) = rule.default_config_section() {
43 let norm_name = normalize_key(&name); rule_schemas.insert(norm_name.clone(), table);
45 norm_name
46 } else {
47 let norm_name = normalize_key(rule.name()); rule_schemas.insert(norm_name.clone(), toml::map::Map::new());
49 norm_name
50 };
51
52 if let Some(aliases) = rule.config_aliases() {
54 rule_aliases.insert(norm_name, aliases);
55 }
56 }
57
58 RuleRegistry {
59 rule_schemas,
60 rule_aliases,
61 }
62 }
63
64 pub fn rule_names(&self) -> std::collections::BTreeSet<String> {
66 self.rule_schemas.keys().cloned().collect()
67 }
68
69 pub fn config_keys_for(&self, rule: &str) -> Option<std::collections::BTreeSet<String>> {
71 self.rule_schemas.get(rule).map(|schema| {
72 let mut all_keys = std::collections::BTreeSet::new();
73
74 all_keys.insert("severity".to_string());
76 all_keys.insert("enabled".to_string());
77
78 for key in schema.keys() {
80 all_keys.insert(key.clone());
81 }
82
83 for key in schema.keys() {
85 all_keys.insert(key.replace('_', "-"));
87 all_keys.insert(key.replace('-', "_"));
89 all_keys.insert(normalize_key(key));
91 }
92
93 if let Some(aliases) = self.rule_aliases.get(rule) {
95 for alias_key in aliases.keys() {
96 all_keys.insert(alias_key.clone());
97 all_keys.insert(alias_key.replace('_', "-"));
99 all_keys.insert(alias_key.replace('-', "_"));
100 all_keys.insert(normalize_key(alias_key));
101 }
102 }
103
104 all_keys
105 })
106 }
107
108 pub fn expected_value_for(&self, rule: &str, key: &str) -> Option<&toml::Value> {
112 let schema = self.rule_schemas.get(rule)?;
113
114 if let Some(aliases) = self.rule_aliases.get(rule)
116 && let Some(canonical_key) = aliases.get(key)
117 && let Some(value) = schema.get(canonical_key)
118 {
119 return filter_nullable_sentinel(value);
120 }
121
122 if let Some(value) = schema.get(key) {
124 return filter_nullable_sentinel(value);
125 }
126
127 let key_variants = [
129 key.replace('-', "_"), key.replace('_', "-"), normalize_key(key), ];
133
134 for variant in &key_variants {
135 if let Some(value) = schema.get(variant) {
136 return filter_nullable_sentinel(value);
137 }
138 }
139
140 None
141 }
142
143 pub fn resolve_rule_name(&self, name: &str) -> Option<String> {
150 let normalized = normalize_key(name);
152 if self.rule_schemas.contains_key(&normalized) {
153 return Some(normalized);
154 }
155
156 resolve_rule_name_alias(name).map(std::string::ToString::to_string)
158 }
159}
160
161fn filter_nullable_sentinel(value: &toml::Value) -> Option<&toml::Value> {
164 if crate::rule_config_serde::is_nullable_sentinel(value) {
165 None
166 } else {
167 Some(value)
168 }
169}
170
171pub static RULE_ALIAS_MAP: phf::Map<&'static str, &'static str> = phf::phf_map! {
174 "MD001" => "MD001",
176 "MD003" => "MD003",
177 "MD004" => "MD004",
178 "MD005" => "MD005",
179 "MD007" => "MD007",
180 "MD009" => "MD009",
181 "MD010" => "MD010",
182 "MD011" => "MD011",
183 "MD012" => "MD012",
184 "MD013" => "MD013",
185 "MD014" => "MD014",
186 "MD018" => "MD018",
187 "MD019" => "MD019",
188 "MD020" => "MD020",
189 "MD021" => "MD021",
190 "MD022" => "MD022",
191 "MD023" => "MD023",
192 "MD024" => "MD024",
193 "MD025" => "MD025",
194 "MD026" => "MD026",
195 "MD027" => "MD027",
196 "MD028" => "MD028",
197 "MD029" => "MD029",
198 "MD030" => "MD030",
199 "MD031" => "MD031",
200 "MD032" => "MD032",
201 "MD033" => "MD033",
202 "MD034" => "MD034",
203 "MD035" => "MD035",
204 "MD036" => "MD036",
205 "MD037" => "MD037",
206 "MD038" => "MD038",
207 "MD039" => "MD039",
208 "MD040" => "MD040",
209 "MD041" => "MD041",
210 "MD042" => "MD042",
211 "MD043" => "MD043",
212 "MD044" => "MD044",
213 "MD045" => "MD045",
214 "MD046" => "MD046",
215 "MD047" => "MD047",
216 "MD048" => "MD048",
217 "MD049" => "MD049",
218 "MD050" => "MD050",
219 "MD051" => "MD051",
220 "MD052" => "MD052",
221 "MD053" => "MD053",
222 "MD054" => "MD054",
223 "MD055" => "MD055",
224 "MD056" => "MD056",
225 "MD057" => "MD057",
226 "MD058" => "MD058",
227 "MD059" => "MD059",
228 "MD060" => "MD060",
229 "MD061" => "MD061",
230 "MD062" => "MD062",
231 "MD063" => "MD063",
232 "MD064" => "MD064",
233 "MD065" => "MD065",
234 "MD066" => "MD066",
235 "MD067" => "MD067",
236 "MD068" => "MD068",
237 "MD069" => "MD069",
238 "MD070" => "MD070",
239 "MD071" => "MD071",
240 "MD072" => "MD072",
241 "MD073" => "MD073",
242 "MD074" => "MD074",
243 "MD075" => "MD075",
244 "MD076" => "MD076",
245 "MD077" => "MD077",
246
247 "HEADING-INCREMENT" => "MD001",
249 "HEADING-STYLE" => "MD003",
250 "UL-STYLE" => "MD004",
251 "LIST-INDENT" => "MD005",
252 "UL-INDENT" => "MD007",
253 "NO-TRAILING-SPACES" => "MD009",
254 "NO-HARD-TABS" => "MD010",
255 "NO-REVERSED-LINKS" => "MD011",
256 "NO-MULTIPLE-BLANKS" => "MD012",
257 "LINE-LENGTH" => "MD013",
258 "COMMANDS-SHOW-OUTPUT" => "MD014",
259 "NO-MISSING-SPACE-ATX" => "MD018",
260 "NO-MULTIPLE-SPACE-ATX" => "MD019",
261 "NO-MISSING-SPACE-CLOSED-ATX" => "MD020",
262 "NO-MULTIPLE-SPACE-CLOSED-ATX" => "MD021",
263 "BLANKS-AROUND-HEADINGS" => "MD022",
264 "HEADING-START-LEFT" => "MD023",
265 "NO-DUPLICATE-HEADING" => "MD024",
266 "SINGLE-TITLE" => "MD025",
267 "SINGLE-H1" => "MD025",
268 "NO-TRAILING-PUNCTUATION" => "MD026",
269 "NO-MULTIPLE-SPACE-BLOCKQUOTE" => "MD027",
270 "NO-BLANKS-BLOCKQUOTE" => "MD028",
271 "OL-PREFIX" => "MD029",
272 "LIST-MARKER-SPACE" => "MD030",
273 "BLANKS-AROUND-FENCES" => "MD031",
274 "BLANKS-AROUND-LISTS" => "MD032",
275 "NO-INLINE-HTML" => "MD033",
276 "NO-BARE-URLS" => "MD034",
277 "HR-STYLE" => "MD035",
278 "NO-EMPHASIS-AS-HEADING" => "MD036",
279 "NO-SPACE-IN-EMPHASIS" => "MD037",
280 "NO-SPACE-IN-CODE" => "MD038",
281 "NO-SPACE-IN-LINKS" => "MD039",
282 "FENCED-CODE-LANGUAGE" => "MD040",
283 "FIRST-LINE-HEADING" => "MD041",
284 "FIRST-LINE-H1" => "MD041",
285 "NO-EMPTY-LINKS" => "MD042",
286 "REQUIRED-HEADINGS" => "MD043",
287 "PROPER-NAMES" => "MD044",
288 "NO-ALT-TEXT" => "MD045",
289 "CODE-BLOCK-STYLE" => "MD046",
290 "SINGLE-TRAILING-NEWLINE" => "MD047",
291 "CODE-FENCE-STYLE" => "MD048",
292 "EMPHASIS-STYLE" => "MD049",
293 "STRONG-STYLE" => "MD050",
294 "LINK-FRAGMENTS" => "MD051",
295 "REFERENCE-LINKS-IMAGES" => "MD052",
296 "LINK-IMAGE-REFERENCE-DEFINITIONS" => "MD053",
297 "LINK-IMAGE-STYLE" => "MD054",
298 "TABLE-PIPE-STYLE" => "MD055",
299 "TABLE-COLUMN-COUNT" => "MD056",
300 "EXISTING-RELATIVE-LINKS" => "MD057",
301 "BLANKS-AROUND-TABLES" => "MD058",
302 "DESCRIPTIVE-LINK-TEXT" => "MD059",
303 "TABLE-CELL-ALIGNMENT" => "MD060",
304 "TABLE-FORMAT" => "MD060",
305 "FORBIDDEN-TERMS" => "MD061",
306 "LINK-DESTINATION-WHITESPACE" => "MD062",
307 "HEADING-CAPITALIZATION" => "MD063",
308 "NO-MULTIPLE-CONSECUTIVE-SPACES" => "MD064",
309 "BLANKS-AROUND-HORIZONTAL-RULES" => "MD065",
310 "FOOTNOTE-VALIDATION" => "MD066",
311 "FOOTNOTE-DEFINITION-ORDER" => "MD067",
312 "EMPTY-FOOTNOTE-DEFINITION" => "MD068",
313 "NO-DUPLICATE-LIST-MARKERS" => "MD069",
314 "NESTED-CODE-FENCE" => "MD070",
315 "BLANK-LINE-AFTER-FRONTMATTER" => "MD071",
316 "FRONTMATTER-KEY-SORT" => "MD072",
317 "TOC-VALIDATION" => "MD073",
318 "MKDOCS-NAV" => "MD074",
319 "ORPHANED-TABLE-ROWS" => "MD075",
320 "LIST-ITEM-SPACING" => "MD076",
321 "LIST-CONTINUATION-INDENT" => "MD077",
322};
323
324pub fn resolve_rule_name_alias(key: &str) -> Option<&'static str> {
328 let normalized_key = key.to_ascii_uppercase().replace('_', "-");
330
331 RULE_ALIAS_MAP.get(normalized_key.as_str()).copied()
333}
334
335pub fn resolve_rule_name(name: &str) -> String {
343 resolve_rule_name_alias(name).map_or_else(|| normalize_key(name), std::string::ToString::to_string)
344}
345
346pub fn resolve_rule_names(input: &str) -> std::collections::HashSet<String> {
350 input
351 .split(',')
352 .map(str::trim)
353 .filter(|s| !s.is_empty())
354 .map(resolve_rule_name)
355 .collect()
356}
357
358pub fn is_valid_rule_name(name: &str) -> bool {
362 if name.eq_ignore_ascii_case("all") {
364 return true;
365 }
366 resolve_rule_name_alias(name).is_some()
367}