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}
18
19#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
21#[serde(rename_all = "kebab-case")]
22pub enum LengthMode {
23 #[serde(alias = "chars", alias = "characters")]
26 Chars,
27 #[default]
30 #[serde(alias = "display", alias = "visual_width")]
31 Visual,
32 Bytes,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38#[serde(rename_all = "kebab-case")]
39pub struct MD013Config {
40 #[serde(default = "default_line_length", alias = "line_length")]
42 pub line_length: LineLength,
43
44 #[serde(default = "default_code_blocks", alias = "code_blocks")]
46 pub code_blocks: bool,
47
48 #[serde(default = "default_tables")]
54 pub tables: bool,
55
56 #[serde(default = "default_headings")]
58 pub headings: bool,
59
60 #[serde(default = "default_paragraphs")]
64 pub paragraphs: bool,
65
66 #[serde(default)]
68 pub strict: bool,
69
70 #[serde(default, alias = "enable_reflow", alias = "enable-reflow")]
72 pub reflow: bool,
73
74 #[serde(default, alias = "reflow_mode")]
76 pub reflow_mode: ReflowMode,
77
78 #[serde(default, alias = "length_mode")]
83 pub length_mode: LengthMode,
84
85 #[serde(default)]
90 pub abbreviations: Vec<String>,
91}
92
93fn default_line_length() -> LineLength {
94 LineLength::from_const(80)
95}
96
97fn default_code_blocks() -> bool {
98 true
99}
100
101fn default_tables() -> bool {
102 false
103}
104
105fn default_headings() -> bool {
106 true
107}
108
109fn default_paragraphs() -> bool {
110 true
111}
112
113impl Default for MD013Config {
114 fn default() -> Self {
115 Self {
116 line_length: default_line_length(),
117 code_blocks: default_code_blocks(),
118 tables: default_tables(),
119 headings: default_headings(),
120 paragraphs: default_paragraphs(),
121 strict: false,
122 reflow: false,
123 reflow_mode: ReflowMode::default(),
124 length_mode: LengthMode::default(),
125 abbreviations: Vec::new(),
126 }
127 }
128}
129
130impl MD013Config {
131 pub fn abbreviations_for_reflow(&self) -> Option<Vec<String>> {
134 if self.abbreviations.is_empty() {
135 None
136 } else {
137 Some(self.abbreviations.clone())
138 }
139 }
140}
141
142impl RuleConfig for MD013Config {
143 const RULE_NAME: &'static str = "MD013";
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_reflow_mode_deserialization_kebab_case() {
152 let toml_str = r#"
155 reflow-mode = "sentence-per-line"
156 "#;
157 let config: MD013Config = toml::from_str(toml_str).unwrap();
158 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
159
160 let toml_str = r#"
161 reflow-mode = "default"
162 "#;
163 let config: MD013Config = toml::from_str(toml_str).unwrap();
164 assert_eq!(config.reflow_mode, ReflowMode::Default);
165
166 let toml_str = r#"
167 reflow-mode = "normalize"
168 "#;
169 let config: MD013Config = toml::from_str(toml_str).unwrap();
170 assert_eq!(config.reflow_mode, ReflowMode::Normalize);
171 }
172
173 #[test]
174 fn test_reflow_mode_deserialization_snake_case_alias() {
175 let toml_str = r#"
178 reflow-mode = "sentence_per_line"
179 "#;
180 let config: MD013Config = toml::from_str(toml_str).unwrap();
181 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
182 }
183
184 #[test]
185 fn test_field_name_backwards_compatibility() {
186 let toml_str = r#"
189 line_length = 100
190 code_blocks = false
191 reflow_mode = "sentence_per_line"
192 "#;
193 let config: MD013Config = toml::from_str(toml_str).unwrap();
194 assert_eq!(config.line_length.get(), 100);
195 assert!(!config.code_blocks);
196 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
197
198 let toml_str = r#"
200 line-length = 100
201 code_blocks = false
202 reflow-mode = "normalize"
203 "#;
204 let config: MD013Config = toml::from_str(toml_str).unwrap();
205 assert_eq!(config.line_length.get(), 100);
206 assert!(!config.code_blocks);
207 assert_eq!(config.reflow_mode, ReflowMode::Normalize);
208 }
209
210 #[test]
211 fn test_reflow_mode_serialization() {
212 let config = MD013Config {
214 line_length: LineLength::from_const(80),
215 code_blocks: true,
216 tables: true,
217 headings: true,
218 paragraphs: true,
219 strict: false,
220 reflow: true,
221 reflow_mode: ReflowMode::SentencePerLine,
222 length_mode: LengthMode::default(),
223 abbreviations: Vec::new(),
224 };
225
226 let toml_str = toml::to_string(&config).unwrap();
227 assert!(toml_str.contains("sentence-per-line"));
228 assert!(!toml_str.contains("sentence_per_line"));
229 }
230
231 #[test]
232 fn test_reflow_mode_invalid_value() {
233 let toml_str = r#"
235 reflow-mode = "invalid_mode"
236 "#;
237 let result = toml::from_str::<MD013Config>(toml_str);
238 assert!(result.is_err());
239 }
240
241 #[test]
242 fn test_full_config_with_reflow_mode() {
243 let toml_str = r#"
244 line-length = 100
245 code-blocks = false
246 tables = false
247 headings = true
248 strict = true
249 reflow = true
250 reflow-mode = "sentence-per-line"
251 "#;
252 let config: MD013Config = toml::from_str(toml_str).unwrap();
253 assert_eq!(config.line_length.get(), 100);
254 assert!(!config.code_blocks);
255 assert!(!config.tables);
256 assert!(config.headings);
257 assert!(config.strict);
258 assert!(config.reflow);
259 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
260 }
261
262 #[test]
263 fn test_paragraphs_default_true() {
264 let config = MD013Config::default();
266 assert!(config.paragraphs, "paragraphs should default to true");
267 }
268
269 #[test]
270 fn test_paragraphs_deserialization_kebab_case() {
271 let toml_str = r#"
273 paragraphs = false
274 "#;
275 let config: MD013Config = toml::from_str(toml_str).unwrap();
276 assert!(!config.paragraphs);
277 }
278
279 #[test]
280 fn test_paragraphs_full_config() {
281 let toml_str = r#"
283 line-length = 80
284 code-blocks = true
285 tables = true
286 headings = false
287 paragraphs = false
288 reflow = true
289 reflow-mode = "sentence-per-line"
290 "#;
291 let config: MD013Config = toml::from_str(toml_str).unwrap();
292 assert_eq!(config.line_length.get(), 80);
293 assert!(config.code_blocks, "code-blocks should be true");
294 assert!(config.tables, "tables should be true");
295 assert!(!config.headings, "headings should be false");
296 assert!(!config.paragraphs, "paragraphs should be false");
297 assert!(config.reflow, "reflow should be true");
298 assert_eq!(config.reflow_mode, ReflowMode::SentencePerLine);
299 }
300
301 #[test]
302 fn test_abbreviations_for_reflow_empty_vec() {
303 let config = MD013Config {
305 abbreviations: Vec::new(),
306 ..Default::default()
307 };
308 assert!(
309 config.abbreviations_for_reflow().is_none(),
310 "Empty abbreviations should return None for reflow"
311 );
312 }
313
314 #[test]
315 fn test_abbreviations_for_reflow_with_custom() {
316 let config = MD013Config {
318 abbreviations: vec!["Corp".to_string(), "Inc".to_string()],
319 ..Default::default()
320 };
321 let result = config.abbreviations_for_reflow();
322 assert!(result.is_some(), "Custom abbreviations should return Some");
323 let abbrevs = result.unwrap();
324 assert_eq!(abbrevs.len(), 2);
325 assert!(abbrevs.contains(&"Corp".to_string()));
326 assert!(abbrevs.contains(&"Inc".to_string()));
327 }
328}