rumdl_lib/rules/md013_line_length/
md013_config.rs1use crate::rule_config_serde::RuleConfig;
2use crate::types::LineLength;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
7#[serde(rename_all = "kebab-case")]
8pub enum ReflowMode {
9 #[default]
11 Default,
12 Normalize,
14 #[serde(alias = "sentence_per_line")]
16 SentencePerLine,
17 #[serde(alias = "semantic_line_breaks")]
23 SemanticLineBreaks,
24}
25
26#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
28#[serde(rename_all = "kebab-case")]
29pub enum LengthMode {
30 #[serde(alias = "chars", alias = "characters")]
33 Chars,
34 #[default]
37 #[serde(alias = "display", alias = "visual_width")]
38 Visual,
39 Bytes,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45#[serde(rename_all = "kebab-case")]
46pub struct MD013Config {
47 #[serde(default = "default_line_length", alias = "line_length")]
49 pub line_length: LineLength,
50
51 #[serde(default = "default_code_blocks", alias = "code_blocks")]
53 pub code_blocks: bool,
54
55 #[serde(default = "default_tables")]
61 pub tables: bool,
62
63 #[serde(default = "default_headings")]
65 pub headings: bool,
66
67 #[serde(default = "default_paragraphs")]
71 pub paragraphs: bool,
72
73 #[serde(default)]
75 pub strict: bool,
76
77 #[serde(default, alias = "enable_reflow", alias = "enable-reflow")]
79 pub reflow: bool,
80
81 #[serde(default, alias = "reflow_mode")]
83 pub reflow_mode: ReflowMode,
84
85 #[serde(default, alias = "length_mode")]
90 pub length_mode: LengthMode,
91
92 #[serde(default)]
97 pub abbreviations: Vec<String>,
98}
99
100fn default_line_length() -> LineLength {
101 LineLength::from_const(80)
102}
103
104fn default_code_blocks() -> bool {
105 true
106}
107
108fn default_tables() -> bool {
109 false
110}
111
112fn default_headings() -> bool {
113 true
114}
115
116fn default_paragraphs() -> bool {
117 true
118}
119
120impl Default for MD013Config {
121 fn default() -> Self {
122 Self {
123 line_length: default_line_length(),
124 code_blocks: default_code_blocks(),
125 tables: default_tables(),
126 headings: default_headings(),
127 paragraphs: default_paragraphs(),
128 strict: false,
129 reflow: false,
130 reflow_mode: ReflowMode::default(),
131 length_mode: LengthMode::default(),
132 abbreviations: Vec::new(),
133 }
134 }
135}
136
137impl MD013Config {
138 pub fn abbreviations_for_reflow(&self) -> Option<Vec<String>> {
141 if self.abbreviations.is_empty() {
142 None
143 } else {
144 Some(self.abbreviations.clone())
145 }
146 }
147}
148
149impl RuleConfig for MD013Config {
150 const RULE_NAME: &'static str = "MD013";
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_reflow_mode_deserialization_kebab_case() {
159 let toml_str = r#"
162 reflow-mode = "sentence-per-line"
163 "#;
164 let config: MD013Config = toml::from_str(toml_str).unwrap();
165 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
166
167 let toml_str = r#"
168 reflow-mode = "default"
169 "#;
170 let config: MD013Config = toml::from_str(toml_str).unwrap();
171 assert_eq!(config.reflow_mode, ReflowMode::Default);
172
173 let toml_str = r#"
174 reflow-mode = "normalize"
175 "#;
176 let config: MD013Config = toml::from_str(toml_str).unwrap();
177 assert_eq!(config.reflow_mode, ReflowMode::Normalize);
178
179 let toml_str = r#"
180 reflow-mode = "semantic-line-breaks"
181 "#;
182 let config: MD013Config = toml::from_str(toml_str).unwrap();
183 assert_eq!(config.reflow_mode, ReflowMode::SemanticLineBreaks);
184 }
185
186 #[test]
187 fn test_reflow_mode_deserialization_snake_case_alias() {
188 let toml_str = r#"
191 reflow-mode = "sentence_per_line"
192 "#;
193 let config: MD013Config = toml::from_str(toml_str).unwrap();
194 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
195
196 let toml_str = r#"
197 reflow-mode = "semantic_line_breaks"
198 "#;
199 let config: MD013Config = toml::from_str(toml_str).unwrap();
200 assert_eq!(config.reflow_mode, ReflowMode::SemanticLineBreaks);
201 }
202
203 #[test]
204 fn test_field_name_backwards_compatibility() {
205 let toml_str = r#"
208 line_length = 100
209 code_blocks = false
210 reflow_mode = "sentence_per_line"
211 "#;
212 let config: MD013Config = toml::from_str(toml_str).unwrap();
213 assert_eq!(config.line_length.get(), 100);
214 assert!(!config.code_blocks);
215 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
216
217 let toml_str = r#"
219 line-length = 100
220 code_blocks = false
221 reflow-mode = "normalize"
222 "#;
223 let config: MD013Config = toml::from_str(toml_str).unwrap();
224 assert_eq!(config.line_length.get(), 100);
225 assert!(!config.code_blocks);
226 assert_eq!(config.reflow_mode, ReflowMode::Normalize);
227 }
228
229 #[test]
230 fn test_reflow_mode_serialization() {
231 let config = MD013Config {
233 line_length: LineLength::from_const(80),
234 code_blocks: true,
235 tables: true,
236 headings: true,
237 paragraphs: true,
238 strict: false,
239 reflow: true,
240 reflow_mode: ReflowMode::SentencePerLine,
241 length_mode: LengthMode::default(),
242 abbreviations: Vec::new(),
243 };
244
245 let toml_str = toml::to_string(&config).unwrap();
246 assert!(toml_str.contains("sentence-per-line"));
247 assert!(!toml_str.contains("sentence_per_line"));
248
249 let config = MD013Config {
251 reflow_mode: ReflowMode::SemanticLineBreaks,
252 ..config
253 };
254 let toml_str = toml::to_string(&config).unwrap();
255 assert!(toml_str.contains("semantic-line-breaks"));
256 assert!(!toml_str.contains("semantic_line_breaks"));
257 }
258
259 #[test]
260 fn test_reflow_mode_invalid_value() {
261 let toml_str = r#"
263 reflow-mode = "invalid_mode"
264 "#;
265 let result = toml::from_str::<MD013Config>(toml_str);
266 assert!(result.is_err());
267 }
268
269 #[test]
270 fn test_full_config_with_reflow_mode() {
271 let toml_str = r#"
272 line-length = 100
273 code-blocks = false
274 tables = false
275 headings = true
276 strict = true
277 reflow = true
278 reflow-mode = "sentence-per-line"
279 "#;
280 let config: MD013Config = toml::from_str(toml_str).unwrap();
281 assert_eq!(config.line_length.get(), 100);
282 assert!(!config.code_blocks);
283 assert!(!config.tables);
284 assert!(config.headings);
285 assert!(config.strict);
286 assert!(config.reflow);
287 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
288 }
289
290 #[test]
291 fn test_paragraphs_default_true() {
292 let config = MD013Config::default();
294 assert!(config.paragraphs, "paragraphs should default to true");
295 }
296
297 #[test]
298 fn test_paragraphs_deserialization_kebab_case() {
299 let toml_str = r#"
301 paragraphs = false
302 "#;
303 let config: MD013Config = toml::from_str(toml_str).unwrap();
304 assert!(!config.paragraphs);
305 }
306
307 #[test]
308 fn test_paragraphs_full_config() {
309 let toml_str = r#"
311 line-length = 80
312 code-blocks = true
313 tables = true
314 headings = false
315 paragraphs = false
316 reflow = true
317 reflow-mode = "sentence-per-line"
318 "#;
319 let config: MD013Config = toml::from_str(toml_str).unwrap();
320 assert_eq!(config.line_length.get(), 80);
321 assert!(config.code_blocks, "code-blocks should be true");
322 assert!(config.tables, "tables should be true");
323 assert!(!config.headings, "headings should be false");
324 assert!(!config.paragraphs, "paragraphs should be false");
325 assert!(config.reflow, "reflow should be true");
326 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
327 }
328
329 #[test]
330 fn test_abbreviations_for_reflow_empty_vec() {
331 let config = MD013Config {
333 abbreviations: Vec::new(),
334 ..Default::default()
335 };
336 assert!(
337 config.abbreviations_for_reflow().is_none(),
338 "Empty abbreviations should return None for reflow"
339 );
340 }
341
342 #[test]
343 fn test_abbreviations_for_reflow_with_custom() {
344 let config = MD013Config {
346 abbreviations: vec!["Corp".to_string(), "Inc".to_string()],
347 ..Default::default()
348 };
349 let result = config.abbreviations_for_reflow();
350 assert!(result.is_some(), "Custom abbreviations should return Some");
351 let abbrevs = result.unwrap();
352 assert_eq!(abbrevs.len(), 2);
353 assert!(abbrevs.contains(&"Corp".to_string()));
354 assert!(abbrevs.contains(&"Inc".to_string()));
355 }
356}