├── .codespellignore ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── codespell.yml │ ├── linting.yml │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── .yamllint ├── .yardopts ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── MIT-LICENSE.md ├── README.md ├── Rakefile ├── config ├── default.yml └── obsoletion.yml ├── docs ├── antora.yml └── modules │ └── ROOT │ ├── nav.adoc │ └── pages │ ├── cops.adoc │ ├── cops_rspec.adoc │ ├── development.adoc │ ├── index.adoc │ ├── installation.adoc │ ├── third_party_rspec_syntax_extensions.adoc │ ├── upgrade_to_version_2.adoc │ ├── upgrade_to_version_3.adoc │ └── usage.adoc ├── lib ├── rubocop-rspec.rb └── rubocop │ ├── cop │ ├── rspec │ │ ├── align_left_let_brace.rb │ │ ├── align_right_let_brace.rb │ │ ├── any_instance.rb │ │ ├── around_block.rb │ │ ├── base.rb │ │ ├── be.rb │ │ ├── be_empty.rb │ │ ├── be_eq.rb │ │ ├── be_eql.rb │ │ ├── be_nil.rb │ │ ├── before_after_all.rb │ │ ├── change_by_zero.rb │ │ ├── class_check.rb │ │ ├── contain_exactly.rb │ │ ├── context_method.rb │ │ ├── context_wording.rb │ │ ├── describe_class.rb │ │ ├── describe_method.rb │ │ ├── describe_symbol.rb │ │ ├── described_class.rb │ │ ├── described_class_module_wrapping.rb │ │ ├── dialect.rb │ │ ├── duplicated_metadata.rb │ │ ├── empty_example_group.rb │ │ ├── empty_hook.rb │ │ ├── empty_line_after_example.rb │ │ ├── empty_line_after_example_group.rb │ │ ├── empty_line_after_final_let.rb │ │ ├── empty_line_after_hook.rb │ │ ├── empty_line_after_subject.rb │ │ ├── empty_metadata.rb │ │ ├── empty_output.rb │ │ ├── eq.rb │ │ ├── example_length.rb │ │ ├── example_without_description.rb │ │ ├── example_wording.rb │ │ ├── excessive_docstring_spacing.rb │ │ ├── expect_actual.rb │ │ ├── expect_change.rb │ │ ├── expect_in_hook.rb │ │ ├── expect_in_let.rb │ │ ├── expect_output.rb │ │ ├── focus.rb │ │ ├── hook_argument.rb │ │ ├── hooks_before_examples.rb │ │ ├── identical_equality_assertion.rb │ │ ├── implicit_block_expectation.rb │ │ ├── implicit_expect.rb │ │ ├── implicit_subject.rb │ │ ├── include_examples.rb │ │ ├── indexed_let.rb │ │ ├── instance_spy.rb │ │ ├── instance_variable.rb │ │ ├── is_expected_specify.rb │ │ ├── it_behaves_like.rb │ │ ├── iterated_expectation.rb │ │ ├── leading_subject.rb │ │ ├── leaky_constant_declaration.rb │ │ ├── let_before_examples.rb │ │ ├── let_setup.rb │ │ ├── match_array.rb │ │ ├── message_chain.rb │ │ ├── message_expectation.rb │ │ ├── message_spies.rb │ │ ├── metadata_style.rb │ │ ├── missing_example_group_argument.rb │ │ ├── missing_expectation_target_method.rb │ │ ├── mixin │ │ │ ├── comments_help.rb │ │ │ ├── empty_line_separation.rb │ │ │ ├── file_help.rb │ │ │ ├── final_end_location.rb │ │ │ ├── inside_example_group.rb │ │ │ ├── location_help.rb │ │ │ ├── metadata.rb │ │ │ ├── namespace.rb │ │ │ ├── skip_or_pending.rb │ │ │ ├── top_level_group.rb │ │ │ └── variable.rb │ │ ├── multiple_describes.rb │ │ ├── multiple_expectations.rb │ │ ├── multiple_memoized_helpers.rb │ │ ├── multiple_subjects.rb │ │ ├── named_subject.rb │ │ ├── nested_groups.rb │ │ ├── no_expectation_example.rb │ │ ├── not_to_not.rb │ │ ├── overwriting_setup.rb │ │ ├── pending.rb │ │ ├── pending_without_reason.rb │ │ ├── predicate_matcher.rb │ │ ├── receive_counts.rb │ │ ├── receive_messages.rb │ │ ├── receive_never.rb │ │ ├── redundant_around.rb │ │ ├── redundant_predicate_matcher.rb │ │ ├── remove_const.rb │ │ ├── repeated_description.rb │ │ ├── repeated_example.rb │ │ ├── repeated_example_group_body.rb │ │ ├── repeated_example_group_description.rb │ │ ├── repeated_include_example.rb │ │ ├── repeated_subject_call.rb │ │ ├── return_from_stub.rb │ │ ├── scattered_let.rb │ │ ├── scattered_setup.rb │ │ ├── shared_context.rb │ │ ├── shared_examples.rb │ │ ├── single_argument_message_chain.rb │ │ ├── skip_block_inside_example.rb │ │ ├── sort_metadata.rb │ │ ├── spec_file_path_format.rb │ │ ├── spec_file_path_suffix.rb │ │ ├── stubbed_mock.rb │ │ ├── subject_declaration.rb │ │ ├── subject_stub.rb │ │ ├── undescriptive_literals_description.rb │ │ ├── unspecified_exception.rb │ │ ├── variable_definition.rb │ │ ├── variable_name.rb │ │ ├── verified_double_reference.rb │ │ ├── verified_doubles.rb │ │ ├── void_expect.rb │ │ └── yield.rb │ └── rspec_cops.rb │ ├── rspec.rb │ └── rspec │ ├── align_let_brace.rb │ ├── concept.rb │ ├── config_formatter.rb │ ├── cop │ └── generator.rb │ ├── corrector │ └── move_node.rb │ ├── description_extractor.rb │ ├── example.rb │ ├── example_group.rb │ ├── hook.rb │ ├── language.rb │ ├── node.rb │ ├── plugin.rb │ ├── shared_contexts │ └── default_rspec_language_config_context.rb │ ├── version.rb │ └── wording.rb ├── rubocop-rspec.gemspec ├── spec ├── project │ ├── changelog_spec.rb │ └── default_config_spec.rb ├── rubocop │ ├── cli │ │ ├── autocorrect_spec.rb │ │ └── run_spec.rb │ ├── cop │ │ └── rspec │ │ │ ├── align_left_let_brace_spec.rb │ │ │ ├── align_right_let_brace_spec.rb │ │ │ ├── any_instance_spec.rb │ │ │ ├── around_block_spec.rb │ │ │ ├── base_spec.rb │ │ │ ├── be_empty_spec.rb │ │ │ ├── be_eq_spec.rb │ │ │ ├── be_eql_spec.rb │ │ │ ├── be_nil_spec.rb │ │ │ ├── be_spec.rb │ │ │ ├── before_after_all_spec.rb │ │ │ ├── change_by_zero_spec.rb │ │ │ ├── class_check_spec.rb │ │ │ ├── contain_exactly_spec.rb │ │ │ ├── context_method_spec.rb │ │ │ ├── context_wording_spec.rb │ │ │ ├── describe_class_spec.rb │ │ │ ├── describe_method_spec.rb │ │ │ ├── describe_symbol_spec.rb │ │ │ ├── described_class_module_wrapping_spec.rb │ │ │ ├── described_class_spec.rb │ │ │ ├── dialect_spec.rb │ │ │ ├── duplicated_metadata_spec.rb │ │ │ ├── empty_example_group_spec.rb │ │ │ ├── empty_hook_spec.rb │ │ │ ├── empty_line_after_example_group_spec.rb │ │ │ ├── empty_line_after_example_spec.rb │ │ │ ├── empty_line_after_final_let_spec.rb │ │ │ ├── empty_line_after_hook_spec.rb │ │ │ ├── empty_line_after_subject_spec.rb │ │ │ ├── empty_metadata_spec.rb │ │ │ ├── empty_output_spec.rb │ │ │ ├── eq_spec.rb │ │ │ ├── example_length_spec.rb │ │ │ ├── example_without_description_spec.rb │ │ │ ├── example_wording_spec.rb │ │ │ ├── excessive_docstring_spacing_spec.rb │ │ │ ├── expect_actual_spec.rb │ │ │ ├── expect_change_spec.rb │ │ │ ├── expect_in_hook_spec.rb │ │ │ ├── expect_in_let_spec.rb │ │ │ ├── expect_output_spec.rb │ │ │ ├── focus_spec.rb │ │ │ ├── hook_argument_spec.rb │ │ │ ├── hooks_before_examples_spec.rb │ │ │ ├── identical_equality_assertion_spec.rb │ │ │ ├── implicit_block_expectation_spec.rb │ │ │ ├── implicit_expect_spec.rb │ │ │ ├── implicit_subject_spec.rb │ │ │ ├── include_examples_spec.rb │ │ │ ├── indexed_let_spec.rb │ │ │ ├── instance_spy_spec.rb │ │ │ ├── instance_variable_spec.rb │ │ │ ├── is_expected_specify_spec.rb │ │ │ ├── it_behaves_like_spec.rb │ │ │ ├── iterated_expectation_spec.rb │ │ │ ├── leading_subject_spec.rb │ │ │ ├── leaky_constant_declaration_spec.rb │ │ │ ├── let_before_examples_spec.rb │ │ │ ├── let_setup_spec.rb │ │ │ ├── match_array_spec.rb │ │ │ ├── message_chain_spec.rb │ │ │ ├── message_expectation_spec.rb │ │ │ ├── message_spies_spec.rb │ │ │ ├── metadata_style_spec.rb │ │ │ ├── missing_example_group_argument_spec.rb │ │ │ ├── missing_expectation_target_method_spec.rb │ │ │ ├── mixin │ │ │ ├── location_help_spec.rb │ │ │ ├── metadata_spec.rb │ │ │ ├── skip_or_pending_spec.rb │ │ │ └── top_level_group_spec.rb │ │ │ ├── multiple_describes_spec.rb │ │ │ ├── multiple_expectations_spec.rb │ │ │ ├── multiple_memoized_helpers_spec.rb │ │ │ ├── multiple_subjects_spec.rb │ │ │ ├── named_subject_spec.rb │ │ │ ├── nested_groups_spec.rb │ │ │ ├── no_expectation_example_spec.rb │ │ │ ├── not_to_not_spec.rb │ │ │ ├── overwriting_setup_spec.rb │ │ │ ├── pending_spec.rb │ │ │ ├── pending_without_reason_spec.rb │ │ │ ├── predicate_matcher_spec.rb │ │ │ ├── receive_counts_spec.rb │ │ │ ├── receive_messages_spec.rb │ │ │ ├── receive_never_spec.rb │ │ │ ├── redundant_around_spec.rb │ │ │ ├── redundant_predicate_matcher_spec.rb │ │ │ ├── remove_const_spec.rb │ │ │ ├── repeated_description_spec.rb │ │ │ ├── repeated_example_group_body_spec.rb │ │ │ ├── repeated_example_group_description_spec.rb │ │ │ ├── repeated_example_spec.rb │ │ │ ├── repeated_include_example_spec.rb │ │ │ ├── repeated_subject_call_spec.rb │ │ │ ├── return_from_stub_spec.rb │ │ │ ├── scattered_let_spec.rb │ │ │ ├── scattered_setup_spec.rb │ │ │ ├── shared_context_spec.rb │ │ │ ├── shared_examples_spec.rb │ │ │ ├── single_argument_message_chain_spec.rb │ │ │ ├── skip_block_inside_example_spec.rb │ │ │ ├── sort_metadata_spec.rb │ │ │ ├── spec_file_path_format_spec.rb │ │ │ ├── spec_file_path_suffix_spec.rb │ │ │ ├── stubbed_mock_spec.rb │ │ │ ├── subject_declaration_spec.rb │ │ │ ├── subject_stub_spec.rb │ │ │ ├── undescriptive_literals_description_spec.rb │ │ │ ├── unspecified_exception_spec.rb │ │ │ ├── variable_definition_spec.rb │ │ │ ├── variable_name_spec.rb │ │ │ ├── verified_double_reference_spec.rb │ │ │ ├── verified_doubles_spec.rb │ │ │ ├── void_expect_spec.rb │ │ │ └── yield_spec.rb │ └── rspec │ │ ├── config_formatter_spec.rb │ │ ├── description_extractor_spec.rb │ │ ├── example_group_spec.rb │ │ ├── example_spec.rb │ │ ├── hook_spec.rb │ │ └── wording_spec.rb ├── shared │ ├── detects_style_behavior.rb │ └── smoke_test_examples.rb ├── smoke_tests │ ├── empty_spec.rb │ ├── factory_bot_spec.rb │ ├── no_tests_spec.rb │ └── weird_rspec_spec.rb ├── spec_helper.rb └── support │ ├── cli_spec_behavior.rb │ ├── expect_offense.rb │ └── file_helper.rb └── tasks ├── cops_documentation.rake ├── create_release_notes.rake └── cut_release.rake /.codespellignore: -------------------------------------------------------------------------------- 1 | xdescribe 2 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # .git-blame-ignore-revs 2 | # Add mdformat to the workflow in GitHub Actions 3 | 4874a5a4a2a58e76d343aaa02279cd93b16f5a30 4 | # Move node patterns into private scope 5 | 089491fb1fa173145f8e6eb1b511d4a7a1bf28ff 6 | # Don't always define node patterns in private scope 7 | ce09cb2b25b9f3778bf032d0caaa862da6635b54 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | CHANGELOG.md merge=union 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rubocop/rubocop-rspec 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report an issue with RuboCop RSpec you've discovered. 4 | --- 5 | 6 | *Be clear, concise and precise in your description of the problem. 7 | Open an issue with a descriptive title and a summary in grammatically correct, 8 | complete sentences.* 9 | 10 | *Use the template below when reporting bugs. Please, make sure that 11 | you're running the latest stable RuboCop RSpec and that the problem you're reporting 12 | hasn't been reported (and potentially fixed) already.* 13 | 14 | *Before filing the ticket you should replace all text above the horizontal 15 | rule with your own words.* 16 | 17 | *In the case of false positive or false negative, please add the 18 | corresponding cop name.* 19 | 20 | ______________________________________________________________________ 21 | 22 | ## Expected behavior 23 | 24 | Describe here how you expected RuboCop RSpec to behave in this particular situation. 25 | 26 | ## Actual behavior 27 | 28 | Describe here what actually happened. 29 | Please use `rubocop --debug` when pasting rubocop output as it contains additional information. 30 | 31 | ## Steps to reproduce the problem 32 | 33 | This is extremely important! Providing us with a reliable way to reproduce 34 | a problem will expedite its solution. 35 | 36 | ## RuboCop RSpec version 37 | 38 | Include the output of `rubocop -V` or `bundle exec rubocop -V` if using Bundler. 39 | If you see extension cop versions (e.g. `rubocop-performance`, `rubocop-rake`, and others) 40 | output by `rubocop -V`, include them as well. Here's an example: 41 | 42 | ```shell 43 | $ [bundle exec] rubocop -V 44 | 1.67.0 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 2.7, running on ruby 3.4.0) [arm64-darwin23] 45 | - rubocop-performance 1.22.1 46 | - rubocop-rake 0.6.0 47 | - rubocop-rspec 3.1.0 48 | ``` 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest new RuboCop RSpec features or improvements to existing features. 4 | --- 5 | 6 | ## Is your feature request related to a problem? Please describe. 7 | 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | ## Describe the solution you'd like 11 | 12 | A clear and concise description of what you want to happen. 13 | 14 | ## Describe alternatives you've considered 15 | 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | ## Additional context 19 | 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Replace this text with a summary of the changes in your PR. The more detailed you are, the better.** 2 | 3 | ______________________________________________________________________ 4 | 5 | Before submitting the PR make sure the following are checked: 6 | 7 | - [ ] Feature branch is up-to-date with `master` (if not - rebase it). 8 | - [ ] Squashed related commits together. 9 | - [ ] Added tests. 10 | - [ ] Updated documentation. 11 | - [ ] Added an entry to the `CHANGELOG.md` if the new code introduces user-observable changes. 12 | - [ ] The build (`bundle exec rake`) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit). 13 | 14 | If you have created a new cop: 15 | 16 | - [ ] Added the new cop to `config/default.yml`. 17 | - [ ] The cop is configured as `Enabled: pending` in `config/default.yml`. 18 | - [ ] The cop is configured as `Enabled: true` in `.rubocop.yml`. 19 | - [ ] The cop documents examples of good and bad code. 20 | - [ ] The tests assert both that bad code is reported and that good code is not reported. 21 | - [ ] Set `VersionAdded: "<>"` in `default/config.yml`. 22 | 23 | If you have modified an existing cop's configuration options: 24 | 25 | - [ ] Set `VersionChanged: "<>"` in `config/default.yml`. 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | name: CodeSpell 2 | on: 3 | - pull_request 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 6 | cancel-in-progress: true 7 | jobs: 8 | codespell: 9 | name: CodeSpell 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: CodeSpell 14 | uses: codespell-project/actions-codespell@master 15 | with: 16 | check_filenames: true 17 | check_hidden: true 18 | ignore_words_file: .codespellignore 19 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | on: 3 | - pull_request 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 6 | cancel-in-progress: true 7 | jobs: 8 | yamllint: 9 | name: Yamllint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Yamllint 14 | uses: karancode/yamllint-github-action@master 15 | with: 16 | yamllint_strict: true 17 | yamllint_format: parsable 18 | yamllint_comment: true 19 | env: 20 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | mdformat: 22 | name: Mdformat 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Mdformat 27 | uses: ydah/mdformat-action@main 28 | with: 29 | number: true 30 | env: 31 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: master 5 | paths: lib/rubocop/rspec/version.rb 6 | jobs: 7 | publish: 8 | name: Publish to RubyGems 9 | runs-on: ubuntu-latest 10 | if: github.repository_owner == 'rubocop' 11 | permissions: 12 | actions: write 13 | contents: write 14 | id-token: write 15 | pull-requests: write 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Ruby 19 | uses: ruby/setup-ruby@v1 20 | with: 21 | bundler-cache: true 22 | ruby-version: ruby 23 | - uses: rubygems/release-gem@v1 24 | - name: Create a GitHub release 25 | env: 26 | GH_TOKEN: ${{ github.token }} 27 | run: | 28 | bundle exec rake create_release_notes 29 | gh release create $(git tag --points-at @) \ 30 | --title "RuboCop RSpec $(git tag --points-at @)" \ 31 | --notes-file relnotes.md 32 | - name: Replace version in Antora config 33 | env: 34 | GH_TOKEN: ${{ github.token }} 35 | run: | 36 | sed -i 's/version:.*$/version: ~/' docs/antora.yml 37 | if ! git diff --exit-code docs/antora.yml; then 38 | branch=switch-docs-version-$(git tag --points-at @) 39 | git config user.name "${GITHUB_ACTOR}" 40 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 41 | git checkout -b "$branch" 42 | git add docs/antora.yml 43 | git commit -m "Switch docs version back" 44 | git push -u origin "$branch" 45 | gh pr create --fill --head "$branch" 46 | fi 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rdoc generated 2 | rdoc 3 | 4 | # yard generated 5 | doc 6 | .yardoc 7 | 8 | # bundler 9 | .bundle 10 | Gemfile.lock 11 | Gemfile.local 12 | 13 | # jeweler generated 14 | pkg 15 | 16 | /vendor 17 | 18 | .ruby-gemset 19 | .ruby-version 20 | 21 | # simplecov generated 22 | coverage 23 | 24 | # vscode generated 25 | .vscode 26 | 27 | /relnotes.md 28 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config --no-offense-counts --no-auto-gen-timestamp` 3 | # using RuboCop version 1.72.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | Rake/MethodDefinitionInTask: 10 | Exclude: 11 | - 'tasks/cut_release.rake' 12 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | SimpleCov.start do 4 | enable_coverage :branch 5 | minimum_coverage line: 100, branch: 100 6 | add_filter '/spec/' 7 | add_filter '/vendor/bundle/' 8 | end 9 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | rules: 4 | comments: 5 | min-spaces-from-content: 1 6 | document-start: disable 7 | line-length: 8 | allow-non-breakable-inline-mappings: true 9 | max: 100 10 | truthy: 11 | check-keys: false 12 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | --hide-void-return 3 | --tag safety:"Cop Safety Information" 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The RuboCop Community Code of Conduct 2 | 3 | **Note:** We have picked the following code of conduct based on [Ruby's own 4 | code of conduct](https://www.ruby-lang.org/en/conduct/). 5 | 6 | This document provides a few simple community guidelines for a safe, respectful, 7 | productive, and collaborative place for any person who is willing to contribute 8 | to the RuboCop community. It applies to all "collaborative spaces", which are 9 | defined as community communications channels (such as mailing lists, submitted 10 | patches, commit comments, etc.). 11 | 12 | - Participants will be tolerant of opposing views. 13 | - Participants must ensure that their language and actions are free of personal 14 | attacks and disparaging personal remarks. 15 | - When interpreting the words and actions of others, participants should always 16 | assume good intentions. 17 | - Behaviour which can be reasonably considered harassment will not be tolerated. 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'bump' 8 | gem 'rack' 9 | gem 'rake' 10 | gem 'rspec', '~> 3.11' 11 | gem 'rubocop-performance', '~> 1.24' 12 | gem 'rubocop-rake', '~> 0.7' 13 | gem 'simplecov', '>= 0.19' 14 | gem 'yard' 15 | 16 | local_gemfile = 'Gemfile.local' 17 | eval_gemfile(local_gemfile) if File.exist?(local_gemfile) 18 | -------------------------------------------------------------------------------- /MIT-LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ian MacLeod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/obsoletion.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration of obsolete/deprecated cops used by `ConfigObsoletion`. 3 | # 4 | # See: https://docs.rubocop.org/rubocop/extensions.html#config-obsoletions 5 | # 6 | 7 | # Cop parameters that have been changed 8 | # Can be treated as a warning instead of a failure with `severity: warning` 9 | changed_parameters: 10 | - cops: 11 | - RSpec/VariableName 12 | parameters: IgnoredPatterns 13 | alternative: AllowedPatterns 14 | severity: warning 15 | - cops: RSpec/VerifiedDoubleReference 16 | parameters: EnforcedStyle 17 | reason: String references are not verifying unless the class is loaded. 18 | 19 | split: 20 | RSpec/FilePath: 21 | alternatives: 22 | - RSpec/SpecFilePathFormat 23 | - RSpec/SpecFilePathSuffix 24 | 25 | removed: 26 | RSpec/Capybara/FeatureMethods: 27 | reason: > 28 | this cop has migrated to `RSpec/Dialect`. Please use `RSpec/Dialect` instead 29 | RSpec/StringAsInstanceDoubleConstant: 30 | reason: Please use `RSpec/VerifiedDoubleReference` instead 31 | 32 | extracted: 33 | RSpec/Rails/*: rubocop-rspec_rails 34 | RSpec/FactoryBot/*: rubocop-factory_bot 35 | RSpec/Capybara/*: rubocop-capybara 36 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: rubocop-rspec 2 | title: RuboCop RSpec 3 | version: ~ 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Home] 2 | * xref:installation.adoc[Installation] 3 | * xref:usage.adoc[Usage] 4 | * xref:cops.adoc[Cops] 5 | * xref:upgrade_to_version_2.adoc[Upgrade to 2.x] 6 | * xref:upgrade_to_version_3.adoc[Upgrade to 3.x] 7 | * xref:third_party_rspec_syntax_extensions.adoc[RSpec syntax extensions in third-party gems] 8 | * xref:development.adoc[Development] 9 | * Cops Documentation 10 | ** xref:cops_rspec.adoc[RSpec] 11 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = RuboCop RSpec 2 | 3 | https://rspec.info/[RSpec]-specific analysis for your projects, as an extension to 4 | https://github.com/rubocop/rubocop[RuboCop]. 5 | 6 | RuboCop RSpec follows the https://docs.rubocop.org/rubocop/versioning.html[RuboCop versioning guide]. 7 | In a nutshell, between major versions new cops are introduced in a special `pending` status. 8 | That means that they won't be run unless explicitly told otherwise. 9 | RuboCop will warn on start that certain cops are neither explicitly enabled and disabled. 10 | On a major version release, all `pending` cops are enabled. 11 | 12 | == Project Goals 13 | 14 | * Enforce the guidelines and best practices outlined in the community https://rspec.rubystyle.guide[RSpec style guide] 15 | * Simplify the process of adopting new RSpec functionality 16 | 17 | == Non-goals of RuboCop RSpec 18 | 19 | === Enforcing `should` vs. `expect` syntax 20 | 21 | Enforcing 22 | 23 | [source,ruby] 24 | ---- 25 | expect(calculator.compute(line_item)).to eq(5) 26 | ---- 27 | 28 | over 29 | 30 | [source,ruby] 31 | ---- 32 | calculator.compute(line_item).should == 5 33 | ---- 34 | 35 | is a feature of RSpec itself - you can read about it in the "Disable should syntax" section of https://rspec.info/features/3-12/rspec-expectations/syntax-configuration[RSpec Documentation]. 36 | 37 | === Enforcing an explicit RSpec receiver for top-level methods (disabling monkey patching) 38 | 39 | Enforcing 40 | 41 | [source,ruby] 42 | ---- 43 | RSpec.describe MyClass do 44 | ... 45 | end 46 | ---- 47 | 48 | over 49 | 50 | [source,ruby] 51 | ---- 52 | describe MyClass do 53 | ... 54 | end 55 | ---- 56 | 57 | can be achieved using RSpec's `disable_monkey_patching!` method, which you can read more about in the https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode[RSpec Documentation]. This will also prevent `should` from being defined on every object in your system. 58 | 59 | Before disabling `should` you will need all your specs to use the `expect` syntax. You can use http://yujinakayama.me/transpec/[Transpec], which will do the conversion for you. 60 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/installation.adoc: -------------------------------------------------------------------------------- 1 | = Installation 2 | 3 | Just install the `rubocop-rspec` gem 4 | 5 | [source,bash] 6 | ---- 7 | gem install rubocop-rspec 8 | ---- 9 | 10 | or if you use bundler put this in your `Gemfile` 11 | 12 | [source,ruby] 13 | ---- 14 | gem 'rubocop-rspec' 15 | ---- 16 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/third_party_rspec_syntax_extensions.adoc: -------------------------------------------------------------------------------- 1 | = RSpec syntax extensions in third-party gems 2 | 3 | Some gems, e.g. https://github.com/CanCanCommunity/cancancan[cancancan], https://github.com/palkan/action_policy[action_policy] and https://github.com/varvet/pundit[pundit] provide their own extensions and aliases to RSpec syntax. Also, RSpec extensions like https://github.com/palkan/test-prof[test-prof], https://github.com/rspec/rspec-its[rspec-its] and https://github.com/zverok/saharspec#its-addons[saharspec] do. 4 | 5 | By default, RuboCop RSpec is not aware of those syntax extensions, and does not intend to gather all of them in the default configuration file. 6 | It is possible for the gems to provide configuration for RuboCop RSpec to allow proper detection of RSpec elements. 7 | RuboCop https://docs.rubocop.org/rubocop/configuration.html#inheriting-configuration-from-a-dependency-gem[provides third-party gems with an ability to configure RuboCop]. 8 | 9 | == Packaging configuration for RuboCop RSpec 10 | 11 | NOTE: Due to https://github.com/rubocop/rubocop-rspec/issues/1126[a bug], this feature doesn't work properly for `rubocop-rspec` 2.5.0 and earlier versions. 12 | 13 | For a third-party gem, it's sufficient to follow three steps: 14 | 15 | 1. Provide a configuration file (e.g. `.rubocop_rspec_alias_config.yml` or `config/rubocop-rspec.yml`). 16 | Please check with the xref:usage.adoc#rspec-dsl-configuration[RSpec DSL configuration] how different elements of RSpec syntax can be configured. 17 | 18 | 2. Add a section to their documentation how users can benefit from using RuboCop RSpec with full detection of their syntax extensions. 19 | Example: 20 | 21 | ## Usage with RuboCop RSpec 22 | Please add the following to your `.rubocop.yml` to make RuboCop RSpec aware of our cool syntax extensions: 23 | inherit_gem: 24 | third-party-gem: .rubocop_rspec_alias_config.yml 25 | 26 | 3. Include the configuration file to their package by updating their `gemspec`'s `spec.files` to include the aforementioned configuration file. 27 | 28 | See pull requests: https://github.com/test-prof/test-prof/pull/199[test-prof], and https://github.com/palkan/action_policy/pull/138[action_policy] for a less trivial example. 29 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/upgrade_to_version_3.adoc: -------------------------------------------------------------------------------- 1 | = Upgrade to Version 3.x 2 | :doctype: book 3 | 4 | == Configuration File Update 5 | 6 | In version 3.x: 7 | 8 | - cop departments are extracted to another gem. (`Capybara`, `FactoryBot`, `Rails`) 9 | 10 | [discrete] 11 | === Extraction of cop departments. (`Capybara`, `FactoryBot`, `Rails`) 12 | 13 | If you are using the RSpec/Capybara, RSpec/FactoryBot, or RSpec/Rails departments -- or have one in a `require` list in your rubocop.yml -- you need to install the corresponding gem and add it to your `.rubocop.yml` file: 14 | 15 | * Capybara: `rubocop-capybara` 16 | * FactoryBot: `rubocop-factory_bot` 17 | * Rails: `rubocop-rspec_rails` 18 | 19 | For example, if you are using the RSpec/Capybara department, you need to install the `rubocop-capybara` gem and add it to your `.rubocop.yml` file: 20 | 21 | [source,ruby] 22 | ---- 23 | # Gemfile 24 | group :test do 25 | gem 'rubocop-rspec' 26 | gem 'rubocop-capybara' 27 | end 28 | ---- 29 | 30 | [source,yaml] 31 | ---- 32 | require: 33 | - rubocop-rspec 34 | - rubocop-capybara 35 | ---- 36 | 37 | And you need to remove the old department in your `.rubocop.yml` file: 38 | 39 | [source,yaml] 40 | ---- 41 | RSpec/Capybara: 42 | Enabled: false 43 | ---- 44 | 45 | For another example, if you are not using these departments, you don't need to do anything. 46 | And when you update to RuboCop RSpec v3.0.0, you need to remove the old departments from your `.rubocop.yml` file, e.g.: 47 | 48 | [source,yaml] 49 | ---- 50 | RSpec/Capybara: 51 | Enabled: false 52 | RSpec/FactoryBot: 53 | Enabled: false 54 | RSpec/Rails: 55 | Enabled: false 56 | ---- 57 | 58 | == Troubleshooting 59 | 60 | If you're seeing `cannot load such file` when running rubocop, even after installing the gems, restart the server with `rubocop --restart-server`. 61 | -------------------------------------------------------------------------------- /lib/rubocop-rspec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'yaml' 5 | 6 | require 'rubocop' 7 | 8 | require_relative 'rubocop/rspec' 9 | require_relative 'rubocop/rspec/language' 10 | require_relative 'rubocop/rspec/node' 11 | require_relative 'rubocop/rspec/plugin' 12 | require_relative 'rubocop/rspec/version' 13 | require_relative 'rubocop/rspec/wording' 14 | 15 | require_relative 'rubocop/cop/rspec/mixin/file_help' 16 | require_relative 'rubocop/cop/rspec/mixin/final_end_location' 17 | require_relative 'rubocop/cop/rspec/mixin/inside_example_group' 18 | require_relative 'rubocop/cop/rspec/mixin/location_help' 19 | require_relative 'rubocop/cop/rspec/mixin/metadata' 20 | require_relative 'rubocop/cop/rspec/mixin/namespace' 21 | require_relative 'rubocop/cop/rspec/mixin/skip_or_pending' 22 | require_relative 'rubocop/cop/rspec/mixin/top_level_group' 23 | require_relative 'rubocop/cop/rspec/mixin/variable' 24 | 25 | # Dependent on `RuboCop::Cop::RSpec::FinalEndLocation`. 26 | require_relative 'rubocop/cop/rspec/mixin/comments_help' 27 | require_relative 'rubocop/cop/rspec/mixin/empty_line_separation' 28 | 29 | require_relative 'rubocop/cop/rspec/base' 30 | require_relative 'rubocop/rspec/align_let_brace' 31 | require_relative 'rubocop/rspec/concept' 32 | require_relative 'rubocop/rspec/corrector/move_node' 33 | require_relative 'rubocop/rspec/example' 34 | require_relative 'rubocop/rspec/example_group' 35 | require_relative 'rubocop/rspec/hook' 36 | 37 | require_relative 'rubocop/cop/rspec_cops' 38 | 39 | # We have to register our autocorrect incompatibilities in RuboCop's cops 40 | # as well so we do not hit infinite loops 41 | 42 | RuboCop::Cop::Layout::ExtraSpacing.singleton_class.prepend( 43 | Module.new do 44 | def autocorrect_incompatible_with 45 | super.push(RuboCop::Cop::RSpec::AlignLeftLetBrace) 46 | .push(RuboCop::Cop::RSpec::AlignRightLetBrace) 47 | end 48 | end 49 | ) 50 | 51 | RuboCop::AST::Node.include(RuboCop::RSpec::Node) 52 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/align_left_let_brace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks that left braces for adjacent single line lets are aligned. 7 | # 8 | # @example 9 | # # bad 10 | # let(:foobar) { blahblah } 11 | # let(:baz) { bar } 12 | # let(:a) { b } 13 | # 14 | # # good 15 | # let(:foobar) { blahblah } 16 | # let(:baz) { bar } 17 | # let(:a) { b } 18 | # 19 | class AlignLeftLetBrace < Base 20 | extend AutoCorrector 21 | 22 | MSG = 'Align left let brace' 23 | 24 | def self.autocorrect_incompatible_with 25 | [Layout::ExtraSpacing] 26 | end 27 | 28 | def on_new_investigation 29 | super 30 | return if processed_source.blank? 31 | 32 | token_aligner.offending_tokens.each do |let| 33 | add_offense(let.loc.begin) do |corrector| 34 | corrector.insert_before( 35 | let.loc.begin, token_aligner.indent_for(let) 36 | ) 37 | end 38 | end 39 | end 40 | 41 | private 42 | 43 | def token_aligner 44 | RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/align_right_let_brace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks that right braces for adjacent single line lets are aligned. 7 | # 8 | # @example 9 | # # bad 10 | # let(:foobar) { blahblah } 11 | # let(:baz) { bar } 12 | # let(:a) { b } 13 | # 14 | # # good 15 | # let(:foobar) { blahblah } 16 | # let(:baz) { bar } 17 | # let(:a) { b } 18 | # 19 | class AlignRightLetBrace < Base 20 | extend AutoCorrector 21 | 22 | MSG = 'Align right let brace' 23 | 24 | def self.autocorrect_incompatible_with 25 | [Layout::ExtraSpacing] 26 | end 27 | 28 | def on_new_investigation 29 | super 30 | return if processed_source.blank? 31 | 32 | token_aligner.offending_tokens.each do |let| 33 | add_offense(let.loc.end) do |corrector| 34 | corrector.insert_before( 35 | let.loc.end, token_aligner.indent_for(let) 36 | ) 37 | end 38 | end 39 | end 40 | 41 | private 42 | 43 | def token_aligner 44 | RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/any_instance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check that instances are not being stubbed globally. 7 | # 8 | # Prefer instance doubles over stubbing any instance of a class 9 | # 10 | # @example 11 | # # bad 12 | # describe MyClass do 13 | # before { allow_any_instance_of(MyClass).to receive(:foo) } 14 | # end 15 | # 16 | # # good 17 | # describe MyClass do 18 | # let(:my_instance) { instance_double(MyClass) } 19 | # 20 | # before do 21 | # allow(MyClass).to receive(:new).and_return(my_instance) 22 | # allow(my_instance).to receive(:foo) 23 | # end 24 | # end 25 | # 26 | class AnyInstance < Base 27 | MSG = 'Avoid stubbing using `%s`.' 28 | RESTRICT_ON_SEND = %i[ 29 | any_instance 30 | allow_any_instance_of 31 | expect_any_instance_of 32 | ].freeze 33 | 34 | def on_send(node) 35 | add_offense(node, message: format(MSG, method: node.method_name)) 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # @abstract parent class to RSpec cops 7 | class Base < ::RuboCop::Cop::Base 8 | include RuboCop::RSpec::Language 9 | 10 | exclude_from_registry 11 | 12 | # Invoke the original inherited hook so our cops are recognized 13 | def self.inherited(subclass) # rubocop:disable Lint/MissingSuper 14 | RuboCop::Cop::Base.inherited(subclass) 15 | end 16 | 17 | # Set the config for dynamic DSL configuration-aware helpers 18 | # that have no other means of accessing the configuration. 19 | def on_new_investigation 20 | super 21 | RuboCop::RSpec::Language.config = config['RSpec']['Language'] 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/be.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check for expectations where `be` is used without argument. 7 | # 8 | # The `be` matcher is too generic, as it pass on everything that is not 9 | # nil or false. If that is the exact intend, use `be_truthy`. In all other 10 | # cases it's better to specify what exactly is the expected value. 11 | # 12 | # @example 13 | # # bad 14 | # expect(foo).to be 15 | # 16 | # # good 17 | # expect(foo).to be_truthy 18 | # expect(foo).to be 1.0 19 | # expect(foo).to be(true) 20 | # 21 | class Be < Base 22 | MSG = "Don't use `be` without an argument." 23 | 24 | RESTRICT_ON_SEND = Runners.all 25 | 26 | # @!method be_without_args(node) 27 | def_node_matcher :be_without_args, <<~PATTERN 28 | (send _ #Runners.all $(send nil? :be)) 29 | PATTERN 30 | 31 | def on_send(node) 32 | be_without_args(node) do |matcher| 33 | add_offense(matcher.loc.selector) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/be_empty.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Prefer using `be_empty` when checking for an empty array. 7 | # 8 | # @example 9 | # # bad 10 | # expect(array).to contain_exactly 11 | # expect(array).to match_array([]) 12 | # 13 | # # good 14 | # expect(array).to be_empty 15 | # 16 | class BeEmpty < Base 17 | extend AutoCorrector 18 | 19 | MSG = 'Use `be_empty` matchers for checking an empty array.' 20 | RESTRICT_ON_SEND = %i[contain_exactly match_array].freeze 21 | 22 | # @!method expect_array_matcher?(node) 23 | def_node_matcher :expect_array_matcher?, <<~PATTERN 24 | (send 25 | (send nil? :expect _) 26 | #Runners.all 27 | ${ 28 | (send nil? :match_array (array)) 29 | (send nil? :contain_exactly) 30 | } 31 | _? 32 | ) 33 | PATTERN 34 | 35 | def on_send(node) 36 | expect_array_matcher?(node.parent) do |expect| 37 | add_offense(expect) do |corrector| 38 | corrector.replace(expect, 'be_empty') 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/be_eq.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check for expectations where `be(...)` can replace `eq(...)`. 7 | # 8 | # The `be` matcher compares by identity while the `eq` matcher compares 9 | # using `==`. Booleans and nil can be compared by identity and therefore 10 | # the `be` matcher is preferable as it is a more strict test. 11 | # 12 | # @safety 13 | # This cop is unsafe because it changes how values are compared. 14 | # 15 | # @example 16 | # # bad 17 | # expect(foo).to eq(true) 18 | # expect(foo).to eq(false) 19 | # expect(foo).to eq(nil) 20 | # 21 | # # good 22 | # expect(foo).to be(true) 23 | # expect(foo).to be(false) 24 | # expect(foo).to be(nil) 25 | # 26 | class BeEq < Base 27 | extend AutoCorrector 28 | 29 | MSG = 'Prefer `be` over `eq`.' 30 | RESTRICT_ON_SEND = %i[eq].freeze 31 | 32 | # @!method eq_type_with_identity?(node) 33 | def_node_matcher :eq_type_with_identity?, <<~PATTERN 34 | (send nil? :eq {boolean nil}) 35 | PATTERN 36 | 37 | def on_send(node) 38 | return unless eq_type_with_identity?(node) 39 | 40 | add_offense(node.loc.selector) do |corrector| 41 | corrector.replace(node.loc.selector, 'be') 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/be_eql.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check for expectations where `be(...)` can replace `eql(...)`. 7 | # 8 | # The `be` matcher compares by identity while the `eql` matcher 9 | # compares using `eql?`. Integers, floats, booleans, symbols, and nil 10 | # can be compared by identity and therefore the `be` matcher is 11 | # preferable as it is a more strict test. 12 | # 13 | # @safety 14 | # This cop is unsafe because it changes how values are compared. 15 | # 16 | # @example 17 | # # bad 18 | # expect(foo).to eql(1) 19 | # expect(foo).to eql(1.0) 20 | # expect(foo).to eql(true) 21 | # expect(foo).to eql(false) 22 | # expect(foo).to eql(:bar) 23 | # expect(foo).to eql(nil) 24 | # 25 | # # good 26 | # expect(foo).to be(1) 27 | # expect(foo).to be(1.0) 28 | # expect(foo).to be(true) 29 | # expect(foo).to be(false) 30 | # expect(foo).to be(:bar) 31 | # expect(foo).to be(nil) 32 | # 33 | # This cop only looks for instances of `expect(...).to eql(...)`. We 34 | # do not check `to_not` or `not_to` since `!eql?` is more strict 35 | # than `!equal?`. We also do not try to flag `eq` because if 36 | # `a == b`, and `b` is comparable by identity, `a` is still not 37 | # necessarily the same type as `b` since the `#==` operator can 38 | # coerce objects for comparison. 39 | # 40 | class BeEql < Base 41 | extend AutoCorrector 42 | 43 | MSG = 'Prefer `be` over `eql`.' 44 | RESTRICT_ON_SEND = %i[to].freeze 45 | 46 | # @!method eql_type_with_identity(node) 47 | def_node_matcher :eql_type_with_identity, <<~PATTERN 48 | (send _ :to $(send nil? :eql {boolean int float sym nil})) 49 | PATTERN 50 | 51 | def on_send(node) 52 | eql_type_with_identity(node) do |eql| 53 | add_offense(eql.loc.selector) do |corrector| 54 | corrector.replace(eql.loc.selector, 'be') 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/before_after_all.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check that before/after(:all/:context) isn't being used. 7 | # 8 | # @example 9 | # # bad - Faster but risk of state leaking between examples 10 | # describe MyClass do 11 | # before(:all) { Widget.create } 12 | # after(:context) { Widget.delete_all } 13 | # end 14 | # 15 | # # good - Slower but examples are properly isolated 16 | # describe MyClass do 17 | # before(:each) { Widget.create } 18 | # after(:each) { Widget.delete_all } 19 | # end 20 | # 21 | class BeforeAfterAll < Base 22 | MSG = 'Beware of using `%s` as it may cause state to leak ' \ 23 | 'between tests. If you are using `rspec-rails`, and ' \ 24 | '`use_transactional_fixtures` is enabled, then records created ' \ 25 | 'in `%s` are not automatically rolled back.' 26 | 27 | RESTRICT_ON_SEND = Set[:before, :after].freeze 28 | 29 | # @!method before_or_after_all(node) 30 | def_node_matcher :before_or_after_all, <<~PATTERN 31 | $(send _ RESTRICT_ON_SEND (sym {:all :context})) 32 | PATTERN 33 | 34 | def on_send(node) 35 | before_or_after_all(node) do |hook| 36 | add_offense( 37 | node, 38 | message: format(MSG, hook: hook.source) 39 | ) 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/contain_exactly.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks where `contain_exactly` is used. 7 | # 8 | # This cop checks for the following: 9 | # 10 | # - Prefer `match_array` when matching array values. 11 | # - Prefer `be_empty` when using `contain_exactly` with no arguments. 12 | # 13 | # @example 14 | # # bad 15 | # it { is_expected.to contain_exactly(*array1, *array2) } 16 | # 17 | # # good 18 | # it { is_expected.to match_array(array1 + array2) } 19 | # 20 | # # good 21 | # it { is_expected.to contain_exactly(content, *array) } 22 | # 23 | class ContainExactly < Base 24 | extend AutoCorrector 25 | 26 | MSG = 'Prefer `match_array` when matching array values.' 27 | RESTRICT_ON_SEND = %i[contain_exactly].freeze 28 | 29 | def on_send(node) 30 | return if node.arguments.empty? 31 | 32 | check_populated_collection(node) 33 | end 34 | 35 | private 36 | 37 | def check_populated_collection(node) 38 | return unless node.each_child_node.all?(&:splat_type?) 39 | 40 | add_offense(node) do |corrector| 41 | autocorrect_for_populated_array(node, corrector) 42 | end 43 | end 44 | 45 | def autocorrect_for_populated_array(node, corrector) 46 | arrays = node.arguments.map do |splat_node| 47 | splat_node.children.first 48 | end 49 | corrector.replace( 50 | node, 51 | "match_array(#{arrays.map(&:source).join(' + ')})" 52 | ) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/context_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # `context` should not be used for specifying methods. 7 | # 8 | # @example 9 | # # bad 10 | # context '#foo_bar' do 11 | # # ... 12 | # end 13 | # 14 | # context '.foo_bar' do 15 | # # ... 16 | # end 17 | # 18 | # # good 19 | # describe '#foo_bar' do 20 | # # ... 21 | # end 22 | # 23 | # describe '.foo_bar' do 24 | # # ... 25 | # end 26 | # 27 | class ContextMethod < Base 28 | extend AutoCorrector 29 | 30 | MSG = 'Use `describe` for testing methods.' 31 | 32 | # @!method context_method(node) 33 | def_node_matcher :context_method, <<~PATTERN 34 | (block 35 | (send #rspec? :context 36 | ${(str #method_name?) (dstr (str #method_name?) ...)} 37 | ...) 38 | ...) 39 | PATTERN 40 | 41 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 42 | context_method(node) do |context| 43 | add_offense(context) do |corrector| 44 | corrector.replace(node.send_node.loc.selector, 'describe') 45 | end 46 | end 47 | end 48 | 49 | private 50 | 51 | def method_name?(description) 52 | description.start_with?('.', '#') 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/describe_method.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks that the second argument to `describe` specifies a method. 7 | # 8 | # @example 9 | # # bad 10 | # describe MyClass, 'do something' do 11 | # end 12 | # 13 | # # good 14 | # describe MyClass, '#my_instance_method' do 15 | # end 16 | # 17 | # describe MyClass, '.my_class_method' do 18 | # end 19 | # 20 | class DescribeMethod < Base 21 | include TopLevelGroup 22 | 23 | MSG = 'The second argument to describe should be the method ' \ 24 | "being tested. '#instance' or '.class'." 25 | 26 | # @!method second_string_literal_argument(node) 27 | def_node_matcher :second_string_literal_argument, <<~PATTERN 28 | (block 29 | (send #rspec? :describe _first_argument ${str dstr} ...) 30 | ...) 31 | PATTERN 32 | 33 | # @!method method_name?(node) 34 | def_node_matcher :method_name?, <<~PATTERN 35 | {(str #method_name_prefix?) (dstr (str #method_name_prefix?) ...)} 36 | PATTERN 37 | 38 | def on_top_level_group(node) 39 | second_string_literal_argument(node) do |argument| 40 | add_offense(argument) unless method_name?(argument) 41 | end 42 | end 43 | 44 | private 45 | 46 | def method_name_prefix?(description) 47 | description.start_with?('.', '#') 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/describe_symbol.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Avoid describing symbols. 7 | # 8 | # @example 9 | # # bad 10 | # describe :my_method do 11 | # # ... 12 | # end 13 | # 14 | # # good 15 | # describe '#my_method' do 16 | # # ... 17 | # end 18 | # 19 | # @see https://github.com/rspec/rspec-core/issues/1610 20 | class DescribeSymbol < Base 21 | MSG = 'Avoid describing symbols.' 22 | RESTRICT_ON_SEND = %i[describe].freeze 23 | 24 | # @!method describe_symbol?(node) 25 | def_node_matcher :describe_symbol?, <<~PATTERN 26 | (send #rspec? :describe $sym ...) 27 | PATTERN 28 | 29 | def on_send(node) 30 | describe_symbol?(node) do |match| 31 | add_offense(match) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/described_class_module_wrapping.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Avoid opening modules and defining specs within them. 7 | # 8 | # @example 9 | # # bad 10 | # module MyModule 11 | # RSpec.describe MyClass do 12 | # # ... 13 | # end 14 | # end 15 | # 16 | # # good 17 | # RSpec.describe MyModule::MyClass do 18 | # # ... 19 | # end 20 | # 21 | # @see https://github.com/rubocop/rubocop-rspec/issues/735 22 | class DescribedClassModuleWrapping < Base 23 | MSG = 'Avoid opening modules and defining specs within them.' 24 | 25 | # @!method include_rspec_blocks?(node) 26 | def_node_search :include_rspec_blocks?, <<~PATTERN 27 | (any_block (send #explicit_rspec? #ExampleGroups.all ...) ...) 28 | PATTERN 29 | 30 | def on_module(node) 31 | return unless include_rspec_blocks?(node) 32 | 33 | add_offense(node) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/duplicated_metadata.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Avoid duplicated metadata. 7 | # 8 | # @example 9 | # # bad 10 | # describe 'Something', :a, :a 11 | # 12 | # # good 13 | # describe 'Something', :a 14 | class DuplicatedMetadata < Base 15 | extend AutoCorrector 16 | 17 | include Metadata 18 | include RangeHelp 19 | 20 | MSG = 'Avoid duplicated metadata.' 21 | 22 | def on_metadata(symbols, _hash) 23 | symbols.each do |symbol| 24 | on_metadata_symbol(symbol) 25 | end 26 | end 27 | 28 | private 29 | 30 | def on_metadata_symbol(node) 31 | return unless duplicated?(node) 32 | 33 | add_offense(node) do |corrector| 34 | autocorrect(corrector, node) 35 | end 36 | end 37 | 38 | def autocorrect(corrector, node) 39 | corrector.remove( 40 | range_with_surrounding_comma( 41 | range_with_surrounding_space( 42 | node.source_range, 43 | side: :left 44 | ), 45 | :left 46 | ) 47 | ) 48 | end 49 | 50 | def duplicated?(node) 51 | node.left_siblings.any? do |sibling| 52 | sibling.eql?(node) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/empty_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks for empty before and after hooks. 7 | # 8 | # @example 9 | # # bad 10 | # before {} 11 | # after do; end 12 | # before(:all) do 13 | # end 14 | # after(:all) { } 15 | # 16 | # # good 17 | # before { create_users } 18 | # after do 19 | # cleanup_users 20 | # end 21 | # before(:all) do 22 | # create_feed 23 | # end 24 | # after(:all) { cleanup_feed } 25 | # 26 | class EmptyHook < Base 27 | extend AutoCorrector 28 | include RuboCop::Cop::RangeHelp 29 | 30 | MSG = 'Empty hook detected.' 31 | 32 | # @!method empty_hook?(node) 33 | def_node_matcher :empty_hook?, <<~PATTERN 34 | (block $(send nil? #Hooks.all ...) _ nil?) 35 | PATTERN 36 | 37 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 38 | empty_hook?(node) do |hook| 39 | add_offense(hook) do |corrector| 40 | corrector.remove( 41 | range_with_surrounding_space(node.source_range, side: :left) 42 | ) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/empty_line_after_example_group.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks if there is an empty line after example group blocks. 7 | # 8 | # @example 9 | # # bad 10 | # RSpec.describe Foo do 11 | # describe '#bar' do 12 | # end 13 | # describe '#baz' do 14 | # end 15 | # end 16 | # 17 | # # good 18 | # RSpec.describe Foo do 19 | # describe '#bar' do 20 | # end 21 | # 22 | # describe '#baz' do 23 | # end 24 | # end 25 | # 26 | class EmptyLineAfterExampleGroup < Base 27 | extend AutoCorrector 28 | include EmptyLineSeparation 29 | 30 | MSG = 'Add an empty line after `%s`.' 31 | 32 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 33 | return unless spec_group?(node) 34 | 35 | missing_separating_line_offense(node) do |method| 36 | format(MSG, example_group: method) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/empty_line_after_final_let.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks if there is an empty line after the last let block. 7 | # 8 | # @example 9 | # # bad 10 | # let(:foo) { bar } 11 | # let(:something) { other } 12 | # it { does_something } 13 | # 14 | # # good 15 | # let(:foo) { bar } 16 | # let(:something) { other } 17 | # 18 | # it { does_something } 19 | # 20 | class EmptyLineAfterFinalLet < Base 21 | extend AutoCorrector 22 | include EmptyLineSeparation 23 | 24 | MSG = 'Add an empty line after the last `%s`.' 25 | 26 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 27 | return unless example_group_with_body?(node) 28 | 29 | final_let = node.body.child_nodes.reverse.find { |child| let?(child) } 30 | 31 | return if final_let.nil? 32 | 33 | missing_separating_line_offense(final_let) do |method| 34 | format(MSG, let: method) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/empty_line_after_subject.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks if there is an empty line after subject block. 7 | # 8 | # @example 9 | # # bad 10 | # subject(:obj) { described_class } 11 | # let(:foo) { bar } 12 | # 13 | # # good 14 | # subject(:obj) { described_class } 15 | # 16 | # let(:foo) { bar } 17 | # 18 | class EmptyLineAfterSubject < Base 19 | extend AutoCorrector 20 | include EmptyLineSeparation 21 | include InsideExampleGroup 22 | 23 | MSG = 'Add an empty line after `%s`.' 24 | 25 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 26 | return unless subject?(node) 27 | return unless inside_example_group?(node) 28 | 29 | missing_separating_line_offense(node) do |method| 30 | format(MSG, subject: method) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/empty_metadata.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Avoid empty metadata hash. 7 | # 8 | # @example EnforcedStyle: symbol (default) 9 | # # bad 10 | # describe 'Something', {} 11 | # 12 | # # good 13 | # describe 'Something' 14 | class EmptyMetadata < Base 15 | extend AutoCorrector 16 | 17 | include Metadata 18 | include RangeHelp 19 | 20 | MSG = 'Avoid empty metadata hash.' 21 | 22 | def on_metadata(_symbols, hash) 23 | return unless hash&.pairs&.empty? 24 | return if hash.children.any?(&:kwsplat_type?) 25 | 26 | add_offense(hash) do |corrector| 27 | remove_empty_metadata(corrector, hash) 28 | end 29 | end 30 | 31 | private 32 | 33 | def remove_empty_metadata(corrector, node) 34 | corrector.remove( 35 | range_with_surrounding_comma( 36 | range_with_surrounding_space( 37 | node.source_range, 38 | side: :left 39 | ), 40 | :left 41 | ) 42 | ) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/empty_output.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check that the `output` matcher is not called with an empty string. 7 | # 8 | # @example 9 | # # bad 10 | # expect { foo }.to output('').to_stdout 11 | # expect { bar }.not_to output('').to_stderr 12 | # 13 | # # good 14 | # expect { foo }.not_to output.to_stdout 15 | # expect { bar }.to output.to_stderr 16 | # 17 | class EmptyOutput < Base 18 | extend AutoCorrector 19 | 20 | MSG = 'Use `%s` instead of matching on an empty output.' 21 | RESTRICT_ON_SEND = Runners.all 22 | 23 | # @!method matching_empty_output(node) 24 | def_node_matcher :matching_empty_output, <<~PATTERN 25 | (send 26 | (block 27 | (send nil? :expect) ... 28 | ) 29 | #Runners.all 30 | (send $(send nil? :output (str empty?)) ...) 31 | ) 32 | PATTERN 33 | 34 | def on_send(send_node) 35 | matching_empty_output(send_node) do |node| 36 | runner = send_node.method?(:to) ? 'not_to' : 'to' 37 | message = format(MSG, runner: runner) 38 | add_offense(node, message: message) do |corrector| 39 | corrector.replace(send_node.loc.selector, runner) 40 | corrector.replace(node, 'output') 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/eq.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Use `eq` instead of `be ==` to compare objects. 7 | # 8 | # @example 9 | # # bad 10 | # expect(foo).to be == 42 11 | # 12 | # # good 13 | # expect(foo).to eq 42 14 | # 15 | class Eq < Base 16 | extend AutoCorrector 17 | include RangeHelp 18 | 19 | MSG = 'Use `eq` instead of `be ==` to compare objects.' 20 | RESTRICT_ON_SEND = Runners.all 21 | 22 | # @!method be_equals(node) 23 | def_node_matcher :be_equals, <<~PATTERN 24 | (send _ #Runners.all $(send (send nil? :be) :== _)) 25 | PATTERN 26 | 27 | def on_send(node) 28 | be_equals(node) do |matcher| 29 | range = offense_range(matcher) 30 | add_offense(range) do |corrector| 31 | corrector.replace(range, 'eq') 32 | end 33 | end 34 | end 35 | 36 | private 37 | 38 | def offense_range(matcher) 39 | range_between( 40 | matcher.source_range.begin_pos, 41 | matcher.loc.selector.end_pos 42 | ) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/example_length.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks for long examples. 7 | # 8 | # A long example is usually more difficult to understand. Consider 9 | # extracting out some behavior, e.g. with a `let` block, or a helper 10 | # method. 11 | # 12 | # @example 13 | # # bad 14 | # it do 15 | # service = described_class.new 16 | # more_setup 17 | # more_setup 18 | # result = service.call 19 | # expect(result).to be(true) 20 | # end 21 | # 22 | # # good 23 | # it do 24 | # service = described_class.new 25 | # result = service.call 26 | # expect(result).to be(true) 27 | # end 28 | # 29 | # You can set constructs you want to fold with `CountAsOne`. 30 | # Available are: 'array', 'hash', 'heredoc', and 'method_call'. 31 | # Each construct will be counted as one line regardless of 32 | # its actual size. 33 | # 34 | # @example CountAsOne: ['array', 'heredoc', 'method_call'] 35 | # 36 | # it do 37 | # array = [ # +1 38 | # 1, 39 | # 2 40 | # ] 41 | # 42 | # hash = { # +3 43 | # key: 'value' 44 | # } 45 | # 46 | # msg = <<~HEREDOC # +1 47 | # Heredoc 48 | # content. 49 | # HEREDOC 50 | # 51 | # foo( # +1 52 | # 1, 53 | # 2 54 | # ) 55 | # end # 6 points 56 | # 57 | class ExampleLength < Base 58 | include CodeLength 59 | 60 | LABEL = 'Example' 61 | 62 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 63 | return unless example?(node) 64 | 65 | check_code_length(node) 66 | end 67 | 68 | private 69 | 70 | def cop_label 71 | LABEL 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/expect_in_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Do not use `expect` in hooks such as `before`. 7 | # 8 | # @example 9 | # # bad 10 | # before do 11 | # expect(something).to eq 'foo' 12 | # end 13 | # 14 | # # bad 15 | # after do 16 | # expect_any_instance_of(Something).to receive(:foo) 17 | # end 18 | # 19 | # # good 20 | # it do 21 | # expect(something).to eq 'foo' 22 | # end 23 | # 24 | class ExpectInHook < Base 25 | MSG = 'Do not use `%s` in `%s` hook' 26 | 27 | # @!method expectation(node) 28 | def_node_search :expectation, '(send nil? #Expectations.all ...)' 29 | 30 | def on_block(node) 31 | return unless hook?(node) 32 | return if node.body.nil? 33 | 34 | expectation(node.body) do |expect| 35 | add_offense(expect.loc.selector, 36 | message: message(expect, node)) 37 | end 38 | end 39 | 40 | alias on_numblock on_block 41 | 42 | private 43 | 44 | def message(expect, hook) 45 | format(MSG, expect: expect.method_name, hook: hook.method_name) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/expect_in_let.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Do not use `expect` in let. 7 | # 8 | # @example 9 | # # bad 10 | # let(:foo) do 11 | # expect(something).to eq 'foo' 12 | # end 13 | # 14 | # # good 15 | # it do 16 | # expect(something).to eq 'foo' 17 | # end 18 | # 19 | class ExpectInLet < Base 20 | MSG = 'Do not use `%s` in let' 21 | 22 | # @!method expectation(node) 23 | def_node_search :expectation, '(send nil? #Expectations.all ...)' 24 | 25 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 26 | return unless let?(node) 27 | return if node.body.nil? 28 | 29 | expectation(node.body) do |expect| 30 | add_offense(expect.loc.selector, message: message(expect)) 31 | end 32 | end 33 | 34 | private 35 | 36 | def message(expect) 37 | format(MSG, expect: expect.method_name) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/expect_output.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks for opportunities to use `expect { ... }.to output`. 7 | # 8 | # @example 9 | # # bad 10 | # $stdout = StringIO.new 11 | # my_app.print_report 12 | # $stdout = STDOUT 13 | # expect($stdout.string).to eq('Hello World') 14 | # 15 | # # good 16 | # expect { my_app.print_report }.to output('Hello World').to_stdout 17 | # 18 | class ExpectOutput < Base 19 | MSG = 'Use `expect { ... }.to output(...).to_%s` ' \ 20 | 'instead of mutating $%s.' 21 | 22 | def on_gvasgn(node) 23 | return unless inside_example_scope?(node) 24 | 25 | name = node.name[1..] 26 | return unless name.eql?('stdout') || name.eql?('stderr') 27 | 28 | add_offense(node.loc.name, message: format(MSG, name: name)) 29 | end 30 | 31 | private 32 | 33 | # Detect if we are inside the scope of a single example 34 | # 35 | # We want to encourage using `expect { ... }.to output` so 36 | # we only care about situations where you would replace with 37 | # an expectation. Therefore, assignments to stderr or stdout 38 | # within a `before(:all)` or otherwise outside of an example 39 | # don't matter. 40 | def inside_example_scope?(node) 41 | return false if node.nil? || example_group?(node) 42 | return true if example?(node) 43 | return RuboCop::RSpec::Hook.new(node).example? if hook?(node) 44 | 45 | inside_example_scope?(node.parent) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/identical_equality_assertion.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks for equality assertions with identical expressions on both sides. 7 | # 8 | # @example 9 | # # bad 10 | # expect(foo.bar).to eq(foo.bar) 11 | # expect(foo.bar).to eql(foo.bar) 12 | # 13 | # # good 14 | # expect(foo.bar).to eq(2) 15 | # expect(foo.bar).to eql(2) 16 | # 17 | class IdenticalEqualityAssertion < Base 18 | MSG = 'Identical expressions on both sides of the equality ' \ 19 | 'may indicate a flawed test.' 20 | RESTRICT_ON_SEND = %i[to].freeze 21 | 22 | # @!method equality_check?(node) 23 | def_node_matcher :equality_check?, <<~PATTERN 24 | (send (send nil? :expect $_) :to 25 | {(send nil? {:eql :eq :be} $_) 26 | (send (send nil? :be) :== $_)}) 27 | PATTERN 28 | 29 | def on_send(node) 30 | equality_check?(node) do |left, right| 31 | add_offense(node) if left == right 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/include_examples.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks for usage of `include_examples`. 7 | # 8 | # `include_examples`, unlike `it_behaves_like`, does not create its 9 | # own context. As such, using `subject`, `let`, `before`/`after`, etc. 10 | # within shared examples included with `include_examples` can have 11 | # unexpected behavior and side effects. 12 | # 13 | # Prefer using `it_behaves_like` instead. 14 | # 15 | # @example 16 | # # bad 17 | # include_examples 'examples' 18 | # 19 | # # good 20 | # it_behaves_like 'examples' 21 | # 22 | class IncludeExamples < Base 23 | extend AutoCorrector 24 | 25 | MSG = 'Prefer `it_behaves_like` over `include_examples`.' 26 | 27 | RESTRICT_ON_SEND = %i[include_examples].freeze 28 | 29 | def on_send(node) 30 | selector = node.loc.selector 31 | 32 | add_offense(selector) do |corrector| 33 | corrector.replace(selector, 'it_behaves_like') 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/instance_spy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks for `instance_double` used with `have_received`. 7 | # 8 | # @example 9 | # # bad 10 | # it do 11 | # foo = instance_double(Foo).as_null_object 12 | # expect(foo).to have_received(:bar) 13 | # end 14 | # 15 | # # good 16 | # it do 17 | # foo = instance_spy(Foo) 18 | # expect(foo).to have_received(:bar) 19 | # end 20 | # 21 | class InstanceSpy < Base 22 | extend AutoCorrector 23 | 24 | MSG = 'Use `instance_spy` when you check your double ' \ 25 | 'with `have_received`.' 26 | 27 | # @!method null_double(node) 28 | def_node_search :null_double, <<~PATTERN 29 | (lvasgn $_ 30 | (send 31 | $(send nil? :instance_double 32 | ...) :as_null_object)) 33 | PATTERN 34 | 35 | # @!method have_received_usage(node) 36 | def_node_search :have_received_usage, <<~PATTERN 37 | (send 38 | (send nil? :expect 39 | (lvar $_)) :to 40 | (send nil? :have_received 41 | ...) 42 | ...) 43 | PATTERN 44 | 45 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 46 | return unless example?(node) 47 | 48 | null_double(node) do |var, receiver| 49 | have_received_usage(node) do |expected| 50 | next if expected != var 51 | 52 | add_offense(receiver) do |corrector| 53 | autocorrect(corrector, receiver) 54 | end 55 | end 56 | end 57 | end 58 | 59 | private 60 | 61 | def autocorrect(corrector, node) 62 | replacement = 'instance_spy' 63 | corrector.replace(node.loc.selector, replacement) 64 | 65 | double_source_map = node.parent.loc 66 | as_null_object_range = double_source_map 67 | .dot 68 | .join(double_source_map.selector) 69 | corrector.remove(as_null_object_range) 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/is_expected_specify.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check for `specify` with `is_expected` and one-liner expectations. 7 | # 8 | # @example 9 | # # bad 10 | # specify { is_expected.to be_truthy } 11 | # 12 | # # good 13 | # it { is_expected.to be_truthy } 14 | # 15 | # # good 16 | # specify do 17 | # # ... 18 | # end 19 | # specify { expect(sqrt(4)).to eq(2) } 20 | # 21 | class IsExpectedSpecify < Base 22 | extend AutoCorrector 23 | 24 | RESTRICT_ON_SEND = %i[specify].freeze 25 | IS_EXPECTED_METHODS = ::Set[:is_expected, :are_expected].freeze 26 | MSG = 'Use `it` instead of `specify`.' 27 | 28 | # @!method offense?(node) 29 | def_node_matcher :offense?, <<~PATTERN 30 | (block (send _ :specify) _ (send (send _ IS_EXPECTED_METHODS) ...)) 31 | PATTERN 32 | 33 | def on_send(node) 34 | block_node = node.parent 35 | return unless block_node&.single_line? && offense?(block_node) 36 | 37 | selector = node.loc.selector 38 | add_offense(selector) do |corrector| 39 | corrector.replace(selector, 'it') 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/it_behaves_like.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks that only one `it_behaves_like` style is used. 7 | # 8 | # @example `EnforcedStyle: it_behaves_like` (default) 9 | # # bad 10 | # it_should_behave_like 'a foo' 11 | # 12 | # # good 13 | # it_behaves_like 'a foo' 14 | # 15 | # @example `EnforcedStyle: it_should_behave_like` 16 | # # bad 17 | # it_behaves_like 'a foo' 18 | # 19 | # # good 20 | # it_should_behave_like 'a foo' 21 | # 22 | class ItBehavesLike < Base 23 | extend AutoCorrector 24 | include ConfigurableEnforcedStyle 25 | 26 | MSG = 'Prefer `%s` over `%s` when including ' \ 27 | 'examples in a nested context.' 28 | RESTRICT_ON_SEND = %i[it_behaves_like it_should_behave_like].freeze 29 | 30 | # @!method example_inclusion_offense(node) 31 | def_node_matcher :example_inclusion_offense, '(send _ % ...)' 32 | 33 | def on_send(node) 34 | example_inclusion_offense(node, alternative_style) do 35 | add_offense(node) do |corrector| 36 | corrector.replace(node.loc.selector, style.to_s) 37 | end 38 | end 39 | end 40 | 41 | private 42 | 43 | def message(_node) 44 | format(MSG, replacement: style, original: alternative_style) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/iterated_expectation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check that `all` matcher is used instead of iterating over an array. 7 | # 8 | # @example 9 | # # bad 10 | # it 'validates users' do 11 | # [user1, user2, user3].each { |user| expect(user).to be_valid } 12 | # end 13 | # 14 | # # good 15 | # it 'validates users' do 16 | # expect([user1, user2, user3]).to all(be_valid) 17 | # end 18 | # 19 | class IteratedExpectation < Base 20 | MSG = 'Prefer using the `all` matcher instead ' \ 21 | 'of iterating over an array.' 22 | 23 | # @!method each?(node) 24 | def_node_matcher :each?, <<~PATTERN 25 | (block 26 | (send ... :each) 27 | (args (arg $_)) 28 | $(...) 29 | ) 30 | PATTERN 31 | 32 | # @!method each_numblock?(node) 33 | def_node_matcher :each_numblock?, <<~PATTERN 34 | (numblock 35 | (send ... :each) _ $(...) 36 | ) 37 | PATTERN 38 | 39 | # @!method expectation?(node) 40 | def_node_matcher :expectation?, <<~PATTERN 41 | (send (send nil? :expect (lvar %)) :to ...) 42 | PATTERN 43 | 44 | def on_block(node) 45 | each?(node) do |arg, body| 46 | if single_expectation?(body, arg) || only_expectations?(body, arg) 47 | add_offense(node.send_node) 48 | end 49 | end 50 | end 51 | 52 | def on_numblock(node) 53 | each_numblock?(node) do |body| 54 | if single_expectation?(body, :_1) || only_expectations?(body, :_1) 55 | add_offense(node.send_node) 56 | end 57 | end 58 | end 59 | 60 | private 61 | 62 | def single_expectation?(body, arg) 63 | expectation?(body, arg) 64 | end 65 | 66 | def only_expectations?(body, arg) 67 | return false unless body.each_child_node.any? 68 | 69 | body.each_child_node.all? { |child| expectation?(child, arg) } 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/match_array.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks where `match_array` is used. 7 | # 8 | # This cop checks for the following: 9 | # 10 | # - Prefer `contain_exactly` when matching an array with values. 11 | # - Prefer `eq` when using `match_array` with an empty array literal. 12 | # 13 | # @example 14 | # # bad 15 | # it { is_expected.to match_array([content1, content2]) } 16 | # 17 | # # good 18 | # it { is_expected.to contain_exactly(content1, content2) } 19 | # 20 | # # good 21 | # it { is_expected.to match_array([content] + array) } 22 | # 23 | # # good 24 | # it { is_expected.to match_array(%w(tremble in fear foolish mortals)) } 25 | # 26 | class MatchArray < Base 27 | extend AutoCorrector 28 | 29 | MSG = 'Prefer `contain_exactly` when matching an array literal.' 30 | RESTRICT_ON_SEND = %i[match_array].freeze 31 | 32 | # @!method match_array_with_empty_array?(node) 33 | def_node_matcher :match_array_with_empty_array?, <<~PATTERN 34 | (send nil? :match_array (array)) 35 | PATTERN 36 | 37 | def on_send(node) 38 | return unless node.first_argument&.array_type? 39 | return if match_array_with_empty_array?(node) 40 | 41 | check_populated_array(node) 42 | end 43 | 44 | private 45 | 46 | def check_populated_array(node) 47 | return if node.first_argument.percent_literal? 48 | 49 | add_offense(node) do |corrector| 50 | array_contents = node.arguments.flat_map(&:to_a) 51 | corrector.replace( 52 | node, 53 | "contain_exactly(#{array_contents.map(&:source).join(', ')})" 54 | ) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/message_chain.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Check that chains of messages are not being stubbed. 7 | # 8 | # @example 9 | # # bad 10 | # allow(foo).to receive_message_chain(:bar, :baz).and_return(42) 11 | # 12 | # # good 13 | # thing = Thing.new(baz: 42) 14 | # allow(foo).to receive(:bar).and_return(thing) 15 | # 16 | class MessageChain < Base 17 | MSG = 'Avoid stubbing using `%s`.' 18 | RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze 19 | 20 | def on_send(node) 21 | add_offense( 22 | node.loc.selector, message: format(MSG, method: node.method_name) 23 | ) 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec/message_expectation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpec 6 | # Checks for consistent message expectation style. 7 | # 8 | # This cop can be configured in your configuration using the 9 | # `EnforcedStyle` option and supports `--auto-gen-config`. 10 | # 11 | # @example `EnforcedStyle: allow` (default) 12 | # 13 | # # bad 14 | # expect(foo).to receive(:bar) 15 | # 16 | # # good 17 | # allow(foo).to receive(:bar) 18 | # 19 | # @example `EnforcedStyle: expect` 20 | # 21 | # # bad 22 | # allow(foo).to receive(:bar) 23 | # 24 | # # good 25 | # expect(foo).to receive(:bar) 26 | # 27 | class MessageExpectation < Base 28 | include ConfigurableEnforcedStyle 29 | 30 | MSG = 'Prefer `%