Skip to content

Lint & Format

Consistent linting and formatting keep our Vue codebase readable, predictable, and easy to review. This document defines the baseline configuration and conventions we expect across all projects.

Goals

  • Use ESLint v9+ with the Flat Config format as the baseline linter.
  • Use eslint-plugin-vue with its flat/recommended preset.
  • Lint TypeScript with typescript-eslint and @vue/eslint-config-typescript (type‑aware where feasible).
  • Use Prettier for all formatting decisions.
  • Use eslint-plugin-playwright for E2E tests, and @vitest/eslint-plugin plus eslint-plugin-testing-library for unit and integration tests (see 09.Testing).
  • Enforce a small set of clarity rules at lint time.
  • Require a clear justification when disabling rules on a file or code block.

Why this matters

Without commit-time enforcement, formatting inconsistencies and lint violations slip into the repo, creating noise in code reviews, wasting reviewer time, and slowing down merges. Automated checks eliminate these distractions by catching them early. This keeps pull requests focused on logic and architecture, improves productivity, and reduces friction between teammates during reviews.

Linting

Version & Config Style

  • ESLint v9 or later is required.
  • Use the Flat Config (eslint.config.mjs or .js), not legacy .eslintrc.* files.

Vue Support

  • Include eslint-plugin-vue and spread its flat preset (...vue.configs['flat/recommended']).
  • This enables the correct parser for .vue SFCs and sensible defaults for template and script linting.

TypeScript Integration

  • Use @vue/eslint-config-typescript through the defineConfigWithVueTs helper and the vueTsConfigs.recommended preset so the Vue and TypeScript rule sets stay aligned.
  • When type-aware rules are worth their cost, opt in with projectService: true and the type-checked presets (e.g., vueTsConfigs['flat/recommended-type-checked']). Type-aware runs are slower and require a usable tsconfig.json; if performance becomes an issue, drop back to the non-type-checked preset.
  • Verify the exact disabled-rule set of eslint-config-prettier against your installed version rather than asserting a fixed list in this document.

General Rules

Rules or overrides must be agreed by the whole team; only deviate from presets when there is a clear, shared need and plan to revisit.

  • Curly braces are mandatory for all control statements (no inline if/single-line bodies without braces).
  • Semicolons are mandatory for JavaScript and TypeScript.
  • Prefer explicit, short, focused rules; push formatting choices to Prettier.
  • When introducing a new ESLint plugin, give it a dedicated config entry in eslint.config.mjs so its purpose stays obvious. Flat Config merges objects shallowly, so take care not to overwrite an existing rule by spreading a broader preset on top.
  • Each file pattern (*.vue, *.ts, etc.) should have its own config block unless multiple extensions truly share the same setup; this keeps scope clear and avoids accidental bleed-over.
  • Style rules that no longer ship with ESLint v9 core (such as no-multiple-empty-lines and padding-line-between-statements) must come from @stylistic/eslint-plugin. Place that config block aftereslint-config-prettier so Prettier still wins on layout. Verify the exact set of disabled rules in your installed version of eslint-config-prettier rather than asserting it here.

Inline Rule Suppressions

If you must disable a rule, you must document why. Keep the disable scope as small as possible.

ts
// Reason: third-party lib returns `unknown`;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload: unknown = lib.read()

Note: Any rule suppression without an accompanying explanation is not allowed in PRs.

Formatting

Prettier owns formatting. Do not argue with it—configure it once and let it run everywhere (CLI, editor, CI). ESLint stays focused on code-quality and structural rules; layout decisions belong to Prettier.

Required Prettier settings:

  • Print width: 120 (soft wrap target for code & markdown).
  • Trailing commas: all (keeps diffs focused on the changed line).
  • Semicolons: true.
  • Single quotes: true in JS/TS; fall back to " only when it prevents extra escaping.
  • 2 spaces indentation: matches the wider Vue ecosystem and keeps deeply nested templates readable.

Optional Prettier settings worth standardizing across the team:

  • bracketSameLine: true keeps the closing > of multi-line HTML and Vue template elements attached to the last child. It does not affect JS/TS closing brackets (], }); those always attach to their block.
  • endOfLine: 'lf' to keep line endings consistent across Windows and macOS/Linux contributors.

