A Svelte linter written in Rust. Drop-in replacement for eslint-plugin-svelte — same rules, same diagnostics, 50-1000x faster.
This entire codebase was written by an LLM. A coding agent (Claude Code) ran in an autonomous loop against real-world benchmarks — fixing lint rule parity, eliminating false positives, and optimizing hot paths — until the numbers converged. The human wrote
program.md(the spec); the machine wrote everything insrc/.
Tested against 4 real-world Svelte codebases (3,466 files total), each installed with its own production eslint config and plugin chain:
| Repo | Files | eslint-plugin-svelte | oxvelte | Speedup | Parity |
|---|---|---|---|---|---|
| shadcn-svelte | 1,641 | 2,385ms | 44ms | 54x | 0/0 exact |
| open-webui | 549 | 11,297ms | 60ms | 188x | 7/9 rules exact (compile related rules excluded) |
| immich | 400 | 32,138ms | 32ms | 1,004x | 0/0 exact |
| sveltejs/kit | 876 | 16,772ms | 30ms | 559x | 3/3 rules exact |
The fastest way to lint a SvelteKit project is oxlint + oxvelte together. They handle different concerns with zero overlap:
| Tool | What it lints | File types |
|---|---|---|
| oxlint | General JS/TS rules (no-unused-vars, no-console, type checks, imports, etc.) |
.js, .ts, .svelte (script blocks) |
| oxvelte | Svelte-specific rules (infinite-reactive-loop, no-at-html-tags, template issues, etc.) |
.svelte |
oxlint already extracts and lints <script> blocks from .svelte files. oxvelte adds the Svelte-specific rules that oxlint doesn't have — reactive patterns, template structure, style scoping, component conventions.
# Install both
npm install -D oxlint
cargo install --git https://github.com/tolgaouz/oxvelte.git
# Add to package.json
{
"scripts": {
"lint": "oxlint && oxvelte lint src/"
}
}
That's it. Both tools work out of the box with zero config and sensible defaults.
For a typical SvelteKit project with TypeScript:
{
"scripts": {
"lint": "oxlint --tsconfig tsconfig.json && oxvelte lint src/",
"lint:fix": "oxlint --fix --tsconfig tsconfig.json && oxvelte lint --fix src/"
},
"devDependencies": {
"oxlint": "^1.58.0"
}
}
# optional: configure oxlint
# .oxlintrc.json
{
"plugins": ["typescript", "import", "unicorn"],
"rules": {
"no-console": "warn"
}
}
# optional: configure oxvelte
# oxvelte.config.json
{
"rules": {
"svelte/no-at-html-tags": "error"
},
"settings": {
"svelte": {
"kit": {
"files": { "routes": "src/routes" }
}
}
}
}
oxlint doesn't have a third-party Rust plugin system — all plugins are compiled into the binary. There's a JS plugin API in alpha, but it doesn't fully support .svelte files yet and would lose the performance advantage of native Rust.
Running them as separate binaries is actually fine:
svelte/* rulesIf you're migrating from the ESLint setup (eslint-plugin-svelte + @eslint/js + typescript-eslint), here's how the tools map:
| ESLint stack | Replacement |
|---|---|
@eslint/js (core JS rules) |
oxlint |
typescript-eslint |
oxlint --tsconfig |
eslint-plugin-svelte |
oxvelte |
eslint-plugin-import |
oxlint (built-in --import-plugin) |
# Before (ESLint, ~3-10 seconds)
eslint src/
# After (oxlint + oxvelte, ~200-400ms)
oxlint && oxvelte lint src/
If you have an existing eslint-plugin-svelte config, oxvelte can convert it:
oxvelte migrate eslint.config.js --write
Both tools return non-zero exit codes on errors, so they work naturally in CI:
# GitHub Actions
- run: npx oxlint --tsconfig tsconfig.json
- run: oxvelte lint src/
For JSON output (useful for custom reporters or IDE integration):
oxlint --format json
oxvelte lint --json src/
cargo install --git https://github.com/tolgaouz/oxvelte.git
git clone https://github.com/tolgaouz/oxvelte.git
cd oxvelte
cargo build --release
The binary will be at ./target/release/oxvelte.
oxvelte lint src/ # lint with recommended rules
oxvelte lint --json src/ # JSON output
oxvelte lint --fix src/ # auto-fix where supported
oxvelte lint --all-rules src/ # run all 78 rules
oxvelte rules # list available rules
Create oxvelte.config.json in your project root:
{
"rules": {
"svelte/no-at-html-tags": "error",
"svelte/button-has-type": ["warn", { "button": false }],
"svelte/no-inline-styles": "off"
},
"settings": {
"svelte": {
"kit": {
"files": { "routes": "src/routes" }
}
}
}
}
Rules use the same names and options as eslint-plugin-svelte. Severity can be "off", "warn", or "error" (or 0, 1, 2). Options are passed as the second element of a tuple: ["error", { ...options }].
Settings configure framework-specific behavior. The svelte.kit.files.routes setting tells SvelteKit-aware rules where your route files live.
Without a config file, oxvelte runs the recommended ruleset (same as eslint-plugin-svelte's flat/recommended).
Project-specific conventions that don't belong in the shared ruleset — forbidden imports, naming schemes, required attributes — can be written in JavaScript and loaded via customRules:
{
"rules": { "custom/no-div-without-class": "error" },
"customRules": ["./rules/*.js"]
}
// ./rules/no-div-without-class.js
export default {
name: "custom/no-div-without-class",
run(ctx) {
ctx.walk((node) => {
if (node.type === "Element" && node.name === "div") {
const hasClass = node.attributes.some(
(a) => a.type === "NormalAttribute" && a.name === "class",
);
if (!hasClass) ctx.diagnostic("div must have a class attribute", node.span);
}
});
},
};
Rules run in an embedded Boa engine — no Node.js dependency, no IPC. Requires the custom-rules feature at build time (cargo install … --features custom-rules).
Full reference — AST shape, ctx API, auto-fix, limitations — in docs/custom-rules.md.
--fix)A few eslint-plugin-svelte rules are not implemented by design:
valid-compile — this rule is the Svelte compiler. Running it in a linter means invoking the full compiler on every file, which defeats the purpose of a fast native tool. Svelte already reports these errors at build time.no-unused-svelte-ignore — requires the Svelte compiler to know which diagnostics were actually suppressed. Again, Svelte itself warns about this at build time.indent — a formatting rule, not a lint rule. eslint-plugin-svelte itself marks it recommended: false with conflictWithPrettier: true. oxc tracks ESLint's indent as 🚫 Not intending to implement ("Deprecated stylistic rule, can be used via the stylistic eslint plugin as a JS Plugin if necessary"), and @typescript-eslint/indent is likewise deprecated upstream. Layout belongs in a formatter — use Prettier or oxfmt; don't re-encode it as lint diagnostics.These rules add latency with zero incremental value — your build step (or formatter) already catches them.
src/ all Rust code
main.rs CLI entry point
parser/ Svelte template parser
linter/rules/ lint rules (one file per rule)
ast.rs Svelte AST types
Cargo.toml dependencies
README.md you are here
MIT