Skip to content

Commit

Permalink
feat(prettier): indent for class definition (#6059)
Browse files Browse the repository at this point in the history
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 :/
  • Loading branch information
Sysix authored Oct 6, 2024
1 parent abd3a9f commit fe25b65
Showing 1 changed file with 107 additions and 33 deletions.
140 changes: 107 additions & 33 deletions crates/oxc_prettier/src/format/class.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
use std::ops::Add;

use oxc_ast::ast::*;
use oxc_span::GetSpan;

use super::assignment::AssignmentLikeNode;
use crate::{
array,
doc::{Doc, DocBuilder},
format::assignment,
hardline, space, ss, Format, Prettier,
doc::{Doc, DocBuilder, Group, IfBreak, Line},
format::{assignment, Separator},
group, hardline, if_break, indent, indent_if_break, line, softline, space, ss, Format,
Prettier,
};

pub(super) fn print_class<'a>(p: &mut Prettier<'a>, class: &Class<'a>) -> Doc<'a> {
let mut parts = p.vec();
let mut heritage_clauses_parts = p.vec();
let mut group_parts = p.vec();

// Keep old behaviour of extends in same line
// If there is only on extends and there are not comments
// ToDo: implement comment checks
// @link <https://github.com/prettier/prettier/blob/aa3853b7765645b3f3d8a76e41cf6d70b93c01fd/src/language-js/print/class.js#L62>
let group_mode = class.implements.as_ref().is_some_and(|v| !v.is_empty());

if let Some(super_class) = &class.super_class {
let mut extend_parts = p.vec();

extend_parts.push(ss!("extends "));
extend_parts.push(super_class.format(p));

if let Some(super_type_parameters) = &class.super_type_parameters {
extend_parts.push(super_type_parameters.format(p));
}

extend_parts.push(space!());

if group_mode {
heritage_clauses_parts.push(softline!());
}

heritage_clauses_parts.push(Doc::Array(extend_parts));
}

heritage_clauses_parts.push(print_heritage_clauses_implements(p, class));

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

parts.push(ss!("class "));

if let Some(id) = &class.id {
parts.push(id.format(p));
group_parts.push(id.format(p));
}

if let Some(params) = &class.type_parameters {
parts.push(params.format(p));
group_parts.push(params.format(p));
}

if class.id.is_some() || class.type_parameters.is_some() {
parts.push(space!());
group_parts.push(space!());
}

if let Some(super_class) = &class.super_class {
parts.push(ss!("extends "));
parts.push(super_class.format(p));

if let Some(super_type_parameters) = &class.super_type_parameters {
parts.push(super_type_parameters.format(p));
}

parts.push(space!());
}
if group_mode {
let printend_parts_group = if should_indent_only_heritage_clauses(class) {
array!(p, Doc::Array(group_parts), indent!(p, Doc::Array(heritage_clauses_parts)))
} else {
indent!(p, Doc::Array(group_parts), group!(p, Doc::Array(heritage_clauses_parts)))
};

if let Some(implements) = &class.implements {
if implements.len() > 0 {
parts.push(ss!("implements "));

let mut print_comma = false;
for implementation in implements {
if print_comma {
parts.push(ss!(", "));
} else {
print_comma = true;
}
parts.push(printend_parts_group);

parts.push(implementation.format(p));
}

parts.push(space!());
if !class.body.body.is_empty() && has_multiple_heritage(class) {
parts.extend(hardline!());
}
} else {
parts.push(array!(p, Doc::Array(group_parts), Doc::Array(heritage_clauses_parts)));
}

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

let mut parts = p.vec();
// TODO is class_body.len() != 0, print hardline after heritage

parts.push(ss!("{"));
if !parts_inner.is_empty() {
let indent = {
Expand Down Expand Up @@ -354,3 +371,60 @@ fn should_print_semicolon_after_class_property<'a>(
}
}
}

/**
* @link <https://github.com/prettier/prettier/blob/aa3853b7765645b3f3d8a76e41cf6d70b93c01fd/src/language-js/print/class.js#L148>
*/
fn print_heritage_clauses_implements<'a>(p: &mut Prettier<'a>, class: &Class<'a>) -> Doc<'a> {
let mut parts = p.vec();

if class.implements.is_none() {
return Doc::Array(parts);
}

let implements = class.implements.as_ref().unwrap();

if implements.len() == 0 {
return Doc::Array(parts);
}

if should_indent_only_heritage_clauses(class) {
parts.push(Doc::IfBreak(IfBreak {
flat_content: p.boxed(ss!("")),
break_contents: p.boxed(line!()),
group_id: None, // ToDo - how to attach group id
}));
} else if class.super_class.is_some() {
parts.extend(hardline!());
} else {
parts.push(softline!());
}

parts.push(ss!("implements "));

let implements_docs = implements.iter().map(|v| v.format(p)).collect();

parts.push(indent!(
p,
group!(p, softline!(), Doc::Array(p.join(Separator::CommaLine, implements_docs)))
));
parts.push(space!());

Doc::Group(Group::new(parts))
}

fn should_indent_only_heritage_clauses(class: &Class) -> bool {
// Todo - Check for Comments
// @link https://github.com/prettier/prettier/blob/aa3853b7765645b3f3d8a76e41cf6d70b93c01fd/src/language-js/print/class.js#L137
class.type_parameters.is_some() && !has_multiple_heritage(class)
}

fn has_multiple_heritage(class: &Class) -> bool {
let mut len = i32::from(class.super_class.is_some());

if let Some(implements) = &class.implements {
len = len.add(i32::try_from(implements.len()).unwrap());
}

len > 1
}

0 comments on commit fe25b65

Please sign in to comment.