diff --git a/docs/rules/no-interpolation-inline-snapshot.md b/docs/rules/no-interpolation-inline-snapshot.md new file mode 100644 index 000000000..def3a9472 --- /dev/null +++ b/docs/rules/no-interpolation-inline-snapshot.md @@ -0,0 +1,54 @@ +# No string interpolation inside inline snapshots (no-interpolation-inline-snapshot) + +Prevents the use of string interpolations within `toMatchInlineSnapshot`. + +## Rule Details + +Interpolation prevents snapshots from being updated. Instead, properties should +be overloaded with a matcher by passing the optional `propertyMatchers` argument +to `toMatchInlineSnapshot`. + +Examples of **incorrect** code for this rule: + +```js +expect(something).toMatchInlineSnapshot( + `Object { + property: ${interpolated} + }`, +); + +expect(something).toMatchInlineSnapshot( + { other: expect.any(Number) }, + `Object { + other: Any, + property: ${interpolated} + }`, +); +``` + +Examples of **correct** code for this rule: + +```js +expect(something).toMatchInlineSnapshot(); + +expect(something).toMatchInlineSnapshot( + `Object { + property: 1 + }`, +); + +expect(something).toMatchInlineSnapshot( + { property: expect.any(Date) }, + `Object { + property: Any + }`, +); +``` + +\*Note that this rule will not trigger if the helper function is never used even +thought the `expect` will not execute. Rely on a rule like no-unused-vars for +this case. + +## When Not To Use It + +Don't use this rule on non-jest test files. diff --git a/src/__tests__/__snapshots__/rules.test.ts.snap b/src/__tests__/__snapshots__/rules.test.ts.snap index 831fe0269..4a750b42b 100644 --- a/src/__tests__/__snapshots__/rules.test.ts.snap +++ b/src/__tests__/__snapshots__/rules.test.ts.snap @@ -23,6 +23,7 @@ Object { "jest/no-hooks": "error", "jest/no-identical-title": "error", "jest/no-if": "error", + "jest/no-interpolation-inline-snapshot": "error", "jest/no-jasmine-globals": "error", "jest/no-jest-import": "error", "jest/no-large-snapshots": "error", diff --git a/src/__tests__/rules.test.ts b/src/__tests__/rules.test.ts index b5ebf1e03..f90174218 100644 --- a/src/__tests__/rules.test.ts +++ b/src/__tests__/rules.test.ts @@ -3,7 +3,7 @@ import { resolve } from 'path'; import plugin from '../'; const ruleNames = Object.keys(plugin.rules); -const numberOfRules = 40; +const numberOfRules = 41; describe('rules', () => { it('should have a corresponding doc for each rule', () => { diff --git a/src/rules/__tests__/no-interpolation-inline-snapshot.test.ts b/src/rules/__tests__/no-interpolation-inline-snapshot.test.ts new file mode 100644 index 000000000..7600302a6 --- /dev/null +++ b/src/rules/__tests__/no-interpolation-inline-snapshot.test.ts @@ -0,0 +1,40 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import resolveFrom from 'resolve-from'; +import rule from '../no-interpolation-inline-snapshot'; + +const ruleTester = new TSESLint.RuleTester({ + parser: resolveFrom(require.resolve('eslint'), 'espree'), + parserOptions: { + ecmaVersion: 2017, + }, +}); + +ruleTester.run('no-interpolation-inline-snapshot', rule, { + valid: [ + 'expect(something).toMatchInlineSnapshot();', + 'expect(something).toMatchInlineSnapshot(`No interpolation`);', + 'expect(something).toMatchInlineSnapshot({}, `No interpolation`);', + ], + invalid: [ + { + code: 'expect(something).toMatchInlineSnapshot(`${interpolated}`);', + errors: [ + { + endColumn: 58, + column: 41, + messageId: 'noInterpolation', + }, + ], + }, + { + code: 'expect(something).toMatchInlineSnapshot({}, `${interpolated}`);', + errors: [ + { + endColumn: 62, + column: 45, + messageId: 'noInterpolation', + }, + ], + }, + ], +}); diff --git a/src/rules/no-interpolation-inline-snapshot.ts b/src/rules/no-interpolation-inline-snapshot.ts new file mode 100644 index 000000000..85db261c3 --- /dev/null +++ b/src/rules/no-interpolation-inline-snapshot.ts @@ -0,0 +1,48 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; +import { createRule } from './utils'; + +export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: 'Disallow string interpolation inside inline snapshots.', + recommended: false, + }, + messages: { + noInterpolation: + 'Do not use string interpolation inside of inline snapshots', + }, + schema: [], + type: 'suggestion', + }, + defaultOptions: [], + create(context) { + return { + CallExpression(node) { + const { callee, arguments: nodeArguments } = node; + + if ( + callee.type !== AST_NODE_TYPES.MemberExpression || + callee.property.type !== AST_NODE_TYPES.Identifier || + callee.property.name !== 'toMatchInlineSnapshot' + ) { + return; + } + + // Check all since the optional 'propertyMatchers' argument might be present + nodeArguments.forEach(argument => { + if ( + argument.type === AST_NODE_TYPES.TemplateLiteral && + argument.expressions.length > 0 + ) { + context.report({ + messageId: 'noInterpolation', + node: argument, + }); + } + }); + }, + }; + }, +});