Skip to content

Biome formatter wins the Prettier challenge

The Prettier challenge banner, with the Biome logo over itThe Prettier challenge banner, with the Biome logo over it
Emanuele Stoppa & Biome Core Team, Biome Maintainers

With the release of Biome v1.4.0, we claim the bounty of the Prettier challenge!

With v1.4.0, you’ll get a better formatter experience, more formatting options, new VSCode features, new sponsors and more!

You can upgrade Biome by running the following command:

Terminal window
npm install --save-dev --save-exact @biomejs/biome@1.4.0
pnpm update --save-exact @biomejs/biome@1.4.0
yarn upgrade --exact @biomejs/biome@1.4.0

Biome formatter has now over 96% in terms of compatibility against Prettier! This score is computed for JavaScript, TypeScript, and JSX formatting.

Merit of challenge that was launched by Christopher Chedeau, one of the Prettier’s creators.

The challenge attracted the attention of many people, and some of them decided to contribute to Biome to claim part of the bounty. I did see something amazing: contributors had an amazing coordination, they took ownership of the tasks and delivered the solution in a matter of hours.

I believe the main factors that made this possible are three:

  1. Money. It’s a fact, and it’s completely fine if someone decides to contribute only for earning a small stipend out of it.
  2. Communication. We used GitHub as only medium of coordination. We provided information, instructions and help on how to deliver.
  3. Infrastructure. Biome relies on a solid testing infrastructure, built by previous Rome Tools employees and contributors. It’s able to catch every reformat bug, provide granular diffs and warn the user if the emitted output is the different from the one emitted by Prettier.

Before the challenge, Biome had roughly a compatibility rate of 85%, based on our internal metrics (JavaScript, TypeScript and JSX, on options parity). Even though 85% might seem high, the impact of a low number such as 15% on big code bases is huge, and people might feel intimidated by so many changes, causing early adopters to receive frictions when bring Biome to their team. A member of our community shared some insights:

As a great example of how much even just that last 5% has improved things for large codebases (and specifically with bracketSpacing and now bracketSameLine implemented) i ran it one project in our monorepo […].

Just last week, this number [diagnostics] was more than 6,000. Even with the bracket options ignored, it was still more than 1000, and now there are only 200 left!

Although the challenge is over, we are committed to improve even more the compatibility score with prettier. Any contribution in this regard is very welcome.

The challenge has also uncovered some cases in Prettier’s emitted output that we decided to not follow. We have created a new section in our website that explains them. Our hope is to make this section smaller with the time.

If there’s a divergence that isn’t documented in our website, you should consider that a bug and file an issue.

With this challenge, we added new options to the formatter:

  • lineEnding

    Use this option to match the line endings of your OS. We support lf (line feed - \n), cr (carriage return - \r) and crlf (carriage return line feed - \r\n).

  • bracketSameLine

    example.js
    // Existing behavior. Now also the default, meaning `bracketSameLine: false`.
    <Foo
    className={somethingReallyLongThatForcesThisToWrap}
    anotherReallyLongAttribute={withAValueThatsSurelyTooLong}
    soThatEverythingWraps
    >
    Hello
    </Foo>
    <Foo
    selfClosingTags={likeThisOne}
    stillPlaceTheBracket={onItsOwnLine}
    toIndicateThat={itClosesItself}
    />

    After formatting with "bracketSameLine": true:

    example.js
    // New behavior, with `bracketSameLine: true`.
    <Foo
    className={somethingReallyLongThatForcesThisToWrap}
    anotherReallyLongAttribute={withAValueThatsSurelyTooLong}
    soThatEverythingWraps>
    Hello
    </Foo>
    <Foo
    selfClosingTags={likeThisOne}
    stillPlaceTheBracket={onItsOwnLine}
    />
  • bracketSpacing

    example.js
    import { sort } from "sort.js";
    const value = { sort };

    After formatting with "bracketSpacing": false:

    example.js
    import {sort} from "sort.js";
    const value = {sort};

The VSCode has been moved to a new repository.

We removed the bundled binary from the extension, and you’ll be able to download the version that you want. Here’s a small video of how it works:

From today, we release a nightly version of the extension. This is a version meant for early adopters and to test things before they are officially released.

People that rely on Biome LSP will be pleased that they can now pass a custom configuration to the command lsp-proxy, using the option --config-path. The same option is accepted by the command start:

Terminal window
biome --config-path=../path/where/config/is lsp-proxy
biome --config-path=../path/where/config/is start

The CLI now exposes the option --diagnostic-level, that allows to filter the kind of diagnostics printed to terminal.

Terminal window
biome check --diagnostic-level=error ./src

New lint rules, and promoted rule

Section titled New lint rules, and promoted rule

