test(rome_formatter): enable TSX parsing for tsx files
MODIFY
crates/rome_formatter/src/formatter.rs
crates/rome_formatter/src/formatter.rs
@@ -35,12 +35,16 @@ pub struct Formatter {
pub enum TrailingSeparator {
Allowed,
Disallowed,
+ Mandatory,
}
impl TrailingSeparator {
pub fn is_allowed(&self) -> bool {
matches!(self, TrailingSeparator::Allowed)
}
+ pub fn is_mandatory(&self) -> bool {
+ matches!(self, TrailingSeparator::Mandatory)
+ }
}
impl Default for TrailingSeparator {
@@ -277,6 +281,8 @@ impl Formatter {
// in order to remove only the token itself when the group doesn't break
// but still print its associated trivias unconditionally
self.format_replaced(&separator, if_group_breaks(Token::from(&separator)))?
+ } else if trailing_separator.is_mandatory() {
+ separator.format(self)?
} else {
empty_element()
}
@@ -286,6 +292,8 @@ impl Formatter {
} else if index == last_index {
if trailing_separator.is_allowed() {
if_group_breaks(separator_factory())
+ } else if trailing_separator.is_mandatory() {
+ separator_factory()
} else {
empty_element()
}

MODIFY
crates/rome_formatter/src/lib.rs
crates/rome_formatter/src/lib.rs
@@ -596,9 +596,9 @@ mod test {
// use this test check if your snippet prints as you wish, without using a snapshot
fn quick_test() {
let src = r#"
-3 + 3 === 7
+ const functionName1 = <T,>(arg) => false;
"#;
- let syntax = SourceType::ts();
+ let syntax = SourceType::tsx();
let tree = parse(src, 0, syntax.clone());
let result = format(FormatOptions::default(), &tree.syntax()).unwrap();
check_reformat(CheckReformatParams {

MODIFY
crates/rome_formatter/src/ts/bindings/type_parameters.rs
crates/rome_formatter/src/ts/bindings/type_parameters.rs
-use crate::formatter::TrailingSeparator;
-use crate::{
- join_elements, soft_line_break_or_space, token, FormatElement, FormatResult, Formatter,
- ToFormatElement,
-};
+use crate::formatter_traits::FormatTokenAndNode;
+use crate::{FormatElement, FormatResult, Formatter, ToFormatElement};
use rome_js_syntax::{TsTypeParameters, TsTypeParametersFields};
impl ToFormatElement for TsTypeParameters {
@@ -12,13 +9,8 @@ impl ToFormatElement for TsTypeParameters {
r_angle_token,
l_angle_token,
} = self.as_fields();
- let items =
- formatter.format_separated(&items, || token(","), TrailingSeparator::default())?;
+ let items = items.format(formatter)?;
- formatter.format_delimited_soft_block_indent(
- &l_angle_token?,
- join_elements(soft_line_break_or_space(), items),
- &r_angle_token?,
- )
+ formatter.format_delimited_soft_block_indent(&l_angle_token?, items, &r_angle_token?)
}
}

MODIFY
crates/rome_formatter/src/ts/lists/type_parameter_list.rs
crates/rome_formatter/src/ts/lists/type_parameter_list.rs
@@ -3,13 +3,24 @@ use crate::{
join_elements, soft_line_break_or_space, token, FormatElement, FormatResult, Formatter,
ToFormatElement,
};
-use rome_js_syntax::TsTypeParameterList;
+use rome_js_syntax::{AstSeparatedList, TsTypeParameterList};
impl ToFormatElement for TsTypeParameterList {
fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
+ // nodes and formatter are not aware of the source type (TSX vs TS), which means we can't
+ // exactly pin point the exact case.
+ //
+ // This is just an heuristic to avoid removing the trailing comma from a TSX grammar.
+ // This means that, if we are in a TS context and we have a trailing comma, the formatter won't remove it.
+ // It's an edge case, while waiting for a better solution,
+ let trailing_separator = if self.len() == 1 && self.trailing_separator().is_some() {
+ TrailingSeparator::Mandatory
+ } else {
+ TrailingSeparator::default()
+ };
Ok(join_elements(
soft_line_break_or_space(),
- formatter.format_separated(self, || token(","), TrailingSeparator::default())?,
+ formatter.format_separated(self, || token(","), trailing_separator)?,
))
}
}

MODIFY
crates/rome_formatter/tests/prettier_tests.rs
crates/rome_formatter/tests/prettier_tests.rs
@@ -20,7 +20,7 @@ mod check_reformat;
static REPORTER: DiffReport = DiffReport::new();
-tests_macros::gen_tests! {"tests/specs/prettier/**/*.{js,ts,jsx}", crate::test_snapshot, "script"}
+tests_macros::gen_tests! {"tests/specs/prettier/**/*.{js,ts,jsx,tsx}", crate::test_snapshot, "script"}
const PRETTIER_IGNORE: &str = "prettier-ignore";
const ROME_IGNORE: &str = "rome-ignore format: prettier ignore";

ADD
crates/rome_formatter/tests/specs/prettier/typescript/assignment/issue-10848.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: issue-10848.tsx
+
+---
+# Input
+```js
+const MyComponent: React.VoidFunctionComponent<MyComponentProps> = ({ x }) => {
+ const a = useA()
+ return <div>x = {x}; a = {a}</div>
+}
+
+const MyComponent2: React.VoidFunctionComponent<MyComponent2Props> = ({ x, y }) => {
+ const a = useA()
+ return <div>x = {x}; y = {y}; a = {a}</div>
+}
+
+const MyComponentWithLongName1: React.VoidFunctionComponent<MyComponentWithLongNameProps> = ({ x, y }) => {
+ const a = useA()
+ return <div>x = {x}; y = {y}; a = {a}</div>
+}
+
+const MyComponentWithLongName2: React.VoidFunctionComponent<MyComponentWithLongNameProps> = ({ x, y, anotherPropWithLongName1, anotherPropWithLongName2, anotherPropWithLongName3, anotherPropWithLongName4 }) => {
+ const a = useA()
+ return <div>x = {x}; y = {y}; a = {a}</div>
+}
+
+const MyGenericComponent: React.VoidFunctionComponent<MyGenericComponentProps<number>> = ({ x, y }) => {
+ const a = useA()
+ return <div>x = {x}; y = {y}; a = {a}</div>
+}
+
+export const ExportToExcalidrawPlus: React.FC<{
+ elements: readonly NonDeletedExcalidrawElement[];
+ appState: AppState;
+ onError: (error: Error) => void;
+}> = ({ elements, appState, onError }) => {
+ return null;
+}
+
+const Query: FunctionComponent<QueryProps> = ({
+ children,
+ type,
+ resource,
+ payload,
+ // Provides an undefined onSuccess just so the key `onSuccess` is defined
+ // This is used to detect options in useDataProvider
+ options = { onSuccess: undefined },
+}) =>
+ children(
+ useQuery(
+ { type, resource, payload },
+ { ...options, withDeclarativeSideEffectsSupport: true }
+ )
+ );
+
+```
+
+# Output
+```js
+const MyComponent: React.VoidFunctionComponent<MyComponentProps> = ({ x }) => {
+ const a = useA();
+ return <div>x = {x}; a = {a}</div>;
+};
+
+const MyComponent2: React.VoidFunctionComponent<MyComponent2Props> = ({ x, y }) => {
+ const a = useA();
+ return <div>x = {x}; y = {y}; a = {a}</div>;
+};
+
+const MyComponentWithLongName1: React.VoidFunctionComponent<
+ MyComponentWithLongNameProps
+> = ({ x, y }) => {
+ const a = useA();
+ return <div>x = {x}; y = {y}; a = {a}</div>;
+};
+
+const MyComponentWithLongName2: React.VoidFunctionComponent<
+ MyComponentWithLongNameProps
+> = (
+ {
+ x,
+ y,
+ anotherPropWithLongName1,
+ anotherPropWithLongName2,
+ anotherPropWithLongName3,
+ anotherPropWithLongName4,
+ },
+) => {
+ const a = useA();
+ return <div>x = {x}; y = {y}; a = {a}</div>;
+};
+
+const MyGenericComponent: React.VoidFunctionComponent<
+ MyGenericComponentProps<number>
+> = ({ x, y }) => {
+ const a = useA();
+ return <div>x = {x}; y = {y}; a = {a}</div>;
+};
+
+export const ExportToExcalidrawPlus: React.FC<
+ {
+ elements: readonly NonDeletedExcalidrawElement[],
+ appState: AppState,
+ onError: (error: Error) => void,
+ }
+> = ({ elements, appState, onError }) => {
+ return null;
+};
+
+const Query: FunctionComponent<QueryProps> = (
+ {
+ children,
+ type,
+ resource,
+ payload,
+ // Provides an undefined onSuccess just so the key `onSuccess` is defined
+ // This is used to detect options in useDataProvider
+ options = { onSuccess: undefined },
+ },
+) =>
+ children(
+ useQuery(
+ { type, resource, payload },
+ { ...options, withDeclarativeSideEffectsSupport: true },
+ ),
+ );
+
+```
+
+

ADD
crates/rome_formatter/tests/specs/prettier/typescript/tsx/generic-component.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: generic-component.tsx
+
+---
+# Input
+```js
+const c1 = <MyComponent<number> data={12} />
+
+const c2 = <MyComponent<number> />
+
+const c3 = <MyComponent<number> attr="value" />
+
+```
+
+# Output
+```js
+const c1 = <MyComponent<number> data={12} />;
+
+const c2 = <MyComponent<number> />;
+
+const c3 = <MyComponent<number> attr="value" />;
+
+```
+
+

ADD
crates/rome_formatter/tests/specs/prettier/typescript/tsx/keyword.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: keyword.tsx
+
+---
+# Input
+```js
+<try />;
+<object />
+
+```
+
+# Output
+```js
+<try />;
+<object />;
+
+```
+
+

ADD
crates/rome_formatter/tests/specs/prettier/typescript/tsx/member-expression.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: member-expression.tsx
+
+---
+# Input
+```js
+(<a />).method();
+(<a />).property;
+(<a />)["computed"];
+(<a />)["computed"]();
+(
+ <div>
+ <a>foo</a>
+ </div>
+).method();
+(
+ <div>
+ <a>foo</a>
+ </div>
+).property;
+(
+ <div>
+ <a>foo</a>
+ </div>
+)["computed"];
+(
+ <div>
+ <a>foo</a>
+ </div>
+)["computed"]();
+
+```
+
+# Output
+```js
+(<a />).method();
+(<a />).property;
+(<a />)["computed"];
+(<a />)["computed"]();
+(
+ <div>
+ <a>foo</a>
+ </div>
+).method();
+(
+ <div>
+ <a>foo</a>
+ </div>
+).property;
+(
+ <div>
+ <a>foo</a>
+ </div>
+)["computed"];
+(
+ <div>
+ <a>foo</a>
+ </div>
+)["computed"]();
+
+```
+
+

ADD
crates/rome_formatter/tests/specs/prettier/typescript/tsx/react.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: react.tsx
+
+---
+# Input
+```js
+const MyCoolList = ({ things }) =>
+ <ul>
+ {things.map(MyCoolThing)}
+ </ul>;
+
+const MyCoolThing = ({ thingo }) => <li>{thingo}</li>;
+
+```
+
+# Output
+```js
+const MyCoolList = ({ things }) =>
+ <ul>
+ {things.map(MyCoolThing)}
+ </ul>;
+
+const MyCoolThing = ({ thingo }) => <li>{thingo}</li>;
+
+```
+
+

ADD
crates/rome_formatter/tests/specs/prettier/typescript/tsx/this.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: this.tsx
+
+---
+# Input
+```js
+<this.state.Component />;
+<this.Component />;
+
+```
+
+# Output
+```js
+<this.state.Component />;
+<this.Component />;
+
+```
+
+

ADD
crates/rome_formatter/tests/specs/prettier/typescript/tsx/type-parameters.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: type-parameters.tsx
+
+---
+# Input
+```js
+const functionName1 = <T,>(arg) => false;
+const functionName2 = <T extends any>(arg) => false;
+const functionName3 = <T, S>(arg) => false;
+
+function functionName4<T>() {
+ return false;
+}
+
+functionName5<T>();
+
+interface Interface1<T> {
+ one: "one";
+}
+
+interface Interface2 {
+ two: Two<T>;
+}
+
+type Type1<T> = "type1";
+
+type Type2 = Two<T>;
+
+```
+
+# Output
+```js
+const functionName1 = <T,>(arg) => false;
+const functionName2 = <T extends any>(arg) => false;
+const functionName3 = <T, S>(arg) => false;
+
+function functionName4<T>() {
+ return false;
+}
+
+functionName5<T>();
+
+interface Interface1<T> { one: "one" }
+
+interface Interface2 { two: Two<T> }
+
+type Type1<T> = "type1";
+
+type Type2 = Two<T>;
+
+```
+
+

ADD
crates/rome_formatter/tests/specs/prettier/typescript/tsx/url.tsx.snap
+---
+source: crates/rome_formatter/tests/prettier_tests.rs
+assertion_line: 151
+expression: url.tsx
+
+---
+# Input
+```js
+const link = <a href="example.com">http://example.com</a>;
+
+const first = <div>http://example.com</div>;
+
+ const second = <>http://example.com</>;
+
+ const third = <div><br />http://example.com</div>;
+
+ const fourth = <div><span></span>http://example.com</div>;
+
+ const fifth = <div>{}http://example.com</div>;
+
+```
+
+# Output
+```js
+const link = <a href="example.com">http://example.com</a>;
+
+const first = <div>http://example.com</div>;
+
+const second = <>http://example.com</>;
+
+const third = <div><br />http://example.com</div>;
+
+const fourth = <div><span></span>http://example.com</div>;
+
+const fifth = <div>{}http://example.com</div>;
+
+```
+
+

MODIFY
crates/rome_js_parser/src/tests.rs
crates/rome_js_parser/src/tests.rs
@@ -14,16 +14,10 @@ use std::path::{Path, PathBuf};
#[test]
fn parser_smoke_test() {
let src = r#"
-function test() {}
-@test
-class Test {}
-@test.a?.c @test @test
-class Test2{}
-@test export class Test {}
-@test export default class Test {}
+const functionName1 = <T,>(arg) => false;
"#;
- let module = parse(src, 0, SourceType::ts());
+ let module = parse(src, 0, SourceType::tsx());
assert_errors_are_absent(&module, Path::new("parser_smoke_test"));
}

test(rome_formatter): enable TSX parsing for tsx files (#2295)