1use crate::config::{ConfigSource, SourcedConfig, SourcedValue};
6use serde::Deserialize;
7use std::collections::HashMap;
8use std::fs;
9
10#[derive(Debug, Deserialize)]
12pub struct MarkdownlintConfig(pub HashMap<String, serde_yml::Value>);
13
14pub fn load_markdownlint_config(path: &str) -> Result<MarkdownlintConfig, String> {
16 let content = fs::read_to_string(path).map_err(|e| format!("Failed to read config file {path}: {e}"))?;
17
18 if path.ends_with(".json") || path.ends_with(".jsonc") {
19 serde_json::from_str(&content).map_err(|e| format!("Failed to parse JSON: {e}"))
20 } else if path.ends_with(".yaml") || path.ends_with(".yml") {
21 serde_yml::from_str(&content).map_err(|e| format!("Failed to parse YAML: {e}"))
22 } else {
23 serde_json::from_str(&content)
24 .or_else(|_| serde_yml::from_str(&content))
25 .map_err(|e| format!("Failed to parse config as JSON or YAML: {e}"))
26 }
27}
28
29pub fn markdownlint_to_rumdl_rule_key(key: &str) -> Option<&'static str> {
33 crate::config::resolve_rule_name_alias(key)
35}
36
37fn normalize_toml_table_keys(val: toml::Value) -> toml::Value {
38 match val {
39 toml::Value::Table(table) => {
40 let mut new_table = toml::map::Map::new();
41 for (k, v) in table {
42 let norm_k = crate::config::normalize_key(&k);
43 new_table.insert(norm_k, normalize_toml_table_keys(v));
44 }
45 toml::Value::Table(new_table)
46 }
47 toml::Value::Array(arr) => toml::Value::Array(arr.into_iter().map(normalize_toml_table_keys).collect()),
48 other => other,
49 }
50}
51
52fn map_markdownlint_options_to_rumdl(
56 rule_key: &str,
57 table: toml::map::Map<String, toml::Value>,
58) -> Option<toml::map::Map<String, toml::Value>> {
59 let mut mapped = toml::map::Map::new();
60
61 match rule_key {
62 "MD013" => {
63 for (k, v) in table {
65 match k.as_str() {
66 "code-block-line-length" | "code_block_line_length" => {
69 log::warn!(
72 "Ignoring markdownlint option 'code_block_line_length' for MD013. Use 'code-blocks = false' in rumdl to disable line length checking in code blocks."
73 );
74 }
75 "heading-line-length" | "heading_line_length" => {
76 log::warn!(
78 "Ignoring markdownlint option 'heading_line_length' for MD013. Use 'headings = false' in rumdl to disable line length checking in headings."
79 );
80 }
81 "stern" => {
82 mapped.insert("strict".to_string(), v);
84 }
85 _ => {
87 mapped.insert(k, v);
88 }
89 }
90 }
91 Some(mapped)
92 }
93 "MD054" => {
94 for (k, v) in table {
97 match k.as_str() {
98 "style" | "styles" => {
99 log::warn!(
102 "Ignoring markdownlint option '{k}' for MD054. rumdl uses individual boolean flags (autolink, inline, full, collapsed, shortcut, url-inline) instead. Please configure these directly."
103 );
104 }
105 _ => {
107 mapped.insert(k, v);
108 }
109 }
110 }
111 Some(mapped)
112 }
113 _ => Some(table),
115 }
116}
117
118impl MarkdownlintConfig {
120 pub fn map_to_sourced_rumdl_config(&self, file_path: Option<&str>) -> SourcedConfig {
122 let mut sourced_config = SourcedConfig::default();
123 let file = file_path.map(|s| s.to_string());
124 for (key, value) in &self.0 {
125 let mapped = markdownlint_to_rumdl_rule_key(key);
126 if let Some(rumdl_key) = mapped {
127 let norm_rule_key = rumdl_key.to_ascii_uppercase();
128 let toml_value: Option<toml::Value> = serde_yml::from_value::<toml::Value>(value.clone()).ok();
129 let toml_value = toml_value.map(normalize_toml_table_keys);
130 let rule_config = sourced_config.rules.entry(norm_rule_key.clone()).or_default();
131 if let Some(tv) = toml_value {
132 if let toml::Value::Table(mut table) = tv {
133 table = match map_markdownlint_options_to_rumdl(&norm_rule_key, table) {
135 Some(mapped) => mapped,
136 None => continue, };
138
139 if norm_rule_key == "MD007" && !table.contains_key("style") {
141 table.insert("style".to_string(), toml::Value::String("fixed".to_string()));
142 }
143
144 for (k, v) in table {
145 let norm_config_key = k; rule_config
147 .values
148 .entry(norm_config_key.clone())
149 .and_modify(|sv| {
150 sv.value = v.clone();
151 sv.source = ConfigSource::ProjectConfig;
152 sv.overrides.push(crate::config::ConfigOverride {
153 value: v.clone(),
154 source: ConfigSource::ProjectConfig,
155 file: file.clone(),
156 line: None,
157 });
158 })
159 .or_insert_with(|| SourcedValue {
160 value: v.clone(),
161 source: ConfigSource::ProjectConfig,
162 overrides: vec![crate::config::ConfigOverride {
163 value: v,
164 source: ConfigSource::ProjectConfig,
165 file: file.clone(),
166 line: None,
167 }],
168 });
169 }
170 } else {
171 rule_config
172 .values
173 .entry("value".to_string())
174 .and_modify(|sv| {
175 sv.value = tv.clone();
176 sv.source = ConfigSource::ProjectConfig;
177 sv.overrides.push(crate::config::ConfigOverride {
178 value: tv.clone(),
179 source: ConfigSource::ProjectConfig,
180 file: file.clone(),
181 line: None,
182 });
183 })
184 .or_insert_with(|| SourcedValue {
185 value: tv.clone(),
186 source: ConfigSource::ProjectConfig,
187 overrides: vec![crate::config::ConfigOverride {
188 value: tv,
189 source: ConfigSource::ProjectConfig,
190 file: file.clone(),
191 line: None,
192 }],
193 });
194
195 if norm_rule_key == "MD007" && !rule_config.values.contains_key("style") {
197 rule_config.values.insert(
198 "style".to_string(),
199 SourcedValue {
200 value: toml::Value::String("fixed".to_string()),
201 source: ConfigSource::ProjectConfig,
202 overrides: vec![crate::config::ConfigOverride {
203 value: toml::Value::String("fixed".to_string()),
204 source: ConfigSource::ProjectConfig,
205 file: file.clone(),
206 line: None,
207 }],
208 },
209 );
210 }
211 }
212 } else {
213 log::error!(
214 "Could not convert value for rule key {key:?} to rumdl's internal config format. This likely means the configuration value is invalid or not supported for this rule. Please check your markdownlint config."
215 );
216 std::process::exit(1);
217 }
218 }
219 }
220 if let Some(_f) = file {
221 sourced_config.loaded_files.push(_f);
222 }
223 sourced_config
224 }
225
226 pub fn map_to_sourced_rumdl_config_fragment(
228 &self,
229 file_path: Option<&str>,
230 ) -> crate::config::SourcedConfigFragment {
231 let mut fragment = crate::config::SourcedConfigFragment::default();
232 let file = file_path.map(|s| s.to_string());
233
234 let mut disabled_rules = Vec::new();
236 let mut enabled_rules = Vec::new();
237
238 for (key, value) in &self.0 {
239 let mapped = markdownlint_to_rumdl_rule_key(key);
240 if let Some(rumdl_key) = mapped {
241 let norm_rule_key = rumdl_key.to_ascii_uppercase();
242 if value.is_bool() {
244 if !value.as_bool().unwrap_or(false) {
245 disabled_rules.push(norm_rule_key.clone());
247 } else {
248 enabled_rules.push(norm_rule_key.clone());
250 }
251 continue;
252 }
253 let toml_value: Option<toml::Value> = serde_yml::from_value::<toml::Value>(value.clone()).ok();
254 let toml_value = toml_value.map(normalize_toml_table_keys);
255 let rule_config = fragment.rules.entry(norm_rule_key.clone()).or_default();
256 if let Some(tv) = toml_value {
257 let tv = if norm_rule_key == "MD013" && tv.is_integer() {
260 let mut table = toml::map::Map::new();
261 table.insert("line-length".to_string(), tv);
262 toml::Value::Table(table)
263 } else {
264 tv
265 };
266
267 if let toml::Value::Table(mut table) = tv {
268 table = match map_markdownlint_options_to_rumdl(&norm_rule_key, table) {
270 Some(mapped) => mapped,
271 None => continue, };
273
274 if norm_rule_key == "MD007" && !table.contains_key("style") {
276 table.insert("style".to_string(), toml::Value::String("fixed".to_string()));
277 }
278
279 for (rk, rv) in table {
280 let norm_rk = crate::config::normalize_key(&rk);
281 let sv = rule_config.values.entry(norm_rk.clone()).or_insert_with(|| {
282 crate::config::SourcedValue::new(rv.clone(), crate::config::ConfigSource::ProjectConfig)
283 });
284 sv.push_override(rv, crate::config::ConfigSource::ProjectConfig, file.clone(), None);
285 }
286 } else {
287 rule_config
288 .values
289 .entry("value".to_string())
290 .and_modify(|sv| {
291 sv.value = tv.clone();
292 sv.source = crate::config::ConfigSource::ProjectConfig;
293 sv.overrides.push(crate::config::ConfigOverride {
294 value: tv.clone(),
295 source: crate::config::ConfigSource::ProjectConfig,
296 file: file.clone(),
297 line: None,
298 });
299 })
300 .or_insert_with(|| crate::config::SourcedValue {
301 value: tv.clone(),
302 source: crate::config::ConfigSource::ProjectConfig,
303 overrides: vec![crate::config::ConfigOverride {
304 value: tv,
305 source: crate::config::ConfigSource::ProjectConfig,
306 file: file.clone(),
307 line: None,
308 }],
309 });
310
311 if norm_rule_key == "MD007" && !rule_config.values.contains_key("style") {
313 rule_config.values.insert(
314 "style".to_string(),
315 crate::config::SourcedValue {
316 value: toml::Value::String("fixed".to_string()),
317 source: crate::config::ConfigSource::ProjectConfig,
318 overrides: vec![crate::config::ConfigOverride {
319 value: toml::Value::String("fixed".to_string()),
320 source: crate::config::ConfigSource::ProjectConfig,
321 file: file.clone(),
322 line: None,
323 }],
324 },
325 );
326 }
327 }
328 }
329 }
330 }
331
332 if !disabled_rules.is_empty() {
334 fragment.global.disable.push_override(
335 disabled_rules,
336 crate::config::ConfigSource::ProjectConfig,
337 file.clone(),
338 None,
339 );
340 }
341
342 if !enabled_rules.is_empty() {
344 fragment.global.enable.push_override(
345 enabled_rules,
346 crate::config::ConfigSource::ProjectConfig,
347 file.clone(),
348 None,
349 );
350 }
351
352 if let Some(_f) = file {
353 }
355 fragment
356 }
357}
358
359#[cfg(test)]
362mod tests {
363 use super::*;
364 use std::io::Write;
365 use tempfile::NamedTempFile;
366
367 #[test]
368 fn test_markdownlint_to_rumdl_rule_key() {
369 assert_eq!(markdownlint_to_rumdl_rule_key("MD001"), Some("MD001"));
371 assert_eq!(markdownlint_to_rumdl_rule_key("MD058"), Some("MD058"));
372
373 assert_eq!(markdownlint_to_rumdl_rule_key("heading-increment"), Some("MD001"));
375 assert_eq!(markdownlint_to_rumdl_rule_key("HEADING-INCREMENT"), Some("MD001"));
376 assert_eq!(markdownlint_to_rumdl_rule_key("ul-style"), Some("MD004"));
377 assert_eq!(markdownlint_to_rumdl_rule_key("no-trailing-spaces"), Some("MD009"));
378 assert_eq!(markdownlint_to_rumdl_rule_key("line-length"), Some("MD013"));
379 assert_eq!(markdownlint_to_rumdl_rule_key("single-title"), Some("MD025"));
380 assert_eq!(markdownlint_to_rumdl_rule_key("single-h1"), Some("MD025"));
381 assert_eq!(markdownlint_to_rumdl_rule_key("no-bare-urls"), Some("MD034"));
382 assert_eq!(markdownlint_to_rumdl_rule_key("code-block-style"), Some("MD046"));
383 assert_eq!(markdownlint_to_rumdl_rule_key("code-fence-style"), Some("MD048"));
384
385 assert_eq!(markdownlint_to_rumdl_rule_key("heading_increment"), Some("MD001"));
387 assert_eq!(markdownlint_to_rumdl_rule_key("HEADING_INCREMENT"), Some("MD001"));
388 assert_eq!(markdownlint_to_rumdl_rule_key("ul_style"), Some("MD004"));
389 assert_eq!(markdownlint_to_rumdl_rule_key("no_trailing_spaces"), Some("MD009"));
390 assert_eq!(markdownlint_to_rumdl_rule_key("line_length"), Some("MD013"));
391 assert_eq!(markdownlint_to_rumdl_rule_key("single_title"), Some("MD025"));
392 assert_eq!(markdownlint_to_rumdl_rule_key("single_h1"), Some("MD025"));
393 assert_eq!(markdownlint_to_rumdl_rule_key("no_bare_urls"), Some("MD034"));
394 assert_eq!(markdownlint_to_rumdl_rule_key("code_block_style"), Some("MD046"));
395 assert_eq!(markdownlint_to_rumdl_rule_key("code_fence_style"), Some("MD048"));
396
397 assert_eq!(markdownlint_to_rumdl_rule_key("md001"), Some("MD001"));
399 assert_eq!(markdownlint_to_rumdl_rule_key("Md001"), Some("MD001"));
400 assert_eq!(markdownlint_to_rumdl_rule_key("Line-Length"), Some("MD013"));
401 assert_eq!(markdownlint_to_rumdl_rule_key("Line_Length"), Some("MD013"));
402
403 assert_eq!(markdownlint_to_rumdl_rule_key("MD999"), None);
405 assert_eq!(markdownlint_to_rumdl_rule_key("invalid-rule"), None);
406 assert_eq!(markdownlint_to_rumdl_rule_key(""), None);
407 }
408
409 #[test]
410 fn test_normalize_toml_table_keys() {
411 use toml::map::Map;
412
413 let mut table = Map::new();
415 table.insert("snake_case".to_string(), toml::Value::String("value1".to_string()));
416 table.insert("kebab-case".to_string(), toml::Value::String("value2".to_string()));
417 table.insert("MD013".to_string(), toml::Value::Integer(100));
418
419 let normalized = normalize_toml_table_keys(toml::Value::Table(table));
420
421 if let toml::Value::Table(norm_table) = normalized {
422 assert!(norm_table.contains_key("snake-case"));
423 assert!(norm_table.contains_key("kebab-case"));
424 assert!(norm_table.contains_key("MD013"));
425 assert_eq!(
426 norm_table.get("snake-case").unwrap(),
427 &toml::Value::String("value1".to_string())
428 );
429 assert_eq!(
430 norm_table.get("kebab-case").unwrap(),
431 &toml::Value::String("value2".to_string())
432 );
433 } else {
434 panic!("Expected normalized value to be a table");
435 }
436
437 let array = toml::Value::Array(vec![toml::Value::String("test".to_string()), toml::Value::Integer(42)]);
439 let normalized_array = normalize_toml_table_keys(array.clone());
440 assert_eq!(normalized_array, array);
441
442 let simple = toml::Value::String("simple".to_string());
444 assert_eq!(normalize_toml_table_keys(simple.clone()), simple);
445 }
446
447 #[test]
448 fn test_load_markdownlint_config_json() {
449 let mut temp_file = NamedTempFile::new().unwrap();
450 writeln!(
451 temp_file,
452 r#"{{
453 "MD013": {{ "line_length": 100 }},
454 "MD025": true,
455 "MD026": false,
456 "heading-style": {{ "style": "atx" }}
457 }}"#
458 )
459 .unwrap();
460
461 let config = load_markdownlint_config(temp_file.path().to_str().unwrap()).unwrap();
462 assert_eq!(config.0.len(), 4);
463 assert!(config.0.contains_key("MD013"));
464 assert!(config.0.contains_key("MD025"));
465 assert!(config.0.contains_key("MD026"));
466 assert!(config.0.contains_key("heading-style"));
467 }
468
469 #[test]
470 fn test_load_markdownlint_config_yaml() {
471 let mut temp_file = NamedTempFile::new().unwrap();
472 writeln!(
473 temp_file,
474 r#"MD013:
475 line_length: 120
476MD025: true
477MD026: false
478ul-style:
479 style: dash"#
480 )
481 .unwrap();
482
483 let path = temp_file.path().with_extension("yaml");
484 std::fs::rename(temp_file.path(), &path).unwrap();
485
486 let config = load_markdownlint_config(path.to_str().unwrap()).unwrap();
487 assert_eq!(config.0.len(), 4);
488 assert!(config.0.contains_key("MD013"));
489 assert!(config.0.contains_key("ul-style"));
490 }
491
492 #[test]
493 fn test_load_markdownlint_config_invalid() {
494 let mut temp_file = NamedTempFile::new().unwrap();
495 writeln!(temp_file, "invalid json/yaml content {{").unwrap();
496
497 let result = load_markdownlint_config(temp_file.path().to_str().unwrap());
498 assert!(result.is_err());
499 }
500
501 #[test]
502 fn test_load_markdownlint_config_nonexistent() {
503 let result = load_markdownlint_config("/nonexistent/file.json");
504 assert!(result.is_err());
505 assert!(result.unwrap_err().contains("Failed to read config file"));
506 }
507
508 #[test]
509 fn test_map_to_sourced_rumdl_config() {
510 let mut config_map = HashMap::new();
511 config_map.insert(
512 "MD013".to_string(),
513 serde_yml::Value::Mapping({
514 let mut map = serde_yml::Mapping::new();
515 map.insert(
516 serde_yml::Value::String("line_length".to_string()),
517 serde_yml::Value::Number(serde_yml::Number::from(100)),
518 );
519 map
520 }),
521 );
522 config_map.insert("MD025".to_string(), serde_yml::Value::Bool(true));
523 config_map.insert("MD026".to_string(), serde_yml::Value::Bool(false));
524
525 let mdl_config = MarkdownlintConfig(config_map);
526 let sourced_config = mdl_config.map_to_sourced_rumdl_config(Some("test.json"));
527
528 assert!(sourced_config.rules.contains_key("MD013"));
530 let md013_config = &sourced_config.rules["MD013"];
531 assert!(md013_config.values.contains_key("line-length"));
532 assert_eq!(md013_config.values["line-length"].value, toml::Value::Integer(100));
533 assert_eq!(md013_config.values["line-length"].source, ConfigSource::ProjectConfig);
534
535 assert_eq!(sourced_config.loaded_files.len(), 1);
537 assert_eq!(sourced_config.loaded_files[0], "test.json");
538 }
539
540 #[test]
541 fn test_map_to_sourced_rumdl_config_fragment() {
542 let mut config_map = HashMap::new();
543
544 config_map.insert(
546 "line-length".to_string(),
547 serde_yml::Value::Number(serde_yml::Number::from(120)),
548 );
549
550 config_map.insert("MD025".to_string(), serde_yml::Value::Bool(false));
552
553 config_map.insert("MD026".to_string(), serde_yml::Value::Bool(true));
555
556 config_map.insert(
558 "MD003".to_string(),
559 serde_yml::Value::Mapping({
560 let mut map = serde_yml::Mapping::new();
561 map.insert(
562 serde_yml::Value::String("style".to_string()),
563 serde_yml::Value::String("atx".to_string()),
564 );
565 map
566 }),
567 );
568
569 let mdl_config = MarkdownlintConfig(config_map);
570 let fragment = mdl_config.map_to_sourced_rumdl_config_fragment(Some("test.yaml"));
571
572 assert!(fragment.rules.contains_key("MD013"));
574 let md013_config = &fragment.rules["MD013"];
575 assert!(md013_config.values.contains_key("line-length"));
576 assert_eq!(md013_config.values["line-length"].value, toml::Value::Integer(120));
577
578 assert!(fragment.global.disable.value.contains(&"MD025".to_string()));
580
581 assert!(fragment.global.enable.value.contains(&"MD026".to_string()));
583
584 assert!(fragment.rules.contains_key("MD003"));
586 let md003_config = &fragment.rules["MD003"];
587 assert!(md003_config.values.contains_key("style"));
588 }
589
590 #[test]
591 fn test_edge_cases() {
592 let mut config_map = HashMap::new();
593
594 let empty_config = MarkdownlintConfig(HashMap::new());
596 let sourced = empty_config.map_to_sourced_rumdl_config(None);
597 assert!(sourced.rules.is_empty());
598
599 config_map.insert("unknown-rule".to_string(), serde_yml::Value::Bool(true));
601 config_map.insert("MD999".to_string(), serde_yml::Value::Bool(true));
602
603 let config = MarkdownlintConfig(config_map);
604 let sourced = config.map_to_sourced_rumdl_config(None);
605 assert!(sourced.rules.is_empty()); }
607
608 #[test]
609 fn test_complex_rule_configurations() {
610 let mut config_map = HashMap::new();
611
612 config_map.insert(
614 "MD044".to_string(),
615 serde_yml::Value::Mapping({
616 let mut map = serde_yml::Mapping::new();
617 map.insert(
618 serde_yml::Value::String("names".to_string()),
619 serde_yml::Value::Sequence(vec![
620 serde_yml::Value::String("JavaScript".to_string()),
621 serde_yml::Value::String("GitHub".to_string()),
622 ]),
623 );
624 map
625 }),
626 );
627
628 config_map.insert(
630 "MD003".to_string(),
631 serde_yml::Value::Mapping({
632 let mut map = serde_yml::Mapping::new();
633 map.insert(
634 serde_yml::Value::String("style".to_string()),
635 serde_yml::Value::String("atx".to_string()),
636 );
637 map
638 }),
639 );
640
641 let mdl_config = MarkdownlintConfig(config_map);
642 let sourced = mdl_config.map_to_sourced_rumdl_config(None);
643
644 assert!(sourced.rules.contains_key("MD044"));
646 let md044_config = &sourced.rules["MD044"];
647 assert!(md044_config.values.contains_key("names"));
648
649 assert!(sourced.rules.contains_key("MD003"));
651 let md003_config = &sourced.rules["MD003"];
652 assert!(md003_config.values.contains_key("style"));
653 assert_eq!(
654 md003_config.values["style"].value,
655 toml::Value::String("atx".to_string())
656 );
657 }
658
659 #[test]
660 fn test_value_types() {
661 let mut config_map = HashMap::new();
662
663 config_map.insert(
665 "MD007".to_string(),
666 serde_yml::Value::Number(serde_yml::Number::from(4)),
667 ); config_map.insert(
669 "MD009".to_string(),
670 serde_yml::Value::Mapping({
671 let mut map = serde_yml::Mapping::new();
672 map.insert(
673 serde_yml::Value::String("br_spaces".to_string()),
674 serde_yml::Value::Number(serde_yml::Number::from(2)),
675 );
676 map.insert(
677 serde_yml::Value::String("strict".to_string()),
678 serde_yml::Value::Bool(true),
679 );
680 map
681 }),
682 );
683
684 let mdl_config = MarkdownlintConfig(config_map);
685 let sourced = mdl_config.map_to_sourced_rumdl_config(None);
686
687 assert!(sourced.rules.contains_key("MD007"));
689 assert!(sourced.rules["MD007"].values.contains_key("value"));
690
691 assert!(sourced.rules.contains_key("MD009"));
693 let md009_config = &sourced.rules["MD009"];
694 assert!(md009_config.values.contains_key("br-spaces"));
695 assert!(md009_config.values.contains_key("strict"));
696 }
697
698 #[test]
699 fn test_all_rule_aliases() {
700 let aliases = vec![
702 ("heading-increment", "MD001"),
703 ("heading-style", "MD003"),
704 ("ul-style", "MD004"),
705 ("list-indent", "MD005"),
706 ("ul-indent", "MD007"),
707 ("no-trailing-spaces", "MD009"),
708 ("no-hard-tabs", "MD010"),
709 ("no-reversed-links", "MD011"),
710 ("no-multiple-blanks", "MD012"),
711 ("line-length", "MD013"),
712 ("commands-show-output", "MD014"),
713 ("no-missing-space-atx", "MD018"),
715 ("no-multiple-space-atx", "MD019"),
716 ("no-missing-space-closed-atx", "MD020"),
717 ("no-multiple-space-closed-atx", "MD021"),
718 ("blanks-around-headings", "MD022"),
719 ("heading-start-left", "MD023"),
720 ("no-duplicate-heading", "MD024"),
721 ("single-title", "MD025"),
722 ("single-h1", "MD025"),
723 ("no-trailing-punctuation", "MD026"),
724 ("no-multiple-space-blockquote", "MD027"),
725 ("no-blanks-blockquote", "MD028"),
726 ("ol-prefix", "MD029"),
727 ("list-marker-space", "MD030"),
728 ("blanks-around-fences", "MD031"),
729 ("blanks-around-lists", "MD032"),
730 ("no-inline-html", "MD033"),
731 ("no-bare-urls", "MD034"),
732 ("hr-style", "MD035"),
733 ("no-emphasis-as-heading", "MD036"),
734 ("no-space-in-emphasis", "MD037"),
735 ("no-space-in-code", "MD038"),
736 ("no-space-in-links", "MD039"),
737 ("fenced-code-language", "MD040"),
738 ("first-line-heading", "MD041"),
739 ("first-line-h1", "MD041"),
740 ("no-empty-links", "MD042"),
741 ("required-headings", "MD043"),
742 ("proper-names", "MD044"),
743 ("no-alt-text", "MD045"),
744 ("code-block-style", "MD046"),
745 ("single-trailing-newline", "MD047"),
746 ("code-fence-style", "MD048"),
747 ("emphasis-style", "MD049"),
748 ("strong-style", "MD050"),
749 ("link-fragments", "MD051"),
750 ("reference-links-images", "MD052"),
751 ("link-image-reference-definitions", "MD053"),
752 ("link-image-style", "MD054"),
753 ("table-pipe-style", "MD055"),
754 ("table-column-count", "MD056"),
755 ("existing-relative-links", "MD057"),
756 ("blanks-around-tables", "MD058"),
757 ("descriptive-link-text", "MD059"),
758 ("table-cell-alignment", "MD060"),
759 ("table-format", "MD060"),
760 ("forbidden-terms", "MD061"),
761 ("nested-code-fence", "MD070"),
762 ("blank-line-after-frontmatter", "MD071"),
763 ("frontmatter-key-sort", "MD072"),
764 ];
765
766 for (alias, expected) in aliases {
767 assert_eq!(
768 markdownlint_to_rumdl_rule_key(alias),
769 Some(expected),
770 "Alias {alias} should map to {expected}"
771 );
772 }
773 }
774}