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

feat(rome_js_formatter, rome_cli): add arrowParentheses option #4667

Merged
merged 7 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ multiple files:

- 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)

- 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)

### Linter

- [`noDuplicateParameters`](https://docs.rome.tools/lint/rules/noduplicateparameters/): enhanced rule to manage constructor parameters.
Expand Down
62 changes: 62 additions & 0 deletions crates/rome_cli/tests/commands/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ const APPLY_TRAILING_COMMA_AFTER: &str = r#"const a = [
];
"#;

const APPLY_ARROW_PARENTHESES_BEFORE: &str = r#"
action => {}
(action) => {}
({ action }) => {}
([ action ]) => {}
(...action) => {}
(action = 1) => {}
"#;

const APPLY_ARROW_PARENTHESES_AFTER: &str = r#"action => {};
action => {};
({ action }) => {};
([action]) => {};
(...action) => {};
(action = 1) => {};
"#;

const DEFAULT_CONFIGURATION_BEFORE: &str = r#"function f() {
return { a, b }
}"#;
Expand Down Expand Up @@ -707,6 +724,51 @@ fn applies_custom_trailing_comma() {
));
}

#[test]
fn applies_custom_arrow_parentheses() {
let mut fs = MemoryFileSystem::default();
let mut console = BufferConsole::default();

let file_path = Path::new("file.js");
fs.insert(file_path.into(), APPLY_ARROW_PARENTHESES_BEFORE.as_bytes());

let result = run_cli(
DynRef::Borrowed(&mut fs),
&mut console,
Args::from(
[
("format"),
("--arrow-parentheses"),
("as-needed"),
("--write"),
file_path.as_os_str().to_str().unwrap(),
]
.as_slice(),
),
);

assert!(result.is_ok(), "run_cli returned {result:?}");

let mut file = fs
.open(file_path)
.expect("formatting target file was removed by the CLI");

let mut content = String::new();
file.read_to_string(&mut content)
.expect("failed to read file from memory FS");

assert_eq!(content, APPLY_ARROW_PARENTHESES_AFTER);

drop(file);
assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"applies_custom_arrow_parentheses",
fs,
console,
result,
));
}

#[test]
fn trailing_comma_parse_errors() {
let mut console = BufferConsole::default();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ The configuration that is contained inside the file `rome.json`
syntactic structures. Defaults to "all".
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
only in for statements where it is necessary because of ASI.
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
Defaults to "always".

Global options applied to all commands
--colors=<off|force> Set the formatting mode for markup: "off" prints everything as plain
Expand Down
2 changes: 2 additions & 0 deletions crates/rome_cli/tests/snapshots/main_commands_ci/ci_help.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ The configuration that is contained inside the file `rome.json`
syntactic structures. Defaults to "all".
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
only in for statements where it is necessary because of ASI.
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
Defaults to "always".

Global options applied to all commands
--colors=<off|force> Set the formatting mode for markup: "off" prints everything as plain
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/rome_cli/tests/snap_test.rs
expression: content
---
## `file.js`

```js
action => {};
action => {};
({ action }) => {};
([action]) => {};
(...action) => {};
(action = 1) => {};

```

# Emitted Messages

```block
Formatted 1 file(s) in <TIME>
```


Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Available options:
syntactic structures. Defaults to "all".
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
only in for statements where it is necessary because of ASI.
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
Defaults to "always".
--stdin-file-path=PATH A file name with its extension to pass when reading from standard in,
e.g. echo 'let a;' | rome format --stdin-file-path=file.js".
--write Writes formatted files to file system.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ The configuration that is contained inside the file `rome.json`
syntactic structures. Defaults to "all".
--semicolons=<always|as-needed> Whether the formatter prints semicolons for all statements or
only in for statements where it is necessary because of ASI.
--arrow-parentheses=<always|as-needed> Whether to add non-necessary parentheses to arrow functions.
Defaults to "always".

Global options applied to all commands
--colors=<off|force> Set the formatting mode for markup: "off" prints everything as plain
Expand Down
77 changes: 76 additions & 1 deletion crates/rome_js_formatter/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ pub struct JsFormatOptions {
/// 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).
semicolons: Semicolons,

/// Whether to add non-necessary parentheses to arrow functions. Defaults to "always".
arrow_parentheses: ArrowParentheses,

/// Information related to the current file
source_type: JsFileSource,
}
Expand All @@ -171,9 +174,15 @@ impl JsFormatOptions {
quote_properties: QuoteProperties::default(),
trailing_comma: TrailingComma::default(),
semicolons: Semicolons::default(),
arrow_parentheses: ArrowParentheses::default(),
}
}

pub fn with_arrow_parentheses(mut self, arrow_parentheses: ArrowParentheses) -> Self {
self.arrow_parentheses = arrow_parentheses;
self
}

pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self {
self.indent_style = indent_style;
self
Expand Down Expand Up @@ -209,6 +218,10 @@ impl JsFormatOptions {
self
}

pub fn arrow_parentheses(&self) -> ArrowParentheses {
self.arrow_parentheses
}

pub fn quote_style(&self) -> QuoteStyle {
self.quote_style
}
Expand Down Expand Up @@ -263,7 +276,8 @@ impl fmt::Display for JsFormatOptions {
writeln!(f, "JSX quote style: {}", self.jsx_quote_style)?;
writeln!(f, "Quote properties: {}", self.quote_properties)?;
writeln!(f, "Trailing comma: {}", self.trailing_comma)?;
writeln!(f, "Semicolons: {}", self.semicolons)
writeln!(f, "Semicolons: {}", self.semicolons)?;
writeln!(f, "Arrow parentheses: {}", self.arrow_parentheses)
}
}

Expand Down Expand Up @@ -487,3 +501,64 @@ impl VisitNode<JsonLanguage> for Semicolons {
Some(())
}
}

#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema),
serde(rename_all = "camelCase")
)]
pub enum ArrowParentheses {
#[default]
Always,
AsNeeded,
}

impl ArrowParentheses {
pub(crate) const KNOWN_VALUES: &'static [&'static str] = &["always", "asNeeded"];

pub const fn is_as_needed(&self) -> bool {
matches!(self, Self::AsNeeded)
}

pub const fn is_always(&self) -> bool {
matches!(self, Self::Always)
}
}

impl FromStr for ArrowParentheses {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"as-needed" | "AsNeeded" => Ok(Self::AsNeeded),
"always" | "Always" => Ok(Self::Always),
_ => Err("Value not supported for Arrow parentheses. Supported values are 'as-needed' and 'always'."),
}
}
}

impl fmt::Display for ArrowParentheses {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArrowParentheses::AsNeeded => write!(f, "As needed"),
ArrowParentheses::Always => write!(f, "Always"),
}
}
}

impl VisitNode<JsonLanguage> for ArrowParentheses {
fn visit_member_value(
&mut self,
node: &SyntaxNode<JsonLanguage>,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<()> {
let node = with_only_known_variants(node, ArrowParentheses::KNOWN_VALUES, diagnostics)?;
if node.inner_string_text().ok()?.text() == "asNeeded" {
*self = ArrowParentheses::AsNeeded;
} else {
*self = ArrowParentheses::Always;
}
Some(())
}
}
72 changes: 55 additions & 17 deletions crates/rome_js_formatter/src/js/bindings/parameters.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::prelude::*;
use rome_formatter::{write, CstFormatContext};

use crate::js::expressions::arrow_function_expression::can_avoid_parentheses;
use crate::js::lists::parameter_list::FormatJsAnyParameterList;
use crate::utils::test_call::is_test_call_argument;
use rome_js_syntax::parameter_ext::{AnyJsParameterList, AnyParameter};
use rome_js_syntax::{
AnyJsConstructorParameter, AnyJsFormalParameter, AnyTsType, JsConstructorParameters,
JsParameters, JsSyntaxToken,
AnyJsConstructorParameter, AnyJsFormalParameter, AnyTsType, JsArrowFunctionExpression,
JsConstructorParameters, JsParameters, JsSyntaxToken,
};
use rome_rowan::{declare_node_union, SyntaxResult};

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

let parentheses_not_needed = self
.as_arrow_function_expression()
.map_or(false, |expression| can_avoid_parentheses(&expression, f));

match layout {
ParameterLayout::NoParameters => {
write!(
Expand All @@ -60,27 +65,50 @@ impl Format<JsFormatContext> for FormatAnyJsParameters {
)
}
ParameterLayout::Hug => {
if !parentheses_not_needed {
write!(f, [l_paren_token.format()])?;
} else {
write!(f, [format_removed(&l_paren_token)])?;
}

write!(
f,
[
l_paren_token.format(),
FormatJsAnyParameterList::with_layout(&list, ParameterLayout::Hug),
&r_paren_token.format()
]
)
[FormatJsAnyParameterList::with_layout(
&list,
ParameterLayout::Hug
)]
)?;

if !parentheses_not_needed {
write!(f, [&r_paren_token.format()])?;
} else {
write!(f, [format_removed(&r_paren_token)])?;
}

Ok(())
}
ParameterLayout::Default => {
if !parentheses_not_needed {
write!(f, [l_paren_token.format()])?;
} else {
write!(f, [format_removed(&l_paren_token)])?;
}

write!(
f,
[
l_paren_token.format(),
soft_block_indent(&FormatJsAnyParameterList::with_layout(
&list,
ParameterLayout::Default
)),
r_paren_token.format()
]
)
[soft_block_indent(&FormatJsAnyParameterList::with_layout(
&list,
ParameterLayout::Default
))]
)?;

if !parentheses_not_needed {
write!(f, [r_paren_token.format()])?;
} else {
write!(f, [format_removed(&r_paren_token)])?;
}

Ok(())
}
}
}
Expand Down Expand Up @@ -128,6 +156,16 @@ impl FormatAnyJsParameters {

Ok(result)
}

fn as_arrow_function_expression(&self) -> Option<JsArrowFunctionExpression> {
match self {
FormatAnyJsParameters::JsParameters(parameters) => parameters
.syntax()
.parent()
.and_then(JsArrowFunctionExpression::cast),
FormatAnyJsParameters::JsConstructorParameters(_) => None,
}
}
}

#[derive(Copy, Debug, Clone, Eq, PartialEq)]
Expand Down
Loading