1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use crate::prelude::*;
use biome_formatter::{write, Buffer, CstFormatContext};
use biome_js_syntax::JsBlockStatement;
use biome_js_syntax::{AnyJsStatement, JsEmptyStatement};
use biome_js_syntax::JsBlockStatementFields;
use biome_js_syntax::JsSyntaxKind;
use biome_rowan::{AstNode, AstNodeList, SyntaxNodeOptionExt};
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatJsBlockStatement;
impl FormatNodeRule<JsBlockStatement> for FormatJsBlockStatement {
fn fmt_fields(&self, node: &JsBlockStatement, f: &mut JsFormatter) -> FormatResult<()> {
let JsBlockStatementFields {
l_curly_token,
statements,
r_curly_token,
} = node.as_fields();
write!(f, [l_curly_token.format()])?;
let r_curly_token = r_curly_token?;
let comments = f.context().comments();
if is_empty_block(node, comments) {
let has_dangling_comments = comments.has_dangling_comments(node.syntax());
for stmt in statements
.iter()
.filter_map(|stmt| JsEmptyStatement::cast(stmt.into_syntax()))
{
f.state_mut().track_token(&stmt.semicolon_token()?)
}
if has_dangling_comments {
write!(
f,
[format_dangling_comments(node.syntax()).with_block_indent()]
)?;
} else if is_non_collapsible(node) {
write!(f, [hard_line_break()])?;
}
} else {
write!(f, [block_indent(&statements.format())])?;
}
write!(f, [r_curly_token.format()])
}
fn fmt_dangling_comments(&self, _: &JsBlockStatement, _: &mut JsFormatter) -> FormatResult<()> {
// Formatted inside of `fmt_fields`
Ok(())
}
}
fn is_empty_block(block: &JsBlockStatement, comments: &JsComments) -> bool {
// add extra branch to avoid formatting the same code twice and generating different code,
// here is an example:
// ```js
// try
// /* missing comment */
// {;}
// finally {}
// ```
// if we don't add the extra branch, this function will return false, because `block.statement` has one empty statement,
// and would be formatted as :
// ```js
// try
// /* missing comment */
// {}
// finally {}
// ```
// for the second time, the function would return true, because the block is empty and `parent.syntax.kind` is `JS_TRY_FINALLY_STATEMENT`, which would hit the branch `Some(_) => true`,
// finally the code would be formatted as:
// ```js
// try
/* missing comment */
// {
// } finally {
// }
// ```
block.statements().is_empty()
|| block.statements().iter().all(|s| {
matches!(s, AnyJsStatement::JsEmptyStatement(_))
&& !comments.has_comments(s.syntax())
&& !comments.is_suppressed(s.syntax())
})
}
// Formatting of curly braces for an:
// * empty block: same line `{}`,
// * empty block that is the 'cons' or 'alt' of an if statement: two lines `{\n}`
// * non empty block: put each stmt on its own line: `{\nstmt1;\nstmt2;\n}`
// * non empty block with comments (trailing comments on {, or leading comments on })
fn is_non_collapsible(block: &JsBlockStatement) -> bool {
// reference https://github.com/prettier/prettier/blob/b188c905cfaeb238a122b4a95c230da83f2f3226/src/language-js/print/block.js#L19
let parent = block.syntax().parent();
match parent.kind() {
Some(
JsSyntaxKind::JS_FUNCTION_BODY
| JsSyntaxKind::JS_FOR_STATEMENT
| JsSyntaxKind::JS_WHILE_STATEMENT
| JsSyntaxKind::JS_DO_WHILE_STATEMENT
| JsSyntaxKind::TS_MODULE_DECLARATION
| JsSyntaxKind::TS_DECLARE_FUNCTION_DECLARATION,
) => false,
// prettier collapse the catch block when it don't have `finalizer`, insert a new line when it has `finalizer`
Some(JsSyntaxKind::JS_CATCH_CLAUSE) => {
// SAFETY: since parent node have `Some(kind)`, this must not be `None`
let parent_unwrap = parent.unwrap();
let finally_clause = parent_unwrap.next_sibling();
matches!(
finally_clause.map(|finally| finally.kind()),
Some(JsSyntaxKind::JS_FINALLY_CLAUSE),
)
}
Some(_) => true,
None => false,
}
}