Rules
Vue Doctor ships custom oxlint rules plus a curated set of eslint-plugin-vue template rules.
Work in progress
Individual rule pages (why it matters, bad and good examples, fix, options) will be generated from the rule registry in a later release. Until then this page lists the categories.
Rule IDs
Every rule has one public ID, used in findings, --format json output, baselines, config and suppression comments:
| Rules | ID shape | Example |
|---|---|---|
| Vue Doctor's own rules | vue-doctor/<category>/<rule> | vue-doctor/security/no-eval, vue-doctor/bundle-size/no-moment |
| Template rules (eslint-plugin-vue) | vue/<rule> | vue/require-v-for-key |
| Dead code (knip) | knip/<type> | knip/files |
<category> is the kebab-case category name: reactivity, ecosystem, architecture, performance, security, supply-chain, bundle-size, correctness, nuxt, server.
A trailing /* selects a whole group, in rules and ignore.rules: vue-doctor/security/*, vue-doctor/*, vue/*.
Deprecated 1.x IDs
The 1.x forms no-eval and vue-doctor/no-eval (and bare no-template-target-blank for template rules) are still accepted in rules, ignore.rules and suppression comments. They resolve to the canonical rule and print one deprecation warning per distinct ID per run naming the replacement. Switch to the canonical ID; the aliases will be removed in a future major version.
Template rules from eslint-plugin-vue (for example vue/require-v-for-key, vue/no-mutating-props and vue/no-template-target-blank) are reported under the same score. To silence a rule see Suppressing findings or Configuration.
Project checks
Some rules look at project files instead of source code, for example whether a .env.local is committed, whether a lockfile exists or whether public/ contains a private key. They run in their own analyzer, "project checks" (engine fs in the table below), alongside lint, template and dead-code checks. They only read the project, never use the network, and honour rules, ignore, baselines and the cache like every other rule. In a git repository they use git ls-files; elsewhere they walk the directory. Findings never contain secret values, only variable names and file locations. Skip them with diagnose(dir, { projectChecks: false }) or silence single rules in the config.
| Rule | What it reports |
|---|---|
vue-doctor/security/no-committed-env | A tracked .env file: always for *.local files (by convention never committed), otherwise when it sets non-empty values for secret-looking names or known secret formats. .env.example, .sample, .template, .defaults are ignored. |
vue-doctor/security/no-secret-in-public-env-file | VITE_*, VUE_APP_*, NUXT_PUBLIC_* and PUBLIC_* variables in .env* files whose name looks secret (or whose value is in a secret format). Publishable and anon keys are fine. |
vue-doctor/security/no-secret-in-public-env | The same names read in source: import.meta.env.VITE_*, process.env.NUXT_PUBLIC_*, useRuntimeConfig().public.*. |
vue-doctor/security/no-llm-sdk-in-client | LLM provider SDKs (openai, @anthropic-ai/sdk, @google/generative-ai, @google/genai, @ai-sdk/<provider>, groq-sdk, @mistralai/mistralai, cohere-ai, ...) imported in client code, dangerouslyAllowBrowser: true, and fetch/$fetch/axios/ky calls to provider API hosts (api.openai.com, api.anthropic.com, ...). Server code (server/, *.server.*, config files), tests and type-only imports are fine, as are useChat from @ai-sdk/vue and requests to your own /api/chat. A warning, since a configured proxy URL would not expose a key. |
vue-doctor/nuxt/no-secret-in-public-runtime-config | Secret-looking keys (or secret-format values) under runtimeConfig.public in nuxt.config, and in app.config, which is entirely public. |
vue-doctor/nuxt/no-sensitive-public-file | Files in public/ (and Nuxt static/) that are deployed to the web root: .env*, private keys (*.pem, *.key, id_rsa*), database files and dumps (*.sql, *.sqlite, *.db), source maps, backups (*.bak, *.old, *~), logs, .DS_Store and a .git/ folder. Untracked and git-ignored files count, since they are deployed too. |
vue-doctor/supply-chain/lockfile-integrity | No lockfile for a project with dependencies (looked up in the monorepo root too), lockfiles of several package managers side by side, and packages in the lockfile resolved from a host other than the configured registry (registry.npmjs.org by default, plus registry= and @scope:registry= of .npmrc and npmRegistryServer of .yarnrc.yml). Reads npm, pnpm and Yarn classic lockfiles. |
vue-doctor/supply-chain/no-remote-dependency-spec | package.json dependencies installed from a git repository, an http:// URL or a tarball URL instead of the registry. Git dependencies pinned to a full commit hash are fine. |
vue-doctor/supply-chain/no-dependency-install-scripts | Direct dependencies that run preinstall, install or postinstall scripts (npm lockfile hasInstallScript, pnpm requiresBuild). A low-confidence warning: many native packages need one, the point is to know which code runs at install time. |
The secret-name rules are medium-confidence signals, so they are warnings. The same goes for the supply-chain and public-file rules: they cannot know whether a mirror, a git dependency or a file in public/ is intended. Bare apiKey names (Firebase, Maps and analytics keys are public by design) are only reported with a qualifier such as secret, private or a provider like openai.
Severity policy
A rule defaults to error only when it is a Security rule with high confidence or a Correctness rule that reliably indicates a bug. Every other rule (performance, reactivity, architecture, bundle size, Nuxt, server, ecosystem, dead code, and medium or low confidence security rules) defaults to warning. Errors weigh more than warnings in the score, so a project is not marked down hard for style or optimisation advice. Override any rule with the rules config.
All rules
This table is generated from the rule registry.
| Rule | Category | Default severity | Confidence | Frameworks | Engine |
|---|---|---|---|---|---|
vue-doctor/reactivity/no-cascading-mutations | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/no-fetch-in-watch | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/no-missing-await-nextTick | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/no-mutation-in-computed | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/no-reactive-destructure | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/no-reactive-replace | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/no-ref-from-prop | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/no-watch-for-computed | Reactivity | warning | medium | vue, nuxt | oxlint |
vue-doctor/reactivity/prefer-computed | Reactivity | warning | medium | vue, nuxt | oxlint |
vue/no-async-in-computed-properties | Reactivity | warning | high | vue, nuxt | eslint-template |
vue/no-computed-properties-in-data | Reactivity | warning | high | vue, nuxt | eslint-template |
vue/no-mutating-props | Reactivity | warning | high | vue, nuxt | eslint-template |
vue/no-ref-as-operand | Reactivity | warning | high | vue, nuxt | eslint-template |
vue/no-side-effects-in-computed-properties | Reactivity | warning | high | vue, nuxt | eslint-template |
vue-doctor/architecture/no-giant-component | Architecture | warning | low | vue, nuxt | oxlint |
vue-doctor/architecture/no-nested-component-definition | Architecture | warning | medium | vue, nuxt | oxlint |
vue/component-name-in-template-casing | Architecture | warning | high | vue, nuxt | eslint-template |
vue-doctor/performance/async-parallel | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/client-passive-event-listeners | Performance | warning | high | vue, nuxt | oxlint |
vue-doctor/performance/js-batch-dom-css | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/js-cache-storage | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/js-combine-iterations | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/js-early-exit | Performance | warning | low | vue, nuxt | oxlint |
vue-doctor/performance/js-hoist-regexp | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/js-index-maps | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/js-min-max-loop | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/js-set-map-lookups | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/js-tosorted-immutable | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/no-deep-watch | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/no-global-css-variable-animation | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/no-large-animated-blur | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/no-layout-property-animation | Performance | warning | high | vue, nuxt | oxlint |
vue-doctor/performance/no-permanent-will-change | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/no-scale-from-zero | Performance | warning | medium | vue, nuxt | oxlint |
vue-doctor/performance/no-transition-all | Performance | warning | high | vue, nuxt | oxlint |
vue/no-use-v-if-with-v-for | Performance | warning | high | vue, nuxt | eslint-template |
vue-doctor/security/llm-prompt-injection | Security | warning | low | vue, nuxt | oxlint |
vue-doctor/security/no-committed-env | Security | warning | medium | vue, nuxt | fs |
vue-doctor/security/no-dynamic-code | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/no-eval | Security | error | high | vue, nuxt | oxlint |
vue-doctor/security/no-hardcoded-secret | Security | error | high | vue, nuxt | oxlint |
vue-doctor/security/no-injection | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/no-javascript-url | Security | warning | high | vue, nuxt | oxlint |
vue-doctor/security/no-llm-sdk-in-client | Security | warning | high | vue, nuxt | oxlint |
vue-doctor/security/no-open-redirect | Security | warning | medium | nuxt | oxlint |
vue-doctor/security/no-prototype-pollution | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/no-secret-in-public-env | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/no-secret-in-public-env-file | Security | warning | medium | vue, nuxt | fs |
vue-doctor/security/no-secret-named-literal | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/no-ssrf | Security | warning | medium | nuxt | oxlint |
vue-doctor/security/no-token-in-web-storage | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/no-unsafe-html-sink | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/no-user-controlled-url | Security | warning | medium | vue, nuxt | oxlint |
vue-doctor/security/postmessage-origin-check | Security | warning | medium | vue, nuxt | oxlint |
vue/no-template-target-blank | Security | warning | high | vue, nuxt | eslint-template |
vue-doctor/supply-chain/lockfile-integrity | Supply Chain | warning | medium | vue, nuxt | fs |
vue-doctor/supply-chain/no-dependency-install-scripts | Supply Chain | warning | low | vue, nuxt | fs |
vue-doctor/supply-chain/no-remote-dependency-spec | Supply Chain | warning | medium | vue, nuxt | fs |
vue-doctor/supply-chain/vulnerable-dependency | Supply Chain | warning | high | vue, nuxt | audit |
vue-doctor/bundle-size/no-barrel-import | Bundle Size | warning | medium | vue, nuxt | oxlint |
vue-doctor/bundle-size/no-full-lodash-import | Bundle Size | warning | high | vue, nuxt | oxlint |
vue-doctor/bundle-size/no-moment | Bundle Size | warning | high | vue, nuxt | oxlint |
vue-doctor/bundle-size/no-undeferred-third-party | Bundle Size | warning | medium | vue, nuxt | oxlint |
vue-doctor/bundle-size/prefer-dynamic-import | Bundle Size | warning | medium | vue, nuxt | oxlint |
vue-doctor/correctness/no-array-index-as-key | Correctness | warning | medium | vue, nuxt | oxlint |
vue-doctor/correctness/no-async-setup-without-suspense | Correctness | warning | medium | vue, nuxt | oxlint |
vue-doctor/correctness/no-direct-dom-manipulation | Correctness | warning | medium | vue, nuxt | oxlint |
vue-doctor/correctness/no-prevent-default | Correctness | warning | medium | vue, nuxt | oxlint |
vue-doctor/correctness/no-this-in-setup | Correctness | error | high | vue, nuxt | oxlint |
vue-doctor/correctness/prefer-defineProps-destructure | Correctness | warning | low | vue, nuxt | oxlint |
vue-doctor/correctness/require-defineprops-types | Correctness | warning | medium | vue, nuxt | oxlint |
vue-doctor/correctness/require-emits-declaration | Correctness | warning | high | vue, nuxt | oxlint |
vue/no-dupe-keys | Correctness | error | high | vue, nuxt | eslint-template |
vue/no-duplicate-attributes | Correctness | error | high | vue, nuxt | eslint-template |
vue/no-template-shadow | Correctness | warning | high | vue, nuxt | eslint-template |
vue/require-explicit-emits | Correctness | warning | high | vue, nuxt | eslint-template |
vue/require-v-for-key | Correctness | error | high | vue, nuxt | eslint-template |
vue/return-in-computed-property | Correctness | error | high | vue, nuxt | eslint-template |
vue/valid-v-bind | Correctness | error | high | vue, nuxt | eslint-template |
vue/valid-v-model | Correctness | error | high | vue, nuxt | eslint-template |
vue/valid-v-on | Correctness | error | high | vue, nuxt | eslint-template |
vue/valid-v-slot | Correctness | error | high | vue, nuxt | eslint-template |
vue-doctor/ecosystem/pinia-no-destructure | Ecosystem | warning | medium | vue, nuxt | oxlint |
vue-doctor/ecosystem/pinia-no-watch-store | Ecosystem | warning | low | vue, nuxt | oxlint |
vue-doctor/ecosystem/router-no-async-guard-without-next | Ecosystem | warning | low | vue, nuxt | oxlint |
vue-doctor/ecosystem/router-no-string-push | Ecosystem | warning | low | vue, nuxt | oxlint |
vue-doctor/nuxt/no-secret-in-public-runtime-config | Nuxt | warning | medium | nuxt | oxlint |
vue-doctor/nuxt/no-sensitive-public-file | Nuxt | warning | medium | vue, nuxt | fs |
vue-doctor/nuxt/nuxt-async-client-component | Nuxt | warning | medium | nuxt | oxlint |
vue-doctor/nuxt/nuxt-no-a-element | Nuxt | warning | high | nuxt | oxlint |
vue-doctor/nuxt/nuxt-no-client-fetch-for-server-data | Nuxt | warning | medium | nuxt | oxlint |
vue-doctor/nuxt/nuxt-no-head-import | Nuxt | warning | high | nuxt | oxlint |
vue-doctor/nuxt/nuxt-no-img-element | Nuxt | warning | high | nuxt | oxlint |
vue-doctor/nuxt/nuxt-no-process-env-in-client | Nuxt | warning | medium | nuxt | oxlint |
vue-doctor/nuxt/nuxt-no-window-in-ssr | Nuxt | warning | medium | nuxt | oxlint |
vue-doctor/nuxt/nuxt-require-seo-meta | Nuxt | warning | medium | nuxt | oxlint |
vue-doctor/nuxt/security-headers | Nuxt | warning | low | nuxt | oxlint |
vue-doctor/server/auth-missing | Server | warning | low | nuxt | oxlint |
vue-doctor/server/csrf-on-mutating-routes | Server | warning | low | nuxt | oxlint |
vue-doctor/server/no-leaky-error-rethrow | Server | warning | medium | nuxt | oxlint |
vue-doctor/server/require-input-validation | Server | warning | medium | nuxt | oxlint |
vue-doctor/server/secure-cookie-flags | Server | warning | medium | nuxt | oxlint |
vue-doctor/server/server-no-console-in-handler | Server | warning | medium | nuxt | oxlint |
vue/no-unused-vars | Dead Code | warning | high | vue, nuxt | eslint-template |