{"record":{"id":"3b50d8a3981cd6de","repo":"oxc-project/oxc","slug":"change-to-throw-new-typeerror","errorCode":null,"errorMessage":"Change to `throw new TypeError(...)`","messagePattern":"Change to `throw new TypeError\\(\\.\\.\\.\\)`","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/oxc_linter/src/rules/unicorn/prefer_type_error.rs","lineNumber":16,"sourceCode":"use oxc_ast::{\n    AstKind,\n    ast::{CallExpression, Expression, MemberExpression, match_member_expression},\n};\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_macros::declare_oxc_lint;\nuse oxc_span::{GetSpan, Span};\nuse oxc_syntax::operator::{BinaryOperator, UnaryOperator};\n\nuse crate::{AstNode, context::LintContext, rule::Rule};\n\nfn prefer_type_error_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\n        \"Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement\",\n    )\n    .with_help(\"Change to `throw new TypeError(...)`\")\n    .with_label(span)\n}\n\n#[derive(Debug, Default, Clone)]\npub struct PreferTypeError;\n\ndeclare_oxc_lint!(\n    /// ### What it does\n    ///\n    /// Enforce throwing a `TypeError` instead of a generic `Error` after a type checking if-statement.\n    ///\n    /// ### Why is this bad?\n    ///\n    /// Throwing a `TypeError` instead of a generic `Error` after a type checking if-statement is more specific and helps to catch bugs.\n    ///\n    /// ### Examples\n    ///\n    /// Examples of **incorrect** code for this rule:","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/oxc-project/oxc/blob/b79db0c08f127179073fc2ec517fcff422fe1893/crates/oxc_linter/src/rules/unicorn/prefer_type_error.rs#L1-L34","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","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."],"exampleFix":"// before\nif (!Array.isArray(foo)) {\n    throw new Error('Expected foo to be an array');\n}\n\n// after (oxlint --fix rewrites only the callee)\nif (!Array.isArray(foo)) {\n    throw new TypeError('Expected foo to be an array');\n}","handlingStrategy":"type-guard","validationCode":"// Run oxlint with this rule before committing so violations never reach CI:\n// .husky/pre-commit  (or lint-staged config)\n//   npx oxlint --fix --rules unicorn/prefer-type-error=error \"${@}\"","typeGuard":"// Centralize the pattern so it is correct by construction:\nfunction assertArray(value: unknown, name = 'value'): asserts value is unknown[] {\n  if (!Array.isArray(value)) {\n    throw new TypeError(`Expected ${name} to be an array, got ${typeof value}`);\n  }\n}\n\nfunction assertString(value: unknown, name = 'value'): asserts value is string {\n  if (typeof value !== 'string') {\n    throw new TypeError(`Expected ${name} to be a string, got ${typeof value}`);\n  }\n}","tryCatchPattern":"// Because guards now throw TypeError, consumers can branch on the constructor:\ntry {\n  process(input);\n} catch (err) {\n  if (err instanceof TypeError) {\n    // caller passed the wrong type — bad input, not a bug in this module\n    return badRequest(err.message);\n  }\n  throw err; // unknown errors keep propagating\n}","preventionTips":["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."],"tags":["lint","oxlint","unicorn","javascript","error-handling","style","autofix","type-checking"],"backgroundTag":"typeerror-vs-error","analyzedSha":"b79db0c08f127179073fc2ec517fcff422fe1893","analyzedAt":"2026-08-16T23:05:27.737Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}