oxc-project/oxc · error · OxcDiagnostic

Change to `throw new TypeError(...)`

Error message

Change to `throw new TypeError(...)`

What it means

Diagnostic from oxlint's vue/no-watch-after-await rule (crates/oxc_linter/src/rules/vue/no_watch_after_await.rs). It reports `watch` and `watchEffect` calls that appear after an `await` expression inside a `setup()` function of a plain `<script>` block. Vue binds watchers to the current component instance, which only exists synchronously during setup; after the first `await` that context is lost, so the watcher may fail to register or never auto-dispose.

Solutions

  1. Move every `watch`/`watchEffect` call above the first `await` in `setup()`.
  2. If the watcher needs awaited data, register it inside `onMounted` (or another lifecycle hook) instead of after the await.
  3. Keep setup() synchronous and move async work into a composable invoked from `onMounted`, returning the watcher stop-handle for cleanup.
  4. If the placement is intentional and verified safe, suppress with an inline oxlint disable comment for vue/no-watch-after-await.

Example fix

// before
<script>
import { watch } from 'vue'
export default {
  async setup() {
    await loadUser()
    watch(user, () => {}) // reported
  }
}
</script>

// after
<script>
import { watch } from 'vue'
export default {
  async setup() {
    watch(user, () => {})
    await loadUser()
  }
}
</script>
Defensive patterns

Strategy: validation

Validate before calling

// pre-commit gate: flag .vue files whose setup() awaits before a watch call
const fs = require('fs');
for (const f of process.argv.slice(2)) {
  const src = fs.readFileSync(f, 'utf8');
  const setupIdx = src.indexOf('setup(');
  if (setupIdx === -1) continue;
  const body = src.slice(setupIdx);
  const awaitIdx = body.search(/\bawait\b/);
  const watchIdx = body.search(/\b(watch|watchEffect)\s*(\?\.)?\s*\(/);
  if (awaitIdx !== -1 && watchIdx > awaitIdx) {
    console.warn(`${f}: watch/watchEffect appears after an await in setup()`);
  }
}

Prevention

When it happens

Trigger: A `.vue` file with a non-setup `<script>` where `export default {...}` or `defineComponent({...})` declares an async `setup()` (function or arrow) that awaits and then calls `watch(...)`/`watchEffect(...)` as a direct expression statement. Only identifiers imported from the module 'vue' count (aliases like `watch as A` are tracked; `watch?.(...)` is flagged). Calls wrapped in other expressions (`const a = watch()`, `d(watch())`, object/array literals) and calls inside nested functions are intentionally ignored, and `<script setup>` blocks are not checked.

Common situations: Fetching data in setup() before subscribing to it; refactors that introduce top-level-style awaits into Options API setup; porting eslint-plugin-vue configs to oxlint; moving watchers below an await during async initialization work.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/3b50d8a3981cd6de. Report an issue: GitHub.

Appendix: source

Thrown at crates/oxc_linter/src/rules/unicorn/prefer_type_error.rs:16

use oxc_ast::{
    AstKind,
    ast::{CallExpression, Expression, MemberExpression, match_member_expression},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};

use crate::{AstNode, context::LintContext, rule::Rule};

fn prefer_type_error_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn(
        "Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement",
    )
    .with_help("Change to `throw new TypeError(...)`")
    .with_label(span)
}

#[derive(Debug, Default, Clone)]
pub struct PreferTypeError;

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Enforce throwing a `TypeError` instead of a generic `Error` after a type checking if-statement.
    ///
    /// ### Why is this bad?
    ///
    /// Throwing a `TypeError` instead of a generic `Error` after a type checking if-statement is more specific and helps to catch bugs.
    ///
    /// ### Examples
    ///
    /// Examples of **incorrect** code for this rule:

View on GitHub (pinned to e1e7af627c)