Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

Commit ec54d1d

Browse files
feat(rome_js_formatter, rome_cli): add arrowParentheses option (#4667)
1 parent 5510f5f commit ec54d1d

File tree

217 files changed

+1216
-47
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

217 files changed

+1216
-47
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ if no error diagnostics are emitted.
101101

102102
- Added a new option called `--jsx-quote-style` to the formatter. This option allows you to choose between single and double quotes for JSX attributes. [#4486](https://github.com/rome/tools/issues/4486)
103103

104+
- Added a new option called `--arrow-parentheses` to the formatter. This option allows you to set the parentheses style for arrow functions. [#4666](https://github.com/rome/tools/issues/4666)
105+
104106
### Linter
105107

106108
- [`noDuplicateParameters`](https://docs.rome.tools/lint/rules/noduplicateparameters/): enhanced rule to manage constructor parameters.

crates/rome_cli/tests/commands/format.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ const APPLY_TRAILING_COMMA_AFTER: &str = r#"const a = [
5252
];
5353
"#;
5454

55+
const APPLY_ARROW_PARENTHESES_BEFORE: &str = r#"
56+
action => {}
57+
(action) => {}
58+
({ action }) => {}
59+
([ action ]) => {}
60+
(...action) => {}
61+
(action = 1) => {}
62+
"#;
63+
64+
const APPLY_ARROW_PARENTHESES_AFTER: &str = r#"action => {};
65+
action => {};
66+
({ action }) => {};
67+
([action]) => {};
68+
(...action) => {};
69+
(action = 1) => {};
70+
"#;
71+
5572
const DEFAULT_CONFIGURATION_BEFORE: &str = r#"function f() {
5673
return { a, b }
5774
}"#;
@@ -707,6 +724,51 @@ fn applies_custom_trailing_comma() {
707724
));
708725
}
709726

727+
#[test]
728+
fn applies_custom_arrow_parentheses() {
729+
let mut fs = MemoryFileSystem::default();
730+
let mut console = BufferConsole::default();
731+
732+
let file_path = Path::new("file.js");
733+
fs.insert(file_path.into(), APPLY_ARROW_PARENTHESES_BEFORE.as_bytes());
734+
735+
let result = run_cli(
736+
DynRef::Borrowed(&mut fs),
737+
&mut console,
738+
Args::from(
739+
[
740+
("format"),
741+
("--arrow-parentheses"),
742+
("as-needed"),
743+
("--write"),
744+
file_path.as_os_str().to_str().unwrap(),
745+
]
746+
.as_slice(),
747+
),
748+
);
749+
750+
assert!(result.is_ok(), "run_cli returned {result:?}");
751+
752+
let mut file = fs
753+
.open(file_path)
754+
.expect("formatting target file was removed by the CLI");
755+
756+
let mut content = String::new();
757+
file.read_to_string(&mut content)
758+
.expect("failed to read file from memory FS");
759+
760+
assert_eq!(content, APPLY_ARROW_PARENTHESES_AFTER);
761+
762+
drop(file);
763+
assert_cli_snapshot(SnapshotPayload::new(
764+
module_path!(),
765+
"applies_custom_arrow_parentheses",
766+
fs,
767+
console,
768+
result,
769+
));
770+
}
771+
710772
#[test]
711773
fn trailing_comma_parse_errors() {
712774
let mut console = BufferConsole::default();

crates/rome_cli/tests/snapshots/main_commands_check/check_help.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ The configuration that is contained inside the file `rome.json`
3333
syntactic structures. Defaults to "all".
3434
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
3535
only in for statements where it is necessary because of ASI.
36+
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
37+
Defaults to "always".
3638
3739
Global options applied to all commands
3840
--colors=<off|force> Set the formatting mode for markup: "off" prints everything as plain

crates/rome_cli/tests/snapshots/main_commands_ci/ci_help.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ The configuration that is contained inside the file `rome.json`
3434
syntactic structures. Defaults to "all".
3535
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
3636
only in for statements where it is necessary because of ASI.
37+
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
38+
Defaults to "always".
3739
3840
Global options applied to all commands
3941
--colors=<off|force> Set the formatting mode for markup: "off" prints everything as plain
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
source: crates/rome_cli/tests/snap_test.rs
3+
expression: content
4+
---
5+
## `file.js`
6+
7+
```js
8+
action => {};
9+
action => {};
10+
({ action }) => {};
11+
([action]) => {};
12+
(...action) => {};
13+
(action = 1) => {};
14+
15+
```
16+
17+
# Emitted Messages
18+
19+
```block
20+
Formatted 1 file(s) in <TIME>
21+
```
22+
23+

crates/rome_cli/tests/snapshots/main_commands_format/format_help.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ Available options:
5959
syntactic structures. Defaults to "all".
6060
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
6161
only in for statements where it is necessary because of ASI.
62+
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
63+
Defaults to "always".
6264
--stdin-file-path=PATH A file name with its extension to pass when reading from standard in,
6365
e.g. echo 'let a;' | rome format --stdin-file-path=file.js".
6466
--write Writes formatted files to file system.

crates/rome_cli/tests/snapshots/main_commands_lint/check_help.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ The configuration that is contained inside the file `rome.json`
3333
syntactic structures. Defaults to "all".
3434
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
3535
only in for statements where it is necessary because of ASI.
36+
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
37+
Defaults to "always".
3638
3739
Global options applied to all commands
3840
--colors=<off|force> Set the formatting mode for markup: "off" prints everything as plain

crates/rome_js_formatter/src/context.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ pub struct JsFormatOptions {
156156
/// Whether the formatter prints semicolons for all statements, class members, and type members or only when necessary because of [ASI](https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-automatic-semicolon-insertion).
157157
semicolons: Semicolons,
158158

159+
/// Whether to add non-necessary parentheses to arrow functions. Defaults to "always".
160+
arrow_parentheses: ArrowParentheses,
161+
159162
/// Information related to the current file
160163
source_type: JsFileSource,
161164
}
@@ -171,9 +174,15 @@ impl JsFormatOptions {
171174
quote_properties: QuoteProperties::default(),
172175
trailing_comma: TrailingComma::default(),
173176
semicolons: Semicolons::default(),
177+
arrow_parentheses: ArrowParentheses::default(),
174178
}
175179
}
176180

