Skip to content
Draft
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
2 changes: 1 addition & 1 deletion docs/guides/format/migrate-from-oxfmt.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ If `bun fmt` and oxfmt 0.72 print different code, that's a bug. [Open an issue](
- **An invalid TOML file is left alone**, with a warning. oxfmt rewrites the parts it can read.
- **A file that isn't valid UTF-8 is still formatted**, and the invalid bytes are kept. oxfmt exits with code `2`.
- **A function's only parameter doesn't move on the second run.** When an object type in the return type doesn't fit on the line, oxfmt gives the parameter its own line, then joins `m(a: string): {` again the next time it runs. Bun prints the joined form the first time.
- **Where oxfmt's output changes what a file means, Bun's differs.** That's rare. In TOML oxfmt joins the lines of an inline table with a comment, which comments out the rest of the table. Bun keeps one entry per line. In Svelte oxfmt drops the comment in `{#if a /* b */}`, the space in `<hr />some <b>words</b>`, and an empty `{:catch}`, and it empties the `<script>` after a comment that mentions `<style>`. Bun keeps them.
- **Where oxfmt's output changes what a file means, Bun's differs.** That's rare. In TOML oxfmt joins the lines of an inline table with a comment, which comments out the rest of the table. Bun keeps one entry per line. In TypeScript with `arrowParens: "avoid"` oxfmt prints `a ? b => ({ c }) : d => (e): T => f`, which `tsc` reads as `a ? b => (({ c }): d => (e)) : T => f`. Bun prints `(d) =>` there. In Svelte oxfmt drops the comment in `{#if a /* b */}`, the space in `<hr />some <b>words</b>`, and an empty `{:catch}`, and it empties the `<script>` after a comment that mentions `<style>`. Bun keeps them. To find a file where formatting would change what the code means, run `bun fmt --debug-check`.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/format/migrate-from-prettier.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ If `bun fmt` and Prettier 3 print different code, that's a bug. [Open an issue](
- **`--cache` does nothing.** Every run formats every file.
- **Exit code `2` means something went wrong**, like a syntax error or a skipped file. Prettier uses `1` for some of these.
- **Bytes that aren't valid UTF-8 are kept.** Prettier replaces them.
- **Where Prettier's output changes what a file means, Bun's differs.** That's rare. In a Handlebars template Prettier drops `<!DOCTYPE html>`, and it joins the lines of a `<script>`, so a `// comment` swallows the code after it. In CSS it turns `Font\ Awesome` into `Font\Awesome`. In Svelte the plugin drops the comment in `{#if a /* b */}`, the space in `<hr />some <b>words</b>`, and an empty `{:catch}`, and it empties the `<script>` after a comment that mentions `<style>`. Bun keeps all of them.
- **Where Prettier's output changes what a file means, Bun's differs.** That's rare. In a Handlebars template Prettier drops `<!DOCTYPE html>`, and it joins the lines of a `<script>`, so a `// comment` swallows the code after it. In CSS it turns `Font\ Awesome` into `Font\Awesome`. In TypeScript with `arrowParens: "avoid"` it prints `a ? b => ({ c }) : d => (e): T => f`, which `tsc` reads as `a ? b => (({ c }): d => (e)) : T => f`. Bun prints `(d) =>` there. In Svelte the plugin drops the comment in `{#if a /* b */}`, the space in `<hr />some <b>words</b>`, and an empty `{:catch}`, and it empties the `<script>` after a comment that mentions `<style>`. Bun keeps all of them. To find a file where formatting would change what the code means, run `bun fmt --debug-check`.
- **A function's only parameter doesn't move on the second run.** When an object type in the return type doesn't fit on the line, Prettier gives the parameter its own line, then joins `m(a: string): {` again the next time it runs. Bun prints the joined form the first time.
- **Symlinks that lead out of the repository aren't written through.**
- **`console.log` in your config file is hidden** unless the config fails to load.
Expand Down
12 changes: 10 additions & 2 deletions src/format/ir/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -900,17 +900,25 @@ impl<'a> Formatter<'a> {
/// spaces, content that's only printed when a group breaks is removed, and a `BestFitting` is replaced by its
/// flattest variant.
pub(crate) fn write_without_soft_lines(&mut self, content: &(impl Format<'a> + ?Sized)) {
let captured = self.capture(content);
let captured = self.capture_without_soft_lines(content);
self.remove_soft_lines(captured, &mut Vec::new(), Variants::Flattest);
}

/// Prettier's `removeLines`. Same, but the printer still chooses among the variants and falls back to the most
/// expanded one if none fits.
pub(crate) fn write_with_lines_removed(&mut self, content: &(impl Format<'a> + ?Sized)) {
let captured = self.capture(content);
let captured = self.capture_without_soft_lines(content);
self.remove_soft_lines(captured, &mut Vec::new(), Variants::All);
}

/// Captures `content`, which knows from the context that `remove_soft_lines` gets what it writes.
fn capture_without_soft_lines(&mut self, content: &(impl Format<'a> + ?Sized)) -> Interned {
let outer = self.context.removes_soft_lines.replace(true);
let captured = self.capture(content);
self.context.removes_soft_lines.set(outer);
captured
}

/// Writes the elements at `range` without their soft line breaks.
fn remove_soft_lines(
&mut self,
Expand Down
7 changes: 7 additions & 0 deletions src/format/js/context.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! The state that formatting code can access besides the node it's formatting.

use super::comments::{Comments, FileComments};
use super::utils::conditional::AfterColon;
use super::utils::tailwindcss::TailwindContext;
use crate::cursor::CursorRegion;
use crate::ir::element::FormatElement;
Expand Down Expand Up @@ -54,6 +55,10 @@ pub(crate) struct JsFormatContext<'a> {
pub(crate) line_start: Cell<(u32, u32)>,
/// True if the `a | b` being written is a Vue value and filter.
pub(crate) is_vue_filter_sequence: Cell<bool>,
/// What the conditional that is being written decided about the arrow functions after its `:`.
pub(crate) after_colon: Cell<AfterColon>,
/// True if what is being written loses its soft line breaks, and with them the text that only a broken group has.
pub(crate) removes_soft_lines: Cell<bool>,
/// Parses the code in HTML, overriding `FormatOptions::parse_javascript`.
pub(crate) parse_javascript: Option<JavaScriptParser<'a>>,
}
Expand Down Expand Up @@ -94,6 +99,8 @@ impl<'a> JsFormatContext<'a> {
line_start: Cell::new((0, 0)),
long_member_chain: Cell::new(None),
is_vue_filter_sequence: Cell::new(false),
after_colon: Cell::default(),
removes_soft_lines: Cell::new(false),
parse_javascript: None,
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/format/js/print/arrow_function_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ impl<'a, T: Format<'a>> Format<'a> for FormatConditionalBody<'_, T> {

/// True if the signature is too complex to share a line with others: it has type parameters, a destructuring, rest or
/// default parameter, or parameters and a return type.
fn has_complex_signature(arrow: Func<'_>) -> bool {
#[inline(always)]
pub(crate) fn has_complex_signature(arrow: Func<'_>) -> bool {
if !arrow.type_params().is_empty() || !has_only_simple_parameters(arrow, true) {
return true;
}
Expand Down
19 changes: 16 additions & 3 deletions src/format/js/print/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,10 +401,23 @@ impl<'a> Format<'a> for ParameterList<'a> {
}
}

/// `a => a`: true if the options ask for it and the parameters allow it. Prettier's `shouldPrintParamsWithoutParens`.
/// `a => a`: true if Prettier prints it, and no conditional keeps `(a) =>`.
#[inline]
pub(crate) fn can_avoid_parentheses<'a>(arrow: Func<'a>, f: &Formatter<'a>) -> bool {
f.options().arrow_parentheses.is_as_needed()
&& arrow.params().len() == 1
// Prettier prints `a ? b => ({ c }) : d => (e): T => f`. tsc reads `({ c }): d => (e)` as an arrow function.
prettier_avoids_parentheses(arrow, f)
&& !f.context().after_colon.get().keeps_parentheses_of(arrow)
}

/// `a => a`: true if the options ask for it and the parameters allow it. Prettier's `shouldPrintParamsWithoutParens`.
#[inline]
pub(crate) fn prettier_avoids_parentheses<'a>(arrow: Func<'a>, f: &Formatter<'a>) -> bool {
f.options().arrow_parentheses.is_as_needed() && has_only_a_name(arrow, f)
}

/// True if the parameters of `arrow` are one name, with no type, default or comment.
fn has_only_a_name<'a>(arrow: Func<'a>, f: &Formatter<'a>) -> bool {
arrow.params().len() == 1
&& arrow.this_param().is_none()
&& arrow.type_params().is_empty()
&& arrow.return_type().is_none()
Expand Down
Loading
Loading