-
-
Notifications
You must be signed in to change notification settings - Fork 509
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter/eslint-plugin-vitest): implement require-local-test-conte…
…xt-for-concurrent-snapshots (#4951) Related to #4656 --------- Co-authored-by: Wang Wenzhe <[email protected]>
- Loading branch information
1 parent
8d3f61b
commit ed9a1c4
Showing
3 changed files
with
259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
crates/oxc_linter/src/rules/vitest/require_local_test_context_for_concurrent_snapshots.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
use oxc_ast::{ast::MemberExpression, AstKind}; | ||
use oxc_diagnostics::OxcDiagnostic; | ||
use oxc_macros::declare_oxc_lint; | ||
use oxc_span::{GetSpan, Span}; | ||
|
||
use crate::{ | ||
context::LintContext, | ||
rule::Rule, | ||
utils::{ | ||
collect_possible_jest_call_node, is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, | ||
PossibleJestNode, | ||
}, | ||
}; | ||
|
||
#[inline] | ||
fn is_snapshot_method(property_name: &str) -> bool { | ||
matches!( | ||
property_name, | ||
"toMatchSnapshot" | ||
| "toMatchInlineSnapshot" | ||
| "toMatchFileSnapshot" | ||
| "toThrowErrorMatchingSnapshot" | ||
| "toThrowErrorMatchingInlineSnapshot" | ||
) | ||
} | ||
|
||
#[inline] | ||
fn is_test_or_describe_node(member_expr: &MemberExpression) -> bool { | ||
if let Some(id) = member_expr.object().get_identifier_reference() { | ||
if matches!( | ||
JestFnKind::from(id.name.as_str()), | ||
JestFnKind::General(JestGeneralFnKind::Describe | JestGeneralFnKind::Test) | ||
) { | ||
if let Some(property_name) = member_expr.static_property_name() { | ||
return property_name == "concurrent"; | ||
} | ||
} | ||
} | ||
false | ||
} | ||
|
||
fn require_local_test_context_for_concurrent_snapshots_diagnostic(span0: Span) -> OxcDiagnostic { | ||
OxcDiagnostic::warn("Require local Test Context for concurrent snapshot tests") | ||
.with_help("Use local Test Context instead") | ||
.with_label(span0) | ||
} | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct RequireLocalTestContextForConcurrentSnapshots; | ||
|
||
declare_oxc_lint!( | ||
/// ### What it does | ||
/// The rule is intended to ensure that concurrent snapshot tests are executed within a properly configured local test context. | ||
/// | ||
/// ### Examples | ||
/// | ||
/// Examples of **incorrect** code for this rule: | ||
/// ```js | ||
/// test.concurrent('myLogic', () => { | ||
/// expect(true).toMatchSnapshot(); | ||
/// }) | ||
/// | ||
/// describe.concurrent('something', () => { | ||
/// test('myLogic', () => { | ||
/// expect(true).toMatchInlineSnapshot(); | ||
/// }) | ||
/// }) | ||
/// | ||
/// ``` | ||
/// | ||
/// Examples of **correct** code for this rule: | ||
/// ```js | ||
/// test.concurrent('myLogic', ({ expect }) => { | ||
/// expect(true).toMatchSnapshot(); | ||
/// }) | ||
/// | ||
/// test.concurrent('myLogic', (context) => { | ||
/// context.expect(true).toMatchSnapshot(); | ||
/// }) | ||
/// ``` | ||
RequireLocalTestContextForConcurrentSnapshots, | ||
correctness | ||
); | ||
|
||
impl Rule for RequireLocalTestContextForConcurrentSnapshots { | ||
fn run_once(&self, ctx: &LintContext) { | ||
for possible_jest_node in &collect_possible_jest_call_node(ctx) { | ||
Self::run(possible_jest_node, ctx); | ||
} | ||
} | ||
} | ||
|
||
impl RequireLocalTestContextForConcurrentSnapshots { | ||
fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) { | ||
let node = possible_jest_node.node; | ||
if let AstKind::CallExpression(call_expr) = node.kind() { | ||
if !is_type_of_jest_fn_call(call_expr, possible_jest_node, ctx, &[JestFnKind::Expect]) { | ||
return; | ||
} | ||
|
||
let Some(member_expr) = call_expr.callee.as_member_expression() else { return }; | ||
|
||
let Some(property_name) = member_expr.static_property_name() else { return }; | ||
|
||
if !is_snapshot_method(property_name) { | ||
return; | ||
} | ||
|
||
let test_or_describe_node_found = | ||
ctx.nodes().iter_parents(possible_jest_node.node.id()).any(|node| { | ||
if let AstKind::CallExpression(ancestor_call_expr) = node.kind() { | ||
if let Some(ancestor_member_expr) = | ||
ancestor_call_expr.callee.as_member_expression() | ||
{ | ||
return is_test_or_describe_node(ancestor_member_expr); | ||
} | ||
} | ||
|
||
false | ||
}); | ||
|
||
if test_or_describe_node_found { | ||
ctx.diagnostic(require_local_test_context_for_concurrent_snapshots_diagnostic( | ||
call_expr.span(), | ||
)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn test() { | ||
use crate::tester::Tester; | ||
|
||
let pass = vec![ | ||
r#"it("something", () => { expect(true).toBe(true) })"#, | ||
r#"it.concurrent("something", () => { expect(true).toBe(true) })"#, | ||
r#"it("something", () => { expect(1).toMatchSnapshot() })"#, | ||
r#"it.concurrent("something", ({ expect }) => { expect(1).toMatchSnapshot() })"#, | ||
r#"it.concurrent("something", ({ expect }) => { expect(1).toMatchInlineSnapshot("1") })"#, | ||
r#"describe.concurrent("something", () => { it("something", () => { expect(true).toBe(true) }) })"#, | ||
r#"describe.concurrent("something", () => { it("something", ({ expect }) => { expect(1).toMatchSnapshot() }) })"#, | ||
r#"describe.concurrent("something", () => { it("something", ({ expect }) => { expect(1).toMatchInlineSnapshot() }) })"#, | ||
r#"describe("something", () => { it("something", (context) => { context.expect(1).toMatchInlineSnapshot() }) })"#, | ||
r#"describe("something", () => { it("something", (context) => { expect(1).toMatchInlineSnapshot() }) })"#, | ||
r#"it.concurrent("something", (context) => { context.expect(1).toMatchSnapshot() })"#, | ||
]; | ||
|
||
let fail = vec![ | ||
r#"it.concurrent("should fail", () => { expect(true).toMatchSnapshot() })"#, | ||
r#"it.concurrent("should fail", () => { expect(true).toMatchInlineSnapshot("true") })"#, | ||
r#"describe.concurrent("failing", () => { it("should fail", () => { expect(true).toMatchSnapshot() }) })"#, | ||
r#"describe.concurrent("failing", () => { it("should fail", () => { expect(true).toMatchInlineSnapshot("true") }) })"#, | ||
r#"it.concurrent("something", (context) => { expect(true).toMatchSnapshot() })"#, | ||
r#"it.concurrent("something", () => { | ||
expect(true).toMatchSnapshot(); | ||
expect(true).toMatchSnapshot(); | ||
})"#, | ||
r#"it.concurrent("something", () => { | ||
expect(true).toBe(true); | ||
expect(true).toMatchSnapshot(); | ||
})"#, | ||
r#"it.concurrent("should fail", () => { expect(true).toMatchFileSnapshot("./test/basic.output.html") })"#, | ||
r#"it.concurrent("should fail", () => { expect(foo()).toThrowErrorMatchingSnapshot() })"#, | ||
r#"it.concurrent("should fail", () => { expect(foo()).toThrowErrorMatchingInlineSnapshot("bar") })"#, | ||
]; | ||
|
||
Tester::new(RequireLocalTestContextForConcurrentSnapshots::NAME, pass, fail) | ||
.test_and_snapshot(); | ||
} |
85 changes: 85 additions & 0 deletions
85
crates/oxc_linter/src/snapshots/require_local_test_context_for_concurrent_snapshots.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
--- | ||
source: crates/oxc_linter/src/tester.rs | ||
--- | ||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:38] | ||
1 │ it.concurrent("should fail", () => { expect(true).toMatchSnapshot() }) | ||
· ────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:38] | ||
1 │ it.concurrent("should fail", () => { expect(true).toMatchInlineSnapshot("true") }) | ||
· ────────────────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:66] | ||
1 │ describe.concurrent("failing", () => { it("should fail", () => { expect(true).toMatchSnapshot() }) }) | ||
· ────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:66] | ||
1 │ describe.concurrent("failing", () => { it("should fail", () => { expect(true).toMatchInlineSnapshot("true") }) }) | ||
· ────────────────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:43] | ||
1 │ it.concurrent("something", (context) => { expect(true).toMatchSnapshot() }) | ||
· ────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:2:21] | ||
1 │ it.concurrent("something", () => { | ||
2 │ expect(true).toMatchSnapshot(); | ||
· ────────────────────────────── | ||
3 │ | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:4:21] | ||
3 │ | ||
4 │ expect(true).toMatchSnapshot(); | ||
· ────────────────────────────── | ||
5 │ }) | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:4:21] | ||
3 │ | ||
4 │ expect(true).toMatchSnapshot(); | ||
· ────────────────────────────── | ||
5 │ }) | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:38] | ||
1 │ it.concurrent("should fail", () => { expect(true).toMatchFileSnapshot("./test/basic.output.html") }) | ||
· ──────────────────────────────────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:38] | ||
1 │ it.concurrent("should fail", () => { expect(foo()).toThrowErrorMatchingSnapshot() }) | ||
· ──────────────────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead | ||
|
||
⚠ eslint-plugin-vitest(require-local-test-context-for-concurrent-snapshots): Require local Test Context for concurrent snapshot tests | ||
╭─[require_local_test_context_for_concurrent_snapshots.tsx:1:38] | ||
1 │ it.concurrent("should fail", () => { expect(foo()).toThrowErrorMatchingInlineSnapshot("bar") }) | ||
· ─────────────────────────────────────────────────────── | ||
╰──── | ||
help: Use local Test Context instead |