diff --git a/crates/oxc_formatter/src/write/class.rs b/crates/oxc_formatter/src/write/class.rs index 73dd78a7cc3d0..3ea38d2aadddc 100644 --- a/crates/oxc_formatter/src/write/class.rs +++ b/crates/oxc_formatter/src/write/class.rs @@ -469,13 +469,30 @@ impl<'a> Format<'a> for FormatClass<'a, '_> { /// /// Heritage clauses are grouped when: /// 1. Superclass and/or implements are more than one -/// 2. There are comments in the heritage clause area -/// 3. There are trailing line comments after type parameters +/// 2. The superclass is a member expression without type arguments, or the implements clause is a qualified name without type arguments +/// 3. There are comments in the heritage clause area +/// 4. There are trailing line comments after type parameters fn should_group<'a>(class: &Class<'a>, f: &Formatter<'_, 'a>) -> bool { if usize::from(class.super_class.is_some()) + class.implements.len() > 1 { return true; } + if !class.is_expression() + && ((class.super_type_arguments.is_none() + && class + .super_class + .as_ref() + .is_some_and(|super_class| + super_class.is_member_expression() || + matches!(&super_class, Expression::ChainExpression(chain) if chain.expression.is_member_expression()) + )) + || class.implements.first().is_some_and(|implements| { + implements.type_arguments.is_none() && implements.expression.is_qualified_name() + })) + { + return true; + } + let comments = f.comments(); let id_span = class.id.as_ref().map(GetSpan::span); diff --git a/crates/oxc_formatter/src/write/mod.rs b/crates/oxc_formatter/src/write/mod.rs index de5eb5cdae67f..32f2e9916eeb1 100644 --- a/crates/oxc_formatter/src/write/mod.rs +++ b/crates/oxc_formatter/src/write/mod.rs @@ -1301,10 +1301,16 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSInterfaceDeclaration<'a>> { let extends = self.extends(); let body = self.body(); + // Determines whether to use group mode for formatting the `extends` clause. + // 1. If there are multiple `extends`, we always use group mode. + // 2. If there is a single `extends` that is a member expression without type arguments, we use group mode. + // 3. If there are comments between the `id` and the `extends`, we use group mode. let group_mode = extends.len() > 1 || extends.as_ref().first().is_some_and(|first| { - let prev_span = type_parameters.as_ref().map_or(id.span(), GetSpan::span); - f.comments().has_comment_in_range(prev_span.end, first.span().start) + (first.expression.is_member_expression() && first.type_arguments.is_none()) || { + let prev_span = type_parameters.as_ref().map_or(id.span(), GetSpan::span); + f.comments().has_comment_in_range(prev_span.end, first.span().start) + } }); let format_id = format_with(|f| { diff --git a/crates/oxc_formatter/tests/fixtures/ts/class/issue-16259.ts b/crates/oxc_formatter/tests/fixtures/ts/class/issue-16259.ts new file mode 100644 index 0000000000000..d007ff37ba061 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/class/issue-16259.ts @@ -0,0 +1,6 @@ +export class longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + +export interface longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + \ No newline at end of file diff --git a/crates/oxc_formatter/tests/fixtures/ts/class/issue-16259.ts.snap b/crates/oxc_formatter/tests/fixtures/ts/class/issue-16259.ts.snap new file mode 100644 index 0000000000000..d2e6d28788b43 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/class/issue-16259.ts.snap @@ -0,0 +1,30 @@ +--- +source: crates/oxc_formatter/tests/fixtures/mod.rs +--- +==================== Input ==================== +export class longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + +export interface longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +export class longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + +export interface longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + +------------------- +{ printWidth: 100 } +------------------- +export class longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + +export interface longlonglonglonglonglonglonglonglonglonglongclassname + extends someobjectsomepropertysomeotherproperty.SomeClass {} + +===================== End ===================== diff --git a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md index 1ab91b2483ddd..a3b3a9ecf1aa3 100644 --- a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md @@ -1,4 +1,4 @@ -ts compatibility: 574/604 (95.03%) +ts compatibility: 575/604 (95.20%) # Failed @@ -17,7 +17,6 @@ ts compatibility: 574/604 (95.03%) | typescript/chain-expression/test.ts | 💥 | 0.00% | | typescript/class/empty-method-body.ts | 💥 | 80.00% | | typescript/class/quoted-property.ts | 💥 | 66.67% | -| typescript/class-and-interface/heritage-break/member-expression-like.ts | 💥 | 54.55% | | typescript/comments/mapped_types.ts | 💥 | 96.77% | | typescript/comments/method_types.ts | 💥 | 82.05% | | typescript/decorators-ts/angular.ts | 💥 | 87.50% |