Skip to main content

stylua_lib/
editorconfig.rs

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
13// Extracted from ec4rs::property
14macro_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
109// Override StyLua config with EditorConfig properties
110fn 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            // If the feature is not enabled, ignore the value
195            #[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
209// Read the EditorConfig files that would apply to a file at the given path
210pub 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}