Vue rules
The full @geoql/oxlint-plugin-vue-doctor rule catalogue. ai-slop, composition, performance, reactivity.
The Vue plugin ships 12 rules across four categories. All rules are
auto-loaded by @geoql/vue-doctor and @geoql/nuxt-doctor — no
plugin registration needed.
ai-slop
Patterns that AI agents emit reflexively. None of these are bugs, but all of them are tells — the model is optimizing for looking right instead of being right.
| rule id | severity | what it catches |
|---|---|---|
no-destructure-props-without-toRefs | warn | destructuring defineProps() without toRefs |
no-destructure-reactive-without-toRefs | warn | destructuring reactive() without toRefs |
no-em-dash-in-str | info | — in string literals |
no-imports-from-vue-when-auto-imported | warn | explicit import { ref } from 'vue' in Nuxt |
no-non-null-assertion-on-ref-value | error | ref.value! — drops reactivity guarantees |
no-em-dash-in-str is the rule most projects disable first. The
doctor ships it on by default because em-dashes are the single most
reliable stylistic fingerprint of LLM-generated prose, but if your
team has a copy editor, you almost certainly don't need it.
composition
Vue 3 idioms for <script setup> and the Composition API.
| rule id | severity | what it catches |
|---|---|---|
defineProps-typed | warn | defineProps() without a type or runtime schema |
prefer-script-setup-for-new-files | info | new .vue files using Options API |
defineProps-typed is the highest-signal rule in this set. A
defineProps() call without a type is a regression — the macro was
specifically designed to carry TypeScript types, and skipping the
type means every consumer has to fall back to inference.
performance
Runtime and build-time perf landmines. Smaller set, but each rule catches something that's hard to spot in code review.
| rule id | severity | what it catches |
|---|---|---|
prefer-defineAsyncComponent-on-route | warn | eager-importing a route component (kills code-splitting) |
reactivity
The rules in this set all guard against the silent failure modes of Vue's reactivity system. The model "works" but the template never re-renders, or it re-renders in a way that breaks memoization.
| rule id | severity | what it catches |
|---|---|---|
prefer-readonly-for-injected | warn | mutable inject() — the parent may not react |
prefer-shallowRef-for-large-data | info | ref(hugeArray) for read-only data |
watch-without-cleanup | error | watch() with a side-effect that needs teardown |
watch-without-cleanup is the only error in the Vue set. Uncleaned
watchers leak — the previous callback keeps running after the
component unmounts, and you'll see it as a 100% CPU tail in
production.
The doctor emits a finding per watcher, not per file. A single .vue
with five unwatched timers will report five findings. The score
deduction uses √-decay so the cost shrinks fast, but the SARIF will
show all five.
What's not here
A few things we deliberately don't ship in the Vue plugin:
- General ESLint-style correctness — that's what oxlint is for.
The doctor does not duplicate
vue/no-mutating-propsorvue/no-unused-components. Run oxlint alongside. - Type-correctness for
<template>— the<template>AST is lossy on purpose; inferring ref types from<MyComp :foo="x" />is not a doctor job. - A11y checks —
eslint-plugin-vue-a11yexists, and the doctor will not reimplement it.
If you find a rule that should be in the doctor but isn't, open an issue. We add rules when there's a documented AI-agent pattern behind them.