181+
pub fn with_arrow_parentheses(mut self, arrow_parentheses: ArrowParentheses) -> Self {
182+
self.arrow_parentheses = arrow_parentheses;
183+
self
184+
}
185+
177186
pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self {
178187
self.indent_style = indent_style;
179188
self
@@ -209,6 +218,10 @@ impl JsFormatOptions {
209218
self
210219
}
211220

221+
pub fn arrow_parentheses(&self) -> ArrowParentheses {
222+
self.arrow_parentheses
223+
}
224+
212225
pub fn quote_style(&self) -> QuoteStyle {
213226
self.quote_style
214227
}
@@ -263,7 +276,8 @@ impl fmt::Display for JsFormatOptions {
263276
writeln!(f, "JSX quote style: {}", self.jsx_quote_style)?;
264277
writeln!(f, "Quote properties: {}", self.quote_properties)?;
265278
writeln!(f, "Trailing comma: {}", self.trailing_comma)?;
266-
writeln!(f, "Semicolons: {}", self.semicolons)
279+
writeln!(f, "Semicolons: {}", self.semicolons)?;
280+
writeln!(f, "Arrow parentheses: {}", self.arrow_parentheses)
267281
}
268282
}
269283

@@ -487,3 +501,64 @@ impl VisitNode<JsonLanguage> for Semicolons {
487501
Some(())
488502
}
489503
}
504+
505+
#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
506+
#[cfg_attr(
507+
feature = "serde",
508+
derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
509+
serde(rename_all = "camelCase")
510+
)]
511+
pub enum ArrowParentheses {
512+
#[default]
513+
Always,
514+
AsNeeded,
515+
}
516+
517+
impl ArrowParentheses {
518+
pub(crate) const KNOWN_VALUES: &'static [&'static str] = &["always", "asNeeded"];
519+
520+
pub const fn is_as_needed(&self) -> bool {
521+
matches!(self, Self::AsNeeded)
522+
}
523+
524+
pub const fn is_always(&self) -> bool {
525+
matches!(self, Self::Always)
526+
}
527+
}
528+
529+
impl FromStr for ArrowParentheses {
530+
type Err = &'static str;
531+
532+
fn from_str(s: &str) -> Result<Self, Self::Err> {
533+
match s {
534+
"as-needed" | "AsNeeded" => Ok(Self::AsNeeded),
535+
"always" | "Always" => Ok(Self::Always),
536+
_ => Err("Value not supported for Arrow parentheses. Supported values are 'as-needed' and 'always'."),
537+
}
538+
}
539+
}
540+
541+
impl fmt::Display for ArrowParentheses {
542+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
543+
match self {
544+
ArrowParentheses::AsNeeded => write!(f, "As needed"),
545+
ArrowParentheses::Always => write!(f, "Always"),
546+
}
547+
}
548+
}
549+
550+
impl VisitNode<JsonLanguage> for ArrowParentheses {
551+
fn visit_member_value(
552+
&mut self,
553+
node: &SyntaxNode<JsonLanguage>,
554+
diagnostics: &mut Vec<DeserializationDiagnostic>,
555+
) -> Option<()> {
556+
let node = with_only_known_variants(node, ArrowParentheses::KNOWN_VALUES, diagnostics)?;
557+
if node.inner_string_text().ok()?.text() == "asNeeded" {
558+
*self = ArrowParentheses::AsNeeded;
559+
} else {
560+
*self = ArrowParentheses::Always;
561+
}
562+
Some(())
563+
}
564+
}

