Skip to content

Commit fe25b65

Browse files
authored
feat(prettier): indent for class definition (#6059)
trying to match the output for: https://github.com/prettier/prettier/blob/5b868377c0a8805486156223401e051724689e6f/tests/format/typescript/classes/__snapshots__/format.test.js.snap#L3-L92 <details><summary>main branch output:</summary> ```typescript class MyContractSelectionWidget extends React.Component<void, MyContractSelectionWidgetPropsType, void> implements SomethingLarge { method() {} } class DisplayObject1 extends utils.EventEmitter implements interaction_InteractiveTarget {} class DisplayObject2 extends utils.EventEmitter implements interaction_InteractiveTarget {} class DisplayObject3 extends utils.EventEmitter implements interaction_InteractiveTarget, somethingElse_SomeOtherThing, somethingElseAgain_RunningOutOfNames {} class DisplayObject4 extends utils.EventEmitter implements interaction_InteractiveTarget {} class Readable extends events.EventEmitter implements NodeJS_ReadableStream {} class InMemoryAppender extends log4javascript.Appender implements ICachedLogMessageProvider {} class Foo extends Immutable.Record({ ipaddress: "", }) { ipaddress: string; } export class VisTimelineComponent implements AfterViewInit, OnChanges, OnDestroy {} export class VisTimelineComponent2 implements AfterViewInit, OnChanges, OnDestroy, AndSomethingReallyReallyLong {} ``` </details> <details><summary>this branch output:</summary> ```typescript class MyContractSelectionWidget extends React.Component<void, MyContractSelectionWidgetPropsType, void> implements SomethingLarge { method() {} } class DisplayObject1 extends utils.EventEmitter implements interaction_InteractiveTarget {} class DisplayObject2 extends utils.EventEmitter implements interaction_InteractiveTarget {} class DisplayObject3 extends utils.EventEmitter implements interaction_InteractiveTarget, somethingElse_SomeOtherThing, somethingElseAgain_RunningOutOfNames {} class DisplayObject4 extends utils.EventEmitter implements interaction_InteractiveTarget {} class Readable extends events.EventEmitter implements NodeJS_ReadableStream {} class InMemoryAppender extends log4javascript.Appender implements ICachedLogMessageProvider {} class Foo extends Immutable.Record({ ipaddress: "", }) { ipaddress: string; } export class VisTimelineComponent implements AfterViewInit, OnChanges, OnDestroy {} export class VisTimelineComponent2 implements AfterViewInit, OnChanges, OnDestroy, AndSomethingReallyReallyLong {} ``` </details> Sadly I can't fix the `class Readable` line :/
1 parent abd3a9f commit fe25b65

File tree

1 file changed

+107
-33
lines changed

1 file changed

+107
-33
lines changed

crates/oxc_prettier/src/format/class.rs

Lines changed: 107 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,48 @@
1+
use std::ops::Add;
2+
13
use oxc_ast::ast::*;
24
use oxc_span::GetSpan;
35

46
use super::assignment::AssignmentLikeNode;
57
use crate::{
68
array,
7-
doc::{Doc, DocBuilder},
8-
format::assignment,
9-
hardline, space, ss, Format, Prettier,
9+
doc::{Doc, DocBuilder, Group, IfBreak, Line},
10+
format::{assignment, Separator},
11+
group, hardline, if_break, indent, indent_if_break, line, softline, space, ss, Format,
12+
Prettier,
1013
};
1114

1215
pub(super) fn print_class<'a>(p: &mut Prettier<'a>, class: &Class<'a>) -> Doc<'a> {
1316
let mut parts = p.vec();
17+
let mut heritage_clauses_parts = p.vec();
18+
let mut group_parts = p.vec();
19+
20+
// Keep old behaviour of extends in same line
21+
// If there is only on extends and there are not comments
22+
// ToDo: implement comment checks
23+
// @link <https://github.com/prettier/prettier/blob/aa3853b7765645b3f3d8a76e41cf6d70b93c01fd/src/language-js/print/class.js#L62>
24+
let group_mode = class.implements.as_ref().is_some_and(|v| !v.is_empty());
25+
26+
if let Some(super_class) = &class.super_class {
27+
let mut extend_parts = p.vec();
28+
29+
extend_parts.push(ss!("extends "));
30+
extend_parts.push(super_class.format(p));
31+
32+
if let Some(super_type_parameters) = &class.super_type_parameters {
33+
extend_parts.push(super_type_parameters.format(p));
34+
}
35+
36+
extend_parts.push(space!());
37+
38+
if group_mode {
39+
heritage_clauses_parts.push(softline!());
40+
}
41+
42+
heritage_clauses_parts.push(Doc::Array(extend_parts));
43+
}
44+
45+
heritage_clauses_parts.push(print_heritage_clauses_implements(p, class));
1446

1547
for decorator in &class.decorators {
1648
parts.push(ss!("@"));
@@ -27,46 +59,33 @@ pub(super) fn print_class<'a>(p: &mut Prettier<'a>, class: &Class<'a>) -> Doc<'a
2759
}
2860

2961
parts.push(ss!("class "));
62+
3063
if let Some(id) = &class.id {
31-
parts.push(id.format(p));
64+
group_parts.push(id.format(p));
3265
}
3366

3467
if let Some(params) = &class.type_parameters {
35-
parts.push(params.format(p));
68+
group_parts.push(params.format(p));
3669
}
3770

3871
if class.id.is_some() || class.type_parameters.is_some() {
39-
parts.push(space!());
72+
group_parts.push(space!());
4073
}
4174

42-
if let Some(super_class) = &class.super_class {
43-
parts.push(ss!("extends "));
44-
parts.push(super_class.format(p));
45-
46-
if let Some(super_type_parameters) = &class.super_type_parameters {
47-
parts.push(super_type_parameters.format(p));
48-
}
49-
50-
parts.push(space!());
51-
}
75+
if group_mode {
76+
let printend_parts_group = if should_indent_only_heritage_clauses(class) {
77+
array!(p, Doc::Array(group_parts), indent!(p, Doc::Array(heritage_clauses_parts)))
78+
} else {
79+
indent!(p, Doc::Array(group_parts), group!(p, Doc::Array(heritage_clauses_parts)))
80+
};
5281

53-
if let Some(implements) = &class.implements {
54-
if implements.len() > 0 {
55-
parts.push(ss!("implements "));
56-
57-
let mut print_comma = false;
58-
for implementation in implements {
59-
if print_comma {
60-
parts.push(ss!(", "));
61-
} else {
62-
print_comma = true;
63-
}
82+
parts.push(printend_parts_group);
6483

65-
parts.push(implementation.format(p));
66-
}
67-
68-
parts.push(space!());
84+
if !class.body.body.is_empty() && has_multiple_heritage(class) {
85+
parts.extend(hardline!());
6986
}
87+
} else {
88+
parts.push(array!(p, Doc::Array(group_parts), Doc::Array(heritage_clauses_parts)));
7089
}
7190

7291
parts.push(class.body.format(p));
@@ -98,8 +117,6 @@ pub(super) fn print_class_body<'a>(p: &mut Prettier<'a>, class_body: &ClassBody<
98117
// TODO: if there are any dangling comments, print them
99118

100119
let mut parts = p.vec();
101-
// TODO is class_body.len() != 0, print hardline after heritage
102-
103120
parts.push(ss!("{"));
104121
if !parts_inner.is_empty() {
105122
let indent = {
@@ -354,3 +371,60 @@ fn should_print_semicolon_after_class_property<'a>(
354371
}
355372
}
356373
}
374+
375+
/**
376+
* @link <https://github.com/prettier/prettier/blob/aa3853b7765645b3f3d8a76e41cf6d70b93c01fd/src/language-js/print/class.js#L148>
377+
*/
378+
fn print_heritage_clauses_implements<'a>(p: &mut Prettier<'a>, class: &Class<'a>) -> Doc<'a> {
379+
let mut parts = p.vec();
380+
381+
if class.implements.is_none() {
382+
return Doc::Array(parts);
383+
}
384+
385+
let implements = class.implements.as_ref().unwrap();
386+
387+
if implements.len() == 0 {
388+
return Doc::Array(parts);
389+
}
390+
391+
if should_indent_only_heritage_clauses(class) {
392+
parts.push(Doc::IfBreak(IfBreak {
393+
flat_content: p.boxed(ss!("")),
394+
break_contents: p.boxed(line!()),
395+
group_id: None, // ToDo - how to attach group id
396+
}));
397+
} else if class.super_class.is_some() {
398+
parts.extend(hardline!());
399+
} else {
400+
parts.push(softline!());
401+
}
402+
403+
parts.push(ss!("implements "));
404+
405+
let implements_docs = implements.iter().map(|v| v.format(p)).collect();
406+
407+
parts.push(indent!(
408+
p,
409+
group!(p, softline!(), Doc::Array(p.join(Separator::CommaLine, implements_docs)))
410+
));
411+
parts.push(space!());
412+
413+
Doc::Group(Group::new(parts))
414+
}
415+
416+
fn should_indent_only_heritage_clauses(class: &Class) -> bool {
417+
// Todo - Check for Comments
418+
// @link https://github.com/prettier/prettier/blob/aa3853b7765645b3f3d8a76e41cf6d70b93c01fd/src/language-js/print/class.js#L137
419+
class.type_parameters.is_some() && !has_multiple_heritage(class)
420+
}
421+
422+
fn has_multiple_heritage(class: &Class) -> bool {
423+
let mut len = i32::from(class.super_class.is_some());
424+
425+
if let Some(implements) = &class.implements {
426+
len = len.add(i32::try_from(implements.len()).unwrap());
427+
}
428+
429+
len > 1
430+
}

0 commit comments

Comments
 (0)