Skip to content

Commit 0dfe8b3

Browse files
committed
feat(linter): Split jest/require-to-throw-message into Jest and Vitest rules. (#21879)
Generated with Claude Code, reviewed by me. Part of #21664.
1 parent 51229ff commit 0dfe8b3

10 files changed

Lines changed: 283 additions & 93 deletions

File tree

crates/oxc_linter/data/vitest_compatible_jest_rules.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,5 @@
1818
"prefer-to-be",
1919
"prefer-to-have-been-called-times",
2020
"prefer-to-have-length",
21-
"require-hook",
22-
"require-to-throw-message"
21+
"require-hook"
2322
]

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_linter/src/generated/rules_enum.rs

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_linter/src/rules.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,7 @@ pub(crate) mod vitest {
766766
pub mod require_local_test_context_for_concurrent_snapshots;
767767
pub mod require_mock_type_parameters;
768768
pub mod require_test_timeout;
769+
pub mod require_to_throw_message;
769770
pub mod require_top_level_describe;
770771
pub mod valid_describe_callback;
771772
pub mod valid_expect;

crates/oxc_linter/src/rules/jest/require_to_throw_message.rs

Lines changed: 4 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,20 @@
1-
use oxc_ast::AstKind;
2-
use oxc_diagnostics::OxcDiagnostic;
31
use oxc_macros::declare_oxc_lint;
4-
use oxc_span::Span;
52

63
use crate::{
74
context::LintContext,
85
rule::Rule,
9-
utils::{PossibleJestNode, parse_expect_jest_fn_call},
6+
rules::shared::require_to_throw_message::{DOCUMENTATION, run_on_jest_node},
7+
utils::PossibleJestNode,
108
};
119

12-
fn require_to_throw_message_diagnostic(matcher_name: &str, span: Span) -> OxcDiagnostic {
13-
OxcDiagnostic::warn(format!("Require a message for {matcher_name:?}."))
14-
.with_help(format!("Add an error message to {matcher_name:?}"))
15-
.with_label(span)
16-
}
17-
1810
#[derive(Debug, Default, Clone)]
1911
pub struct RequireToThrowMessage;
2012

2113
declare_oxc_lint!(
22-
/// ### What it does
23-
///
24-
/// This rule triggers a warning if `toThrow()` or `toThrowError()` is used without an error message.
25-
///
26-
/// ### Why is this bad?
27-
///
28-
/// Using `toThrow()` or `toThrowError()` without specifying an expected error message
29-
/// makes tests less specific and harder to debug. When a test only checks that an
30-
/// error was thrown but not what kind of error, it can pass even when the wrong
31-
/// error is thrown, potentially hiding bugs. Providing an expected error message
32-
/// or error type makes tests more precise and helps catch regressions more effectively.
33-
///
34-
/// ### Examples
35-
///
36-
/// Examples of **incorrect** code for this rule:
37-
/// ```javascript
38-
/// test('all the things', async () => {
39-
/// expect(() => a()).toThrow();
40-
/// expect(() => a()).toThrowError();
41-
/// await expect(a()).rejects.toThrow();
42-
/// await expect(a()).rejects.toThrowError();
43-
/// });
44-
/// ```
45-
///
46-
/// Examples of **correct** code for this rule:
47-
/// ```javascript
48-
/// test('all the things', async () => {
49-
/// expect(() => a()).toThrow('a');
50-
/// expect(() => a()).toThrowError('a');
51-
/// await expect(a()).rejects.toThrow('a');
52-
/// await expect(a()).rejects.toThrowError('a');
53-
/// });
54-
/// ```
55-
///
56-
/// This rule is compatible with [eslint-plugin-vitest](https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/require-to-throw-message.md),
57-
/// to use it, add the following configuration to your `.oxlintrc.json`:
58-
///
59-
/// ```json
60-
/// {
61-
/// "rules": {
62-
/// "vitest/require-to-throw-message": "error"
63-
/// }
64-
/// }
65-
/// ```
6614
RequireToThrowMessage,
6715
jest,
6816
correctness,
17+
docs = DOCUMENTATION,
6918
version = "0.2.9",
7019
);
7120

@@ -75,47 +24,14 @@ impl Rule for RequireToThrowMessage {
7524
jest_node: &PossibleJestNode<'a, 'c>,
7625
ctx: &'c LintContext<'a>,
7726
) {
78-
Self::run(jest_node, ctx);
79-
}
80-
}
81-
82-
impl RequireToThrowMessage {
83-
pub fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) {
84-
let node = possible_jest_node.node;
85-
let AstKind::CallExpression(call_expr) = node.kind() else {
86-
return;
87-
};
88-
89-
let Some(jest_fn_call) = parse_expect_jest_fn_call(call_expr, possible_jest_node, ctx)
90-
else {
91-
return;
92-
};
93-
94-
let Some(matcher) = jest_fn_call.matcher() else {
95-
return;
96-
};
97-
98-
let Some(matcher_name) = matcher.name() else {
99-
return;
100-
};
101-
102-
let has_not = jest_fn_call.modifiers().iter().any(|modifier| modifier.is_name_equal("not"));
103-
104-
if jest_fn_call.args.is_empty()
105-
&& (matcher_name == "toThrow" || matcher_name == "toThrowError")
106-
&& !has_not
107-
{
108-
ctx.diagnostic(require_to_throw_message_diagnostic(&matcher_name, matcher.span));
109-
}
27+
run_on_jest_node(jest_node, ctx);
11028
}
11129
}
11230

11331
#[test]
11432
fn test() {
11533
use crate::tester::Tester;
11634

117-
// Note: Both Jest and Vitest share the same unit tests
118-
11935
let pass = vec![
12036
// String
12137
("expect(() => { throw new Error('a'); }).toThrow('a');", None),

crates/oxc_linter/src/rules/shared/jest_vitest/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod no_standalone_expect;
2020
pub mod prefer_expect_assertions;
2121
pub mod prefer_to_contain;
2222
pub mod prefer_todo;
23+
pub mod require_to_throw_message;
2324
pub mod require_top_level_describe;
2425
pub mod valid_describe_callback;
2526
pub mod valid_expect;

0 commit comments

Comments
 (0)