Biome is a linter too, and it features 177 rules! In this release, some rules are promoted and new ones are available.

  • noDefaultExport

    export default function f() {}
    nursery/noDefaultExport.js:1:8 lint/nursery/noDefaultExport ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
       Avoid default exports.
    
      > 1 │ export default function f() {};
              ^^^^^^^
        2 │ 
    
       Default exports cannot be easily discovered inside an editor and don't encourage the use of consistent names through a code base.
    
       Use a named export instead.
    
    
  • noAriaHiddenOnFocusable

    <div aria-hidden="true" tabIndex="0" />
    nursery/noAriaHiddenOnFocusable.js:1:1 lint/nursery/noAriaHiddenOnFocusable  FIXABLE  ━━━━━━━━━━━━━━
    
       Disallow aria-hidden="true" from being set on focusable elements.
    
      > 1 │ <div aria-hidden="true" tabIndex="0" />
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        2 │ 
    
       aria-hidden should not be set to true on focusable elements because this can lead to confusing behavior for screen reader users.
    
       Unsafe fix: Remove the aria-hidden attribute from the element.
    
        1 │ <div·aria-hidden="true"·tabIndex="0"·/>
           -------------------
    
  • noImplicitAnyLet

    var a;
    a = 2;
    nursery/noImplicitAnyLet.js:1:5 lint/nursery/noImplicitAnyLet ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
       This variable implicitly has the any type.
    
      > 1 │ var a;
           ^
        2 │ a = 2;
        3 │ 
    
       Variable declarations without type annotation and initialization have implicitly the any type. Declare type or initialize the variable with some value.
    
    
  • useAwait

    async function fetchData() {
    // Missing `await` for the promise returned by `fetch`
    return fetch("/data");
    }
    nursery/useAwait.js:1:1 lint/nursery/useAwait ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
       This async function lacks an await expression.
    
      > 1 │ async function fetchData() {
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      > 2 │ // Missing `await` for the promise returned by `fetch`
      > 3 │   return fetch('/data');
      > 4 │ }
       ^
        5 │ 
    
       Remove this async modifier, or add an await expression in the function.
    
      > 1 │ async function fetchData() {
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      > 2 │ // Missing `await` for the promise returned by `fetch`
      > 3 │   return fetch('/data');
      > 4 │ }
       ^
        5 │ 
    
       Async functions without await expressions may not need to be declared async.
    
    
  • useValidAriaRole

    <div role="datepicker"></div>
    nursery/useValidAriaRole.js:1:1 lint/nursery/useValidAriaRole  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
       Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
    
      > 1 │ <div role="datepicker"></div>
       ^^^^^^^^^^^^^^^^^^^^^^^
        2 │ 
    
       Check WAI-ARIA for valid roles or provide options accordingly.
    
       Unsafe fix: Remove the invalid role attribute.
         Check the list of all valid role attributes.
    
        1 │ <div·role="datepicker"></div>
           -----------------
    
  • useRegexLiterals

new RegExp("abc", "u");
nursery/useRegexLiterals.js:1:1 lint/nursery/useRegexLiterals  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━

   Use a regular expression literal instead of the RegExp constructor.

  > 1 │ new RegExp("abc", "u");
   ^^^^^^^^^^^^^^^^^^^^^^
    2 │ 

   Regular expression literals avoid some escaping required in a string literal, and are easier to analyze statically.

   Safe fix: Use a literal notation instead.

    1  - new·RegExp("abc",·"u");
      1+ /abc/u;
    2 2

  • a11y/noAccessKey

    <input type="submit" accessKey="s" value="Submit" />
    a11y/noAccessKey.js:1:22 lint/a11y/noAccessKey  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
       Avoid the accessKey attribute to reduce inconsistencies between keyboard shortcuts and screen reader keyboard comments.
    
      > 1 │ <input type="submit" accessKey="s" value="Submit" />
                            ^^^^^^^^^^^^^
        2 │ 
    
       Assigning keyboard shortcuts using the accessKey attribute leads to inconsistent keyboard actions across applications.
    
       Unsafe fix: Remove the accessKey attribute.
    
        1 │ <input·type="submit"·accessKey="s"·value="Submit"·/>
                           --------------
    
  • a11y/useHeadingContent

    <h1 />
    a11y/useHeadingContent.js:1:1 lint/a11y/useHeadingContent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
       Provide screen reader accessible content when using heading  elements.
    
      > 1 │ <h1 />
       ^^^^^^
        2 │ 
    
       All headings on a page should have content that is accessible to screen readers.
    
    
  • complexity/useSimpleNumberKeys

    ({ 0x1: 1 });
    complexity/useSimpleNumberKeys.js:1:4 lint/complexity/useSimpleNumberKeys  FIXABLE  ━━━━━━━━━━━━━━━━
    
       Hexadecimal number literal is not allowed here.
    
      > 1 │ ({ 0x1: 1 });
          ^^^
        2 │ 
    
       Safe fix: Replace 0x1 with 1
    
    1  - ({·0x1:·1·});
      1+ ({·1:·1·});
    2 2
    
    

The rule is replaced by correctness/noInvalidNewBuiltin

Since Biome was forked, new people joined the project. They have been helping with in so many ways that you can’t even imagine: new features, side projects, engaging with the community, support, documentation, and more. OSS is not just about coding.

Thank you to:

And a big welcome to our new joined maintainer:

Last but not least, we are proud to announce that we have two new sponsors:

If you want to economically contribute to the project and help it to ship more features, you can do so from the GitHub Sponsorship page or the Open Collective page.

The project is thriving, with more people curious about the project and contributors that want to be involved.

In the next months we will focus on:

  • Publishing a Roadmap. Keep an eye on it, it will involve a lot of interesting work.
  • Rebranding the website with a new logo.
  • Translate the website in Japanese.