fix(rome_js_parser): Better error recover for jsx attribute
MODIFY
crates/rome_js_analyze/tests/specs/jsx/noImplicitBoolean.jsx
crates/rome_js_analyze/tests/specs/jsx/noImplicitBoolean.jsx
<input disabled={0} />;
<input disabled={undefined} />;
<input disabled="false" />;
+// https://github.com/rome/tools/issues/2944
+<div className={asdf asdf} />;
//invalid
<input disabled />;

MODIFY
crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs
crates/rome_js_formatter/src/jsx/attribute/expression_attribute_value.rs
@@ -3,6 +3,7 @@ use crate::prelude::*;
use rome_formatter::write;
use rome_js_syntax::{
JsAnyExpression, JsxExpressionAttributeValue, JsxExpressionAttributeValueFields,
+ TriviaPieceKind,
};
#[derive(Debug, Clone, Default)]
@@ -19,7 +20,6 @@ impl FormatNodeRule<JsxExpressionAttributeValue> for FormatJsxExpressionAttribut
expression,
r_curly_token,
} = node.as_fields();
-
write!(
f,
[group(&format_with(|f| {
@@ -70,7 +70,19 @@ impl FormatNodeRule<JsxExpressionAttributeValue> for FormatJsxExpressionAttribut
write!(f, [soft_block_indent(&expression.format())])?;
};
- write!(f, [line_suffix_boundary(), r_curly_token.format()])
+ write!(f, [line_suffix_boundary(),])?;
+
+ // format if `}` has a `Skipped` leading trivia
+ // <div className={asdf asdf} />;
+ let r_curly_token_ref = r_curly_token.as_ref()?;
+ if matches!(
+ r_curly_token_ref.leading_trivia().first().map(|t| t.kind()),
+ Some(TriviaPieceKind::Skipped)
+ ) {
+ write!(f, [space(), r_curly_token.format()])
+ } else {
+ write!(f, [r_curly_token.format()])
+ }
}))]
)
}

MODIFY
crates/rome_js_formatter/tests/specs/jsx/attributes.jsx
crates/rome_js_formatter/tests/specs/jsx/attributes.jsx
<test {...WithAVeryLongFunctionthat_exceeds_the_line_width_what_happens_with_ithis()} />;
<div {...["Chungking Express", "Fallen Angels", "In the Mood for Love", "Days of Living Wild", "Happy Together"]}/>;
+
+
+// https://github.com/rome/tools/issues/2944
+<div className={asdf asdf} />;
+<div className={asdf
+ /* comment */ asdf } />
\ No newline at end of file

MODIFY
crates/rome_js_formatter/tests/specs/jsx/attributes.jsx.snap
crates/rome_js_formatter/tests/specs/jsx/attributes.jsx.snap
---
source: crates/rome_js_formatter/tests/spec_test.rs
-assertion_line: 257
+assertion_line: 259
expression: attributes.jsx
---
# Input
@@ -50,6 +50,11 @@ expression: attributes.jsx
<test {...WithAVeryLongFunctionthat_exceeds_the_line_width_what_happens_with_ithis()} />;
<div {...["Chungking Express", "Fallen Angels", "In the Mood for Love", "Days of Living Wild", "Happy Together"]}/>;
+
+// https://github.com/rome/tools/issues/2944
+<div className={asdf asdf} />;
+<div className={asdf
+ /* comment */ asdf } />
=============================
# Outputs
## Output 1
@@ -112,6 +117,14 @@ let a = (
]}
/>;
+// https://github.com/rome/tools/issues/2944
+<div className={asdf asdf} />;
+<div
+ className={
+ asdf
+ /* comment */asdf }
+/>;
+
## Lines exceeding width of 80 characters

MODIFY
crates/rome_js_parser/src/syntax/jsx/mod.rs
crates/rome_js_parser/src/syntax/jsx/mod.rs
@@ -605,7 +605,8 @@ fn parse_jsx_attribute_value(p: &mut Parser) -> ParsedSyntax {
_ => ParsedSyntax::Absent,
}
}
-
+// test_err jsx jsx_element_attribute_expression_error
+// <div className={asdf asdf} />;
fn parse_jsx_expression_attribute_value(p: &mut Parser) -> ParsedSyntax {
if !p.at(T!['{']) {
return ParsedSyntax::Absent;
@@ -614,7 +615,12 @@ fn parse_jsx_expression_attribute_value(p: &mut Parser) -> ParsedSyntax {
let m = p.start();
p.bump(T!['{']);
parse_jsx_assignment_expression(p, false).or_add_diagnostic(p, expected_expression);
+ if !p.expect(T!['}']) && p.nth_at(1, T!['}']) {
+ p.parse_as_skipped_trivia_tokens(|p| {
+ p.bump_any();
+ });
p.expect(T!['}']);
+ }
ParsedSyntax::Present(m.complete(p, JSX_EXPRESSION_ATTRIBUTE_VALUE))
}

ADD
crates/rome_js_parser/test_data/inline/err/jsx_element_attribute_expression_error.jsx
+<div className={asdf asdf} />;

MODIFY
crates/rome_rowan/src/cursor/trivia.rs
crates/rome_rowan/src/cursor/trivia.rs
@@ -74,6 +74,11 @@ impl SyntaxTrivia {
self.green_trivia().pieces().last()
}
+ /// Returns the first trivia piece element
+ pub(crate) fn first(&self) -> Option<&TriviaPiece> {
+ self.green_trivia().pieces().first()
+ }
+
/// Iterate over all pieces of the trivia. The iterator returns the offset
/// of the trivia as [TextSize] and its data as [Trivia], which contains its length.
/// See [SyntaxTriviaPiece].

MODIFY
crates/rome_rowan/src/syntax/trivia.rs
crates/rome_rowan/src/syntax/trivia.rs
@@ -608,6 +608,17 @@ impl<L: Language> SyntaxTrivia<L> {
})
}
+ pub fn first(&self) -> Option<SyntaxTriviaPiece<L>> {
+ let piece = self.raw.first()?;
+
+ Some(SyntaxTriviaPiece {
+ raw: self.raw.clone(),
+ offset: self.raw.text_range().end() - piece.length,
+ trivia: *piece,
+ _p: Default::default(),
+ })
+ }
+
pub fn text(&self) -> &str {
self.raw.text()
}

fix(rome_js_parser): Better error recover for jsx attribute #2944 (#2954)

Co-authored-by: Micha Reiser <micha@reiser.io>