@@ -9,39 +9,43 @@ use crate::{
99 Semicolons , SortImports , SortOrder , TrailingCommas ,
1010} ;
1111
12+ /// Configuration options for the formatter.
13+ /// Most options are the same as Prettier's options.
14+ /// See also <https://prettier.io/docs/options>
1215#[ derive( Debug , Default , Clone , Deserialize , Serialize , JsonSchema ) ]
1316#[ serde( rename_all = "camelCase" , default ) ]
1417pub struct Oxfmtrc {
1518 #[ serde( skip_serializing_if = "Option::is_none" ) ]
16- pub indent_style : Option < String > ,
19+ pub use_tabs : Option < bool > ,
1720 #[ serde( skip_serializing_if = "Option::is_none" ) ]
18- pub indent_width : Option < u8 > ,
21+ pub tab_width : Option < u8 > ,
1922 #[ serde( skip_serializing_if = "Option::is_none" ) ]
20- pub line_ending : Option < String > ,
23+ pub end_of_line : Option < String > ,
2124 #[ serde( skip_serializing_if = "Option::is_none" ) ]
22- pub line_width : Option < u16 > ,
25+ pub print_width : Option < u16 > ,
2326 #[ serde( skip_serializing_if = "Option::is_none" ) ]
24- pub quote_style : Option < String > ,
27+ pub single_quote : Option < bool > ,
2528 #[ serde( skip_serializing_if = "Option::is_none" ) ]
26- pub jsx_quote_style : Option < String > ,
29+ pub jsx_single_quote : Option < bool > ,
2730 #[ serde( skip_serializing_if = "Option::is_none" ) ]
28- pub quote_properties : Option < String > ,
31+ pub quote_props : Option < String > ,
2932 #[ serde( skip_serializing_if = "Option::is_none" ) ]
30- pub trailing_commas : Option < String > ,
33+ pub trailing_comma : Option < String > ,
3134 #[ serde( skip_serializing_if = "Option::is_none" ) ]
32- pub semicolons : Option < String > ,
35+ pub semi : Option < bool > ,
3336 #[ serde( skip_serializing_if = "Option::is_none" ) ]
34- pub arrow_parentheses : Option < String > ,
37+ pub arrow_parens : Option < String > ,
3538 #[ serde( skip_serializing_if = "Option::is_none" ) ]
3639 pub bracket_spacing : Option < bool > ,
3740 #[ serde( skip_serializing_if = "Option::is_none" ) ]
3841 pub bracket_same_line : Option < bool > ,
3942 #[ serde( skip_serializing_if = "Option::is_none" ) ]
40- pub attribute_position : Option < String > ,
43+ pub object_wrap : Option < String > ,
4144 #[ serde( skip_serializing_if = "Option::is_none" ) ]
42- pub expand : Option < String > ,
45+ pub single_attribute_per_line : Option < bool > ,
4346 #[ serde( skip_serializing_if = "Option::is_none" ) ]
4447 pub experimental_operator_position : Option < String > ,
48+ // TODO: experimental_ternaries
4549 #[ serde( skip_serializing_if = "Option::is_none" ) ]
4650 pub experimental_sort_imports : Option < SortImportsConfig > ,
4751}
@@ -106,84 +110,112 @@ impl Oxfmtrc {
106110 pub fn into_format_options ( self ) -> Result < FormatOptions , String > {
107111 let mut options = FormatOptions :: default ( ) ;
108112
109- if let Some ( style ) = self . indent_style {
110- options . indent_style =
111- style . parse :: < IndentStyle > ( ) . map_err ( |e| format ! ( "Invalid indent_style: {e}" ) ) ? ;
113+ // [Prettier] useTabs: boolean
114+ if let Some ( use_tabs ) = self . use_tabs {
115+ options . indent_style = if use_tabs { IndentStyle :: Tab } else { IndentStyle :: Space } ;
112116 }
113117
114- if let Some ( width) = self . indent_width {
118+ // [Prettier] tabWidth: number
119+ if let Some ( width) = self . tab_width {
115120 options. indent_width =
116- IndentWidth :: try_from ( width) . map_err ( |e| format ! ( "Invalid indent_width : {e}" ) ) ?;
121+ IndentWidth :: try_from ( width) . map_err ( |e| format ! ( "Invalid tabWidth : {e}" ) ) ?;
117122 }
118123
119- if let Some ( ending) = self . line_ending {
124+ // [Prettier] endOfLine: "lf" | "cr" | "crlf" | "auto"
125+ // NOTE: "auto" is not supported
126+ if let Some ( ending) = self . end_of_line {
120127 options. line_ending =
121- ending. parse :: < LineEnding > ( ) . map_err ( |e| format ! ( "Invalid line_ending : {e}" ) ) ?;
128+ ending. parse :: < LineEnding > ( ) . map_err ( |e| format ! ( "Invalid endOfLine : {e}" ) ) ?;
122129 }
123130
124- if let Some ( width) = self . line_width {
131+ // [Prettier] printWidth: number
132+ if let Some ( width) = self . print_width {
125133 options. line_width =
126- LineWidth :: try_from ( width) . map_err ( |e| format ! ( "Invalid line_width : {e}" ) ) ?;
134+ LineWidth :: try_from ( width) . map_err ( |e| format ! ( "Invalid printWidth : {e}" ) ) ?;
127135 }
128136
129- if let Some ( style) = self . quote_style {
137+ // [Prettier] singleQuote: boolean
138+ if let Some ( single_quote) = self . single_quote {
130139 options. quote_style =
131- style . parse :: < QuoteStyle > ( ) . map_err ( |e| format ! ( "Invalid quote_style: {e}" ) ) ? ;
140+ if single_quote { QuoteStyle :: Single } else { QuoteStyle :: Double } ;
132141 }
133142
134- if let Some ( style) = self . jsx_quote_style {
143+ // [Prettier] jsxSingleQuote: boolean
144+ if let Some ( jsx_single_quote) = self . jsx_single_quote {
135145 options. jsx_quote_style =
136- style . parse :: < QuoteStyle > ( ) . map_err ( |e| format ! ( "Invalid jsx_quote_style: {e}" ) ) ? ;
146+ if jsx_single_quote { QuoteStyle :: Single } else { QuoteStyle :: Double } ;
137147 }
138148
139- if let Some ( props) = self . quote_properties {
140- options. quote_properties = props
141- . parse :: < QuoteProperties > ( )
142- . map_err ( |e| format ! ( "Invalid quote_properties: {e}" ) ) ?;
149+ // [Prettier] quoteProps: "as-needed" | "consistent" | "preserve"
150+ // NOTE: "consistent" is not supported
151+ if let Some ( props) = self . quote_props {
152+ options. quote_properties =
153+ props. parse :: < QuoteProperties > ( ) . map_err ( |e| format ! ( "Invalid quoteProps: {e}" ) ) ?;
143154 }
144155
145- if let Some ( commas) = self . trailing_commas {
156+ // [Prettier] trailingComma: "all" | "es5" | "none"
157+ if let Some ( commas) = self . trailing_comma {
146158 options. trailing_commas = commas
147159 . parse :: < TrailingCommas > ( )
148- . map_err ( |e| format ! ( "Invalid trailing_commas : {e}" ) ) ?;
160+ . map_err ( |e| format ! ( "Invalid trailingComma : {e}" ) ) ?;
149161 }
150162
151- if let Some ( semis ) = self . semicolons {
152- options . semicolons =
153- semis . parse :: < Semicolons > ( ) . map_err ( |e| format ! ( "Invalid semicolons: {e}" ) ) ? ;
163+ // [Prettier] semi: boolean -> Semicolons
164+ if let Some ( semi ) = self . semi {
165+ options . semicolons = if semi { Semicolons :: Always } else { Semicolons :: AsNeeded } ;
154166 }
155167
156- if let Some ( parens) = self . arrow_parentheses {
157- options. arrow_parentheses = parens
168+ // [Prettier] arrowParens: "avoid" | "always"
169+ if let Some ( parens) = self . arrow_parens {
170+ let normalized = match parens. as_str ( ) {
171+ "avoid" => "as-needed" ,
172+ _ => & parens,
173+ } ;
174+ options. arrow_parentheses = normalized
158175 . parse :: < ArrowParentheses > ( )
159- . map_err ( |e| format ! ( "Invalid arrow_parentheses : {e}" ) ) ?;
176+ . map_err ( |e| format ! ( "Invalid arrowParens : {e}" ) ) ?;
160177 }
161178
179+ // [Prettier] bracketSpacing: boolean
162180 if let Some ( spacing) = self . bracket_spacing {
163181 options. bracket_spacing = BracketSpacing :: from ( spacing) ;
164182 }
165183
184+ // [Prettier] bracketSameLine: boolean
166185 if let Some ( same_line) = self . bracket_same_line {
167186 options. bracket_same_line = BracketSameLine :: from ( same_line) ;
168187 }
169188
170- if let Some ( position) = self . attribute_position {
171- options. attribute_position = position
172- . parse :: < AttributePosition > ( )
173- . map_err ( |e| format ! ( "Invalid attribute_position: {e}" ) ) ?;
189+ // [Prettier] singleAttributePerLine: boolean
190+ if let Some ( single_attribute_per_line) = self . single_attribute_per_line {
191+ options. attribute_position = if single_attribute_per_line {
192+ AttributePosition :: Multiline
193+ } else {
194+ AttributePosition :: Auto
195+ } ;
174196 }
175197
176- if let Some ( expand) = self . expand {
198+ // [Prettier] objectWrap: "preserve" | "collapse"
199+ // NOTE: In addition to Prettier, we also support "always"
200+ if let Some ( object_wrap) = self . object_wrap {
201+ let normalized = match object_wrap. as_str ( ) {
202+ "preserve" => "auto" ,
203+ "collapse" => "never" ,
204+ _ => & object_wrap,
205+ } ;
177206 options. expand =
178- expand . parse :: < Expand > ( ) . map_err ( |e| format ! ( "Invalid expand : {e}" ) ) ?;
207+ normalized . parse :: < Expand > ( ) . map_err ( |e| format ! ( "Invalid objectWrap : {e}" ) ) ?;
179208 }
180209
210+ // [Prettier] experimentalOperatorPosition: "start" | "end"
181211 if let Some ( position) = self . experimental_operator_position {
182212 options. experimental_operator_position = position
183213 . parse :: < OperatorPosition > ( )
184214 . map_err ( |e| format ! ( "Invalid experimental_operator_position: {e}" ) ) ?;
185215 }
186216
217+ // Below are our own extensions
218+
187219 if let Some ( sort_imports_config) = self . experimental_sort_imports {
188220 let order = sort_imports_config
189221 . order
@@ -210,11 +242,11 @@ mod tests {
210242 #[ test]
211243 fn test_config_parsing ( ) {
212244 let json = r#"{
213- "indentStyle ": "tab" ,
214- "indentWidth ": 4,
215- "lineWidth ": 100,
216- "quoteStyle ": "single" ,
217- "semicolons ": "as-needed" ,
245+ "useTabs ": true ,
246+ "tabWidth ": 4,
247+ "printWidth ": 100,
248+ "singleQuote ": true ,
249+ "semi ": false ,
218250 "experimentalSortImports": {
219251 "partitionByNewline": true,
220252 "order": "desc",
@@ -228,6 +260,8 @@ mod tests {
228260 assert ! ( options. indent_style. is_tab( ) ) ;
229261 assert_eq ! ( options. indent_width. value( ) , 4 ) ;
230262 assert_eq ! ( options. line_width. value( ) , 100 ) ;
263+ assert ! ( !options. quote_style. is_double( ) ) ;
264+ assert ! ( options. semicolons. is_as_needed( ) ) ;
231265
232266 let sort_imports = options. experimental_sort_imports . unwrap ( ) ;
233267 assert ! ( sort_imports. partition_by_newline) ;
@@ -264,4 +298,35 @@ mod tests {
264298 assert_eq ! ( options. line_width. value( ) , 80 ) ;
265299 assert_eq ! ( options. experimental_sort_imports, None ) ;
266300 }
301+
302+ #[ test]
303+ fn test_arrow_parens_normalization ( ) {
304+ // Test "avoid" -> "as-needed" normalization
305+ let config: Oxfmtrc = serde_json:: from_str ( r#"{"arrowParens": "avoid"}"# ) . unwrap ( ) ;
306+ let options = config. into_format_options ( ) . unwrap ( ) ;
307+ assert ! ( options. arrow_parentheses. is_as_needed( ) ) ;
308+
309+ // Test "always" remains unchanged
310+ let config: Oxfmtrc = serde_json:: from_str ( r#"{"arrowParens": "always"}"# ) . unwrap ( ) ;
311+ let options = config. into_format_options ( ) . unwrap ( ) ;
312+ assert ! ( options. arrow_parentheses. is_always( ) ) ;
313+ }
314+
315+ #[ test]
316+ fn test_object_wrap_normalization ( ) {
317+ // Test "preserve" -> "auto" normalization
318+ let config: Oxfmtrc = serde_json:: from_str ( r#"{"objectWrap": "preserve"}"# ) . unwrap ( ) ;
319+ let options = config. into_format_options ( ) . unwrap ( ) ;
320+ assert_eq ! ( options. expand, Expand :: Auto ) ;
321+
322+ // Test "collapse" -> "never" normalization
323+ let config: Oxfmtrc = serde_json:: from_str ( r#"{"objectWrap": "collapse"}"# ) . unwrap ( ) ;
324+ let options = config. into_format_options ( ) . unwrap ( ) ;
325+ assert_eq ! ( options. expand, Expand :: Never ) ;
326+
327+ // Test "always" remains unchanged
328+ let config: Oxfmtrc = serde_json:: from_str ( r#"{"objectWrap": "always"}"# ) . unwrap ( ) ;
329+ let options = config. into_format_options ( ) . unwrap ( ) ;
330+ assert_eq ! ( options. expand, Expand :: Always ) ;
331+ }
267332}
0 commit comments