diff --git a/crates/oxc_linter/data/vitest_compatible_jest_rules.json b/crates/oxc_linter/data/vitest_compatible_jest_rules.json index 223818bfae03d..9a580167ec4a0 100644 --- a/crates/oxc_linter/data/vitest_compatible_jest_rules.json +++ b/crates/oxc_linter/data/vitest_compatible_jest_rules.json @@ -31,6 +31,7 @@ "prefer-lowercase-title", "prefer-mock-promise-shorthand", "prefer-mock-return-shorthand", + "prefer-snapshot-hint", "prefer-spy-on", "prefer-strict-equal", "prefer-to-be", diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 0f1e1e46bf055..bba17a7ccf685 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -2273,6 +2273,11 @@ impl RuleRunner for crate::rules::jest::prefer_mock_return_shorthand::PreferMock const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::jest::prefer_snapshot_hint::PreferSnapshotHint { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; +} + impl RuleRunner for crate::rules::jest::prefer_spy_on::PreferSpyOn { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[AstType::AssignmentExpression])); diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index 62c0197ff42b8..302f0f2c209e3 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -252,6 +252,7 @@ pub use crate::rules::jest::prefer_jest_mocked::PreferJestMocked as JestPreferJe pub use crate::rules::jest::prefer_lowercase_title::PreferLowercaseTitle as JestPreferLowercaseTitle; pub use crate::rules::jest::prefer_mock_promise_shorthand::PreferMockPromiseShorthand as JestPreferMockPromiseShorthand; pub use crate::rules::jest::prefer_mock_return_shorthand::PreferMockReturnShorthand as JestPreferMockReturnShorthand; +pub use crate::rules::jest::prefer_snapshot_hint::PreferSnapshotHint as JestPreferSnapshotHint; pub use crate::rules::jest::prefer_spy_on::PreferSpyOn as JestPreferSpyOn; pub use crate::rules::jest::prefer_strict_equal::PreferStrictEqual as JestPreferStrictEqual; pub use crate::rules::jest::prefer_to_be::PreferToBe as JestPreferToBe; @@ -1079,6 +1080,7 @@ pub enum RuleEnum { JestPreferLowercaseTitle(JestPreferLowercaseTitle), JestPreferMockPromiseShorthand(JestPreferMockPromiseShorthand), JestPreferMockReturnShorthand(JestPreferMockReturnShorthand), + JestPreferSnapshotHint(JestPreferSnapshotHint), JestPreferSpyOn(JestPreferSpyOn), JestPreferStrictEqual(JestPreferStrictEqual), JestPreferToBe(JestPreferToBe), @@ -1835,7 +1837,8 @@ const JEST_PREFER_JEST_MOCKED_ID: usize = JEST_PREFER_HOOKS_ON_TOP_ID + 1usize; const JEST_PREFER_LOWERCASE_TITLE_ID: usize = JEST_PREFER_JEST_MOCKED_ID + 1usize; const JEST_PREFER_MOCK_PROMISE_SHORTHAND_ID: usize = JEST_PREFER_LOWERCASE_TITLE_ID + 1usize; const JEST_PREFER_MOCK_RETURN_SHORTHAND_ID: usize = JEST_PREFER_MOCK_PROMISE_SHORTHAND_ID + 1usize; -const JEST_PREFER_SPY_ON_ID: usize = JEST_PREFER_MOCK_RETURN_SHORTHAND_ID + 1usize; +const JEST_PREFER_SNAPSHOT_HINT_ID: usize = JEST_PREFER_MOCK_RETURN_SHORTHAND_ID + 1usize; +const JEST_PREFER_SPY_ON_ID: usize = JEST_PREFER_SNAPSHOT_HINT_ID + 1usize; const JEST_PREFER_STRICT_EQUAL_ID: usize = JEST_PREFER_SPY_ON_ID + 1usize; const JEST_PREFER_TO_BE_ID: usize = JEST_PREFER_STRICT_EQUAL_ID + 1usize; const JEST_PREFER_TO_CONTAIN_ID: usize = JEST_PREFER_TO_BE_ID + 1usize; @@ -2653,6 +2656,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(_) => JEST_PREFER_LOWERCASE_TITLE_ID, Self::JestPreferMockPromiseShorthand(_) => JEST_PREFER_MOCK_PROMISE_SHORTHAND_ID, Self::JestPreferMockReturnShorthand(_) => JEST_PREFER_MOCK_RETURN_SHORTHAND_ID, + Self::JestPreferSnapshotHint(_) => JEST_PREFER_SNAPSHOT_HINT_ID, Self::JestPreferSpyOn(_) => JEST_PREFER_SPY_ON_ID, Self::JestPreferStrictEqual(_) => JEST_PREFER_STRICT_EQUAL_ID, Self::JestPreferToBe(_) => JEST_PREFER_TO_BE_ID, @@ -3469,6 +3473,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(_) => JestPreferLowercaseTitle::NAME, Self::JestPreferMockPromiseShorthand(_) => JestPreferMockPromiseShorthand::NAME, Self::JestPreferMockReturnShorthand(_) => JestPreferMockReturnShorthand::NAME, + Self::JestPreferSnapshotHint(_) => JestPreferSnapshotHint::NAME, Self::JestPreferSpyOn(_) => JestPreferSpyOn::NAME, Self::JestPreferStrictEqual(_) => JestPreferStrictEqual::NAME, Self::JestPreferToBe(_) => JestPreferToBe::NAME, @@ -4301,6 +4306,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(_) => JestPreferLowercaseTitle::CATEGORY, Self::JestPreferMockPromiseShorthand(_) => JestPreferMockPromiseShorthand::CATEGORY, Self::JestPreferMockReturnShorthand(_) => JestPreferMockReturnShorthand::CATEGORY, + Self::JestPreferSnapshotHint(_) => JestPreferSnapshotHint::CATEGORY, Self::JestPreferSpyOn(_) => JestPreferSpyOn::CATEGORY, Self::JestPreferStrictEqual(_) => JestPreferStrictEqual::CATEGORY, Self::JestPreferToBe(_) => JestPreferToBe::CATEGORY, @@ -5136,6 +5142,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(_) => JestPreferLowercaseTitle::FIX, Self::JestPreferMockPromiseShorthand(_) => JestPreferMockPromiseShorthand::FIX, Self::JestPreferMockReturnShorthand(_) => JestPreferMockReturnShorthand::FIX, + Self::JestPreferSnapshotHint(_) => JestPreferSnapshotHint::FIX, Self::JestPreferSpyOn(_) => JestPreferSpyOn::FIX, Self::JestPreferStrictEqual(_) => JestPreferStrictEqual::FIX, Self::JestPreferToBe(_) => JestPreferToBe::FIX, @@ -6039,6 +6046,7 @@ impl RuleEnum { Self::JestPreferMockReturnShorthand(_) => { JestPreferMockReturnShorthand::documentation() } + Self::JestPreferSnapshotHint(_) => JestPreferSnapshotHint::documentation(), Self::JestPreferSpyOn(_) => JestPreferSpyOn::documentation(), Self::JestPreferStrictEqual(_) => JestPreferStrictEqual::documentation(), Self::JestPreferToBe(_) => JestPreferToBe::documentation(), @@ -7543,6 +7551,8 @@ impl RuleEnum { JestPreferMockReturnShorthand::config_schema(generator) .or_else(|| JestPreferMockReturnShorthand::schema(generator)) } + Self::JestPreferSnapshotHint(_) => JestPreferSnapshotHint::config_schema(generator) + .or_else(|| JestPreferSnapshotHint::schema(generator)), Self::JestPreferSpyOn(_) => JestPreferSpyOn::config_schema(generator) .or_else(|| JestPreferSpyOn::schema(generator)), Self::JestPreferStrictEqual(_) => JestPreferStrictEqual::config_schema(generator) @@ -8914,6 +8924,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(_) => "jest", Self::JestPreferMockPromiseShorthand(_) => "jest", Self::JestPreferMockReturnShorthand(_) => "jest", + Self::JestPreferSnapshotHint(_) => "jest", Self::JestPreferSpyOn(_) => "jest", Self::JestPreferStrictEqual(_) => "jest", Self::JestPreferToBe(_) => "jest", @@ -10417,6 +10428,9 @@ impl RuleEnum { Self::JestPreferMockReturnShorthand(_) => Ok(Self::JestPreferMockReturnShorthand( JestPreferMockReturnShorthand::from_configuration(value)?, )), + Self::JestPreferSnapshotHint(_) => { + Ok(Self::JestPreferSnapshotHint(JestPreferSnapshotHint::from_configuration(value)?)) + } Self::JestPreferSpyOn(_) => { Ok(Self::JestPreferSpyOn(JestPreferSpyOn::from_configuration(value)?)) } @@ -11907,6 +11921,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(rule) => rule.to_configuration(), Self::JestPreferMockPromiseShorthand(rule) => rule.to_configuration(), Self::JestPreferMockReturnShorthand(rule) => rule.to_configuration(), + Self::JestPreferSnapshotHint(rule) => rule.to_configuration(), Self::JestPreferSpyOn(rule) => rule.to_configuration(), Self::JestPreferStrictEqual(rule) => rule.to_configuration(), Self::JestPreferToBe(rule) => rule.to_configuration(), @@ -12621,6 +12636,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(rule) => rule.run(node, ctx), Self::JestPreferMockPromiseShorthand(rule) => rule.run(node, ctx), Self::JestPreferMockReturnShorthand(rule) => rule.run(node, ctx), + Self::JestPreferSnapshotHint(rule) => rule.run(node, ctx), Self::JestPreferSpyOn(rule) => rule.run(node, ctx), Self::JestPreferStrictEqual(rule) => rule.run(node, ctx), Self::JestPreferToBe(rule) => rule.run(node, ctx), @@ -13333,6 +13349,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(rule) => rule.run_once(ctx), Self::JestPreferMockPromiseShorthand(rule) => rule.run_once(ctx), Self::JestPreferMockReturnShorthand(rule) => rule.run_once(ctx), + Self::JestPreferSnapshotHint(rule) => rule.run_once(ctx), Self::JestPreferSpyOn(rule) => rule.run_once(ctx), Self::JestPreferStrictEqual(rule) => rule.run_once(ctx), Self::JestPreferToBe(rule) => rule.run_once(ctx), @@ -14113,6 +14130,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(rule) => rule.run_on_jest_node(jest_node, ctx), Self::JestPreferMockPromiseShorthand(rule) => rule.run_on_jest_node(jest_node, ctx), Self::JestPreferMockReturnShorthand(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::JestPreferSnapshotHint(rule) => rule.run_on_jest_node(jest_node, ctx), Self::JestPreferSpyOn(rule) => rule.run_on_jest_node(jest_node, ctx), Self::JestPreferStrictEqual(rule) => rule.run_on_jest_node(jest_node, ctx), Self::JestPreferToBe(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -14857,6 +14875,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(rule) => rule.should_run(ctx), Self::JestPreferMockPromiseShorthand(rule) => rule.should_run(ctx), Self::JestPreferMockReturnShorthand(rule) => rule.should_run(ctx), + Self::JestPreferSnapshotHint(rule) => rule.should_run(ctx), Self::JestPreferSpyOn(rule) => rule.should_run(ctx), Self::JestPreferStrictEqual(rule) => rule.should_run(ctx), Self::JestPreferToBe(rule) => rule.should_run(ctx), @@ -15727,6 +15746,7 @@ impl RuleEnum { Self::JestPreferMockReturnShorthand(_) => { JestPreferMockReturnShorthand::IS_TSGOLINT_RULE } + Self::JestPreferSnapshotHint(_) => JestPreferSnapshotHint::IS_TSGOLINT_RULE, Self::JestPreferSpyOn(_) => JestPreferSpyOn::IS_TSGOLINT_RULE, Self::JestPreferStrictEqual(_) => JestPreferStrictEqual::IS_TSGOLINT_RULE, Self::JestPreferToBe(_) => JestPreferToBe::IS_TSGOLINT_RULE, @@ -16686,6 +16706,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(_) => JestPreferLowercaseTitle::HAS_CONFIG, Self::JestPreferMockPromiseShorthand(_) => JestPreferMockPromiseShorthand::HAS_CONFIG, Self::JestPreferMockReturnShorthand(_) => JestPreferMockReturnShorthand::HAS_CONFIG, + Self::JestPreferSnapshotHint(_) => JestPreferSnapshotHint::HAS_CONFIG, Self::JestPreferSpyOn(_) => JestPreferSpyOn::HAS_CONFIG, Self::JestPreferStrictEqual(_) => JestPreferStrictEqual::HAS_CONFIG, Self::JestPreferToBe(_) => JestPreferToBe::HAS_CONFIG, @@ -17468,6 +17489,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(rule) => rule.types_info(), Self::JestPreferMockPromiseShorthand(rule) => rule.types_info(), Self::JestPreferMockReturnShorthand(rule) => rule.types_info(), + Self::JestPreferSnapshotHint(rule) => rule.types_info(), Self::JestPreferSpyOn(rule) => rule.types_info(), Self::JestPreferStrictEqual(rule) => rule.types_info(), Self::JestPreferToBe(rule) => rule.types_info(), @@ -18180,6 +18202,7 @@ impl RuleEnum { Self::JestPreferLowercaseTitle(rule) => rule.run_info(), Self::JestPreferMockPromiseShorthand(rule) => rule.run_info(), Self::JestPreferMockReturnShorthand(rule) => rule.run_info(), + Self::JestPreferSnapshotHint(rule) => rule.run_info(), Self::JestPreferSpyOn(rule) => rule.run_info(), Self::JestPreferStrictEqual(rule) => rule.run_info(), Self::JestPreferToBe(rule) => rule.run_info(), @@ -18978,6 +19001,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::JestPreferLowercaseTitle(JestPreferLowercaseTitle::default()), RuleEnum::JestPreferMockPromiseShorthand(JestPreferMockPromiseShorthand::default()), RuleEnum::JestPreferMockReturnShorthand(JestPreferMockReturnShorthand::default()), + RuleEnum::JestPreferSnapshotHint(JestPreferSnapshotHint::default()), RuleEnum::JestPreferSpyOn(JestPreferSpyOn::default()), RuleEnum::JestPreferStrictEqual(JestPreferStrictEqual::default()), RuleEnum::JestPreferToBe(JestPreferToBe::default()), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 567dc60b53189..9c7f86740803f 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -366,6 +366,7 @@ pub(crate) mod jest { pub mod prefer_lowercase_title; pub mod prefer_mock_promise_shorthand; pub mod prefer_mock_return_shorthand; + pub mod prefer_snapshot_hint; pub mod prefer_spy_on; pub mod prefer_strict_equal; pub mod prefer_to_be; diff --git a/crates/oxc_linter/src/rules/jest/prefer_snapshot_hint.rs b/crates/oxc_linter/src/rules/jest/prefer_snapshot_hint.rs new file mode 100644 index 0000000000000..9775e83b19c71 --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/prefer_snapshot_hint.rs @@ -0,0 +1,1118 @@ +use std::ops::Deref; + +use rustc_hash::FxHashMap; +use schemars::JsonSchema; +use serde::Deserialize; + +use oxc_ast::{ + AstKind, + ast::{CallExpression, Expression}, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::NodeId; +use oxc_span::Span; + +use crate::{ + context::LintContext, + rule::{DefaultRuleConfig, Rule}, + rules::PossibleJestNode, + utils::{ + JestFnKind, JestGeneralFnKind, collect_possible_jest_call_node, parse_expect_jest_fn_call, + }, +}; + +fn snapshot_matcher_too_many_arguments_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("`toMatchSnapshot` takes at most two arguments.") + .with_help("Pass a hint string, or a property matcher object followed by a hint string.") + .with_label(span) +} + +fn snapshot_missing_hint_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Snapshot is missing a hint.") + .with_help("Include a hint string to identify this snapshot in the snapshot file.") + .with_label(span) +} + +fn snapshot_hint_must_be_string_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Snapshot hint must be a string literal.") + .with_help( + "Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second.", + ) + .with_label(span) +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, JsonSchema, Default)] +#[serde(rename_all = "lowercase")] +pub enum SnapshotHintMode { + Always, + #[default] + Multi, +} + +#[derive(Debug, Default, Clone, Deserialize)] +pub struct PreferSnapshotHint(Box); + +impl Deref for PreferSnapshotHint { + type Target = SnapshotHintMode; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Enforces including a hint string with snapshot matchers (toMatchSnapshot and toThrowErrorMatchingSnapshot). + /// + /// ### Why is this bad? + /// + /// Auto-numbered snapshot names are fragile — adding or reordering assertions shifts all subsequent numbers, causing unrelated snapshots to appear changed and obscuring real differences in code review. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule configured as `always`: + /// ```js + /// const snapshotOutput = ({ stdout, stderr }) => { + /// expect(stdout).toMatchSnapshot(); + /// expect(stderr).toMatchSnapshot(); + /// }; + /// + /// describe('cli', () => { + /// describe('--version flag', () => { + /// it('prints the version', async () => { + /// snapshotOutput(await runCli(['--version'])); + /// }); + /// }); + /// + /// describe('--config flag', () => { + /// it('reads the config', async () => { + /// const { stdout, parsedConfig } = await runCli([ + /// '--config', + /// 'jest.config.js', + /// ]); + /// + /// expect(stdout).toMatchSnapshot(); + /// expect(parsedConfig).toMatchSnapshot(); + /// }); + /// + /// it('prints nothing to stderr', async () => { + /// const { stderr } = await runCli(['--config', 'jest.config.js']); + /// + /// expect(stderr).toMatchSnapshot(); + /// }); + /// + /// describe('when the file does not exist', () => { + /// it('throws an error', async () => { + /// await expect( + /// runCli(['--config', 'does-not-exist.js']), + /// ).rejects.toThrowErrorMatchingSnapshot(); + /// }); + /// }); + /// }); + /// }); + /// ``` + /// + /// Examples of **incorrect** code for this rule configured as `multi`: + /// + /// ```js + /// const snapshotOutput = ({ stdout, stderr }) => { + /// expect(stdout).toMatchSnapshot(); + /// expect(stderr).toMatchSnapshot(); + /// }; + /// + /// describe('cli', () => { + /// describe('--version flag', () => { + /// it('prints the version', async () => { + /// snapshotOutput(await runCli(['--version'])); + /// }); + /// }); + /// + /// describe('--config flag', () => { + /// it('reads the config', async () => { + /// const { stdout, parsedConfig } = await runCli([ + /// '--config', + /// 'jest.config.js', + /// ]); + /// + /// expect(stdout).toMatchSnapshot(); + /// expect(parsedConfig).toMatchSnapshot(); + /// }); + /// + /// it('prints nothing to stderr', async () => { + /// const { stderr } = await runCli(['--config', 'jest.config.js']); + /// + /// expect(stderr).toMatchSnapshot(); + /// }); + /// }); + /// }); + /// ``` + /// + /// Examples of **correct** code for this rule configured as `always`: + /// ```js + /// const snapshotOutput = ({ stdout, stderr }, hints) => { + /// expect(stdout).toMatchSnapshot({}, `stdout: ${hints.stdout}`); + /// expect(stderr).toMatchSnapshot({}, `stderr: ${hints.stderr}`); + /// }; + /// + /// describe('cli', () => { + /// describe('--version flag', () => { + /// it('prints the version', async () => { + /// snapshotOutput(await runCli(['--version']), { + /// stdout: 'version string', + /// stderr: 'empty', + /// }); + /// }); + /// }); + /// + /// describe('--config flag', () => { + /// it('reads the config', async () => { + /// const { stdout } = await runCli(['--config', 'jest.config.js']); + /// + /// expect(stdout).toMatchSnapshot({}, 'stdout: config settings'); + /// }); + /// + /// it('prints nothing to stderr', async () => { + /// const { stderr } = await runCli(['--config', 'jest.config.js']); + /// + /// expect(stderr).toMatchInlineSnapshot(); + /// }); + /// + /// describe('when the file does not exist', () => { + /// it('throws an error', async () => { + /// await expect( + /// runCli(['--config', 'does-not-exist.js']), + /// ).rejects.toThrowErrorMatchingSnapshot('stderr: config error'); + /// }); + /// }); + /// }); + /// }); + /// ``` + /// + /// Examples of **correct** code for this rule configured as `multi`: + /// ```js + /// const snapshotOutput = ({ stdout, stderr }, hints) => { + /// expect(stdout).toMatchSnapshot({}, `stdout: ${hints.stdout}`); + /// expect(stderr).toMatchSnapshot({}, `stderr: ${hints.stderr}`); + /// }; + /// + /// describe('cli', () => { + /// describe('--version flag', () => { + /// it('prints the version', async () => { + /// snapshotOutput(await runCli(['--version']), { + /// stdout: 'version string', + /// stderr: 'empty', + /// }); + /// }); + /// }); + /// + /// describe('--config flag', () => { + /// it('reads the config', async () => { + /// const { stdout } = await runCli(['--config', 'jest.config.js']); + /// + /// expect(stdout).toMatchSnapshot(); + /// }); + /// + /// it('prints nothing to stderr', async () => { + /// const { stderr } = await runCli(['--config', 'jest.config.js']); + /// + /// expect(stderr).toMatchInlineSnapshot(); + /// }); + /// + /// describe('when the file does not exist', () => { + /// it('throws an error', async () => { + /// await expect( + /// runCli(['--config', 'does-not-exist.js']), + /// ).rejects.toThrowErrorMatchingSnapshot(); + /// }); + /// }); + /// }); + /// }); + /// ``` + /// + /// This rule is compatible with [eslint-plugin-vitest](https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-snapshot-hint.md), + /// to use it, add the following configuration to your `.oxlintrc.json`: + /// + /// ```json + /// { + /// "rules": { + /// "vitest/prefer-snapshot-hint": "error" + /// } + /// } + /// ``` + PreferSnapshotHint, + jest, + correctness, + config = SnapshotHintMode, +); + +impl Rule for PreferSnapshotHint { + fn from_configuration(value: serde_json::Value) -> Result { + serde_json::from_value::>(value).map(DefaultRuleConfig::into_inner) + } + + fn run_once<'a>(&self, ctx: &LintContext<'a>) { + let mut scoped_expects: FxHashMap>> = + FxHashMap::default(); + let mut possible_jest_nodes = collect_possible_jest_call_node(ctx); + possible_jest_nodes.sort_unstable_by_key(|n| n.node.id()); + + for possible_jest_node in possible_jest_nodes { + Self::check_node(&possible_jest_node, &mut scoped_expects, ctx); + } + + for call_expects in scoped_expects.values() { + self.check_expects(call_expects, ctx); + } + } +} + +fn is_test_node(ancestor_call_expr: &CallExpression<'_>) -> bool { + let Some(id) = ancestor_call_expr.callee_name() else { + return false; + }; + + matches!(JestFnKind::from(id), JestFnKind::General(JestGeneralFnKind::Test)) +} + +fn get_scope_owner(node_id: NodeId, ctx: &LintContext<'_>) -> NodeId { + let mut last_function_id = None; + + for ancestor in ctx.nodes().ancestors(node_id) { + match ancestor.kind() { + AstKind::ArrowFunctionExpression(_) | AstKind::Function(_) => { + last_function_id = Some(ancestor.id()); + + let parent = ctx.nodes().parent_node(ancestor.id()); + if let AstKind::CallExpression(call) = parent.kind() + && is_test_node(call) + { + return ancestor.id(); + } + } + AstKind::Program(_) => { + return last_function_id.unwrap_or(ancestor.id()); + } + _ => {} + } + } + + last_function_id.unwrap_or(NodeId::ROOT) +} + +impl PreferSnapshotHint { + fn check_node<'a>( + possible_jest_node: &PossibleJestNode<'a, '_>, + scoped_expects: &mut FxHashMap>>, + ctx: &LintContext<'a>, + ) { + let node = possible_jest_node.node; + + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + let Some(expect_fn) = parse_expect_jest_fn_call(call_expr, possible_jest_node, ctx) else { + return; + }; + + if !expect_fn.members.iter().any(|member| { + member.is_name_equal("toMatchSnapshot") + || member.is_name_equal("toThrowErrorMatchingSnapshot") + }) { + return; + } + + let scope_owner = get_scope_owner(node.id(), ctx); + + if let Some(expects) = scoped_expects.get_mut(&scope_owner) { + expects.push(call_expr); + } else { + scoped_expects.insert(scope_owner, vec![call_expr]); + } + } + + fn check_expects<'a>( + &self, + scoped_expects: &Vec<&'a CallExpression<'a>>, + ctx: &LintContext<'a>, + ) { + if matches!(**self, SnapshotHintMode::Multi) && scoped_expects.len() < 2 { + return; + } + + for expect_call_expr in scoped_expects { + if expect_call_expr.arguments.len() == 2 { + continue; + } + + let Some(callee_name) = expect_call_expr.callee_name() else { + continue; + }; + + if expect_call_expr.arguments.is_empty() { + ctx.diagnostic(snapshot_missing_hint_diagnostic(expect_call_expr.span)); + continue; + } + + if callee_name == "toMatchSnapshot" && expect_call_expr.arguments.len() > 2 { + ctx.diagnostic(snapshot_matcher_too_many_arguments_diagnostic( + expect_call_expr.span, + )); + continue; + } + + let Some(first_arg) = expect_call_expr.arguments.first() else { + continue; + }; + + if !first_arg.as_expression().is_some_and(Expression::is_string_literal) { + ctx.diagnostic(snapshot_hint_must_be_string_diagnostic(expect_call_expr.span)); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let mut pass = vec![ + ("expect(something).toStrictEqual(somethingElse);", Some(serde_json::json!(["always"]))), + ("a().toEqual('b')", Some(serde_json::json!(["always"]))), + ("expect(a);", Some(serde_json::json!(["always"]))), + (r#"expect(1).toMatchSnapshot({}, "my snapshot");"#, Some(serde_json::json!(["always"]))), + (r#"expect(1).toMatchSnapshot("my snapshot");"#, Some(serde_json::json!(["always"]))), + ("expect(1).toMatchSnapshot(`my snapshot`);", Some(serde_json::json!(["always"]))), + ( + r#"const x = {}; + expect(1).toMatchSnapshot(x, "my snapshot");"#, + Some(serde_json::json!(["always"])), + ), + ( + r#"expect(1).toThrowErrorMatchingSnapshot("my snapshot");"#, + Some(serde_json::json!(["always"])), + ), + ("expect(1).toMatchInlineSnapshot();", Some(serde_json::json!(["always"]))), + ("expect(1).toThrowErrorMatchingInlineSnapshot();", Some(serde_json::json!(["always"]))), + ("expect(something).toStrictEqual(somethingElse);", Some(serde_json::json!(["multi"]))), + ("a().toEqual('b')", Some(serde_json::json!(["multi"]))), + ("expect(a);", Some(serde_json::json!(["multi"]))), + (r#"expect(1).toMatchSnapshot({}, "my snapshot");"#, Some(serde_json::json!(["multi"]))), + ( + r#"expect(1).toThrowErrorMatchingSnapshot("my snapshot");"#, + Some(serde_json::json!(["multi"])), + ), + ("expect(1).toMatchSnapshot({});", Some(serde_json::json!(["multi"]))), + ("expect(1).toThrowErrorMatchingSnapshot();", Some(serde_json::json!(["multi"]))), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(undefined, 'my first snapshot'); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot('this is a hint, all by itself'); + }); + it('is false', () => { + expect(2).toMatchSnapshot('this is a hint'); + expect(2).toMatchSnapshot('and so is this'); + }); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + }); + it('is false', () => { + expect(2).toMatchSnapshot('this is a hint'); + expect(2).toMatchSnapshot('and so is this'); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + }); + it('is false', () => { + expect(2).toThrowErrorMatchingSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toStrictEqual(1); + expect(1).toStrictEqual(2); + expect(1).toMatchSnapshot(); + }); + it('is false', () => { + expect(1).toStrictEqual(1); + expect(1).toStrictEqual(2); + expect(2).toThrowErrorMatchingSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchInlineSnapshot(); + }); + it('is false', () => { + expect(1).toMatchInlineSnapshot(); + expect(1).toMatchInlineSnapshot(); + expect(1).toThrowErrorMatchingInlineSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + }); + it('is false', () => { + expect(1).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "import { it as itIs } from '@jest/globals'; + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + itIs('false', () => { + expect(1).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + expect(value).toBe(1); + }; + expect(value).toBe(1); + }; + it('my test', () => { + expect(1).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(value).toBe(1); + }; + expect(value).toBe(1); + expect(anotherValue).toMatchSnapshot(); + }; + it('my test', () => { + expect(1).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + expect(value).toBe(1); + }; + expect(value).toBe(1); + }; + expect(1).toMatchSnapshot();", + Some(serde_json::json!(["multi"])), + ), + ]; + + let mut fail = vec![ + ("expect(1).toMatchSnapshot();", Some(serde_json::json!(["always"]))), + ("expect(1).toMatchSnapshot({});", Some(serde_json::json!(["always"]))), + ( + r#"const x = "we can't know if this is a string or not"; + expect(1).toMatchSnapshot(x);"#, + Some(serde_json::json!(["always"])), + ), + ("expect(1).toThrowErrorMatchingSnapshot();", Some(serde_json::json!(["always"]))), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + });", + Some(serde_json::json!(["always"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(); + });", + Some(serde_json::json!(["always"])), + ), + ( + r#"it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toThrowErrorMatchingSnapshot("my error"); + });"#, + Some(serde_json::json!(["always"])), + ), + ( + "const expectSnapshot = value => { + expect(value).toMatchSnapshot(); + };", + Some(serde_json::json!(["always"])), + ), + ( + "const expectSnapshot = value => { + expect(value).toThrowErrorMatchingSnapshot(); + };", + Some(serde_json::json!(["always"])), + ), + ( + "it('is true', () => { + { expect(1).toMatchSnapshot(); } + });", + Some(serde_json::json!(["always"])), + ), + ( + r#"const x = "snapshot"; + expect(1).toMatchSnapshot(\\`my $\\{x}\\`);"#, + Some(serde_json::json!(["always"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toThrowErrorMatchingSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toThrowErrorMatchingSnapshot(); + expect(2).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}); + expect(2).toMatchSnapshot({}); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}); + { + expect(2).toMatchSnapshot({}); + } + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + { expect(1).toMatchSnapshot(); } + { expect(2).toMatchSnapshot(); } + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}); + expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}, 'my first snapshot'); + expect(2).toMatchSnapshot(undefined); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}, 'my first snapshot'); + expect(2).toMatchSnapshot(undefined); + expect(2).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(2).toMatchSnapshot(); + expect(1).toMatchSnapshot({}, 'my second snapshot'); + expect(2).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(2).toMatchSnapshot(undefined); + expect(2).toMatchSnapshot(); + expect(1).toMatchSnapshot(null, 'my third snapshot'); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + it('is false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot(); + }); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + it('is false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot('hello world'); + }); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + describe('more tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + }); + it('is false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot('hello world'); + }); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + describe('more tests', () => { + it('is false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot('hello world'); + }); + }); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "import { describe as context, it as itIs } from '@jest/globals'; + describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + context('more tests', () => { + itIs('false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot('hello world'); + }); + }); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = (value, snapshotHint) => { + expect(value).toMatchSnapshot(); + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + }; + expect(value).toBe(1); + expect(value + 1).toMatchSnapshot(null); + expect(value + 2).toThrowErrorMatchingSnapshot(snapshotHint); + };", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = (value, snapshotHint) => { + expect(value).toMatchSnapshot(); + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + expect(value).toBe(1); + expect(value + 1).toMatchSnapshot(null); + expect(value + 2).toMatchSnapshot(null, snapshotHint); + }; + };", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + expect(value).toBe(1); + expect(value + 1).toMatchSnapshot(null); + expect(value + 2).toMatchSnapshot(null, snapshotHint); + }; + expect(value).toThrowErrorMatchingSnapshot(); + };", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + expect(value).toBe(1); + }; + expect(value).toMatchSnapshot(); + }; + it('my test', () => { + expect(1).toMatchSnapshot(); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "const myReusableTestBody = value => { + expect(value).toMatchSnapshot(); + }; + expect(1).toMatchSnapshot(); + expect(1).toThrowErrorMatchingSnapshot();", + Some(serde_json::json!(["multi"])), + ), + ]; + + let vitest_pass = vec![ + ("expect(something).toStrictEqual(somethingElse);", Some(serde_json::json!(["multi"]))), + ("a().toEqual('b')", Some(serde_json::json!(["multi"]))), + ("expect(a);", Some(serde_json::json!(["multi"]))), + (r#"expect(1).toMatchSnapshot({}, "my snapshot");"#, Some(serde_json::json!(["multi"]))), + ( + r#"expect(1).toThrowErrorMatchingSnapshot("my snapshot");"#, + Some(serde_json::json!(["multi"])), + ), + ("expect(1).toMatchSnapshot({});", Some(serde_json::json!(["multi"]))), + ("expect(1).toThrowErrorMatchingSnapshot();", Some(serde_json::json!(["multi"]))), + ( + " + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + it('is true', () => { + expect(1).toMatchSnapshot(undefined, 'my first snapshot'); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot('this is a hint, all by itself'); + }); + + it('is false', () => { + expect(2).toMatchSnapshot('this is a hint'); + expect(2).toMatchSnapshot('and so is this'); + }); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(2).toMatchSnapshot('this is a hint'); + expect(2).toMatchSnapshot('and so is this'); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(2).toThrowErrorMatchingSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + it('is true', () => { + expect(1).toStrictEqual(1); + expect(1).toStrictEqual(2); + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(1).toStrictEqual(1); + expect(1).toStrictEqual(2); + expect(2).toThrowErrorMatchingSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + it('is true', () => { + expect(1).toMatchInlineSnapshot(); + }); + + it('is false', () => { + expect(1).toMatchInlineSnapshot(); + expect(1).toMatchInlineSnapshot(); + expect(1).toThrowErrorMatchingInlineSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(1).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + + expect(value).toBe(1); + }; + + expect(value).toBe(1); + }; + + it('my test', () => { + expect(1).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(value).toBe(1); + }; + + expect(value).toBe(1); + expect(anotherValue).toMatchSnapshot(); + }; + + it('my test', () => { + expect(1).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + " + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + + expect(value).toBe(1); + }; + + expect(value).toBe(1); + }; + + expect(1).toMatchSnapshot(); + ", + Some(serde_json::json!(["multi"])), + ), + ]; + + pass.extend(vitest_pass); + + let vitest_fail = vec![ + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toThrowErrorMatchingSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toThrowErrorMatchingSnapshot(); + expect(2).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}); + expect(2).toMatchSnapshot({}); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}); + { + expect(2).toMatchSnapshot({}); + } + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + { expect(1).toMatchSnapshot(); } + { expect(2).toMatchSnapshot(); } + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}); + expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + });", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}, 'my first snapshot'); + expect(2).toMatchSnapshot(undefined); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(1).toMatchSnapshot({}, 'my first snapshot'); + expect(2).toMatchSnapshot(undefined); + expect(2).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(2).toMatchSnapshot(); + expect(1).toMatchSnapshot({}, 'my second snapshot'); + expect(2).toMatchSnapshot(); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "it('is true', () => { + expect(2).toMatchSnapshot(undefined); + expect(2).toMatchSnapshot(); + expect(1).toMatchSnapshot(null, 'my third snapshot'); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot(); + }); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot('hello world'); + }); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ( + "describe('my tests', () => { + describe('more tests', () => { + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + }); + + it('is false', () => { + expect(2).toMatchSnapshot(); + expect(2).toMatchSnapshot('hello world'); + }); + }); + ", + Some(serde_json::json!(["multi"])), + ), + ]; + + fail.extend(vitest_fail); + + Tester::new(PreferSnapshotHint::NAME, PreferSnapshotHint::PLUGIN, pass, fail) + .with_jest_plugin(true) + .with_vitest_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/jest_prefer_snapshot_hint.snap b/crates/oxc_linter/src/snapshots/jest_prefer_snapshot_hint.snap new file mode 100644 index 0000000000000..6cc8f2c5fdae8 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/jest_prefer_snapshot_hint.snap @@ -0,0 +1,695 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:1:1] + 1 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:1:1] + 1 │ expect(1).toMatchSnapshot({}); + · ───────────────────────────── + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:13] + 1 │ const x = "we can't know if this is a string or not"; + 2 │ expect(1).toMatchSnapshot(x); + · ──────────────────────────── + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:1:1] + 1 │ expect(1).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────── + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toMatchSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(1).toMatchSnapshot(); + 3 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toThrowErrorMatchingSnapshot("my error"); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ const expectSnapshot = value => { + 2 │ expect(value).toMatchSnapshot(); + · ─────────────────────────────── + 3 │ }; + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ const expectSnapshot = value => { + 2 │ expect(value).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────────── + 3 │ }; + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:17] + 1 │ it('is true', () => { + 2 │ { expect(1).toMatchSnapshot(); } + · ─────────────────────────── + 3 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + × Invalid Unicode escape sequence + ╭─[prefer_snapshot_hint.tsx:2:40] + 1 │ const x = "snapshot"; + 2 │ expect(1).toMatchSnapshot(\\`my $\\{x}\\`); + · ─ + ╰──── + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toMatchSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(1).toMatchSnapshot(); + 3 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toThrowErrorMatchingSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(1).toMatchSnapshot(); + 3 │ expect(2).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────── + 3 │ expect(2).toMatchSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(1).toThrowErrorMatchingSnapshot(); + 3 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot({}); + · ───────────────────────────── + 3 │ expect(2).toMatchSnapshot({}); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(1).toMatchSnapshot({}); + 3 │ expect(2).toMatchSnapshot({}); + · ───────────────────────────── + 4 │ }); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot({}); + · ───────────────────────────── + 3 │ { + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:4:17] + 3 │ { + 4 │ expect(2).toMatchSnapshot({}); + · ───────────────────────────── + 5 │ } + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:17] + 1 │ it('is true', () => { + 2 │ { expect(1).toMatchSnapshot(); } + · ─────────────────────────── + 3 │ { expect(2).toMatchSnapshot(); } + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:17] + 2 │ { expect(1).toMatchSnapshot(); } + 3 │ { expect(2).toMatchSnapshot(); } + · ─────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot({}); + · ───────────────────────────── + 3 │ expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(1).toMatchSnapshot({}, 'my first snapshot'); + 3 │ expect(2).toMatchSnapshot(undefined); + · ──────────────────────────────────── + 4 │ }); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(1).toMatchSnapshot({}, 'my first snapshot'); + 3 │ expect(2).toMatchSnapshot(undefined); + · ──────────────────────────────────── + 4 │ expect(2).toMatchSnapshot(); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:4:15] + 3 │ expect(2).toMatchSnapshot(undefined); + 4 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 5 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(1).toMatchSnapshot({}, 'my second snapshot'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:4:15] + 3 │ expect(1).toMatchSnapshot({}, 'my second snapshot'); + 4 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 5 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ it('is true', () => { + 2 │ expect(2).toMatchSnapshot(undefined); + · ──────────────────────────────────── + 3 │ expect(2).toMatchSnapshot(); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:15] + 2 │ expect(2).toMatchSnapshot(undefined); + 3 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 4 │ expect(1).toMatchSnapshot(null, 'my third snapshot'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:6:17] + 5 │ it('is false', () => { + 6 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 7 │ expect(2).toMatchSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:7:17] + 6 │ expect(2).toMatchSnapshot(); + 7 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 8 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:6:17] + 5 │ it('is false', () => { + 6 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 7 │ expect(2).toMatchSnapshot('hello world'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:8:17] + 7 │ it('is false', () => { + 8 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 9 │ expect(2).toMatchSnapshot('hello world'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:7:19] + 6 │ it('is false', () => { + 7 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 8 │ expect(2).toMatchSnapshot('hello world'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:8:19] + 7 │ itIs('false', () => { + 8 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 9 │ expect(2).toMatchSnapshot('hello world'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ const myReusableTestBody = (value, snapshotHint) => { + 2 │ expect(value).toMatchSnapshot(); + · ─────────────────────────────── + 3 │ const innerFn = anotherValue => { + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:4:17] + 3 │ const innerFn = anotherValue => { + 4 │ expect(anotherValue).toMatchSnapshot(); + · ────────────────────────────────────── + 5 │ }; + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:7:15] + 6 │ expect(value).toBe(1); + 7 │ expect(value + 1).toMatchSnapshot(null); + · ─────────────────────────────────────── + 8 │ expect(value + 2).toThrowErrorMatchingSnapshot(snapshotHint); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:8:15] + 7 │ expect(value + 1).toMatchSnapshot(null); + 8 │ expect(value + 2).toThrowErrorMatchingSnapshot(snapshotHint); + · ──────────────────────────────────────────────────────────── + 9 │ }; + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:15] + 1 │ const myReusableTestBody = (value, snapshotHint) => { + 2 │ expect(value).toMatchSnapshot(); + · ─────────────────────────────── + 3 │ const innerFn = anotherValue => { + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:4:17] + 3 │ const innerFn = anotherValue => { + 4 │ expect(anotherValue).toMatchSnapshot(); + · ────────────────────────────────────── + 5 │ expect(value).toBe(1); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:6:17] + 5 │ expect(value).toBe(1); + 6 │ expect(value + 1).toMatchSnapshot(null); + · ─────────────────────────────────────── + 7 │ expect(value + 2).toMatchSnapshot(null, snapshotHint); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:17] + 2 │ const innerFn = anotherValue => { + 3 │ expect(anotherValue).toMatchSnapshot(); + · ────────────────────────────────────── + 4 │ expect(value).toBe(1); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:5:17] + 4 │ expect(value).toBe(1); + 5 │ expect(value + 1).toMatchSnapshot(null); + · ─────────────────────────────────────── + 6 │ expect(value + 2).toMatchSnapshot(null, snapshotHint); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:8:15] + 7 │ }; + 8 │ expect(value).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────────── + 9 │ }; + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:17] + 2 │ const innerFn = anotherValue => { + 3 │ expect(anotherValue).toMatchSnapshot(); + · ────────────────────────────────────── + 4 │ expect(value).toBe(1); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:6:15] + 5 │ }; + 6 │ expect(value).toMatchSnapshot(); + · ─────────────────────────────── + 7 │ }; + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:4:13] + 3 │ }; + 4 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 5 │ expect(1).toThrowErrorMatchingSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:5:13] + 4 │ expect(1).toMatchSnapshot(); + 5 │ expect(1).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────── + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:19] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toMatchSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:19] + 2 │ expect(1).toMatchSnapshot(); + 3 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:21] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toThrowErrorMatchingSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:21] + 2 │ expect(1).toMatchSnapshot(); + 3 │ expect(2).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:21] + 1 │ it('is true', () => { + 2 │ expect(1).toThrowErrorMatchingSnapshot(); + · ──────────────────────────────────────── + 3 │ expect(2).toMatchSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:21] + 2 │ expect(1).toThrowErrorMatchingSnapshot(); + 3 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:21] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot({}); + · ───────────────────────────── + 3 │ expect(2).toMatchSnapshot({}); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:3:21] + 2 │ expect(1).toMatchSnapshot({}); + 3 │ expect(2).toMatchSnapshot({}); + · ───────────────────────────── + 4 │ }); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:20] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot({}); + · ───────────────────────────── + 3 │ { + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:4:19] + 3 │ { + 4 │ expect(2).toMatchSnapshot({}); + · ───────────────────────────── + 5 │ } + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:22] + 1 │ it('is true', () => { + 2 │ { expect(1).toMatchSnapshot(); } + · ─────────────────────────── + 3 │ { expect(2).toMatchSnapshot(); } + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:22] + 2 │ { expect(1).toMatchSnapshot(); } + 3 │ { expect(2).toMatchSnapshot(); } + · ─────────────────────────── + 4 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:21] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:21] + 1 │ it('is true', () => { + 2 │ expect(1).toMatchSnapshot({}); + · ───────────────────────────── + 3 │ expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:3:21] + 2 │ expect(1).toMatchSnapshot({}, 'my first snapshot'); + 3 │ expect(2).toMatchSnapshot(undefined); + · ──────────────────────────────────── + 4 │ }); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:3:21] + 2 │ expect(1).toMatchSnapshot({}, 'my first snapshot'); + 3 │ expect(2).toMatchSnapshot(undefined); + · ──────────────────────────────────── + 4 │ expect(2).toMatchSnapshot(); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:4:21] + 3 │ expect(2).toMatchSnapshot(undefined); + 4 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 5 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:2:21] + 1 │ it('is true', () => { + 2 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 3 │ expect(1).toMatchSnapshot({}, 'my second snapshot'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:4:21] + 3 │ expect(1).toMatchSnapshot({}, 'my second snapshot'); + 4 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 5 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot hint must be a string literal. + ╭─[prefer_snapshot_hint.tsx:2:21] + 1 │ it('is true', () => { + 2 │ expect(2).toMatchSnapshot(undefined); + · ──────────────────────────────────── + 3 │ expect(2).toMatchSnapshot(); + ╰──── + help: Provide a string literal as the hint, or pass a property matcher object as the first argument and the hint string as the second. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:3:21] + 2 │ expect(2).toMatchSnapshot(undefined); + 3 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 4 │ expect(1).toMatchSnapshot(null, 'my third snapshot'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:7:20] + 6 │ it('is false', () => { + 7 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 8 │ expect(2).toMatchSnapshot(); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:8:20] + 7 │ expect(2).toMatchSnapshot(); + 8 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 9 │ }); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:7:20] + 6 │ it('is false', () => { + 7 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 8 │ expect(2).toMatchSnapshot('hello world'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. + + ⚠ eslint-plugin-jest(prefer-snapshot-hint): Snapshot is missing a hint. + ╭─[prefer_snapshot_hint.tsx:9:20] + 8 │ it('is false', () => { + 9 │ expect(2).toMatchSnapshot(); + · ─────────────────────────── + 10 │ expect(2).toMatchSnapshot('hello world'); + ╰──── + help: Include a hint string to identify this snapshot in the snapshot file. diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index 42d515df87d39..3306fa898e1d8 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -37,7 +37,7 @@ pub use self::{ // the crates/oxc_linter/data/vitest_compatible_jest_rules.json // file is also updated. The JSON file is used by the oxlint-migrate // and eslint-plugin-oxlint repos to keep everything synced. -const VITEST_COMPATIBLE_JEST_RULES: [&str; 45] = [ +const VITEST_COMPATIBLE_JEST_RULES: [&str; 46] = [ "consistent-test-it", "expect-expect", "max-expects", @@ -70,6 +70,7 @@ const VITEST_COMPATIBLE_JEST_RULES: [&str; 45] = [ "prefer-lowercase-title", "prefer-mock-promise-shorthand", "prefer-mock-return-shorthand", + "prefer-snapshot-hint", "prefer-spy-on", "prefer-strict-equal", "prefer-to-be",