Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create no-interpolation-in-snapshots rule #553

Merged
merged 21 commits into from
Jul 27, 2020

Conversation

viestat
Copy link
Contributor

@viestat viestat commented Apr 2, 2020

Motivation

This rule disallows the use of string interpolations within toMatchInlineSnapshot since 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:

expect(something).toMatchInlineSnapshot(
  `Object {
    property: ${interpolated}
  }`
);

expect(something).toMatchInlineSnapshot({ other: expect.any(Number) }, 
  `Object {
    other: Any<Number>,
    property: ${interpolated}
  }`
);

Examples of correct code for this rule:

expect(something).toMatchInlineSnapshot();

expect(something).toMatchInlineSnapshot(
  `Object {
    property: 1
  }`
);

expect(something).toMatchInlineSnapshot({ property: expect.any(Date) }, 
  `Object {
    property: Any<Date>
  }`
);

Copy link
Collaborator

@G-Rath G-Rath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've given this a brief look over and requested a few changes :)

src/rules/no-interpolation-inline-snapshot.ts Outdated Show resolved Hide resolved
});

ruleTester.run('no-interpolation-inline-snapshot', rule, {
valid: [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some more tests for cases like:

  • when expect is called without a matcher or modifier
  • when expect is called with a dangling modifier (i.e no matcher)
  • when a property is accessed on expect (i.e expect.toHaveAssertions()
  • when a method called toMatchInlineSnapshot is called with interpolation, both by itself and on an object that looks suspiciously similar to expect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in b6c50ec

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks!

If you could check one more in that has two or more interpolations, just for completeness, that would be cool but not blocking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in 96eaf9b

@viestat viestat requested a review from G-Rath May 4, 2020 12:00
@viestat
Copy link
Contributor Author

viestat commented May 4, 2020

Thanks for the feedback!

Copy link
Collaborator

@G-Rath G-Rath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay; left a few comments.

This rule should also check the toThrowErrorMatchingInlineSnapshot matcher as well :)

Also don't forget to regenerate the README rules table (you can do this by running yarn run tools:generate-rules-table)

docs/rules/no-interpolation-inline-snapshot.md Outdated Show resolved Hide resolved

if (
matcher?.name !== 'toMatchInlineSnapshot' ||
matcher?.arguments === null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might as well just use optional chaining on the forEach below, since matcher?.name !== will also check that matcher is defined

Copy link
Contributor Author

@viestat viestat Jun 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your review!
I am not sure I understand what you mean here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional chaining is cool, but if you use it multiple times in a row on the same object, that's usually a smell that you should just check for existence.

When using optional chaining in a condition, it has the same guarding as doing "obj && obj.prop ", meaning it does a "is defined" check.

For example this:

if(node.parent && node.parent.type === AST_NODE_TYPES.AssignmentExpression) {
  const { right } = node.parent;

  ...
}

Can be simplified to just:

if(node.parent?.type === AST_NODE_TYPES.AssignmentExpression) {
  const { right } = node.parent;

  ...
}

So originally what I was saying when your code was just matcher?.name === 'value' was that you could drop the matcher.arguments === null in favor of just later doing matcher.arguments?.forEach.

However, now that the code has changed to include multiple compares to name, for which I'd recommend using include, and so in turn just do a matcher && as matcher?.name returns type string | undefined which is not accepted by string[].includes (as that expects just string).


tl;dr

  • I would replace the multiple matcher?.name ===s with just matcher && ['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name)
  • I would replace matcher.arguments === null with matcher.arguments?.forEach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in 96eaf9b

@viestat viestat force-pushed the no-interpolation-inline-snapshot branch from 0ba9265 to d83c060 Compare June 29, 2020 12:04
@viestat viestat requested a review from G-Rath June 29, 2020 12:14
Copy link
Collaborator

@G-Rath G-Rath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good! I'm planning on including this rule in the default ruleset in our next major, as it's always an error.

Aside from the few changes I've requested my only other nit is I think no-interpolation-in-snapshots might be a bit more clear as a name and it's technically true for both types of snapshots (and it's a little shorter which is nicer too).


if (
matcher?.name !== 'toMatchInlineSnapshot' ||
matcher?.arguments === null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional chaining is cool, but if you use it multiple times in a row on the same object, that's usually a smell that you should just check for existence.

When using optional chaining in a condition, it has the same guarding as doing "obj && obj.prop ", meaning it does a "is defined" check.

For example this:

if(node.parent && node.parent.type === AST_NODE_TYPES.AssignmentExpression) {
  const { right } = node.parent;

  ...
}

Can be simplified to just:

if(node.parent?.type === AST_NODE_TYPES.AssignmentExpression) {
  const { right } = node.parent;

  ...
}

So originally what I was saying when your code was just matcher?.name === 'value' was that you could drop the matcher.arguments === null in favor of just later doing matcher.arguments?.forEach.

However, now that the code has changed to include multiple compares to name, for which I'd recommend using include, and so in turn just do a matcher && as matcher?.name returns type string | undefined which is not accepted by string[].includes (as that expects just string).


tl;dr

  • I would replace the multiple matcher?.name ===s with just matcher && ['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name)
  • I would replace matcher.arguments === null with matcher.arguments?.forEach

});

ruleTester.run('no-interpolation-inline-snapshot', rule, {
valid: [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks!

If you could check one more in that has two or more interpolations, just for completeness, that would be cool but not blocking.

CHANGELOG.md Outdated
@@ -1,96 +1,111 @@
## [23.17.1](https://github.com/jest-community/eslint-plugin-jest/compare/v23.17.0...v23.17.1) (2020-06-23)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is automatically generated, so should not be formatted with prettier as it'll get overridden the next time a release happens :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was edited by husky commit tasks when I merged the latest changes with master 😓
Removed them with ab2c365

Copy link
Collaborator

@G-Rath G-Rath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of doc nits :)

docs/rules/no-interpolation-inline-snapshot.md Outdated Show resolved Hide resolved
docs/rules/no-interpolation-inline-snapshot.md Outdated Show resolved Hide resolved
be overloaded with a matcher by passing the optional `propertyMatchers` argument
to `toMatchInlineSnapshot`.

Examples of **incorrect** code for this rule:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you chuck a in/correct example for toThrowErrorMatchingInlineSnapshot as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in a405636

@viestat viestat force-pushed the no-interpolation-inline-snapshot branch from 6ae26e8 to 8c448e9 Compare July 1, 2020 09:09
@viestat viestat requested a review from G-Rath July 1, 2020 09:55
src/rules/no-interpolation-in-snapshots.ts Outdated Show resolved Hide resolved
src/rules/no-interpolation-in-snapshots.ts Outdated Show resolved Hide resolved
src/rules/no-interpolation-in-snapshots.ts Outdated Show resolved Hide resolved
@G-Rath G-Rath changed the title feat(rules): add no-interpolation-inline-snapshot rule feat: create no-interpolation-in-snapshots rule Jul 5, 2020
Copy link
Collaborator

@G-Rath G-Rath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good - the only thing left is that the description needs to be synced up so that it says "Disallow string interpolation inside snapshots" everywhere.

I've refactored our generate-rules-table tool in #624 to handle this based on the description property in the actual rules, so feel free to wait until that's been merged, or pull the script in locally and run it :)

@viestat viestat force-pushed the no-interpolation-inline-snapshot branch from 002af65 to e9e2eed Compare July 27, 2020 08:20
@viestat viestat force-pushed the no-interpolation-inline-snapshot branch from a795d72 to 6029742 Compare July 27, 2020 08:40
@SimenB SimenB requested a review from G-Rath July 27, 2020 09:25
Copy link
Collaborator

@G-Rath G-Rath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@G-Rath G-Rath merged commit 8d2c17c into jest-community:master Jul 27, 2020
github-actions bot pushed a commit that referenced this pull request Jul 27, 2020
# [23.19.0](v23.18.2...v23.19.0) (2020-07-27)

### Features

* create `no-interpolation-in-snapshots` rule ([#553](#553)) ([8d2c17c](8d2c17c))
@github-actions
Copy link

🎉 This PR is included in version 23.19.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants