From eec9fd433a9f642c3fe1e4c03760ecc9f7bb17c2 Mon Sep 17 00:00:00 2001 From: Wenzhe Wang Date: Sun, 17 Sep 2023 20:11:52 +0800 Subject: [PATCH] feat(linter): add eslint(jest/no-mocks-import) (#924) --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/jest/no_mocks_import.rs | 116 ++++++++++++++++++ .../src/snapshots/no_mocks_import.snap | 54 ++++++++ 3 files changed, 172 insertions(+) create mode 100644 crates/oxc_linter/src/rules/jest/no_mocks_import.rs create mode 100644 crates/oxc_linter/src/snapshots/no_mocks_import.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 72a19492e..7dbeeabc5 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -110,6 +110,7 @@ mod jest { pub mod no_focused_tests; pub mod no_interpolation_in_snapshots; pub mod no_jasmine_globals; + pub mod no_mocks_import; pub mod no_test_prefixes; pub mod valid_describe_callback; } @@ -210,6 +211,7 @@ oxc_macros::declare_all_lint_rules! { jest::no_done_callback, jest::no_interpolation_in_snapshots, jest::no_jasmine_globals, + jest::no_mocks_import, unicorn::no_instanceof_array, unicorn::no_unnecessary_await, unicorn::no_thenable, diff --git a/crates/oxc_linter/src/rules/jest/no_mocks_import.rs b/crates/oxc_linter/src/rules/jest/no_mocks_import.rs new file mode 100644 index 000000000..5ea64faf0 --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/no_mocks_import.rs @@ -0,0 +1,116 @@ +use std::path::PathBuf; + +use oxc_ast::{ + ast::{Argument, Expression}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory.")] +#[diagnostic( + severity(warning), + help("Instead use `jest.mock` and import from the original module path.") +)] +struct NoMocksImportDiagnostic(#[label] pub Span); + +/// +#[derive(Debug, Default, Clone)] +pub struct NoMocksImport; + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule reports imports from a path containing a __mocks__ component. + /// + /// ### Example + /// ```javascript + /// import thing from './__mocks__/index'; + /// require('./__mocks__/index'); + /// require('__mocks__'); + /// + NoMocksImport, + restriction +); + +impl Rule for NoMocksImport { + fn run_once(&self, ctx: &LintContext) { + let module_records = ctx.semantic().module_record(); + + for import_entry in &module_records.import_entries { + let module_specifier = import_entry.module_request.name().as_str(); + if contains_mocks_dir(module_specifier) { + ctx.diagnostic(NoMocksImportDiagnostic(import_entry.module_request.span())); + } + } + + let Some(require_reference_ids) = ctx.scopes().root_unresolved_references().get("require") + else { + return; + }; + + for reference_id in require_reference_ids { + let reference = ctx.symbols().get_reference(*reference_id); + let Some(parent) = ctx.nodes().parent_node(reference.node_id()) else { + return; + }; + let AstKind::CallExpression(call_expr) = parent.kind() else { + return; + }; + + let Some(Argument::Expression(Expression::StringLiteral(string_literal))) = + call_expr.arguments.get(0) + else { + return; + }; + + if contains_mocks_dir(&string_literal.value) { + ctx.diagnostic(NoMocksImportDiagnostic(string_literal.span)); + } + } + } +} + +fn contains_mocks_dir(value: &str) -> bool { + PathBuf::from(value).components().any(|c| match c { + std::path::Component::Normal(p) => p == std::ffi::OsStr::new("__mocks__"), + _ => false, + }) +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("import something from 'something'", None), + ("require('somethingElse')", None), + ("require('./__mocks__.js')", None), + ("require('./__mocks__x')", None), + ("require('./__mocks__x/x')", None), + ("require('./x__mocks__')", None), + ("require('./x__mocks__/x')", None), + ("require()", None), + ("var path = './__mocks__.js'; require(path)", None), + ("entirelyDifferent(fn)", None), + ]; + + let fail = vec![ + ("require('./__mocks__')", None), + ("require('./__mocks__/')", None), + ("require('./__mocks__/index')", None), + ("require('__mocks__')", None), + ("require('__mocks__/')", None), + ("require('__mocks__/index')", None), + ("import thing from './__mocks__/index'", None), + ]; + + Tester::new(NoMocksImport::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_mocks_import.snap b/crates/oxc_linter/src/snapshots/no_mocks_import.snap new file mode 100644 index 000000000..b68e94345 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_mocks_import.snap @@ -0,0 +1,54 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_mocks_import +--- + ⚠ eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory. + ╭─[no_mocks_import.tsx:1:1] + 1 │ require('./__mocks__') + · ───────────── + ╰──── + help: Instead use `jest.mock` and import from the original module path. + + ⚠ eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory. + ╭─[no_mocks_import.tsx:1:1] + 1 │ require('./__mocks__/') + · ────────────── + ╰──── + help: Instead use `jest.mock` and import from the original module path. + + ⚠ eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory. + ╭─[no_mocks_import.tsx:1:1] + 1 │ require('./__mocks__/index') + · ─────────────────── + ╰──── + help: Instead use `jest.mock` and import from the original module path. + + ⚠ eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory. + ╭─[no_mocks_import.tsx:1:1] + 1 │ require('__mocks__') + · ─────────── + ╰──── + help: Instead use `jest.mock` and import from the original module path. + + ⚠ eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory. + ╭─[no_mocks_import.tsx:1:1] + 1 │ require('__mocks__/') + · ──────────── + ╰──── + help: Instead use `jest.mock` and import from the original module path. + + ⚠ eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory. + ╭─[no_mocks_import.tsx:1:1] + 1 │ require('__mocks__/index') + · ───────────────── + ╰──── + help: Instead use `jest.mock` and import from the original module path. + + ⚠ eslint(jest/no-mocks-import): Mocks should not be manually imported from a `__mocks__` directory. + ╭─[no_mocks_import.tsx:1:1] + 1 │ import thing from './__mocks__/index' + · ─────────────────── + ╰──── + help: Instead use `jest.mock` and import from the original module path. + +