Files
Jarred Sumner 1c10257375 ci: use bun lint and bun fmt; docs: a Linter tab (#45119)
### What does this PR do?

Bun's own CI lints with `bun lint` and formats with `bun fmt`. oxlint
and Prettier are gone from GitHub Actions. It also gives the linter its
own tab in the docs, and records what `bun lint` reports in the docs'
examples, for the website.

#### CI

| Workflow | Before | After |
|---|---|---|
| `lint.yml` | `oxlint --config=oxlint.json --format=github src/js` |
`bun lint --format=github` |
| `format.yml` | `prettier --plugin=prettier-plugin-organize-imports
--write <paths>` | `bun fmt --plugin=prettier-plugin-organize-imports` |
| `packages-ci.yml` | `bunx oxlint@0.15 --deny-warnings`, `bunx prettier
--check .` | `bun lint --deny-warnings`, `bun fmt --check .` |

- **`lint.yml` and `format.yml` install `canary`** instead of 1.4.2,
because neither command is in a release yet. `packages-ci.yml` already
did. There's a comment to pin them to 1.5.0 once it's out. **These two
checks can't pass until canary has #45043.**
- **What gets linted and formatted moves from arguments into
`bunfig.toml`**, so `bun lint` and `bun fmt` do the right thing with no
arguments. `oxlint.json`, `.prettierrc` and `.prettierignore` are
unchanged, and Bun reads all three.
- **The `fmt`, `lint`, `lint:fix` and `prettier` scripts are removed,
not pointed at the new commands.** A project's own script wins over the
command, and on a released Bun, where `bun fmt` doesn't exist, a `fmt`
script that runs `bun fmt` calls itself: 616 nested processes in 5
seconds on 1.4.3. Without the scripts, an old Bun says `Script not found
"fmt"`.
- `oxlint` and `prettier-plugin-organize-imports` are removed from
`devDependencies` (46 lines of `bun.lock`). The plugin is built into
`bun fmt`.
- **`prettier` stays, for editors**: `.vscode/settings.json` points
format-on-save at `./node_modules/prettier`. It goes from 3.6.2 to
3.9.9, the version `bun fmt` matches, so that saving a file and CI
agree.
- The plugin stays a flag in CI and isn't in `.prettierrc`, as before.
On save it would remove an import that the next edit is about to use.

#### What the switch flagged

- **75 files reformatted**, in a commit of their own. All are
differences between Prettier 3.6.2 and 3.9.9: `for (;;)` without a space
before `)`, unions that fit on one line, `{/* comment */}`, table
widths.
- **5 lint errors in `src/js`** that oxlint 1.87 reports and 1.70
didn't: four unused `_` parameters, renamed or removed, and the fuzzer
harness's `eval`, which gets a disable comment.
- **`packages/bun-plugin-svelte`** has no lint config, so it now gets
Bun's default rules. `--fix` turned three `var`s and one `let` into
`let` or `const`. `no-explicit-any` is off for that package, because its
tests pass values of the wrong type on purpose.

#### Docs

The linter was a group inside the Runtime tab. It's now a **Linter** tab
between Test Runner and Guides, which opens on the guide:

| Group | Page | URL |
|---|---|---|
| Getting Started | Linter | `/docs/lint` (was `/docs/runtime/lint`) |
| Rules | Lint rules | `/docs/lint/rules` |
| | ESLint plugins | `/docs/lint/plugins` |
| Formatter | Formatter | `/docs/format` (was `/docs/runtime/format`) |
| Migrate | the four migration guides | unchanged |

Neither old URL has been in a release. The 25 references to them are
updated, including the two in `--help` and the one in the SARIF output.
The site redirects the old URLs.

**`docs/lint/rules/reports.json`** is new: what `bun lint` reports in
each example on the rule pages, with the exact span and message. 3,047
reports in 4,173 examples, one line per page. The website uses it to
underline the problems in the examples, like an editor
(oven-sh/site#291).

- `bun src/codegen/lint-rule-docs.ts --write` generates it, together
with the rest of the generated docs.
- The code that sets up each example as a small project and lints it
moved from `rule-docs.test.ts` into that module, so the generator and
the test run the same thing. The test's checks are unchanged, and it now
also fails if `reports.json` is stale, naming the page and the command.
- A few messages name a file by its whole path. That becomes
`/project/file.js`, so the file is the same on every machine.
- It isn't embedded in the binary.

### How did you verify your code works?

With a build of `main` at #45043:

| | |
|---|---|
| files `bun fmt` visits with no arguments, against the Prettier
script's arguments (`--list-files`) | the same 3,350 |
| the 75 reformatted files, against real Prettier 3.9.9 with the real
plugin | 75 of 75 byte-identical |
| …against real Prettier 3.6.2 | it leaves all 75 as they were, so they
are version differences |
| the 65 of them that are JS or TS, transpiled before and after | 65 of
65 the same bytes |
| a second `bun fmt` | changes 0 files, so autofix can't ping-pong |
| real Prettier 3.9.9 **without** the plugin (what an editor runs) on
all 3,350 files | all formatted |
| `bun lint` with no arguments, against `bun lint --config=oxlint.json
src/js` | identical output |
| the 5 lint errors, against real oxlint 1.87 | the same 5 |
| a violation of the repo's own JS plugin rule, planted in `src/js` |
reported, with real oxlint's message, position and order |
| `debugger;` planted in `bench/` and in `src/` outside `src/js` | not
reported |
| `bun install --frozen-lockfile` | accepts the lockfile |
| Bun 1.4.3 with the new `bunfig.toml` | runs. `bun fmt` says `Script
not found "fmt"` |

All four changed CI steps exit 0 on this branch. `bun lint` takes 140 ms
for 202 files, and `bun fmt` 110 ms for 3,350.

`rule-docs.test.ts` passes (123 tests), and fails with `eslint/eqeqeq:
reports.json is out of date` when one message in the file is changed by
hand.

A build of the first two commits passes the lint, format and check
suites, and `fs.glob`, `fs`, `undici`, `node:module`, `require.cache`,
`node:net` and the `bun-types` tests, which cover the built-in modules
that were edited.

Known: `bunfig.toml` and `.prettierignore` are read from the current
directory only, so `bun fmt` run from inside `test/` doesn't see the
scope and would format the fixtures too. `prettier --write .` in `test/`
has always done the same.
2026-10-13 00:10:32 -07:00
..