feat(rome_rowan): `cast_ref` and `match_ast` macro
MODIFY
crates/rome_js_formatter/src/js/expressions/assignment_expression.rs
crates/rome_js_formatter/src/js/expressions/assignment_expression.rs
@@ -8,10 +8,11 @@ use crate::parentheses::{
use rome_formatter::write;
use rome_js_syntax::{
- JsAnyAssignmentPattern, JsAnyExpression, JsAnyForInitializer, JsAssignmentExpression,
- JsForStatement, JsSyntaxKind, JsSyntaxNode,
+ JsAnyAssignmentPattern, JsAnyExpression, JsAnyForInitializer, JsArrowFunctionExpression,
+ JsAssignmentExpression, JsComputedMemberName, JsExpressionStatement, JsForStatement,
+ JsSequenceExpression, JsSyntaxKind, JsSyntaxNode,
};
-use rome_rowan::AstNode;
+use rome_rowan::{match_ast, AstNode};
#[derive(Debug, Clone, Default)]
pub struct FormatJsAssignmentExpression;
@@ -28,16 +29,17 @@ impl FormatNodeRule<JsAssignmentExpression> for FormatJsAssignmentExpression {
impl NeedsParentheses for JsAssignmentExpression {
fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
- match parent.kind() {
- JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION => false,
+ match_ast! {
+ match parent {
+ JsAssignmentExpression(_) => false,
// `[a = b]`
- JsSyntaxKind::JS_COMPUTED_MEMBER_NAME => false,
+ JsComputedMemberName(_) => false,
- JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => {
+ JsArrowFunctionExpression(_) => {
is_arrow_function_body(self.syntax(), parent)
- }
- JsSyntaxKind::JS_FOR_STATEMENT => {
- let for_statement = JsForStatement::unwrap_cast(parent.clone());
+ },
+
+ JsForStatement(for_statement) => {
let is_initializer = match for_statement.initializer() {
Some(JsAnyForInitializer::JsAnyExpression(expression)) => {
&expression.resolve_syntax() == self.syntax()
@@ -52,8 +54,8 @@ impl NeedsParentheses for JsAssignmentExpression {
== Some(self.syntax());
!(is_initializer || is_update)
- }
- JsSyntaxKind::JS_EXPRESSION_STATEMENT => {
+ },
+ JsExpressionStatement(_) => {
// Parenthesize `{ a } = { a: 5 }`
is_first_in_statement(
self.clone().into(),
@@ -62,8 +64,8 @@ impl NeedsParentheses for JsAssignmentExpression {
self.left(),
Ok(JsAnyAssignmentPattern::JsObjectAssignmentPattern(_))
)
- }
- JsSyntaxKind::JS_SEQUENCE_EXPRESSION => {
+ },
+ JsSequenceExpression(_) => {
let mut child = parent.clone();
for ancestor in parent.ancestors().skip(1) {
@@ -90,10 +92,12 @@ impl NeedsParentheses for JsAssignmentExpression {
}
}
+ true
+ },
+ _ => {
true
}
-
- _ => true,
+ }
}
}
}

MODIFY
crates/rome_js_formatter/src/js/expressions/pre_update_expression.rs
crates/rome_js_formatter/src/js/expressions/pre_update_expression.rs
@@ -4,11 +4,12 @@ use crate::parentheses::{
use crate::prelude::*;
use rome_formatter::write;
+use rome_js_syntax::JsPreUpdateExpressionFields;
use rome_js_syntax::{
JsAnyExpression, JsPreUpdateExpression, JsPreUpdateOperator, JsSyntaxNode, JsUnaryExpression,
JsUnaryOperator,
};
-use rome_js_syntax::{JsPreUpdateExpressionFields, JsSyntaxKind};
+use rome_rowan::match_ast;
#[derive(Debug, Clone, Default)]
pub struct FormatJsPreUpdateExpression;
@@ -30,9 +31,9 @@ impl FormatNodeRule<JsPreUpdateExpression> for FormatJsPreUpdateExpression {
impl NeedsParentheses for JsPreUpdateExpression {
fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
- match parent.kind() {
- JsSyntaxKind::JS_UNARY_EXPRESSION => {
- let unary = JsUnaryExpression::unwrap_cast(parent.clone());
+ match_ast! {
+ match parent {
+ JsUnaryExpression(unary) => {
let parent_operator = unary.operator();
let operator = self.operator();
@@ -40,8 +41,11 @@ impl NeedsParentheses for JsPreUpdateExpression {
&& operator == Ok(JsPreUpdateOperator::Increment))
|| (parent_operator == Ok(JsUnaryOperator::Minus)
&& operator == Ok(JsPreUpdateOperator::Decrement))
+ },
+ _ => {
+ unary_like_expression_needs_parentheses(self.syntax(), parent)
+ }
}
- _ => unary_like_expression_needs_parentheses(self.syntax(), parent),
}
}
}

MODIFY
crates/rome_js_formatter/src/js/expressions/unary_expression.rs
crates/rome_js_formatter/src/js/expressions/unary_expression.rs
@@ -5,9 +5,10 @@ use crate::parentheses::{
unary_like_expression_needs_parentheses, ExpressionNode, NeedsParentheses,
};
+use rome_js_syntax::JsUnaryExpression;
use rome_js_syntax::{JsAnyExpression, JsSyntaxNode};
-use rome_js_syntax::{JsSyntaxKind, JsUnaryExpression};
use rome_js_syntax::{JsUnaryExpressionFields, JsUnaryOperator};
+use rome_rowan::match_ast;
#[derive(Debug, Clone, Default)]
pub struct FormatJsUnaryExpression;
@@ -44,16 +45,19 @@ impl FormatNodeRule<JsUnaryExpression> for FormatJsUnaryExpression {
impl NeedsParentheses for JsUnaryExpression {
fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
- match parent.kind() {
- JsSyntaxKind::JS_UNARY_EXPRESSION => {
- let parent_unary = JsUnaryExpression::unwrap_cast(parent.clone());
+ match_ast! {
+ match parent {
+ JsUnaryExpression(parent_unary) => {
let parent_operator = parent_unary.operator();
let operator = self.operator();
matches!(operator, Ok(JsUnaryOperator::Plus | JsUnaryOperator::Minus))
&& parent_operator == operator
+ },
+ _ => {
+ unary_like_expression_needs_parentheses(self.syntax(), parent)
+ }
}
- _ => unary_like_expression_needs_parentheses(self.syntax(), parent),
}
}
}

MODIFY
crates/rome_js_formatter/src/parentheses.rs
crates/rome_js_formatter/src/parentheses.rs
@@ -50,9 +50,10 @@ use rome_js_syntax::{
JsAnyAssignment, JsAnyAssignmentPattern, JsAnyExpression, JsAnyFunctionBody,
JsAnyLiteralExpression, JsArrowFunctionExpression, JsAssignmentExpression, JsBinaryExpression,
JsBinaryOperator, JsComputedMemberAssignment, JsComputedMemberExpression,
- JsConditionalExpression, JsLanguage, JsSequenceExpression, JsSyntaxKind, JsSyntaxNode,
+ JsConditionalExpression, JsLanguage, JsSequenceExpression, JsStaticMemberAssignment,
+ JsStaticMemberExpression, JsSyntaxKind, JsSyntaxNode,
};
-use rome_rowan::AstNode;
+use rome_rowan::{match_ast, AstNode};
/// Node that may be parenthesized to ensure it forms valid syntax or to improve readability
pub trait NeedsParentheses: AstNode<Language = JsLanguage> {
@@ -635,18 +636,15 @@ pub(crate) fn unary_like_expression_needs_parentheses(
));
debug_assert_is_parent(expression, parent);
- match parent.kind() {
- JsSyntaxKind::JS_BINARY_EXPRESSION => {
- let binary = JsBinaryExpression::unwrap_cast(parent.clone());
-
+ if let Some(binary) = JsBinaryExpression::cast_ref(parent) {
matches!(binary.operator(), Ok(JsBinaryOperator::Exponent))
&& binary
.left()
.map(ExpressionNode::into_resolved_syntax)
.as_ref()
== Ok(expression)
- }
- _ => update_or_lower_expression_needs_parentheses(expression, parent),
+ } else {
+ update_or_lower_expression_needs_parentheses(expression, parent)
}
}
@@ -679,33 +677,29 @@ pub(crate) fn is_member_object(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bo
debug_assert_is_expression(node);
debug_assert_is_parent(node, parent);
- match parent.kind() {
+ match_ast! {
+ match parent {
// Only allows expression in the `object` child.
- JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION => true,
- JsSyntaxKind::JS_STATIC_MEMBER_ASSIGNMENT => true,
-
- JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => {
- let member_expression = JsComputedMemberExpression::unwrap_cast(parent.clone());
-
+ JsStaticMemberExpression(_) => true,
+ JsStaticMemberAssignment(_) => true,
+ JsComputedMemberExpression(member_expression) => {
member_expression
.object()
.map(ExpressionNode::into_resolved_syntax)
.as_ref()
== Ok(node)
- }
-
- JsSyntaxKind::JS_COMPUTED_MEMBER_ASSIGNMENT => {
- let member_assignment = JsComputedMemberAssignment::unwrap_cast(parent.clone());
-
- member_assignment
+ },
+ JsComputedMemberAssignment(assignment) => {
+ assignment
.object()
.map(ExpressionNode::into_resolved_syntax)
.as_ref()
== Ok(node)
- }
+ },
_ => false,
}
}
+}
/// Returns `true` if `node` is the `callee` of a [JsNewExpression] or [JsCallExpression].
pub(crate) fn is_callee(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool {
@@ -729,35 +723,35 @@ pub(crate) fn is_callee(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool {
/// is_conditional_test(`b`, `a ? b : c`) -> false
/// ```
pub(crate) fn is_conditional_test(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool {
- match parent.kind() {
- JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => {
- let conditional = JsConditionalExpression::unwrap_cast(parent.clone());
-
+ match_ast! {
+ match parent {
+ JsConditionalExpression(conditional) => {
conditional
.test()
.map(ExpressionNode::into_resolved_syntax)
.as_ref()
== Ok(node)
+ },
+ _ => false
}
- _ => false,
}
}
pub(crate) fn is_arrow_function_body(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool {
debug_assert_is_expression(node);
- match parent.kind() {
- JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => {
- let arrow = JsArrowFunctionExpression::unwrap_cast(parent.clone());
-
+ match_ast! {
+ match parent {
+ JsArrowFunctionExpression(arrow) => {
match arrow.body() {
Ok(JsAnyFunctionBody::JsAnyExpression(expression)) => {
&expression.resolve_syntax() == node
}
_ => false,
}
+ },
+ _ => false
}
- _ => false,
}
}

MODIFY
crates/rome_js_formatter/src/utils/conditional.rs
crates/rome_js_formatter/src/utils/conditional.rs
@@ -9,7 +9,7 @@ use rome_js_syntax::{
JsThrowStatement, JsUnaryExpression, JsYieldArgument, TsAsExpression, TsConditionalType,
TsNonNullAssertionExpression, TsType,
};
-use rome_rowan::{declare_node_union, AstNode, SyntaxResult};
+use rome_rowan::{declare_node_union, match_ast, AstNode, SyntaxResult};
declare_node_union! {
pub JsAnyConditional = JsConditionalExpression | TsConditionalType
@@ -439,10 +439,9 @@ impl JsAnyConditional {
// expression, while skipping parenthesized expression.
// The iteration "breaks" as soon as a non-member-chain node is found.
for ancestor in ancestors {
- let ancestor = match ancestor.kind() {
- JsSyntaxKind::JS_CALL_EXPRESSION => {
- let call_expression = JsCallExpression::unwrap_cast(ancestor);
-
+ let ancestor = match_ast! {
+ match &ancestor {
+ JsCallExpression(call_expression) => {
if call_expression
.callee()
.map(ExpressionNode::into_resolved)
@@ -453,11 +452,9 @@ impl JsAnyConditional {
} else {
Ancestor::Root(call_expression.into_syntax())
}
- }
-
- JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION => {
- let member_expression = JsStaticMemberExpression::unwrap_cast(ancestor);
+ },
+ JsStaticMemberExpression(member_expression) => {
if member_expression
.object()
.map(ExpressionNode::into_resolved)
@@ -468,10 +465,8 @@ impl JsAnyConditional {
} else {
Ancestor::Root(member_expression.into_syntax())
}
- }
- JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => {
- let member_expression = JsComputedMemberExpression::unwrap_cast(ancestor);
-
+ },
+ JsComputedMemberExpression(member_expression) => {
if member_expression
.object()
.map(ExpressionNode::into_resolved)
@@ -482,10 +477,8 @@ impl JsAnyConditional {
} else {
Ancestor::Root(member_expression.into_syntax())
}
- }
- JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION => {
- let non_null_assertion = TsNonNullAssertionExpression::unwrap_cast(ancestor);
-
+ },
+ TsNonNullAssertionExpression(non_null_assertion) => {
if non_null_assertion
.expression()
.map(ExpressionNode::into_resolved)
@@ -496,10 +489,8 @@ impl JsAnyConditional {
} else {
Ancestor::Root(non_null_assertion.into_syntax())
}
- }
- JsSyntaxKind::JS_NEW_EXPRESSION => {
- let new_expression = JsNewExpression::unwrap_cast(ancestor);
-
+ },
+ JsNewExpression(new_expression) => {
// Skip over new expressions
if new_expression
.callee()
@@ -513,10 +504,8 @@ impl JsAnyConditional {
}
Ancestor::Root(new_expression.into_syntax())
- }
- JsSyntaxKind::TS_AS_EXPRESSION => {
- let as_expression = TsAsExpression::unwrap_cast(ancestor.clone());
-
+ },
+ TsAsExpression(as_expression) => {
if as_expression
.expression()
.map(ExpressionNode::into_resolved)
@@ -529,8 +518,9 @@ impl JsAnyConditional {
}
Ancestor::Root(as_expression.into_syntax())
- }
+ },
_ => Ancestor::Root(ancestor),
+ }
};
match ancestor {

MODIFY
crates/rome_rowan/src/ast/mod.rs
crates/rome_rowan/src/ast/mod.rs
@@ -124,6 +124,20 @@ pub trait AstNode: Clone {
where
Self: Sized;
+ /// Takes a reference of a syntax node and tries to cast it to this AST node.
+ ///
+ /// Only creates a clone of the syntax node if casting the node is possible.
+ fn cast_ref(syntax: &SyntaxNode<Self::Language>) -> Option<Self>
+ where
+ Self: Sized,
+ {
+ if Self::can_cast(syntax.kind()) {
+ Self::cast(syntax.clone())
+ } else {
+ None
+ }
+ }
+
/// Tries to cast the passed syntax node to this AST node.
///
/// # Returns
@@ -154,7 +168,7 @@ pub trait AstNode: Clone {
/// ```
fn try_cast(syntax: SyntaxNode<Self::Language>) -> Result<Self, SyntaxNode<Self::Language>> {
if Self::can_cast(syntax.kind()) {
- Ok(Self::unwrap_cast(syntax))
+ Ok(Self::cast(syntax).expect("Expected casted node because 'can_cast' returned true."))
} else {
Err(syntax)
}
@@ -188,7 +202,8 @@ pub trait AstNode: Clone {
/// ```
fn try_cast_node<T: AstNode<Language = Self::Language>>(node: T) -> Result<Self, T> {
if Self::can_cast(node.syntax().kind()) {
- Ok(Self::unwrap_cast(node.into_syntax()))
+ Ok(Self::cast(node.into_syntax())
+ .expect("Expected casted node because 'can_cast' returned true."))
} else {
Err(node)
}

MODIFY
crates/rome_rowan/src/macros.rs
crates/rome_rowan/src/macros.rs
use crate::{AstNode, Language};
+/// Matches a `SyntaxNode` against an `ast` type.
+///
+/// # Example:
+///
+/// ```
+/// use rome_rowan::{match_ast, AstNode};
+/// use rome_rowan::raw_language::{LiteralExpression, RawLanguageRoot, RawLanguageKind, RawSyntaxTreeBuilder};
+///
+/// let mut builder = RawSyntaxTreeBuilder::new();
+/// builder.start_node(RawLanguageKind::ROOT);
+/// builder.start_node(RawLanguageKind::LITERAL_EXPRESSION);
+/// builder.token(RawLanguageKind::NUMBER_TOKEN, "5");
+/// builder.finish_node();
+/// builder.finish_node();
+///
+/// let root = builder.finish();
+///
+/// let text = match_ast! {
+/// match &root {
+/// RawLanguageRoot(root) => { format!("root: {}", root.text()) },
+/// LiteralExpression(literal) => { format!("literal: {}", literal.text()) },
+/// _ => {
+/// root.text().to_string()
+/// }
+/// }
+/// };
+///
+/// assert_eq!(text, "root: 5");
+/// ```
+#[macro_export]
+macro_rules! match_ast {
+ // Necessary because expressions aren't allowed in front of `{`
+ (match &$node:ident { $($tt:tt)* }) => { match_ast!(match (&$node) { $($tt)* }) };
+ (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
+
+ (match ($node:expr) {
+ $( $( $path:ident )::+ ($it:pat) => $res:expr, )*
+ _ => $catch_all:expr $(,)?
+ }) => {{
+ $( if let Some($it) = $($path::)+cast_ref($node) { $res } else )*
+ { $catch_all }
+ }};
+}
+
/// Declares a custom union AstNode type with an ungram-like syntax
///
/// # Example

feat(rome_rowan): `cast_ref` and `match_ast` macro (#3113)