oxc-project/oxc · warning
Change to `throw new TypeError(...)`
Error message
Change to `throw new TypeError(...)`
What it means
This is an oxlint diagnostic (a port of ESLint's unicorn/prefer-type-error rule, pedantic category, auto-fixable) fired by the Oxc linter, not a runtime exception. It reports a `throw new Error(...)` that is the sole statement of an if-block whose test is a type check, such as `typeof x`, `x instanceof Y`, `Array.isArray(x)`, `Number.isFinite(x)`, or lodash-style `_.isString(x)` guards. The rule's rationale is that when a value has the wrong type, `TypeError` is the semantically precise constructor (matching how built-in engines reject bad argument types) and lets consumers distinguish type mismatches from generic failures. It ships an automatic fix that rewrites the callee `Error` to `TypeError`, leaving the message and arguments untouched.
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 b79db0c08f)
Solutions
- Run `oxlint --fix` (or enable the fix in your editor): the rule registers an automatic fix that replaces only the callee span, turning `new Error(...)` into `new TypeError(...)` with the message preserved.
- Manually change the flagged statement: inside a type-checking if-guard, replace `throw new Error(msg)` with `throw new TypeError(msg)`; semantics are unchanged except the constructor name and `err.name === 'TypeError'`.
- If the generic Error is deliberate (e.g., the condition is not really a type check), suppress it inline with `// oxlint-disable-next-line unicorn/prefer-type-error` or disable the rule in `.oxlintrc.json` under `rules`: {"unicorn/prefer-type-error": "off"}.
- Refactor repeated guards into assertion helpers (e.g., TypeScript `asserts value is X` functions or node:assert's `assert.typeError`-style helpers) so every type rejection throws `TypeError` by construction.
Example fix
// before
if (!Array.isArray(foo)) {
throw new Error('Expected foo to be an array');
}
// after (oxlint --fix rewrites only the callee)
if (!Array.isArray(foo)) {
throw new TypeError('Expected foo to be an array');
} Defensive patterns
Strategy: type-guard
Validate before calling
// Run oxlint with this rule before committing so violations never reach CI:
// .husky/pre-commit (or lint-staged config)
// npx oxlint --fix --rules unicorn/prefer-type-error=error "${@}" Type guard
// Centralize the pattern so it is correct by construction:
function assertArray(value: unknown, name = 'value'): asserts value is unknown[] {
if (!Array.isArray(value)) {
throw new TypeError(`Expected ${name} to be an array, got ${typeof value}`);
}
}
function assertString(value: unknown, name = 'value'): asserts value is string {
if (typeof value !== 'string') {
throw new TypeError(`Expected ${name} to be a string, got ${typeof value}`);
}
} Try / catch
// Because guards now throw TypeError, consumers can branch on the constructor:
try {
process(input);
} catch (err) {
if (err instanceof TypeError) {
// caller passed the wrong type — bad input, not a bug in this module
return badRequest(err.message);
}
throw err; // unknown errors keep propagating
} Prevention
- Adopt the convention: wrong argument/value type -> `TypeError`, invariant/program bug -> `Error`, and review throws against it.
- Wrap common checks (isArray/isString/instanceof) in shared assertion helpers so nobody hand-writes `throw new Error` in guards.
- Keep oxlint (or eslint-plugin-unicorn) in pre-commit hooks with `--fix` so this rule auto-corrects before code lands.
- When writing a guard clause, ask 'is this if-test a type check?' — if yes, default to `TypeError` immediately.
- In TypeScript, prefer `asserts value is X` functions over manual if/throw guards.
When it happens
Trigger: The rule `run` (crates/oxc_linter/src/rules/unicorn/prefer_type_error.rs:56) fires only when ALL of these hold: (1) the node is a ThrowStatement whose argument, after stripping parentheses, is a NewExpression whose callee is exactly the identifier `Error`; (2) the throw is the only statement in its enclosing block (block_stmt.body.len() == 1); (3) that block is the direct consequent of an IfStatement; (4) the if-test is recognized as type-checking by is_type_checking_expr: `typeof` unary, `instanceof` binary, member calls whose property name is in TYPE_CHECKING_IDENTIFIERS (isArray, isString, isElement, isPlainObject, isNaN, isFinite, isFunction, ~35 names) with at least one argument, bare globals `isFinite`/`isNaN` called with arguments, `!` of a type-checking expr, and `&&`/`||`/comparison combinations where every operand is type-checking. Concrete triggers from the rule's own tests: `if (Array.isArray(foo)) { throw new Error('foo is an Array'); }`, `if (foo instanceof bar) { throw new Error(foobar); }`, `if (_.isElement(foo)) { throw new Error; }`, `if (typeof foo == 'Foo') { throw new Error(); }`, `if (!isFinite(foo)) { throw new Error(); }`, `if (isNaN(foo) === false) { throw new Error(); }`. It does NOT fire for `throw new CustomError()`, `new foo.Error()`, `Error.foo()`, bare `throw new Error('...')` outside an if, extra statements before the throw, `if (Array.isArray())` with zero args, or tests mixing in non-type conditions like `foo.bar() === false`.
Common situations: Teams enabling the `unicorn` plugin or the `pedantic` category in `.oxlintrc.json` on a legacy codebase full of guard clauses that throw generic `Error`. Developers migrating from ESLint (eslint-plugin-unicorn) to oxlint who see the same rule reappear in CI. Pre-commit hooks or CI lint gates failing the build after the rule lands (it has existed in oxlint since version 0.0.16). Code reviewed against built-in conventions where `Array.isArray`/`instanceof` argument validation throwing `Error` reads inconsistent. Note oxlint fires this purely on syntax, so a same-named userland function like `wrapper.ary.isArray(foo)` is still treated as a type check.
AI-assisted analysis of oxc-project/oxc@b79db0c08f (2026-08-16).
Data as JSON: /api/errors/3b50d8a3981cd6de.
Report an issue: GitHub.