Skip to content

feat(eslint-config): consider moving @stylistic formatting to bun fmt #59

Description

@amondnet

Context

Follow-up to #56, item 3.

@stylistic formatting rules (65 in the measured config, registered as style/*) all run in JavaScript under Bun's bun lint. Bun also has a formatter, bun fmt, that could take over formatting and leave ESLint to code-quality rules.

Proposal

Consider moving formatting from @stylistic (stylistic option in pleaseai()) to bun fmt, while keeping our style: 2-space indent, single quotes, no semicolons.

Prerequisites and questions

  • Check that bun fmt's Markdown output matches Prettier's. Markdown/JSON/YAML/TOML are a large share of files in docs-heavy repos.
  • Can bun fmt express the current stylistic settings and the antfu/* formatting rules (for example, antfu/top-level-function)?
  • How does this fit with @pleaseai/prettier-config and the please-style CLI setup flow?
  • How do editors (format on save) and lint-staged integrate?
  • Measure the speedup on a real repo.

Caveats

  • bun lint / bun fmt are not in a stable Bun release yet. Revisit once they ship.
  • Dropping @stylistic would change rule IDs and output for consumers. Treat it as opt-in first; making it the default is breaking.

Activity

  1. added theissue type on Oct 15, 2026
  2. amondnet commented on Oct 15, 2026

    @amondnet
    ContributorAuthor

    Evaluation: moving @stylistic formatting to bun fmt

    TL;DR: bun fmt is a native port of Prettier 3, not a configurable @stylistic replacement. It can express 2-space indent, single quotes, and no semicolons, and its Markdown and TS output matched Prettier 3.9.10 byte for byte on this repo. It cannot express several antfu conventions we enforce today: stroustrup braces, +/= at the start of a continued line, parens only for block-body arrows, comma delimiters in inline type literals, and single quotes even when a string has to escape them. On this repo, current ESLint reports 223 style/* errors on the output of bun fmt. The command is also not in a stable Bun release. Recommendation: don't migrate now. Revisit as an opt-in once bun fmt is stable. Details below.

    Environment

    1. What bun fmt is

    • Languages: JS, TS, JSX, CSS, SCSS, Less, HTML, Vue, Svelte, Angular, JSON, YAML, TOML, Markdown, MDX, GraphQL, Handlebars. Astro and other plugin-provided languages are sent to the project's installed Prettier.
    • Contract: "The same output as Prettier." The docs say any difference from Prettier 3 is a bug, apart from a short list of intentional CLI and edge-case differences.
    • Config: a [fmt] section in bunfig.toml, or an existing .prettierrc / prettier.config.* / package.json#prettier / .oxfmtrc.json, plus .editorconfig (indent_*, tab_width, max_line_length, end_of_line). The options are Prettier's: printWidth, tabWidth, useTabs, semi, singleQuote, jsxSingleQuote, quoteProps, trailingComma, bracketSpacing, bracketSameLine, arrowParens, objectWrap, proseWrap, endOfLine, experimentalTernaries, experimentalOperatorPosition, and others. Extras: sortImports, sortTailwindcss, sortPackageJson, jsdoc, [[fmt.overrides]], ignorePatterns.
    • Our three settings: yes. tabWidth = 2, singleQuote = true, semi = false.
    • No brace-style, operator-placement-for-=, or delimiter options. Those are fixed by Prettier's printer.
    • CLI: --check (exit 1 if a file needs formatting, 2 on error), --staged (formats staged files and stages them again; the docs say "so you don't need lint-staged"), --changed[=ref], --stdin-filepath, --doctor.
    • Lint integration: under bun lint, prettier/prettier runs natively against bun fmt output, and there is a built-in bun/format rule. Under plain ESLint, neither exists. prettier/prettier falls back to the installed Prettier.

    2. Coverage of what pleaseai() enforces

    pleaseai() uses lessOpinionated: true and stylistic: { indent: 2, quotes: 'single', semi: false }. That turns on 65 style/* rules. It also turns on these rules, all gated by the same stylistic switch: antfu/consistent-list-newline, antfu/consistent-chaining, core curly: ['error', 'all'], import/newline-after-import, jsdoc/check-alignment, jsdoc/multiline-blocks, 10 jsonc/*, 12 yaml/*, and 11 toml/* layout rules. stylistic: false turns all of these off.

    Method: I formatted the repo's 59 tracked JS/TS files with bun fmt, using {semi: false, singleQuote: true, arrowParens: 'avoid', printWidth: 120}. I then linted the output with the current config through eslint --stdin. The counts below are the errors that remained.

    Rule group Rules bun fmt covers it? Evidence on this repo
    Spacing array-bracket-spacing, arrow-spacing, block-spacing, comma-spacing, computed-property-spacing, generator-star-spacing, key-spacing, keyword-spacing, no-multi-spaces, no-trailing-spaces, no-whitespace-before-property, object-curly-spacing, rest-spread-spacing, semi-spacing, space-before-blocks, space-before-function-paren, space-in-parens, space-infix-ops, space-unary-ops, template-curly-spacing, template-tag-spacing, type-annotation-spacing, type-generic-spacing, type-named-tuple-spacing, yield-star-spacing ✅ Yes 0 errors
    Indentation indent, indent-binary-ops, no-tabs, no-mixed-spaces-and-tabs ✅ Mostly 1 indent-binary-ops
    Semicolons semi ✅ semi = false 0
    Quotes quotes (avoidEscape: false), jsx-quotes, quote-props (consistent-as-needed) ⚠️ Partial. Prettier picks the quote that needs fewer escapes, so 'Plug\'n\'Play' becomes "Plug'n'Play". quoteProps: 'consistent' is close to consistent-as-needed, but I didn't verify it. 85 quotes
    Commas comma-dangle (always-multiline), comma-style ✅ trailingComma = 'all' 0
    Braces and blocks brace-style (stroustrup), padded-blocks, curly ❌ brace-style: Prettier only prints } else {, which is 1TBS. padded-blocks ✅. curly is not formatting, but stylistic: false drops it, so we would have to add it back. 51 brace-style
    Operators and line breaks operator-linebreak (before), multiline-ternary, dot-location, max-statements-per-line, new-parens, no-floating-decimal, wrap-iife, no-extra-parens (functions), no-mixed-operators ⚠️ Partial. Prettier puts operators at the end of the line. With experimentalOperatorPosition: 'start', binary operators move, but = doesn't. The other rules aren't flagged on this repo. 46 operator-linebreak, 11 with 'start'
    Arrow parens arrow-parens (as-needed, requireForBlockBody) ❌ Prettier only has always or avoid. Neither one means "parens only when the body is a block". 21 with avoid. always breaks the other half.
    TS delimiters member-delimiter-style (multiline none, single-line comma) ❌ Prettier prints { a: string; b: number } on one line. 19
    Blank lines and EOF no-multiple-empty-lines, eol-last, lines-between-class-members ✅ First two. lines-between-class-members: Prettier keeps blank lines but doesn't add them. 0
    Comments spaced-comment ❌ Prettier doesn't edit comment text. Keep the rule in ESLint. 0
    JSX jsx-closing-bracket-location, jsx-closing-tag-location, jsx-curly-newline, jsx-curly-spacing, jsx-equals-spacing, jsx-first-prop-new-line, jsx-function-call-newline, jsx-indent-props, jsx-max-props-per-line, jsx-one-expression-per-line, jsx-tag-spacing, jsx-wrap-multilines, jsx-curly-brace-presence ⚠️ Layout comes from Prettier's JSX printer. jsx-curly-brace-presence is not formatting, so it stays in ESLint. Not verified. The repo has no linted JSX.
    antfu/consistent-list-newline ➖ Superseded. Prettier decides by printWidth. It collapsed our multi-line imports and signatures onto one line. 0 errors, large churn
    antfu/consistent-chaining ➖ Superseded. Prettier breaks chains of 3 or more calls. 0 errors
    antfu/top-level-function ❌ Not formatting, and Prettier never changes const f = () => to function f(). It isn't gated by stylistic, so it keeps working in ESLint. n/a
    antfu/if-newline, antfu/curly Not enabled for us. lessOpinionated: true replaces them with core curly: 'all'. For reference, Prettier keeps if (a) b() on one line, so if-newline would not be covered. n/a
    jsonc/*, yaml/* layout 10 + 12 rules ✅ Prettier formats JSON and YAML. Divergence: .jsonc gets trailing commas, which conflicts with jsonc/comma-dangle: never (apps/docs/wrangler.jsonc). It also re-indented the ast-grep __snapshots__/*.yml files, which are generated and would need ignorePatterns. 14 of 66 JSON/YAML files changed
    toml/* layout 11 rules ⚠️ Only with bunfig.toml [fmt]. With a Prettier config present, bun fmt skips TOML. mise.toml and bunfig.toml failed with "No parser could be inferred". Verified
    jsdoc/check-alignment, jsdoc/multiline-blocks ⚠️ Only with jsdoc = true, which also rewrites prose (capitalization, tag order). Not verified
    perfectionist/sort-* (not gated) Keep in ESLint. Don't turn on sortImports: its groups (builtin → external → alias → relative) differ from perfectionist's (types first), and the two would fight. n/a

    Summary: 5 rule families can't be expressed: brace-style, operator-linebreak, arrow-parens, member-delimiter-style, and quotes with avoidEscape: false. Together they produced 222 of the 223 leftover errors. Even with a Prettier-equivalent config, bun fmt would change 36 of 59 JS/TS files (2,886 changed lines) on this repo. Adopting it means adopting Prettier style, not porting ours.

    Side note: @pleaseai/prettier-config sets bracketSpacing: false, which contradicts style/object-curly-spacing: always in pleaseai(). The two packages already disagree today, and bun fmt reads that config as is (verified through package.json#prettier).

    3. Markdown parity with Prettier: verified on this repo

    I formatted the repo's 59 tracked .md/.mdx files twice, once with no config and once with @pleaseai/prettier-config. Each time I ran Prettier 3.9.10 and bun fmt 1.4.4-canary.1 on separate copies, then ran diff -r.

    • Result: byte-identical in both configs (0 differing files). That includes MDX with JSX components, front matter, CJK tables, and code fences.
    • For contrast, the TS/JS files were also byte-identical to Prettier (59 files).
    • --doctor reported every file as "formatted natively". Bun did not hand any file to the installed Prettier.
    • Caveat: this is one small repo. It doesn't prove parity in general, and proseWrap was left at its default preserve.
    • Migration impact: nothing formats Markdown prose today. antfu only lints code blocks, and formatters is off. So bun fmt would be new formatting. It changed 53 of 59 Markdown files, mostly by aligning table columns and turning " into ' in front matter.

    4. Speed (indicative, this repo, 20 cores, idle)

    Task Time
    bun fmt --check, 59 TS/JS files ~9 ms
    prettier --check, same files ~640 ms
    bun fmt --check, 59 md/mdx files ~9 ms
    prettier --check, same files ~1,040 ms
    eslint . (no cache, whole repo) 4.2 to 4.6 s
    Formatting rules' share of ESLint rule time (TIMING) ~335 ms of ~1,150 ms (~29%)

    On this repo, dropping stylistic would save about 0.3 s of a roughly 4 s ESLint run. Most of the wall time goes to config load, parsing, and non-style rules. I didn't measure a large repo or bun lint. Both are worth redoing once bun lint/bun fmt are stable.

    5. Integration impact

    • @pleaseai/prettier-config: this becomes the natural home for the formatter config, because bun fmt reads package.json#prettier: "@pleaseai/prettier-config" directly. But it would need to agree with ESLint: bracketSpacing is the existing mismatch, and arrowParens: 'always' contradicts style/arrow-parens today. Because a Prettier config disables TOML, we may prefer a bunfig.toml [fmt] snippet instead of, or in addition to, the Prettier config.

    • please-style CLI (packages/cli): the prettier-config tool installs prettier and sets package.json#prettier. A bun fmt path would need several changes:

      • a new tool or option that writes [fmt] to bunfig.toml, or reuses the Prettier config without installing prettier
      • a matching pleaseai({ stylistic: false }) template in eslint.config.mjs, with curly added back
      • a doctor check for Bun ≥ the first stable version with fmt

      Bun-only projects benefit. npm/pnpm projects without Bun cannot use it.

    • Editors: the Editor docs tell VS Code users to set prettier.enable: false and formatOnSave: false and to fix through ESLint. Bun documents no editor extension or LSP for bun fmt. Since its output equals Prettier's, the practical setup is the Prettier extension with format-on-save, plus ESLint for code-quality fixes. That reverses the current guidance and needs prettier installed for the editor, which the docs themselves point out. Neovim would use conform.nvim with bun fmt --stdin-filepath.

    • lint-staged: the repo runs eslint --cache --fix on *.{js,ts,md,…} through husky. bun fmt --staged replaces the formatting half without lint-staged. ESLint stays for code-quality rules, so we would keep lint-staged for ESLint or move to bun lint --staged --fix later. Watch for this: bun fmt with an empty file list formats the whole directory. Use --staged or xargs -r, not xargs bun fmt.

    • feat(eslint-config): add canonicalPluginNames option for native bun lint rules #57 (canonicalPluginNames): this proposal is independent of it. stylistic: false removes style/* entirely, so the @stylistic → style rename stops mattering.

    6. Migration impact

    • Rule IDs: with stylistic: false, the 65 style/* rules disappear, along with antfu/consistent-list-newline, antfu/consistent-chaining, curly (unless we add it back), import/newline-after-import, 2 jsdoc/* rules, and 33 jsonc/yaml/toml layout rules. Consumers' eslint-disable style/… comments and rules: { 'style/…': … } overrides start reporting unknown-rule errors, and eslint.rules.customizations in editor settings goes stale.
    • Output: the code style changes visibly: 1TBS braces, trailing operators, ; in inline type literals, wrapping by printWidth. Expect a one-time reformat of most files (on this repo: 36 of 59 TS/JS, 53 of 59 Markdown, 14 of 66 JSON/YAML).
    • Breaking? As a default, yes. It would need a major bump. As an opt-in option, such as formatter: 'bun' (name TBD) that sets stylistic: false and keeps curly, it is non-breaking.

    Recommendation

    1. Don't change the default. bun fmt is canary-only, and it would replace the antfu style with Prettier style rather than speed it up.
    2. When bun fmt ships stable, consider an opt-in option for teams that accept Prettier style. It would turn stylistic off, add curly back, and ship a matching [fmt]/Prettier config. Fix the bracketSpacing and arrowParens mismatch in @pleaseai/prettier-config first.
    3. Markdown can be decided on its own. Parity looked solid here, and nothing formats Markdown prose today. A bun fmt (or Prettier) pass on *.md/*.mdx could be added independently of the JS/TS decision.

    Open questions

    • Are we willing to switch to Prettier style (1TBS braces, trailing operators) in exchange for speed? If not, bun fmt is out of scope for JS/TS.
    • Should @pleaseai/prettier-config be aligned with pleaseai() (bracketSpacing: true, arrowParens: 'avoid') regardless of this proposal?
    • Do we want Markdown prose formatting at all? It touched 53 of 59 files here, mostly table alignment.
    • Where should the formatter config live: bunfig.toml [fmt] (formats TOML, Bun-only) or the Prettier config (works with the editor extension, skips TOML)?
    • Re-measure on a large docs-heavy repo with stable bun lint + bun fmt against eslint --fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions