stylua_lib/
editorconfig.rs

1use crate::{
2    CallParenType, CollapseSimpleStatement, Config, IndentType, LineEndings, QuoteStyle,
3    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}
67
68property_choice! {
69    SpaceAfterFunctionNamesChoice, "space_after_function_names";
70    (Always, "always"),
71    (Definitions, "definitions"),
72    (Calls, "calls"),
73    (Never, "never")
74}
75
76property_choice! {
77    CollapseSimpleStatementChoice, "collapse_simple_statement";
78    (Never, "never"),
79    (FunctionOnly, "functiononly"),
80    (ConditionalOnly, "conditionalonly"),
81    (Always, "always")
82}
83
84property_choice! {
85    SortRequiresChoice, "sort_requires";
86    (True, "true"),
87    (False, "false")
88}
89
90// Override StyLua config with EditorConfig properties
91fn load(mut config: Config, properties: &Properties) -> Config {
92    if let Ok(end_of_line) = properties.get::<EndOfLine>() {
93        match end_of_line {
94            EndOfLine::Cr | EndOfLine::Lf => config.line_endings = LineEndings::Unix,
95            EndOfLine::CrLf => config.line_endings = LineEndings::Windows,
96        }
97    }
98    if let Ok(indent_size) = properties.get::<IndentSize>() {
99        match indent_size {
100            IndentSize::Value(indent_width) => config.indent_width = indent_width,
101            IndentSize::UseTabWidth => {
102                if let Ok(TabWidth::Value(indent_width)) = properties.get::<TabWidth>() {
103                    config.indent_width = indent_width
104                }
105            }
106        }
107    }
108    if let Ok(indent_style) = properties.get::<IndentStyle>() {
109        match indent_style {
110            IndentStyle::Tabs => config.indent_type = IndentType::Tabs,
111            IndentStyle::Spaces => config.indent_type = IndentType::Spaces,
112        }
113    }
114    if let Ok(max_line_length) = properties.get::<MaxLineLen>() {
115        match max_line_length {
116            MaxLineLen::Value(column_width) => config.column_width = column_width,
117            MaxLineLen::Off => config.column_width = usize::MAX,
118        }
119    }
120    if let Ok(quote_type) = properties.get::<QuoteTypeChoice>() {
121        match quote_type {
122            QuoteTypeChoice::Double => config.quote_style = QuoteStyle::AutoPreferDouble,
123            QuoteTypeChoice::Single => config.quote_style = QuoteStyle::AutoPreferSingle,
124            QuoteTypeChoice::Auto => (),
125        }
126    }
127    if let Ok(call_parentheses) = properties.get::<CallParenthesesChoice>() {
128        match call_parentheses {
129            CallParenthesesChoice::Always => config.call_parentheses = CallParenType::Always,
130            CallParenthesesChoice::NoSingleString => {
131                config.call_parentheses = CallParenType::NoSingleString
132            }
133            CallParenthesesChoice::NoSingleTable => {
134                config.call_parentheses = CallParenType::NoSingleTable
135            }
136            CallParenthesesChoice::None => config.call_parentheses = CallParenType::None,
137        }
138    }
139    if let Ok(space_after_function_names) = properties.get::<SpaceAfterFunctionNamesChoice>() {
140        match space_after_function_names {
141            SpaceAfterFunctionNamesChoice::Always => {
142                config.space_after_function_names = SpaceAfterFunctionNames::Always
143            }
144            SpaceAfterFunctionNamesChoice::Definitions => {
145                config.space_after_function_names = SpaceAfterFunctionNames::Definitions
146            }
147            SpaceAfterFunctionNamesChoice::Calls => {
148                config.space_after_function_names = SpaceAfterFunctionNames::Calls
149            }
150            SpaceAfterFunctionNamesChoice::Never => {
151                config.space_after_function_names = SpaceAfterFunctionNames::Never
152            }
153        }
154    }
155    if let Ok(collapse_simple_statement) = properties.get::<CollapseSimpleStatementChoice>() {
156        match collapse_simple_statement {
157            CollapseSimpleStatementChoice::Never => {
158                config.collapse_simple_statement = CollapseSimpleStatement::Never
159            }
160            CollapseSimpleStatementChoice::FunctionOnly => {
161                config.collapse_simple_statement = CollapseSimpleStatement::FunctionOnly
162            }
163            CollapseSimpleStatementChoice::ConditionalOnly => {
164                config.collapse_simple_statement = CollapseSimpleStatement::ConditionalOnly
165            }
166            CollapseSimpleStatementChoice::Always => {
167                config.collapse_simple_statement = CollapseSimpleStatement::Always
168            }
169        }
170    }
171    if let Ok(sort_requires) = properties.get::<SortRequiresChoice>() {
172        match sort_requires {
173            SortRequiresChoice::True => config.sort_requires = SortRequiresConfig { enabled: true },
174            SortRequiresChoice::False => {
175                config.sort_requires = SortRequiresConfig { enabled: false }
176            }
177        }
178    }
179
180    config
181}
182
183// Read the EditorConfig files that would apply to a file at the given path
184pub fn parse(config: Config, path: &Path) -> Result<Config, Error> {
185    let properties = properties_of(path)?;
186
187    if properties.iter().count() == 0 {
188        return Ok(config);
189    }
190
191    log::debug!("editorconfig: found properties for {}", path.display());
192    let new_config = load(config, &properties);
193
194    Ok(new_config)
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    impl From<&Properties> for Config {
202        fn from(properties: &Properties) -> Self {
203            load(Config::default(), properties)
204        }
205    }
206
207    #[test]
208    fn test_end_of_line_cr() {
209        let mut properties = Properties::new();
210        properties.insert_raw_for_key("end_of_line", "CR");
211        let config = Config::from(&properties);
212        assert_eq!(config.line_endings, LineEndings::Unix);
213    }
214
215    #[test]
216    fn test_end_of_line_lf() {
217        let mut properties = Properties::new();
218        properties.insert_raw_for_key("end_of_line", "lf");
219        let config = Config::from(&properties);
220        assert_eq!(config.line_endings, LineEndings::Unix);
221    }
222
223    #[test]
224    fn test_end_of_line_crlf() {
225        let mut properties = Properties::new();
226        properties.insert_raw_for_key("end_of_line", "CrLf");
227        let config = Config::from(&properties);
228        assert_eq!(config.line_endings, LineEndings::Windows);
229    }
230
231    #[test]
232    fn test_indent_size() {
233        let mut properties = Properties::new();
234        properties.insert_raw_for_key("indent_size", "2");
235        let config = Config::from(&properties);
236        assert_eq!(config.indent_width, 2);
237    }
238
239    #[test]
240    fn test_indent_size_use_tab_width() {
241        let mut properties = Properties::new();
242        properties.insert_raw_for_key("tab_width", "8");
243        properties.insert_raw_for_key("indent_size", "tab");
244        let config = Config::from(&properties);
245        assert_eq!(config.indent_width, 8);
246    }
247
248    #[test]
249    fn test_indent_style_space() {
250        let mut properties = Properties::new();
251        properties.insert_raw_for_key("indent_style", "space");
252        let config = Config::from(&properties);
253        assert_eq!(config.indent_type, IndentType::Spaces);
254    }
255
256    #[test]
257    fn test_indent_style_tab() {
258        let mut properties = Properties::new();
259        properties.insert_raw_for_key("indent_style", "Tab");
260        let config = Config::from(&properties);
261        assert_eq!(config.indent_type, IndentType::Tabs);
262    }
263
264    #[test]
265    fn test_max_line_length() {
266        let mut properties = Properties::new();
267        properties.insert_raw_for_key("max_line_length", "80");
268        let config = Config::from(&properties);
269        assert_eq!(config.column_width, 80);
270    }
271
272    #[test]
273    fn test_max_line_length_off() {
274        let mut properties = Properties::new();
275        properties.insert_raw_for_key("max_line_length", "off");
276        let config = Config::from(&properties);
277        assert_eq!(config.column_width, usize::MAX);
278    }
279
280    #[test]
281    fn test_quote_type_double() {
282        let mut properties = Properties::new();
283        properties.insert_raw_for_key("quote_type", "double");
284        let config = Config::from(&properties);
285        assert_eq!(config.quote_style, QuoteStyle::AutoPreferDouble);
286    }
287
288    #[test]
289    fn test_quote_type_single() {
290        let mut properties = Properties::new();
291        properties.insert_raw_for_key("quote_type", "Single");
292        let config = Config::from(&properties);
293        assert_eq!(config.quote_style, QuoteStyle::AutoPreferSingle);
294    }
295
296    #[test]
297    fn test_quote_type_auto() {
298        let mut properties = Properties::new();
299        properties.insert_raw_for_key("quote_type", "auto");
300        let config = Config::from(&properties);
301        assert_eq!(config.quote_style, QuoteStyle::AutoPreferDouble);
302    }
303
304    #[test]
305    fn test_call_parentheses_always() {
306        let mut properties = Properties::new();
307        properties.insert_raw_for_key("call_parentheses", "always");
308        let config = Config::from(&properties);
309        assert_eq!(config.call_parentheses, CallParenType::Always);
310    }
311
312    #[test]
313    fn test_call_parentheses_no_single_string() {
314        let mut properties = Properties::new();
315        properties.insert_raw_for_key("call_parentheses", "NoSingleString");
316        let config = Config::from(&properties);
317        assert_eq!(config.call_parentheses, CallParenType::NoSingleString);
318    }
319
320    #[test]
321    fn test_call_parentheses_no_single_table() {
322        let mut properties = Properties::new();
323        properties.insert_raw_for_key("call_parentheses", "NoSingleTable");
324        let config = Config::from(&properties);
325        assert_eq!(config.call_parentheses, CallParenType::NoSingleTable);
326    }
327
328    #[test]
329    fn test_call_parentheses_none() {
330        let mut properties = Properties::new();
331        properties.insert_raw_for_key("call_parentheses", "None");
332        let config = Config::from(&properties);
333        assert_eq!(config.call_parentheses, CallParenType::None);
334    }
335
336    #[test]
337    fn test_space_after_function_names_always() {
338        let mut properties = Properties::new();
339        properties.insert_raw_for_key("space_after_function_names", "Always");
340        let config = Config::from(&properties);
341        assert_eq!(
342            config.space_after_function_names,
343            SpaceAfterFunctionNames::Always
344        );
345    }
346
347    #[test]
348    fn test_space_after_function_names_definitions() {
349        let mut properties = Properties::new();
350        properties.insert_raw_for_key("space_after_function_names", "Definitions");
351        let config = Config::from(&properties);
352        assert_eq!(
353            config.space_after_function_names,
354            SpaceAfterFunctionNames::Definitions
355        );
356    }
357
358    #[test]
359    fn test_space_after_function_names_calls() {
360        let mut properties = Properties::new();
361        properties.insert_raw_for_key("space_after_function_names", "Calls");
362        let config = Config::from(&properties);
363        assert_eq!(
364            config.space_after_function_names,
365            SpaceAfterFunctionNames::Calls
366        );
367    }
368
369    #[test]
370    fn test_space_after_function_names_never() {
371        let mut properties = Properties::new();
372        properties.insert_raw_for_key("space_after_function_names", "Never");
373        let config = Config::from(&properties);
374        assert_eq!(
375            config.space_after_function_names,
376            SpaceAfterFunctionNames::Never
377        );
378    }
379
380    #[test]
381    fn test_collapse_simple_statement_never() {
382        let mut properties = Properties::new();
383        properties.insert_raw_for_key("collapse_simple_statement", "Never");
384        let config = Config::from(&properties);
385        assert_eq!(
386            config.collapse_simple_statement,
387            CollapseSimpleStatement::Never
388        );
389    }
390
391    #[test]
392    fn test_collapse_simple_statement_function_only() {
393        let mut properties = Properties::new();
394        properties.insert_raw_for_key("collapse_simple_statement", "FunctionOnly");
395        let config = Config::from(&properties);
396        assert_eq!(
397            config.collapse_simple_statement,
398            CollapseSimpleStatement::FunctionOnly
399        );
400    }
401
402    #[test]
403    fn test_collapse_simple_statement_conditional_only() {
404        let mut properties = Properties::new();
405        properties.insert_raw_for_key("collapse_simple_statement", "ConditionalOnly");
406        let config = Config::from(&properties);
407        assert_eq!(
408            config.collapse_simple_statement,
409            CollapseSimpleStatement::ConditionalOnly
410        );
411    }
412
413    #[test]
414    fn test_collapse_simple_statement_always() {
415        let mut properties = Properties::new();
416        properties.insert_raw_for_key("collapse_simple_statement", "always");
417        let config = Config::from(&properties);
418        assert_eq!(
419            config.collapse_simple_statement,
420            CollapseSimpleStatement::Always
421        );
422    }
423
424    #[test]
425    fn test_sort_requires_enabled() {
426        let mut properties = Properties::new();
427        properties.insert_raw_for_key("sort_requires", "true");
428        let config = Config::from(&properties);
429        assert!(config.sort_requires.enabled);
430    }
431
432    #[test]
433    fn test_sort_requires_disabled() {
434        let mut properties = Properties::new();
435        properties.insert_raw_for_key("sort_requires", "false");
436        let config = Config::from(&properties);
437        assert!(!config.sort_requires.enabled);
438    }
439
440    #[test]
441    fn test_invalid_properties() {
442        let mut properties = Properties::new();
443        let default_config = Config::new();
444        let invalid_value = " ";
445        for key in [
446            "end_of_line",
447            "indent_size",
448            "indent_style",
449            "quote_style",
450            "call_parentheses",
451            "collapse_simple_statement",
452            "sort_requires",
453        ] {
454            properties.insert_raw_for_key(key, invalid_value);
455        }
456        let config = Config::from(&properties);
457        assert_eq!(config.line_endings, default_config.line_endings);
458        assert_eq!(config.indent_width, default_config.indent_width);
459        assert_eq!(config.indent_type, default_config.indent_type);
460        assert_eq!(config.column_width, default_config.column_width);
461        assert_eq!(config.quote_style, default_config.quote_style);
462        assert_eq!(config.call_parentheses, default_config.call_parentheses);
463        assert_eq!(
464            config.collapse_simple_statement,
465            default_config.collapse_simple_statement
466        );
467        assert_eq!(
468            config.sort_requires.enabled,
469            default_config.sort_requires.enabled
470        );
471    }
472}