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

Commit

Permalink
feat(rome_js_formatter, rome_cli): add arrowParentheses option (#4667)
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperchupuDev authored Jul 17, 2023
1 parent 5510f5f commit ec54d1d
Show file tree
Hide file tree
Showing 217 changed files with 1,216 additions and 47 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ if no error diagnostics are emitted.

- 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 @@ -59,6 +59,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

0 comments on commit ec54d1d

Please sign in to comment.