biome_js_formatter 0.0.2

Biome's JavaScript formatter
Documentation
use crate::prelude::*;

use crate::parentheses::{
    is_binary_like_left_or_right, is_callee, is_member_object, NeedsParentheses,
};
use crate::ts::expressions::type_assertion_expression::type_cast_like_needs_parens;
use biome_formatter::{format_args, write};
use biome_js_syntax::{AnyJsExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, TsAsExpression};
use biome_js_syntax::{AnyTsType, TsSatisfiesExpression};
use biome_rowan::{declare_node_union, SyntaxResult};

#[derive(Debug, Clone, Default)]
pub struct FormatTsAsExpression;

impl FormatNodeRule<TsAsExpression> for FormatTsAsExpression {
    fn fmt_fields(&self, node: &TsAsExpression, f: &mut JsFormatter) -> FormatResult<()> {
        TsAsOrSatisfiesExpression::from(node.clone()).fmt(f)
    }

    fn needs_parentheses(&self, item: &TsAsExpression) -> bool {
        item.needs_parentheses()
    }
}

impl NeedsParentheses for TsAsExpression {
    fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
        TsAsOrSatisfiesExpression::from(self.clone()).needs_parentheses_with_parent(parent)
    }
}

declare_node_union! {
    pub(crate) TsAsOrSatisfiesExpression = TsAsExpression | TsSatisfiesExpression
}

impl TsAsOrSatisfiesExpression {
    fn ty(&self) -> SyntaxResult<AnyTsType> {
        match self {
            TsAsOrSatisfiesExpression::TsAsExpression(expression) => expression.ty(),
            TsAsOrSatisfiesExpression::TsSatisfiesExpression(expression) => expression.ty(),
        }
    }

    fn operation_token(&self) -> SyntaxResult<JsSyntaxToken> {
        match self {
            TsAsOrSatisfiesExpression::TsAsExpression(expression) => expression.as_token(),
            TsAsOrSatisfiesExpression::TsSatisfiesExpression(expression) => {
                expression.satisfies_token()
            }
        }
    }

    fn expression(&self) -> SyntaxResult<AnyJsExpression> {
        match self {
            TsAsOrSatisfiesExpression::TsAsExpression(expression) => expression.expression(),
            TsAsOrSatisfiesExpression::TsSatisfiesExpression(expression) => expression.expression(),
        }
    }
}

impl Format<JsFormatContext> for TsAsOrSatisfiesExpression {
    fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> {
        let expression = self.expression();
        let operation_token = self.operation_token()?;
        let ty = self.ty()?;

        let format_inner = format_with(|f| {
            write!(f, [expression.format(), space(), operation_token.format()])?;

            if f.comments().has_leading_own_line_comment(ty.syntax()) {
                write!(f, [indent(&format_args![hard_line_break(), &ty.format()])])
            } else {
                write!(f, [space(), ty.format()])
            }
        });

        let parent = self.syntax().parent();

        let is_callee_or_object = parent.map_or(false, |parent| {
            is_callee(self.syntax(), &parent) || is_member_object(self.syntax(), &parent)
        });

        if is_callee_or_object {
            write!(f, [group(&soft_block_indent(&format_inner))])
        } else {
            write!(f, [format_inner])
        }
    }
}

impl NeedsParentheses for TsAsOrSatisfiesExpression {
    fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
        match parent.kind() {
            JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => true,

            _ => {
                type_cast_like_needs_parens(self.syntax(), parent)
                    || is_binary_like_left_or_right(self.syntax(), parent)
            }
        }
    }
}

#[cfg(test)]
mod tests {

    use crate::{assert_needs_parentheses, assert_not_needs_parentheses};
    use biome_js_syntax::{JsFileSource, TsAsExpression};

    #[test]
    fn needs_parentheses() {
        assert_needs_parentheses!("5 as number ? true : false", TsAsExpression);
        assert_needs_parentheses!("cond ? x as number : false", TsAsExpression);
        assert_needs_parentheses!("cond ? true : x as number", TsAsExpression);

        assert_needs_parentheses!("class X extends (B as number) {}", TsAsExpression);

        assert_needs_parentheses!("(x as Function)()", TsAsExpression);
        assert_needs_parentheses!("(x as Function)?.()", TsAsExpression);
        assert_needs_parentheses!("new (x as Function)()", TsAsExpression);

        assert_needs_parentheses!("<number>(x as any)", TsAsExpression);
        assert_needs_parentheses!("(x as any)`template`", TsAsExpression);
        assert_needs_parentheses!("!(x as any)", TsAsExpression);
        assert_needs_parentheses!("[...(x as any)]", TsAsExpression);
        assert_needs_parentheses!("({...(x as any)})", TsAsExpression);
        assert_needs_parentheses!(
            "<test {...(x as any)} />",
            TsAsExpression,
            JsFileSource::tsx()
        );
        assert_needs_parentheses!(
            "<test>{...(x as any)}</test>",
            TsAsExpression,
            JsFileSource::tsx()
        );
        assert_needs_parentheses!("await (x as any)", TsAsExpression);
        assert_needs_parentheses!("(x as any)!", TsAsExpression);

        assert_needs_parentheses!("(x as any).member", TsAsExpression);
        assert_needs_parentheses!("(x as any)[member]", TsAsExpression);
        assert_not_needs_parentheses!("object[x as any]", TsAsExpression);

        assert_needs_parentheses!("(x as any) + (y as any)", TsAsExpression[0]);
        assert_needs_parentheses!("(x as any) + (y as any)", TsAsExpression[1]);

        assert_needs_parentheses!("(x as any) && (y as any)", TsAsExpression[0]);
        assert_needs_parentheses!("(x as any) && (y as any)", TsAsExpression[1]);

        assert_needs_parentheses!("(x as any) in (y as any)", TsAsExpression[0]);
        assert_needs_parentheses!("(x as any) in (y as any)", TsAsExpression[1]);

        assert_needs_parentheses!("(x as any) instanceof (y as any)", TsAsExpression[0]);
        assert_needs_parentheses!("(x as any) instanceof (y as any)", TsAsExpression[1]);

        assert_not_needs_parentheses!("x as number as string", TsAsExpression[1]);
    }
}