use crate::prelude::*;
use crate::utils::AnyJsConditional;
use biome_diagnostics_categories::category;
use biome_formatter::comments::is_doc_comment;
use biome_formatter::{
comments::{
CommentKind, CommentPlacement, CommentStyle, CommentTextPosition, Comments,
DecoratedComment, SourceComment,
},
write,
};
use biome_js_syntax::suppression::parse_suppression_comment;
use biome_js_syntax::JsSyntaxKind::JS_EXPORT;
use biome_js_syntax::{
AnyJsClass, AnyJsName, AnyJsRoot, AnyJsStatement, JsArrayHole, JsArrowFunctionExpression,
JsBlockStatement, JsCallArguments, JsCatchClause, JsEmptyStatement, JsFinallyClause,
JsFormalParameter, JsFunctionBody, JsIdentifierExpression, JsIfStatement, JsLanguage,
JsSyntaxKind, JsSyntaxNode, JsVariableDeclarator, JsWhileStatement, TsInterfaceDeclaration,
};
use biome_rowan::{AstNode, SyntaxNodeOptionExt, SyntaxTriviaPieceComments, TextLen};
pub type JsComments = Comments<JsLanguage>;
#[derive(Default)]
pub struct FormatJsLeadingComment;
impl FormatRule<SourceComment<JsLanguage>> for FormatJsLeadingComment {
type Context = JsFormatContext;
fn fmt(
&self,
comment: &SourceComment<JsLanguage>,
f: &mut Formatter<Self::Context>,
) -> FormatResult<()> {
if is_doc_comment(comment.piece()) {
let mut source_offset = comment.piece().text_range().start();
let mut lines = comment.piece().text().lines();
let first_line = lines.next().unwrap();
write!(f, [dynamic_text(first_line.trim_end(), source_offset)])?;
source_offset += first_line.text_len();
write!(
f,
[align(
1,
&format_once(|f| {
for line in lines {
write!(
f,
[hard_line_break(), dynamic_text(line.trim(), source_offset)]
)?;
source_offset += line.text_len();
}
Ok(())
})
)]
)
} else {
write!(f, [comment.piece().as_piece()])
}
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug, Default)]
pub struct JsCommentStyle;
impl CommentStyle for JsCommentStyle {
type Language = JsLanguage;
fn is_suppression(text: &str) -> bool {
parse_suppression_comment(text)
.filter_map(Result::ok)
.flat_map(|suppression| suppression.categories)
.any(|(key, _)| key == category!("format"))
}
fn get_comment_kind(comment: &SyntaxTriviaPieceComments<JsLanguage>) -> CommentKind {
if comment.text().starts_with("/*") {
if comment.has_newline() {
CommentKind::Block
} else {
CommentKind::InlineBlock
}
} else {
CommentKind::Line
}
}
fn place_comment(
&self,
comment: DecoratedComment<Self::Language>,
) -> CommentPlacement<Self::Language> {
match comment.text_position() {
CommentTextPosition::EndOfLine => handle_typecast_comment(comment)
.or_else(handle_function_declaration_comment)
.or_else(handle_conditional_comment)
.or_else(handle_if_statement_comment)
.or_else(handle_while_comment)
.or_else(handle_try_comment)
.or_else(handle_class_comment)
.or_else(handle_method_comment)
.or_else(handle_for_comment)
.or_else(handle_root_comments)
.or_else(handle_array_hole_comment)
.or_else(handle_variable_declarator_comment)
.or_else(handle_parameter_comment)
.or_else(handle_labelled_statement_comment)
.or_else(handle_call_expression_comment)
.or_else(handle_continue_break_comment)
.or_else(handle_mapped_type_comment)
.or_else(handle_switch_default_case_comment)
.or_else(handle_import_export_specifier_comment),
CommentTextPosition::OwnLine => handle_member_expression_comment(comment)
.or_else(handle_function_declaration_comment)
.or_else(handle_if_statement_comment)
.or_else(handle_while_comment)
.or_else(handle_try_comment)
.or_else(handle_class_comment)
.or_else(handle_method_comment)
.or_else(handle_for_comment)
.or_else(handle_root_comments)
.or_else(handle_parameter_comment)
.or_else(handle_array_hole_comment)
.or_else(handle_labelled_statement_comment)
.or_else(handle_call_expression_comment)
.or_else(handle_mapped_type_comment)
.or_else(handle_continue_break_comment)
.or_else(handle_union_type_comment)
.or_else(handle_import_export_specifier_comment),
CommentTextPosition::SameLine => handle_if_statement_comment(comment)
.or_else(handle_while_comment)
.or_else(handle_for_comment)
.or_else(handle_root_comments)
.or_else(handle_after_arrow_param_comment)
.or_else(handle_array_hole_comment)
.or_else(handle_call_expression_comment)
.or_else(handle_continue_break_comment)
.or_else(handle_class_comment),
}
}
}
fn handle_typecast_comment(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
match comment.following_node() {
Some(following_node) if is_type_comment(comment.piece()) => {
CommentPlacement::leading(following_node.clone(), comment)
}
_ => CommentPlacement::Default(comment),
}
}
fn handle_after_arrow_param_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let is_next_arrow = comment
.following_token()
.map_or(false, |token| token.kind() == JsSyntaxKind::FAT_ARROW);
if JsArrowFunctionExpression::can_cast(comment.enclosing_node().kind()) && is_next_arrow {
CommentPlacement::dangling(comment.enclosing_node().clone(), comment)
} else {
CommentPlacement::Default(comment)
}
}
fn handle_array_hole_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
if let Some(array_hole) = comment.preceding_node().and_then(JsArrayHole::cast_ref) {
CommentPlacement::leading(array_hole.into_syntax(), comment)
} else {
CommentPlacement::Default(comment)
}
}
fn handle_call_expression_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
if let Some(arguments) = comment.following_node().and_then(JsCallArguments::cast_ref) {
return if let Some(Ok(first)) = arguments.args().first() {
CommentPlacement::leading(first.into_syntax(), comment)
} else {
CommentPlacement::dangling(arguments.into_syntax(), comment)
};
}
CommentPlacement::Default(comment)
}
fn handle_continue_break_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let enclosing = comment.enclosing_node();
match enclosing.kind() {
JsSyntaxKind::JS_CONTINUE_STATEMENT | JsSyntaxKind::JS_BREAK_STATEMENT => {
match enclosing.parent() {
Some(parent)
if matches!(
parent.kind(),
JsSyntaxKind::JS_FOR_STATEMENT
| JsSyntaxKind::JS_FOR_OF_STATEMENT
| JsSyntaxKind::JS_FOR_IN_STATEMENT
| JsSyntaxKind::JS_WHILE_STATEMENT
| JsSyntaxKind::JS_DO_WHILE_STATEMENT
| JsSyntaxKind::JS_IF_STATEMENT
| JsSyntaxKind::JS_WITH_STATEMENT
| JsSyntaxKind::JS_LABELED_STATEMENT
) =>
{
CommentPlacement::trailing(parent, comment)
}
_ => CommentPlacement::trailing(enclosing.clone(), comment),
}
}
_ => CommentPlacement::Default(comment),
}
}
fn handle_switch_default_case_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
match (comment.enclosing_node().kind(), comment.following_node()) {
(JsSyntaxKind::JS_DEFAULT_CLAUSE, Some(following)) => {
match JsBlockStatement::cast_ref(following) {
Some(block) if comment.kind().is_line() => {
place_block_statement_comment(block, comment)
}
_ => CommentPlacement::dangling(comment.enclosing_node().clone(), comment),
}
}
_ => CommentPlacement::Default(comment),
}
}
fn handle_labelled_statement_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
match comment.enclosing_node().kind() {
JsSyntaxKind::JS_LABELED_STATEMENT => {
CommentPlacement::leading(comment.enclosing_node().clone(), comment)
}
_ => CommentPlacement::Default(comment),
}
}
fn handle_class_comment(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
if matches!(
comment.enclosing_node().kind(),
JsSyntaxKind::JS_EXTENDS_CLAUSE
| JsSyntaxKind::TS_IMPLEMENTS_CLAUSE
| JsSyntaxKind::TS_EXTENDS_CLAUSE
) {
if comment.preceding_node().is_none() && !comment.text_position().is_same_line() {
if let Some(sibling) = comment.enclosing_node().prev_sibling() {
return CommentPlacement::trailing(sibling, comment);
}
}
return CommentPlacement::Default(comment);
}
if (AnyJsClass::can_cast(comment.enclosing_node().kind())
&& comment
.following_token()
.map_or(false, |token| token.kind() == JsSyntaxKind::CLASS_KW))
|| comment.enclosing_node().kind() == JS_EXPORT
{
if let Some(preceding) = comment.preceding_node() {
if preceding.kind() == JsSyntaxKind::JS_DECORATOR {
return CommentPlacement::trailing(preceding.clone(), comment);
}
}
}
let first_member = if let Some(class) = AnyJsClass::cast_ref(comment.enclosing_node()) {
class.members().first().map(AstNode::into_syntax)
} else if let Some(interface) = TsInterfaceDeclaration::cast_ref(comment.enclosing_node()) {
interface.members().first().map(AstNode::into_syntax)
} else {
return CommentPlacement::Default(comment);
};
if comment.text_position().is_same_line() {
if comment
.following_token()
.map_or(false, |token| token.kind() == JsSyntaxKind::R_CURLY)
&& first_member.is_none()
{
return CommentPlacement::dangling(comment.enclosing_node().clone(), comment);
} else {
return CommentPlacement::Default(comment);
}
}
if let Some(following) = comment.following_node() {
if let Some(member) = first_member {
if following == &member {
return CommentPlacement::leading(member, comment);
}
}
if let Some(preceding) = comment.preceding_node() {
if matches!(
following.kind(),
JsSyntaxKind::JS_EXTENDS_CLAUSE
| JsSyntaxKind::TS_IMPLEMENTS_CLAUSE
| JsSyntaxKind::TS_EXTENDS_CLAUSE
) && !comment.text_position().is_same_line()
{
return CommentPlacement::trailing(preceding.clone(), comment);
}
}
} else if first_member.is_none() {
return CommentPlacement::dangling(comment.enclosing_node().clone(), comment);
}
CommentPlacement::Default(comment)
}
fn handle_method_comment(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
let enclosing = comment.enclosing_node();
let is_method = matches!(
enclosing.kind(),
JsSyntaxKind::JS_METHOD_CLASS_MEMBER
| JsSyntaxKind::JS_METHOD_OBJECT_MEMBER
| JsSyntaxKind::JS_SETTER_CLASS_MEMBER
| JsSyntaxKind::JS_GETTER_CLASS_MEMBER
| JsSyntaxKind::JS_SETTER_OBJECT_MEMBER
| JsSyntaxKind::JS_GETTER_OBJECT_MEMBER
| JsSyntaxKind::JS_CONSTRUCTOR_CLASS_MEMBER
);
if !is_method {
return CommentPlacement::Default(comment);
}
if let Some(following) = comment.following_node() {
if let Some(body) = JsFunctionBody::cast_ref(following) {
if let Some(directive) = body.directives().first() {
return CommentPlacement::leading(directive.into_syntax(), comment);
}
let first_non_empty = body
.statements()
.iter()
.find(|statement| !matches!(statement, AnyJsStatement::JsEmptyStatement(_)));
return match first_non_empty {
None => CommentPlacement::dangling(body.into_syntax(), comment),
Some(statement) => CommentPlacement::leading(statement.into_syntax(), comment),
};
}
}
CommentPlacement::Default(comment)
}
fn handle_root_comments(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
if let Some(root) = AnyJsRoot::cast_ref(comment.enclosing_node()) {
let is_blank = match &root {
AnyJsRoot::JsExpressionSnipped(_) => false,
AnyJsRoot::JsModule(module) => {
module.directives().is_empty() && module.items().is_empty()
}
AnyJsRoot::JsScript(script) => {
script.directives().is_empty() && script.statements().is_empty()
}
};
if is_blank {
return CommentPlacement::leading(root.into_syntax(), comment);
}
}
CommentPlacement::Default(comment)
}
fn handle_member_expression_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let following = match comment.following_node() {
Some(following)
if matches!(
comment.enclosing_node().kind(),
JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION
| JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION
) =>
{
following
}
_ => return CommentPlacement::Default(comment),
};
if AnyJsName::can_cast(following.kind()) || JsIdentifierExpression::can_cast(following.kind()) {
CommentPlacement::leading(comment.enclosing_node().clone(), comment)
} else {
CommentPlacement::Default(comment)
}
}
fn handle_function_declaration_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let is_function_declaration = matches!(
comment.enclosing_node().kind(),
JsSyntaxKind::JS_FUNCTION_DECLARATION
| JsSyntaxKind::JS_FUNCTION_EXPORT_DEFAULT_DECLARATION
);
let following = match comment.following_node() {
Some(following) if is_function_declaration => following,
_ => return CommentPlacement::Default(comment),
};
if let Some(body) = JsFunctionBody::cast_ref(following) {
match body
.statements()
.iter()
.find(|statement| !matches!(statement, AnyJsStatement::JsEmptyStatement(_)))
{
Some(first) => CommentPlacement::leading(first.into_syntax(), comment),
None => CommentPlacement::dangling(body.into_syntax(), comment),
}
} else {
CommentPlacement::Default(comment)
}
}
fn handle_conditional_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let enclosing = comment.enclosing_node();
let (conditional, following) = match (
AnyJsConditional::cast_ref(enclosing),
comment.following_node(),
) {
(Some(conditional), Some(following)) => (conditional, following),
_ => {
return CommentPlacement::Default(comment);
}
};
let token = comment.piece().as_piece().token();
let is_after_operator = conditional.colon_token().as_ref() == Ok(&token)
|| conditional.question_mark_token().as_ref() == Ok(&token);
if is_after_operator {
return CommentPlacement::leading(following.clone(), comment);
}
CommentPlacement::Default(comment)
}
fn handle_if_statement_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
fn handle_else_clause(
comment: DecoratedComment<JsLanguage>,
consequent: JsSyntaxNode,
if_statement: JsSyntaxNode,
) -> CommentPlacement<JsLanguage> {
if consequent.kind() == JsSyntaxKind::JS_BLOCK_STATEMENT {
return CommentPlacement::trailing(consequent, comment);
}
if !comment.kind().is_block()
&& !comment.text_position().is_own_line()
&& comment.preceding_node().is_some()
{
return CommentPlacement::dangling(consequent, comment);
}
CommentPlacement::dangling(if_statement, comment)
}
match (comment.enclosing_node().kind(), comment.following_node()) {
(JsSyntaxKind::JS_IF_STATEMENT, Some(following)) => {
let if_statement = JsIfStatement::unwrap_cast(comment.enclosing_node().clone());
if let Some(preceding) = comment.preceding_node() {
if comment
.following_token()
.map_or(false, |token| token.kind() == JsSyntaxKind::R_PAREN)
{
return CommentPlacement::trailing(preceding.clone(), comment);
}
if following.kind() == JsSyntaxKind::JS_ELSE_CLAUSE {
let consequent = preceding.clone();
let if_statement = comment.enclosing_node().clone();
return handle_else_clause(comment, consequent, if_statement);
}
}
if let Some(block_statement) = JsBlockStatement::cast_ref(following) {
return place_block_statement_comment(block_statement, comment);
}
if let Some(preceding) = comment.preceding_node() {
if JsEmptyStatement::can_cast(following.kind()) {
return CommentPlacement::trailing(preceding.clone(), comment);
}
}
if let Some(if_statement) = JsIfStatement::cast_ref(following) {
if let Ok(nested_consequent) = if_statement.consequent() {
return place_leading_statement_comment(nested_consequent, comment);
}
}
if let Ok(consequent) = if_statement.consequent() {
if consequent.syntax() == following {
return CommentPlacement::leading(following.clone(), comment);
}
}
}
(JsSyntaxKind::JS_ELSE_CLAUSE, _) => {
if let Some(if_statement) = comment
.enclosing_node()
.parent()
.and_then(JsIfStatement::cast)
{
if let Ok(consequent) = if_statement.consequent() {
return handle_else_clause(
comment,
consequent.into_syntax(),
if_statement.into_syntax(),
);
}
}
}
_ => {
}
}
CommentPlacement::Default(comment)
}
fn handle_while_comment(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
let (while_statement, following) = match (
JsWhileStatement::cast_ref(comment.enclosing_node()),
comment.following_node(),
) {
(Some(while_statement), Some(following)) => (while_statement, following),
_ => return CommentPlacement::Default(comment),
};
if let Some(preceding) = comment.preceding_node() {
if comment
.following_token()
.map_or(false, |token| token.kind() == JsSyntaxKind::R_PAREN)
{
return CommentPlacement::trailing(preceding.clone(), comment);
}
}
if let Some(block) = JsBlockStatement::cast_ref(following) {
return place_block_statement_comment(block, comment);
}
if let Some(preceding) = comment.preceding_node() {
if JsEmptyStatement::can_cast(following.kind()) {
return CommentPlacement::trailing(preceding.clone(), comment);
}
}
if let Ok(body) = while_statement.body() {
if body.syntax() == following {
return CommentPlacement::leading(body.into_syntax(), comment);
}
}
CommentPlacement::Default(comment)
}
fn handle_try_comment(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
let following = match comment.following_node() {
Some(following)
if matches!(
comment.enclosing_node().kind(),
JsSyntaxKind::JS_TRY_STATEMENT | JsSyntaxKind::JS_TRY_FINALLY_STATEMENT
) =>
{
let body = if let Some(catch) = JsCatchClause::cast_ref(following) {
catch.body()
} else if let Some(finally) = JsFinallyClause::cast_ref(following) {
finally.body()
} else {
Err(biome_rowan::SyntaxError::MissingRequiredChild)
};
if let Ok(body) = body {
return place_block_statement_comment(body, comment);
}
following
}
Some(following)
if matches!(
comment.enclosing_node().kind(),
JsSyntaxKind::JS_CATCH_CLAUSE | JsSyntaxKind::JS_FINALLY_CLAUSE
) =>
{
following
}
_ => return CommentPlacement::Default(comment),
};
if let Some(block) = JsBlockStatement::cast_ref(following) {
return place_block_statement_comment(block, comment);
}
CommentPlacement::Default(comment)
}
fn handle_for_comment(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
let enclosing = comment.enclosing_node();
let is_for_in_or_of = matches!(
enclosing.kind(),
JsSyntaxKind::JS_FOR_OF_STATEMENT | JsSyntaxKind::JS_FOR_IN_STATEMENT
);
if !is_for_in_or_of && !matches!(enclosing.kind(), JsSyntaxKind::JS_FOR_STATEMENT) {
return CommentPlacement::Default(comment);
}
if comment.text_position().is_own_line() && is_for_in_or_of {
CommentPlacement::leading(enclosing.clone(), comment)
}
else if comment.following_node().map_or(false, |following| {
JsEmptyStatement::can_cast(following.kind())
}) {
if let Some(preceding) = comment.preceding_node() {
CommentPlacement::trailing(preceding.clone(), comment)
} else {
CommentPlacement::dangling(comment.enclosing_node().clone(), comment)
}
} else {
CommentPlacement::Default(comment)
}
}
fn handle_variable_declarator_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let following = match comment.following_node() {
Some(following) => following,
None => return CommentPlacement::Default(comment),
};
fn is_complex_value(value: &JsSyntaxNode) -> bool {
matches!(
value.kind(),
JsSyntaxKind::JS_OBJECT_EXPRESSION
| JsSyntaxKind::JS_ARRAY_EXPRESSION
| JsSyntaxKind::JS_TEMPLATE_EXPRESSION
| JsSyntaxKind::TS_OBJECT_TYPE
| JsSyntaxKind::TS_UNION_TYPE
)
}
let enclosing = comment.enclosing_node();
match enclosing.kind() {
JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION | JsSyntaxKind::TS_TYPE_ALIAS_DECLARATION => {
if is_complex_value(following) || !comment.kind().is_line() {
return CommentPlacement::leading(following.clone(), comment);
}
}
JsSyntaxKind::JS_VARIABLE_DECLARATOR => {
let variable_declarator = JsVariableDeclarator::unwrap_cast(enclosing.clone());
match variable_declarator.initializer() {
Some(initializer)
if initializer.syntax() == following
&& initializer
.expression()
.map_or(false, |expression| is_complex_value(expression.syntax())) =>
{
return CommentPlacement::leading(initializer.into_syntax(), comment);
}
_ => {
}
}
}
JsSyntaxKind::JS_INITIALIZER_CLAUSE => {
let parent_kind = enclosing.parent().kind();
if matches!(
parent_kind,
Some(JsSyntaxKind::JS_VARIABLE_DECLARATOR | JsSyntaxKind::JS_PROPERTY_CLASS_MEMBER)
) {
let not_complex =
matches!(parent_kind, Some(JsSyntaxKind::JS_PROPERTY_CLASS_MEMBER))
|| !is_complex_value(following);
if not_complex
&& !JsCommentStyle::is_suppression(comment.piece().text())
&& comment.kind().is_line()
&& comment.preceding_node().is_none()
{
if let Some(prev_node) = enclosing.prev_sibling() {
return CommentPlacement::trailing(prev_node, comment);
}
}
}
}
_ => {
}
}
CommentPlacement::Default(comment)
}
fn handle_parameter_comment(comment: DecoratedComment<JsLanguage>) -> CommentPlacement<JsLanguage> {
match comment.enclosing_node().kind() {
JsSyntaxKind::JS_FORMAL_PARAMETER | JsSyntaxKind::TS_PROPERTY_PARAMETER => {
if let Some(preceding_node) = comment.preceding_node() {
if comment.following_node().kind() != Some(JsSyntaxKind::JS_DECORATOR) {
return CommentPlacement::trailing(preceding_node.clone(), comment);
}
} else if comment.text_position().is_own_line() {
return CommentPlacement::leading(comment.enclosing_node().clone(), comment);
}
}
JsSyntaxKind::JS_INITIALIZER_CLAUSE => {
if let Some(parameter) = comment
.enclosing_node()
.parent()
.and_then(JsFormalParameter::cast)
{
if comment.text_position().is_end_of_line() && comment.preceding_node().is_none() {
if let Ok(binding) = parameter.binding() {
return CommentPlacement::trailing(binding.into_syntax(), comment);
}
} else if comment.text_position().is_own_line() {
return CommentPlacement::leading(parameter.into_syntax(), comment);
}
}
}
_ => {
}
}
CommentPlacement::Default(comment)
}
fn handle_mapped_type_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
if let (JsSyntaxKind::TS_MAPPED_TYPE, Some(following)) =
(comment.enclosing_node().kind(), comment.following_node())
{
if following.kind() == JsSyntaxKind::TS_TYPE_PARAMETER_NAME {
return CommentPlacement::dangling(comment.enclosing_node().clone(), comment);
}
}
CommentPlacement::Default(comment)
}
fn handle_union_type_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
match (comment.enclosing_node().kind(), comment.preceding_node()) {
(JsSyntaxKind::TS_UNION_TYPE, Some(preceding)) => {
CommentPlacement::trailing(preceding.clone(), comment)
}
_ => CommentPlacement::Default(comment),
}
}
fn handle_import_export_specifier_comment(
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let enclosing_node = comment.enclosing_node();
match enclosing_node.kind() {
JsSyntaxKind::JS_NAMED_IMPORT_SPECIFIER
| JsSyntaxKind::JS_EXPORT_NAMED_SPECIFIER
| JsSyntaxKind::JS_EXPORT_NAMED_SHORTHAND_SPECIFIER
| JsSyntaxKind::JS_EXPORT_NAMED_FROM_SPECIFIER => {
CommentPlacement::leading(enclosing_node.clone(), comment)
}
JsSyntaxKind::JS_IMPORT_ASSERTION_ENTRY => {
CommentPlacement::leading(enclosing_node.clone(), comment)
}
JsSyntaxKind::JS_EXPORT_AS_CLAUSE => {
if let Some(parent) = enclosing_node.parent() {
CommentPlacement::leading(parent, comment)
} else {
CommentPlacement::Default(comment)
}
}
_ => CommentPlacement::Default(comment),
}
}
fn place_leading_statement_comment(
statement: AnyJsStatement,
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
match statement {
AnyJsStatement::JsBlockStatement(block) => place_block_statement_comment(block, comment),
statement => CommentPlacement::leading(statement.into_syntax(), comment),
}
}
fn place_block_statement_comment(
block_statement: JsBlockStatement,
comment: DecoratedComment<JsLanguage>,
) -> CommentPlacement<JsLanguage> {
let first_non_empty = block_statement
.statements()
.iter()
.find(|statement| !matches!(statement, AnyJsStatement::JsEmptyStatement(_)));
match first_non_empty {
None => CommentPlacement::dangling(block_statement.into_syntax(), comment),
Some(statement) => CommentPlacement::leading(statement.into_syntax(), comment),
}
}
pub(crate) fn is_type_comment(comment: &SyntaxTriviaPieceComments<JsLanguage>) -> bool {
let text = comment.text();
if !text.starts_with("/**") {
return false;
}
text.trim_start_matches("/**")
.trim_end_matches("*/")
.split_whitespace()
.any(|word| match word.strip_prefix("@type") {
Some(after) => after.is_empty() || after.starts_with('{'),
None => false,
})
}