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

  1. 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.
  2. 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'`.
  3. 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"}.
  4. 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

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.