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
- Move every `watch`/`watchEffect` call above the first `await` in `setup()`.
- If the watcher needs awaited data, register it inside `onMounted` (or another lifecycle hook) instead of after the await.
- Keep setup() synchronous and move async work into a composable invoked from `onMounted`, returning the watcher stop-handle for cleanup.
- 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
- Call all watch/watchEffect at the top of setup(), before any await.
- Do async data loading in onMounted or a composable, not inline after awaits in setup().
- Run oxlint with the vue plugin in CI so vue/no-watch-after-await gates every merge.
- Prefer <script setup> top-level await (compiler preserves instance context) over async setup().
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
- Unexpected in computed function.
- Unexpected in " " computed property.
- You should not use an arrow function to define a watcher.
- Avoid destructuring from `defineProps`.
- Avoid nesting promises.
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)