Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion crates/oxc_formatter/src/ast_nodes/generated/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4377,7 +4377,6 @@ impl<'a> Format<'a> for AstNode<'a, TSUnionType<'a>> {
if !is_suppressed && format_type_cast_comment_node(self, false, f) {
return;
}
self.format_leading_comments(f);
let needs_parentheses = self.needs_parentheses(f);
if needs_parentheses {
"(".fmt(f);
Expand Down
10 changes: 6 additions & 4 deletions crates/oxc_formatter/src/formatter/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,12 @@ impl<'a> Comments<'a> {

/// Checks if the node has a suppression comment (prettier-ignore).
pub fn is_suppressed(&self, start: u32) -> bool {
self.comments_before(start).iter().any(|comment| {
// TODO: Consider using `oxc-formatter-ignore` instead of `prettier-ignore`
self.source_text.text_for(&comment.content_span()).trim() == "prettier-ignore"
})
self.comments_before(start).iter().any(|comment| self.is_suppression_comment(comment))
}

pub fn is_suppression_comment(&self, comment: &Comment) -> bool {
// TODO: Consider using `oxfmt-ignore` instead of `prettier-ignore`
self.source_text.text_for(&comment.content_span()).trim() == "prettier-ignore"
}

/// Checks if a comment is a type cast comment containing `@type` or `@satisfies`.
Expand Down
3 changes: 2 additions & 1 deletion crates/oxc_formatter/src/utils/assignment_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,12 @@ impl<'a> AssignmentLike<'a, '_> {
_ => false,
}
};

is_generic(&conditional_type.check_type)
|| is_generic(&conditional_type.extends_type)
|| comments.has_comment_before(decl.type_annotation.span().start)
}
// `TSUnionType` has its own indentation logic
TSType::TSUnionType(_) => false,
_ => {
// Check for leading comments on any other type
comments.has_comment_before(decl.type_annotation.span().start)
Expand Down
49 changes: 35 additions & 14 deletions crates/oxc_formatter/src/write/union_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,33 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSUnionType<'a>> {
union_type_at_top = parent;
}

let should_indent = {
let should_indent = !has_leading_comments && {
let parent = union_type_at_top.parent;

// These parents have indent for their content, so we don't need to indent here
!match parent {
AstNodes::TSTypeAliasDeclaration(_) => has_leading_comments,
match parent {
AstNodes::TSTypeAliasDeclaration(alias) => {
!f.comments().printed_comments().last().is_some_and(|comment| {
comment.span.start
> alias.type_parameters().map_or(alias.id.span.end, |tp| tp.span.end)
&& f.comments().is_end_of_line_comment(comment)
})
}
AstNodes::TSTypeAssertion(_)
| AstNodes::TSTupleType(_)
| AstNodes::TSTypeParameterInstantiation(_) => true,
_ => false,
| AstNodes::TSTypeParameterInstantiation(_) => false,
_ => true,
}
};

let types = format_with(|f| {
let suppressed_node_span = if f.comments().is_suppressed(self.span.start) {
self.types.first().unwrap().span()
} else {
Span::default()
};
let is_suppressed = leading_comments
.iter()
.rev()
.any(|comment| f.comments().is_suppression_comment(comment));

if has_leading_comments {
write!(f, FormatLeadingComments::Comments(leading_comments));
}
let suppressed_node_span =
if is_suppressed { self.types.first().unwrap().span() } else { Span::default() };

let leading_soft_line_break_or_space = should_indent && !has_leading_comments;

Expand Down Expand Up @@ -123,7 +127,24 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSUnionType<'a>> {
}
});

write!(f, [group(&content)]);
if has_leading_comments {
let has_own_line_leading_comment = union_type_at_top.types.len() > 1
&& leading_comments.iter().any(|comment| f.comments().is_own_line_comment(comment));
let is_end_of_line_comment = leading_comments
.last()
.is_some_and(|comment| f.comments().is_end_of_line_comment(comment));
write!(
f,
[group(&indent(&format_args!(
has_own_line_leading_comment.then(soft_line_break),
FormatLeadingComments::Comments(leading_comments),
(!is_end_of_line_comment).then(soft_line_break),
group(&content)
)))]
);
} else {
write!(f, [group(&content)]);
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions crates/oxc_formatter/tests/fixtures/ts/union/issue-16176.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default class TestUnionTypeAnnotation1 {
private prop!: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];

private accessor prop2!: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}

export interface TestUnionTypeAnnotation2 {
property: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
export default class TestUnionTypeAnnotation1 {
private prop!: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];

private accessor prop2!: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}

export interface TestUnionTypeAnnotation2 {
property: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}

==================== Output ====================
------------------
{ printWidth: 80 }
------------------
export default class TestUnionTypeAnnotation1 {
private prop!: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];

private accessor prop2: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}