ESLint-owned code style (place the @stylistic config block after eslint-config-prettier so it cannot override Prettier):

  • Curly braces mandatory for all control flow.
  • Import order enforced deterministically via @stylistic/padding-line-between-statements or an equivalent rule.
  • Padding lines between statements so logical sections (imports, declarations, returns) stay scannable.

For attribute order inside Vue templates, follow the reference in 03.Components; do not redefine it here.

Commit Enforcement

All developers must have pre‑commit checks enabled. We use Husky and lint‑staged to run ESLint and Prettier on staged files before a commit is created. This prevents style & lint issues from entering the repo, keeps diffs small, and saves reviewer time. These checks are mandatory for local development and mirrored in CI as a safety net. Commit message formatting and commitlint hooks are owned by 11.Commits-PRs.

Editor Integration

  • Configure your editor (VS Code, WebStorm, etc.) to run Prettier on save so files stay aligned with the shared config without manual steps.
  • Enable ESLint on save to surface violations immediately instead of waiting for pre-commit hooks or CI.
  • Match extension versions to the repo tooling to avoid mismatched formatting (e.g., use workspace-installed Prettier/ESLint when available).
  • When pairing or moving machines, sync your editor settings so the auto-fix workflow remains consistent across the team.
  • Include a root-level .editorconfig as a fallback for basic whitespace and newline rules, so contributors using different editors still share the same baseline behavior.

Auto-fixable violations should run on save and in lint-staged; treat non-fixable violations as a reviewer conversation, not as silent exceptions.

Oxlint (Evaluated Alternative)

Oxlint is fast and ergonomic, but it is not a drop-in replacement for ESLint on Vue projects. The section below frames the decision; ESLint with eslint-plugin-vue remains the only baseline recommendation.

  • ESLint stays the default. A team must opt in to Oxlint through a written decision, not by adding a dependency in passing.
  • No native Vue template linting. Oxlint does not parse <template> or <style> blocks, so rules that depend on the template AST (e.g. vue/no-mutating-props, vue/v-on-event-hyphenation, template-scope unused-vars) cannot run. Native support is tracked behind a language-plugin RFC and has not shipped.
  • Built-in vue plugin is script-only. It covers a growing subset of eslint-plugin-vue rules that apply inside <script> / <script setup>. Treat it as supplementary, not as a replacement for eslint-plugin-vue.
  • Type-aware mode is opt-in and gated. It requires TypeScript 7.0+, a usable tsconfig.json, and a per-project configuration. It is a real performance win on TypeScript-heavy code but is not free.
  • Coexistence is the practical pattern. Run Oxlint first (script blocks and JS/TS files), then ESLint + eslint-plugin-vue for template and rule coverage Oxlint cannot provide. Use eslint-plugin-oxlint to disable overlapping rules in ESLint so the two linters stay in sync.
  • Third-party Vue bridges exist but are early. oxlint-plugin-vize and similar wrappers ship through the JS plugin system; do not adopt them as defaults without a spike and a rollback plan.
  • Editor and CI story is solid for .ts/.js (LSP server, oxlint-action, github/json/junit formats). Vue SFC range fidelity is weaker than ESLint + eslint-plugin-vue; expect some diagnostics to land on virtual source ranges.
  • Windows hosts have known memory issues on large codebases; document the workaround if the team includes Windows contributors.
  • Adoption checklist before turning Oxlint on for a Vue project:
    • Confirm that the Vue template rules you depend on are covered by ESLint + eslint-plugin-vue and acceptable in the residual Oxlint set.
    • Confirm type-aware requirements (TypeScript 7.0+, tsconfig shape) match the project.
    • Confirm every required ESLint plugin either ships natively in Oxlint or runs via the JS plugin bridge with acceptable fidelity.
    • Confirm editor and CI integration (LSP, output formats, PR annotations) match the existing workflow.
    • Confirm a coexistence or cutover plan: which linter runs first, who owns the dedupe plugin config, and how regressions are diagnosed.
  • Re-evaluation. Revisit the Oxlint decision when the language-plugin RFC ships first-party Vue support, or when the type-aware story stabilises for a release the team is willing to depend on.

Reference Configs

Baseline configs live under docs/examples/ so teams can copy them directly and layer project-specific tweaks once the team agrees on the change.