1use crate::{
2 BlockNewlineGaps, CallParenType, CollapseSimpleStatement, Config, IndentType, LineEndings,
3 LuaVersion, QuoteStyle, SortRequiresConfig, SpaceAfterFunctionNames,
4};
5use ec4rs::{
6 properties_of,
7 property::{EndOfLine, IndentSize, IndentStyle, MaxLineLen, TabWidth, UnknownValueError},
8 rawvalue::RawValue,
9 Error, Properties, PropertyKey, PropertyValue,
10};
11use std::path::Path;
12
13macro_rules! property_choice {
15 ($prop_id:ident, $name:literal; $(($variant:ident, $string:literal)),+) => {
16 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
17 #[repr(u8)]
18 pub enum $prop_id {$($variant),+}
19
20 impl PropertyValue for $prop_id {
21 const MAYBE_UNSET: bool = false;
22 type Err = UnknownValueError;
23 fn parse(raw: &RawValue) -> Result<Self, Self::Err> {
24 match raw.into_str().to_lowercase().as_str() {
25 $($string => Ok($prop_id::$variant),)+
26 _ => Err(UnknownValueError)
27 }
28 }
29 }
30
31 impl From<$prop_id> for RawValue {
32 fn from(val: $prop_id) -> RawValue {
33 match val {
34 $($prop_id::$variant => RawValue::from($string)),*
35 }
36 }
37 }
38
39 impl PropertyKey for $prop_id {
40 fn key() -> &'static str {$name}
41 }
42
43 impl std::fmt::Display for $prop_id {
44 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
45 write!(f, "{}", match self {
46 $($prop_id::$variant => $string),*
47 })
48 }
49 }
50 }
51}
52
53property_choice! {
54 QuoteTypeChoice, "quote_type";
55 (Double, "double"),
56 (Single, "single"),
57 (Auto, "auto")
58}
59
60property_choice! {
61 CallParenthesesChoice, "call_parentheses";
62 (Always, "always"),
63 (NoSingleString, "nosinglestring"),
64 (NoSingleTable, "nosingletable"),
65 (None, "none"),
66 (Input, "input")
67}
68
69property_choice! {
70 SpaceAfterFunctionNamesChoice, "space_after_function_names";
71 (Always, "always"),
72 (Definitions, "definitions"),
73 (Calls, "calls"),
74 (Never, "never")
75}
76
77property_choice! {
78 CollapseSimpleStatementChoice, "collapse_simple_statement";
79 (Never, "never"),
80 (FunctionOnly, "functiononly"),
81 (ConditionalOnly, "conditionalonly"),
82 (Always, "always")
83}
84
85property_choice! {
86 SortRequiresChoice, "sort_requires";
87 (True, "true"),
88 (False, "false")
89}
90
91property_choice! {
92 StyluaSyntaxChoice, "stylua_syntax";
93 (All, "all"),
94 (Lua51, "lua51"),
95 (Lua52, "lua52"),
96 (Lua53, "lua53"),
97 (Lua54, "lua54"),
98 (Luau, "luau"),
99 (LuaJIT, "luajit"),
100 (CfxLua, "cfxlua")
101}
102
103property_choice! {
104 StyluaBlockNewlineGapsChoice, "stylua_block_newline_gaps";
105 (Never, "never"),
106 (Preserve, "preserve")
107}
108
109fn load(mut config: Config, properties: &Properties) -> Config {
111 if let Ok(end_of_line) = properties.get::<EndOfLine>() {
112 config.line_endings = match end_of_line {
113 EndOfLine::Cr | EndOfLine::Lf => LineEndings::Unix,
114 EndOfLine::CrLf => LineEndings::Windows,
115 };
116 }
117 if let Ok(indent_size) = properties.get::<IndentSize>() {
118 config.indent_width = match indent_size {
119 IndentSize::Value(indent_width) => indent_width,
120 IndentSize::UseTabWidth => match properties.get::<TabWidth>() {
121 Ok(TabWidth::Value(tab_width)) => tab_width,
122 _ => config.indent_width,
123 },
124 };
125 }
126 if let Ok(indent_style) = properties.get::<IndentStyle>() {
127 config.indent_type = match indent_style {
128 IndentStyle::Tabs => IndentType::Tabs,
129 IndentStyle::Spaces => IndentType::Spaces,
130 };
131 }
132 if let Ok(max_line_length) = properties.get::<MaxLineLen>() {
133 config.column_width = match max_line_length {
134 MaxLineLen::Value(column_width) => column_width,
135 MaxLineLen::Off => usize::MAX,
136 };
137 }
138 if let Ok(quote_type) = properties.get::<QuoteTypeChoice>() {
139 config.quote_style = match quote_type {
140 QuoteTypeChoice::Double => QuoteStyle::AutoPreferDouble,
141 QuoteTypeChoice::Single => QuoteStyle::AutoPreferSingle,
142 QuoteTypeChoice::Auto => config.quote_style,
143 };
144 }
145 if let Ok(call_parentheses) = properties.get::<CallParenthesesChoice>() {
146 config.call_parentheses = match call_parentheses {
147 CallParenthesesChoice::Always => CallParenType::Always,
148 CallParenthesesChoice::NoSingleString => CallParenType::NoSingleString,
149 CallParenthesesChoice::NoSingleTable => CallParenType::NoSingleTable,
150 CallParenthesesChoice::None => CallParenType::None,
151 CallParenthesesChoice::Input => CallParenType::Input,
152 };
153 }
154 if let Ok(space_after_function_names) = properties.get::<SpaceAfterFunctionNamesChoice>() {
155 config.space_after_function_names = match space_after_function_names {
156 SpaceAfterFunctionNamesChoice::Always => SpaceAfterFunctionNames::Always,
157 SpaceAfterFunctionNamesChoice::Definitions => SpaceAfterFunctionNames::Definitions,
158 SpaceAfterFunctionNamesChoice::Calls => SpaceAfterFunctionNames::Calls,
159 SpaceAfterFunctionNamesChoice::Never => SpaceAfterFunctionNames::Never,
160 };
161 }
162 if let Ok(collapse_simple_statement) = properties.get::<CollapseSimpleStatementChoice>() {
163 config.collapse_simple_statement = match collapse_simple_statement {
164 CollapseSimpleStatementChoice::Never => CollapseSimpleStatement::Never,
165 CollapseSimpleStatementChoice::FunctionOnly => CollapseSimpleStatement::FunctionOnly,
166 CollapseSimpleStatementChoice::ConditionalOnly => {
167 CollapseSimpleStatement::ConditionalOnly
168 }
169 CollapseSimpleStatementChoice::Always => CollapseSimpleStatement::Always,
170 };
171 }
172 if let Ok(sort_requires) = properties.get::<SortRequiresChoice>() {
173 config.sort_requires = match sort_requires {
174 SortRequiresChoice::True => SortRequiresConfig { enabled: true },
175 SortRequiresChoice::False => SortRequiresConfig { enabled: false },
176 };
177 }
178 if let Ok(syntax) = properties.get::<StyluaSyntaxChoice>() {
179 config.syntax = match syntax {
180 StyluaSyntaxChoice::All => LuaVersion::All,
181 StyluaSyntaxChoice::Lua51 => LuaVersion::Lua51,
182 #[cfg(feature = "lua52")]
183 StyluaSyntaxChoice::Lua52 => LuaVersion::Lua52,
184 #[cfg(feature = "lua53")]
185 StyluaSyntaxChoice::Lua53 => LuaVersion::Lua53,
186 #[cfg(feature = "lua54")]
187 StyluaSyntaxChoice::Lua54 => LuaVersion::Lua54,
188 #[cfg(feature = "luau")]
189 StyluaSyntaxChoice::Luau => LuaVersion::Luau,
190 #[cfg(feature = "luajit")]
191 StyluaSyntaxChoice::LuaJIT => LuaVersion::LuaJIT,
192 #[cfg(feature = "cfxlua")]
193 StyluaSyntaxChoice::CfxLua => LuaVersion::CfxLua,
194 #[allow(unreachable_patterns)]
196 _ => config.syntax,
197 };
198 }
199 if let Ok(block_newline_gaps) = properties.get::<StyluaBlockNewlineGapsChoice>() {
200 config.block_newline_gaps = match block_newline_gaps {
201 StyluaBlockNewlineGapsChoice::Never => BlockNewlineGaps::Never,
202 StyluaBlockNewlineGapsChoice::Preserve => BlockNewlineGaps::Preserve,
203 };
204 }
205
206 config
207}
208
209pub fn parse(config: Config, path: &Path) -> Result<Config, Error> {
211 let properties = properties_of(path)?;
212
213 if properties.iter().count() == 0 {
214 return Ok(config);
215 }
216
217 log::debug!("editorconfig: found properties for {}", path.display());
218 let new_config = load(config, &properties);
219
220 Ok(new_config)
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 impl From<&Properties> for Config {
228 fn from(properties: &Properties) -> Self {
229 load(Config::default(), properties)
230 }
231 }
232
233 #[test]
234 fn test_end_of_line_cr() {
235 let mut properties = Properties::new();
236 properties.insert_raw_for_key("end_of_line", "CR");
237 let config = Config::from(&properties);
238 assert_eq!(config.line_endings, LineEndings::Unix);
239 }
240
241 #[test]
242 fn test_end_of_line_lf() {
243 let mut properties = Properties::new();
244 properties.insert_raw_for_key("end_of_line", "lf");
245 let config = Config::from(&properties);
246 assert_eq!(config.line_endings, LineEndings::Unix);
247 }
248
249 #[test]
250 fn test_end_of_line_crlf() {
251 let mut properties = Properties::new();
252 properties.insert_raw_for_key("end_of_line", "CrLf");
253 let config = Config::from(&properties);
254 assert_eq!(config.line_endings, LineEndings::Windows);
255 }
256
257 #[test]
258 fn test_indent_size() {
259 let mut properties = Properties::new();
260 properties.insert_raw_for_key("indent_size", "2");
261 let config = Config::from(&properties);
262 assert_eq!(config.indent_width, 2);
263 }
264
265 #[test]
266 fn test_indent_size_use_tab_width() {
267 let mut properties = Properties::new();
268 properties.insert_raw_for_key("tab_width", "8");
269 properties.insert_raw_for_key("indent_size", "tab");
270 let config = Config::from(&properties);
271 assert_eq!(config.indent_width, 8);
272 }
273
274 #[test]
275 fn test_indent_style_space() {
276 let mut properties = Properties::new();
277 properties.insert_raw_for_key("indent_style", "space");
278 let config = Config::from(&properties);
279 assert_eq!(config.indent_type, IndentType::Spaces);
280 }
281
282 #[test]
283 fn test_indent_style_tab() {
284 let mut properties = Properties::new();
285 properties.insert_raw_for_key("indent_style", "Tab");
286 let config = Config::from(&properties);
287 assert_eq!(config.indent_type, IndentType::Tabs);
288 }
289
290 #[test]
291 fn test_max_line_length() {
292 let mut properties = Properties::new();
293 properties.insert_raw_for_key("max_line_length", "80");
294 let config = Config::from(&properties);
295 assert_eq!(config.column_width, 80);
296 }
297
298 #[test]
299 fn test_max_line_length_off() {
300 let mut properties = Properties::new();
301 properties.insert_raw_for_key("max_line_length", "off");
302 let config = Config::from(&properties);
303 assert_eq!(config.column_width, usize::MAX);
304 }
305
306 #[test]
307 fn test_quote_type_double() {
308 let mut properties = Properties::new();
309 properties.insert_raw_for_key("quote_type", "double");
310 let config = Config::from(&properties);
311 assert_eq!(config.quote_style, QuoteStyle::AutoPreferDouble);
312 }
313
314 #[test]
315 fn test_quote_type_single() {
316 let mut properties = Properties::new();
317 properties.insert_raw_for_key("quote_type", "Single");
318 let config = Config::from(&properties);
319 assert_eq!(config.quote_style, QuoteStyle::AutoPreferSingle);
320 }
321
322 #[test]
323 fn test_quote_type_auto() {
324 let mut properties = Properties::new();
325 properties.insert_raw_for_key("quote_type", "auto");
326 let config = Config::from(&properties);
327 assert_eq!(config.quote_style, QuoteStyle::AutoPreferDouble);
328 }
329
330 #[test]
331 fn test_call_parentheses_always() {
332 let mut properties = Properties::new();
333 properties.insert_raw_for_key("call_parentheses", "always");
334 let config = Config::from(&properties);
335 assert_eq!(config.call_parentheses, CallParenType::Always);
336 }
337
338 #[test]
339 fn test_call_parentheses_no_single_string() {
340 let mut properties = Properties::new();
341 properties.insert_raw_for_key("call_parentheses", "NoSingleString");
342 let config = Config::from(&properties);
343 assert_eq!(config.call_parentheses, CallParenType::NoSingleString);
344 }
345
346 #[test]
347 fn test_call_parentheses_no_single_table() {
348 let mut properties = Properties::new();
349 properties.insert_raw_for_key("call_parentheses", "NoSingleTable");
350 let config = Config::from(&properties);
351 assert_eq!(config.call_parentheses, CallParenType::NoSingleTable);
352 }
353
354 #[test]
355 fn test_call_parentheses_none() {
356 let mut properties = Properties::new();
357 properties.insert_raw_for_key("call_parentheses", "None");
358 let config = Config::from(&properties);
359 assert_eq!(config.call_parentheses, CallParenType::None);
360 }
361
362 #[test]
363 fn test_call_parentheses_input() {
364 let mut properties = Properties::new();
365 properties.insert_raw_for_key("call_parentheses", "Input");
366 let config = Config::from(&properties);
367 assert_eq!(config.call_parentheses, CallParenType::Input);
368 }
369
370 #[test]
371 fn test_space_after_function_names_always() {
372 let mut properties = Properties::new();
373 properties.insert_raw_for_key("space_after_function_names", "Always");
374 let config = Config::from(&properties);
375 assert_eq!(
376 config.space_after_function_names,
377 SpaceAfterFunctionNames::Always
378 );
379 }
380
381 #[test]
382 fn test_space_after_function_names_definitions() {
383 let mut properties = Properties::new();
384 properties.insert_raw_for_key("space_after_function_names", "Definitions");
385 let config = Config::from(&properties);
386 assert_eq!(
387 config.space_after_function_names,
388 SpaceAfterFunctionNames::Definitions
389 );
390 }
391
392 #[test]
393 fn test_space_after_function_names_calls() {
394 let mut properties = Properties::new();
395 properties.insert_raw_for_key("space_after_function_names", "Calls");
396 let config = Config::from(&properties);
397 assert_eq!(
398 config.space_after_function_names,
399 SpaceAfterFunctionNames::Calls
400 );
401 }
402
403 #[test]
404 fn test_space_after_function_names_never() {
405 let mut properties = Properties::new();
406 properties.insert_raw_for_key("space_after_function_names", "Never");
407 let config = Config::from(&properties);
408 assert_eq!(
409 config.space_after_function_names,
410 SpaceAfterFunctionNames::Never
411 );
412 }
413
414 #[test]
415 fn test_collapse_simple_statement_never() {
416 let mut properties = Properties::new();
417 properties.insert_raw_for_key("collapse_simple_statement", "Never");
418 let config = Config::from(&properties);
419 assert_eq!(
420 config.collapse_simple_statement,
421 CollapseSimpleStatement::Never
422 );
423 }
424
425 #[test]
426 fn test_collapse_simple_statement_function_only() {
427 let mut properties = Properties::new();
428 properties.insert_raw_for_key("collapse_simple_statement", "FunctionOnly");
429 let config = Config::from(&properties);
430 assert_eq!(
431 config.collapse_simple_statement,
432 CollapseSimpleStatement::FunctionOnly
433 );
434 }
435
436 #[test]
437 fn test_collapse_simple_statement_conditional_only() {
438 let mut properties = Properties::new();
439 properties.insert_raw_for_key("collapse_simple_statement", "ConditionalOnly");
440 let config = Config::from(&properties);
441 assert_eq!(
442 config.collapse_simple_statement,
443 CollapseSimpleStatement::ConditionalOnly
444 );
445 }
446
447 #[test]
448 fn test_collapse_simple_statement_always() {
449 let mut properties = Properties::new();
450 properties.insert_raw_for_key("collapse_simple_statement", "always");
451 let config = Config::from(&properties);
452 assert_eq!(
453 config.collapse_simple_statement,
454 CollapseSimpleStatement::Always
455 );
456 }
457
458 #[test]
459 fn test_sort_requires_enabled() {
460 let mut properties = Properties::new();
461 properties.insert_raw_for_key("sort_requires", "true");
462 let config = Config::from(&properties);
463 assert!(config.sort_requires.enabled);
464 }
465
466 #[test]
467 fn test_sort_requires_disabled() {
468 let mut properties = Properties::new();
469 properties.insert_raw_for_key("sort_requires", "false");
470 let config = Config::from(&properties);
471 assert!(!config.sort_requires.enabled);
472 }
473
474 #[test]
475 fn test_stylua_syntax_all() {
476 let mut properties = Properties::new();
477 properties.insert_raw_for_key("stylua_syntax", "all");
478 let config = Config::from(&properties);
479 assert_eq!(config.syntax, LuaVersion::All);
480 }
481
482 #[test]
483 fn test_stylua_syntax_lua51() {
484 let mut properties = Properties::new();
485 properties.insert_raw_for_key("stylua_syntax", "Lua51");
486 let config = Config::from(&properties);
487 assert_eq!(config.syntax, LuaVersion::Lua51);
488 }
489
490 #[test]
491 fn test_stylua_block_newline_gaps_never() {
492 let mut properties = Properties::new();
493 properties.insert_raw_for_key("stylua_block_newline_gaps", "Never");
494 let config = Config::from(&properties);
495 assert_eq!(config.block_newline_gaps, BlockNewlineGaps::Never);
496 }
497
498 #[test]
499 fn test_stylua_block_newline_gaps_preserve() {
500 let mut properties = Properties::new();
501 properties.insert_raw_for_key("stylua_block_newline_gaps", "Preserve");
502 let config = Config::from(&properties);
503 assert_eq!(config.block_newline_gaps, BlockNewlineGaps::Preserve);
504 }
505
506 #[test]
507 fn test_invalid_properties() {
508 let mut properties = Properties::new();
509 let default_config = Config::new();
510 let invalid_value = " ";
511 for key in [
512 "end_of_line",
513 "indent_size",
514 "indent_style",
515 "quote_style",
516 "call_parentheses",
517 "collapse_simple_statement",
518 "sort_requires",
519 "stylua_syntax",
520 "stylua_block_newline_gaps",
521 ] {
522 properties.insert_raw_for_key(key, invalid_value);
523 }
524 let config = Config::from(&properties);
525 assert_eq!(config.line_endings, default_config.line_endings);
526 assert_eq!(config.indent_width, default_config.indent_width);
527 assert_eq!(config.indent_type, default_config.indent_type);
528 assert_eq!(config.column_width, default_config.column_width);
529 assert_eq!(config.quote_style, default_config.quote_style);
530 assert_eq!(config.call_parentheses, default_config.call_parentheses);
531 assert_eq!(
532 config.collapse_simple_statement,
533 default_config.collapse_simple_statement
534 );
535 assert_eq!(
536 config.sort_requires.enabled,
537 default_config.sort_requires.enabled
538 );
539 assert_eq!(config.syntax, default_config.syntax);
540 assert_eq!(config.block_newline_gaps, default_config.block_newline_gaps);
541 }
542}