export interface TestUnionTypeAnnotation2 {
property: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}

-------------------
{ printWidth: 100 }
-------------------
export default class TestUnionTypeAnnotation1 {
private prop!: /* comment */ LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];

private accessor prop2: /* comment */
LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}

export interface TestUnionTypeAnnotation2 {
property: /* comment */ LongLongLongLongLongLongType[] | LongLongLongLongLongLongType[];
}

===================== End =====================
21 changes: 10 additions & 11 deletions tasks/ast_tools/src/generators/formatter/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const AST_NODE_WITHOUT_PRINTING_COMMENTS_LIST: &[&str] = &[
"TemplateElement",
];

const AST_NODE_WITHOUT_PRINTING_LEADING_COMMENTS_LIST: &[&str] = &["TSUnionType"];

const AST_NODE_NEEDS_PARENTHESES: &[&str] = &[
"TSTypeAssertion",
"TSInferType",
Expand Down Expand Up @@ -109,22 +111,19 @@ fn generate_struct_implementation(

let struct_name = struct_def.name();
let do_not_print_comment = AST_NODE_WITHOUT_PRINTING_COMMENTS_LIST.contains(&struct_name);
let do_not_print_leading_comment = do_not_print_comment
|| AST_NODE_WITHOUT_PRINTING_LEADING_COMMENTS_LIST.contains(&struct_name);

let leading_comments = if do_not_print_comment {
quote! {}
} else {
let leading_comments = (!do_not_print_leading_comment).then(|| {
quote! {
self.format_leading_comments(f);
}
};

let trailing_comments = if do_not_print_comment {
quote! {}
} else {
});
let trailing_comments = (!do_not_print_comment).then(|| {
quote! {
self.format_trailing_comments(f);
}
};
});

let needs_parentheses = parenthesis_type_ids.contains(&struct_def.id);

Expand Down Expand Up @@ -171,7 +170,7 @@ fn generate_struct_implementation(

let write_implementation = if suppressed_check.is_none() {
write_call
} else if trailing_comments.is_empty() {
} else if trailing_comments.is_none() {
quote! {
if is_suppressed {
self.format_leading_comments(f);
Expand Down Expand Up @@ -214,7 +213,7 @@ fn generate_struct_implementation(
}
});

if needs_parentheses_before.is_empty() && trailing_comments.is_empty() {
if needs_parentheses_before.is_empty() && trailing_comments.is_none() {
quote! {
#suppressed_check
#type_cast_comment_formatting
Expand Down
4 changes: 3 additions & 1 deletion tasks/coverage/snapshots/formatter_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ commit: 669c25c0

formatter_typescript Summary:
AST Parsed : 9822/9822 (100.00%)
Positive Passed: 9815/9822 (99.93%)
Positive Passed: 9814/9822 (99.92%)
Mismatch: tasks/coverage/typescript/tests/cases/compiler/amdLikeInputDeclarationEmit.ts

Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/genericTypeAssertions3.ts
Unexpected token
Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/typeAssertionToGenericFunctionType.ts
Unexpected token
Mismatch: tasks/coverage/typescript/tests/cases/compiler/unionTypeWithLeadingOperator.ts

Mismatch: tasks/coverage/typescript/tests/cases/conformance/expressions/contextualTyping/parenthesizedContexualTyping2.ts

Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/expressions/elementAccess/letIdentifierInElementAccess01.ts
Expand Down
7 changes: 3 additions & 4 deletions tasks/prettier_conformance/snapshots/prettier.ts.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ts compatibility: 571/602 (94.85%)
ts compatibility: 572/602 (95.02%)

# Failed

Expand Down Expand Up @@ -32,6 +32,5 @@ ts compatibility: 571/602 (94.85%)
| typescript/object-multiline/multiline.ts | 💥✨ | 23.21% |
| typescript/property-signature/consistent-with-flow/comments.ts | 💥 | 80.00% |
| typescript/union/union-parens.ts | 💥 | 92.59% |
| typescript/union/comments/18106.ts | 💥 | 90.48% |
| typescript/union/consistent-with-flow/comment.ts | 💥 | 82.61% |
| typescript/union/single-type/single-type.ts | 💥 | 0.00% |
| typescript/union/comments/18106.ts | 💥 | 92.68% |
| typescript/union/single-type/single-type.ts | 💥 | 66.67% |
Loading