ESLint plugins
Run ESLint plugins in bun lint, and write your own rules
bun lint runs ESLint plugins, both from npm and your own. Rules get the same context, sourceCode, scopes, and fixers as in ESLint.
This plugin has one rule, which reports TODO comments:
const noTodo = {
meta: {
type: "suggestion",
messages: { todo: "Open an issue instead of leaving a TODO." },
schema: [],
},
create(context) {
const { sourceCode } = context;
return {
Program() {
for (const comment of sourceCode.getAllComments()) {
if (comment.value.includes("TODO")) {
context.report({ loc: comment.loc, messageId: "todo" });
}
}
},
};
},
};
export default {
meta: { name: "local" },
rules: { "no-todo": noTodo },
};Load it from an ESLint config or an oxlint config:
import local from "./lint/plugin.js";
export default [
{
plugins: { local },
rules: { "local/no-todo": "warn" },
},
];{
"jsPlugins": ["./lint/plugin.js"],
"rules": { "local/no-todo": "warn" }
}bun lint/app/src/index.js
1:1 warning Open an issue instead of leaving a TODO local/no-todo
✖ 1 problem (0 errors, 1 warning)
Linted 3 files [73.16ms]You don't need the eslint package. To learn the API, read ESLint's guide to plugins.
bunfig.toml can't load plugins. Once you add an eslint.config.js or .oxlintrc.json, that file decides which
rules are on, and Bun's defaults no longer apply.
Plugins from npm#
Install and import them as usual.
import perfectionist from "eslint-plugin-perfectionist";
export default [
{
plugins: { perfectionist },
rules: { "perfectionist/sort-imports": "error" },
},
];With an ESLint config, six plugins run natively: @typescript-eslint, react, react-hooks, import, n, and prettier. Everything else runs from node_modules, including unicorn, jsx-a11y, promise, jest, and vitest. Bun's built-in versions of those follow oxlint, so Bun only uses them with bunfig.toml or an oxlint config.
bun lint --doctor shows which of your rules run natively and which run in JavaScript.
Performance#
JavaScript rules are slower than native ones. bun lint --timing shows how much time they take.
- Only turn on a plugin's rules for the files that need them. Files without JavaScript rules pay nothing.
- Put a plugin in its own module. If you define it inside
eslint.config.js, every thread has to run the whole config file.
Run the installed package instead#
To run a built-in plugin from node_modules, for example because you pin an older version:
bun lint --no-native-plugin-rules
bun lint --native-plugin-rules=@typescript-eslint,import # only these two stay native[lint]
nativePluginRules = ["@typescript-eslint", "import"]ESLint's core rules are always native. Keep @typescript-eslint native if you use type-aware rules, because JavaScript rules don't get types.
prettier/prettier#
prettier/prettier from eslint-plugin-prettier is native. It compares each file with the output of bun fmt and never starts Prettier.
Bun falls back to the installed package, and says why, when it can't guarantee the same output as your Prettier:
- You have a version other than Prettier 3.9.
- You use a Prettier plugin that
bun fmtdoesn't have. - The file isn't JavaScript or TypeScript.
Without Prettier, use bun/format. It checks files against bun fmt.
Other languages#
Bun parses JavaScript and TypeScript. It hands other files to the eslint package in your project, so keep eslint 8.57 or later installed if you use:
- A plugin language, such as
json/jsonfrom@eslint/json,markdown/gfm, orcss/css. - A parser for another file type, such as
vue-eslint-parser,svelte-eslint-parser, oryaml-eslint-parser. eslint-plugin-html.
You still run bun lint, and these files show up in the same report.
Processors#
Processors work. This config lints the code blocks in Markdown files:
import markdown from "@eslint/markdown";
export default [
{ files: ["**/*.md"], plugins: { markdown }, processor: "markdown/markdown" },
{ files: ["**/*.md/*.js"], rules: { "no-var": "error" } },
];Notes#
- JavaScript rules don't get types.
parserServices.programisnull, even with--type-aware. A rule that calls typescript-eslint'sgetParserServices()doesn't run, andbun lintexits with code2. - Plugins run on several threads. Each thread imports the plugin, so module-level state is per thread, not per run.
- A renamed built-in plugin runs in JavaScript. For example,
@antfu/eslint-configregisters@typescript-eslintasts. - A rule that throws ends the run with exit code
2. With an oxlint config, Bun reports the error for that file and continues. - Type-aware rules skip files that have a processor.