crates/rome_js_formatter/src/js/bindings/parameters.rs

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::prelude::*;
22
use rome_formatter::{write, CstFormatContext};
33

4+
use crate::js::expressions::arrow_function_expression::can_avoid_parentheses;
45
use crate::js::lists::parameter_list::FormatJsAnyParameterList;
56
use crate::utils::test_call::is_test_call_argument;
67
use rome_js_syntax::parameter_ext::{AnyJsParameterList, AnyParameter};
78
use rome_js_syntax::{
8-
AnyJsConstructorParameter, AnyJsFormalParameter, AnyTsType, JsConstructorParameters,
9-
JsParameters, JsSyntaxToken,
9+
AnyJsConstructorParameter, AnyJsFormalParameter, AnyTsType, JsArrowFunctionExpression,
10+
JsConstructorParameters, JsParameters, JsSyntaxToken,
1011
};
1112
use rome_rowan::{declare_node_union, SyntaxResult};
1213

@@ -48,6 +49,10 @@ impl Format<JsFormatContext> for FormatAnyJsParameters {
4849
let l_paren_token = self.l_paren_token()?;
4950
let r_paren_token = self.r_paren_token()?;
5051

52+
let parentheses_not_needed = self
53+
.as_arrow_function_expression()
54+
.map_or(false, |expression| can_avoid_parentheses(&expression, f));
55+
5156
match layout {
5257
ParameterLayout::NoParameters => {
5358
write!(
@@ -60,27 +65,50 @@ impl Format<JsFormatContext> for FormatAnyJsParameters {
6065
)
6166
}
6267
ParameterLayout::Hug => {
68+
if !parentheses_not_needed {
69+
write!(f, [l_paren_token.format()])?;
70+
} else {
71+
write!(f, [format_removed(&l_paren_token)])?;
72+
}
73+
6374
write!(
6475
f,
65-
[
66-
l_paren_token.format(),
67-
FormatJsAnyParameterList::with_layout(&list, ParameterLayout::Hug),
68-
&r_paren_token.format()
69-
]
70-
)
76+
[FormatJsAnyParameterList::with_layout(
77+
&list,
78+
ParameterLayout::Hug
79+
)]
80+
)?;
81+
82+
if !parentheses_not_needed {
83+
write!(f, [&r_paren_token.format()])?;
84+
} else {
85+
write!(f, [format_removed(&r_paren_token)])?;
86+
}
87+
88+
Ok(())
7189
}
7290
ParameterLayout::Default => {
91+
if !parentheses_not_needed {
92+
write!(f, [l_paren_token.format()])?;
93+
} else {
94+
write!(f, [format_removed(&l_paren_token)])?;
95+
}
96+
7397
write!(
7498
f,
75-
[
76-
l_paren_token.format(),
77-
soft_block_indent(&FormatJsAnyParameterList::with_layout(
78-
&list,
79-
ParameterLayout::Default
80-
)),
81-
r_paren_token.format()
82-
]
83-
)
99+
[soft_block_indent(&FormatJsAnyParameterList::with_layout(
100+
&list,
101+
ParameterLayout::Default
102+
))]
103+
)?;
104+
105+
if !parentheses_not_needed {
106+
write!(f, [r_paren_token.format()])?;
107+
} else {
108+
write!(f, [format_removed(&r_paren_token)])?;
109+
}
110+
111+
Ok(())
84112
}
85113
}
86114
}
@@ -128,6 +156,16 @@ impl FormatAnyJsParameters {
128156

129157
Ok(result)
130158
}
159+
160+
fn as_arrow_function_expression(&self) -> Option<JsArrowFunctionExpression> {
161+
match self {
162+
FormatAnyJsParameters::JsParameters(parameters) => parameters
163+
.syntax()
164+
.parent()
165+
.and_then(JsArrowFunctionExpression::cast),
166+
FormatAnyJsParameters::JsConstructorParameters(_) => None,
167+
}
168+
}
131169
}
132170

133171
#[derive(Copy, Debug, Clone, Eq, PartialEq)]

0 commit comments

Comments
 (0)