diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index f1fb38f7d..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,154 +0,0 @@ -version: 2 - -rspec: &rspec - steps: - - checkout - - run: bundle install - - run: rake spec - -rubocop: &rubocop - steps: - - checkout - - run: bundle install - - run: rake internal_investigation - -jobs: - confirm_config_and_documentation: - docker: - - image: circleci/ruby - steps: - - checkout - - run: bundle install - - run: rake confirm_config documentation_syntax_check confirm_documentation - - # Ruby 2.3 - ruby-2.3-rspec: - docker: - - image: circleci/ruby:2.3 - <<: *rspec - ruby-2.3-rubocop: - docker: - - image: circleci/ruby:2.3 - <<: *rubocop - - # Ruby 2.4 - ruby-2.4-rspec: - docker: - - image: circleci/ruby:2.4 - <<: *rspec - ruby-2.4-rubocop: - docker: - - image: circleci/ruby:2.4 - <<: *rubocop - - # Ruby 2.5 - ruby-2.5-rspec: - docker: - - image: circleci/ruby:2.5 - <<: *rspec - ruby-2.5-rubocop: - docker: - - image: circleci/ruby:2.5 - <<: *rubocop - - # Ruby 2.6 - ruby-2.6-rspec: - docker: - - image: circleci/ruby:2.6 - <<: *rspec - ruby-2.6-rubocop: - docker: - - image: circleci/ruby:2.6 - <<: *rubocop - - # Ruby 2.7 - ruby-2.7-rspec: - docker: - - image: circleci/ruby:2.7 - <<: *rspec - ruby-2.7-rubocop: - docker: - - image: circleci/ruby:2.7 - <<: *rubocop - - edge-rubocop: - docker: - - image: circleci/ruby - steps: - - checkout - - run: - name: Use latest RuboCop from `master` - command: | - echo "gem 'rubocop', github: 'rubocop-hq/rubocop'" > Gemfile.local - - run: bundle install --no-cache - - run: rake spec - - run: rake internal_investigation - - # JRuby - jruby: - docker: - - image: circleci/jruby:9 - steps: - - checkout - - run: sudo apt-get install -y make - - run: bundle lock - - restore_cache: - keys: - - bundle-v2-{{ checksum "Gemfile.lock" }} - - bundle-v2- - - run: bundle install --path vendor/bundle - - save_cache: - key: bundle-v2-{{ checksum "Gemfile.lock" }} - paths: - - vendor/bundle - - run: rake internal_investigation spec - - code-climate: - docker: - - image: circleci/ruby - steps: - - checkout - - run: bundle install - - run: - name: Setup Code Climate test-reporter - command: | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - - run: - name: Run specs - command: | - ./cc-test-reporter before-build - rake coverage - ./cc-test-reporter after-build --exit-code $? - -workflows: - version: 2 - build: - jobs: - - confirm_config_and_documentation - - # Use `requires: [confirm_config_and_documentation]` to trick Circle CI into starting the slow jruby job early. - - ruby-2.3-rspec: - requires: [confirm_config_and_documentation] - - ruby-2.3-rubocop: - requires: [confirm_config_and_documentation] - - ruby-2.4-rspec: - requires: [confirm_config_and_documentation] - - ruby-2.4-rubocop: - requires: [confirm_config_and_documentation] - - ruby-2.5-rspec: - requires: [confirm_config_and_documentation] - - ruby-2.5-rubocop: - requires: [confirm_config_and_documentation] - - ruby-2.6-rspec: - requires: [confirm_config_and_documentation] - - ruby-2.6-rubocop: - requires: [confirm_config_and_documentation] - - ruby-2.7-rspec: - requires: [confirm_config_and_documentation] - - ruby-2.7-rubocop: - requires: [confirm_config_and_documentation] - - edge-rubocop: - requires: [confirm_config_and_documentation] - - jruby - - code-climate diff --git a/.codespellignore b/.codespellignore new file mode 100644 index 000000000..744bd7490 --- /dev/null +++ b/.codespellignore @@ -0,0 +1 @@ +xdescribe diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..bc91901da --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,7 @@ +# .git-blame-ignore-revs +# Add mdformat to the workflow in GitHub Actions +4874a5a4a2a58e76d343aaa02279cd93b16f5a30 +# Move node patterns into private scope +089491fb1fa173145f8e6eb1b511d4a7a1bf28ff +# Don't always define node patterns in private scope +ce09cb2b25b9f3778bf032d0caaa862da6635b54 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..30abe34a1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @rubocop/rubocop-rspec diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index eb300f83e..105999a15 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,34 +1,93 @@ # Contributing -If you encounter problems or have ideas for improvements or new features, please report them to the [issue tracker](https://github.com/rubocop-hq/rubocop-rspec/issues) or submit a pull request. Please, try to follow these guidelines when you do so. +If you encounter problems or have ideas for improvements or new features, please report them to the [issue tracker](https://github.com/rubocop/rubocop-rspec/issues) or submit a pull request. Please, try to follow these guidelines when you do so. ## Issue reporting -* Check that the issue has not already been reported. -* Check that the issue has not already been fixed in the latest code (a.k.a. `master`). -* Check if the issue is a non-goal of RuboCop RSpec. -* Be clear, concise, and precise in your description of the problem. -* Open an issue with a descriptive title and a summary in grammatically correct, complete sentences. -* Report the versions of `rubocop-rspec`, as well as the output of `rubocop -V`. -* Include any relevant code to the issue summary. +- Check that the issue has not already been reported. +- Check that the issue has not already been fixed in the latest code (a.k.a. `master`). +- Check if the issue is a non-goal of RuboCop RSpec. +- Be clear, concise, and precise in your description of the problem. +- Open an issue with a descriptive title and a summary in grammatically correct, complete sentences. +- Report the versions of `rubocop-rspec`, as well as the output of `rubocop -V`. +- Include any relevant code to the issue summary. ## Pull requests -1. Fork the project. -2. Create a feature branch. -3. Make sure to add tests. -4. Make sure the test suite passes (run `rake`). -5. Add a [changelog](https://github.com/rubocop-hq/rubocop-rspec/blob/master/CHANGELOG.md) entry. -6. Commit your changes. -7. Push to the branch. -8. Create new Pull Request. +1. Fork the project. +2. Create a feature branch. +3. Make sure to add tests. +4. Make sure the test suite passes (run `rake`). +5. Add a [changelog](https://github.com/rubocop/rubocop-rspec/blob/master/CHANGELOG.md) entry. +6. Commit your changes. +7. Push to the branch. +8. Create new Pull Request. + +### Spell Checking + +We are running [codespell](https://github.com/codespell-project/codespell) with [GitHub Actions](https://github.com/rubocop/rubocop-rspec/blob/master/.github/workflows/codespell.yml) to check spelling and +[codespell](https://pypi.org/project/codespell/). +`codespell` is written in [Python](https://www.python.org/) and you can run it with: + +```console +$ codespell --ignore-words=.codespellignore +``` + +### Linting YAML files + +We are running [yamllint](https://github.com/adrienverge/yamllint) for linting YAML files. This is also run by [GitHub Actions](https://github.com/rubocop/rubocop-rspec/blob/master/.github/workflows/linting.yml). +`yamllint` is written in [Python](https://www.python.org/) and you can run it with: + +```console +$ yamllint . +``` + +### Formatting Markdown files + +We are running [mdformat](https://github.com/executablebooks/mdformat) for formatting Markdown files. This is also run by [GitHub Actions](https://github.com/rubocop/rubocop-rspec/blob/master/.github/workflows/linting.yml). +`mdformat` is written in [Python](https://www.python.org/) and you can run it with: + +```console +$ mdformat . --number +``` + +### Test Coverage - Line and Branch + +We are using [Simplecov](https://github.com/colszowka/simplecov) to track test coverage. + +It is included and reported when you run `bundle exec rake` or `bundle exec rspec`. + +To view the coverage report, open the `coverage/index.html` file in your browser. + +E.g. on macOS: + +```console +$ open coverage/index.html +``` + +If you have unreachable lines, you can add `# :nocov` around those lines. The code itself or a comment should explain why the line is unreachable. + +Example: + +```ruby +# :nocov: +raise ArgumentError("Unsupported style :#{style}") +# :nocov: +``` + +This can happen for a few reasons, including: + +1. When you handle config with a case statement and there is no else block. +2. When matching with a node pattern even when you handle all cases: all other node types will be excluded before reaching your handler, because the node pattern will not match them. + +You will need full line and branch coverage to merge. This helps detect edge cases and prevent errors. ## Creating new cops -* Document examples of good and bad code in your cop. -* Add an entry to `config/default.yml`. It's an ordered list, so be sure to insert at the appropriate place. -* Run `bundle exec rake`. This will verify that the build passes as well as generate documentation and ensure that `config/default.yml` is up to date (don't forget to commit the documentation). -* Add tests for as many use cases as you can think of. Always add tests for both bad code that should register an offense and good code that should not. -* Common pitfalls: - * If your cop inspects code outside of an example, check for false positives when similarly named variables are used inside of the example. - * If your cop inspects code inside of an example, check that it works when the example is empty (empty `describe`, `it`, etc.). +- Document examples of good and bad code in your cop. +- Add an entry to `config/default.yml`. It's an ordered list, so be sure to insert at the appropriate place. +- Run `bundle exec rake`. This will verify that the build passes as well as generate documentation and ensure that `config/default.yml` is up to date (don't forget to commit the documentation). +- Add tests for as many use cases as you can think of. Always add tests for both bad code that should register an offense and good code that should not. +- Common pitfalls: + - If your cop inspects code outside of an example, check for false positives when similarly named variables are used inside of the example. + - If your cop inspects code inside of an example, check that it works when the example is empty (empty `describe`, `it`, etc.). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..e356c939e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,48 @@ +--- +name: Bug Report +about: Report an issue with RuboCop RSpec you've discovered. +--- + +*Be clear, concise and precise in your description of the problem. +Open an issue with a descriptive title and a summary in grammatically correct, +complete sentences.* + +*Use the template below when reporting bugs. Please, make sure that +you're running the latest stable RuboCop RSpec and that the problem you're reporting +hasn't been reported (and potentially fixed) already.* + +*Before filing the ticket you should replace all text above the horizontal +rule with your own words.* + +*In the case of false positive or false negative, please add the +corresponding cop name.* + +______________________________________________________________________ + +## Expected behavior + +Describe here how you expected RuboCop RSpec to behave in this particular situation. + +## Actual behavior + +Describe here what actually happened. +Please use `rubocop --debug` when pasting rubocop output as it contains additional information. + +## Steps to reproduce the problem + +This is extremely important! Providing us with a reliable way to reproduce +a problem will expedite its solution. + +## RuboCop RSpec version + +Include the output of `rubocop -V` or `bundle exec rubocop -V` if using Bundler. +If you see extension cop versions (e.g. `rubocop-performance`, `rubocop-rake`, and others) +output by `rubocop -V`, include them as well. Here's an example: + +```shell +$ [bundle exec] rubocop -V +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] + - rubocop-performance 1.22.1 + - rubocop-rake 0.6.0 + - rubocop-rspec 3.1.0 +``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..0cffb2019 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature Request +about: Suggest new RuboCop RSpec features or improvements to existing features. +--- + +## Is your feature request related to a problem? Please describe. + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +## Describe the solution you'd like + +A clear and concise description of what you want to happen. + +## Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +## Additional context + +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3b40d7539..8695fedf6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,17 +1,25 @@ **Replace this text with a summary of the changes in your PR. The more detailed you are, the better.** ---- +______________________________________________________________________ Before submitting the PR make sure the following are checked: -* [ ] Feature branch is up-to-date with `master` (if not - rebase it). -* [ ] Squashed related commits together. -* [ ] Added tests. -* [ ] Added an entry to the [changelog](https://github.com/rubocop-hq/rubocop-rspec/blob/master/CHANGELOG.md) if the new code introduces user-observable changes. -* [ ] The build (`bundle exec rake`) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit). +- [ ] Feature branch is up-to-date with `master` (if not - rebase it). +- [ ] Squashed related commits together. +- [ ] Added tests. +- [ ] Updated documentation. +- [ ] Added an entry to the `CHANGELOG.md` if the new code introduces user-observable changes. +- [ ] The build (`bundle exec rake`) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit). If you have created a new cop: -* [ ] Added the new cop to `config/default.yml`. -* [ ] The cop documents examples of good and bad code. -* [ ] The tests assert both that bad code is reported and that good code is not reported. +- [ ] Added the new cop to `config/default.yml`. +- [ ] The cop is configured as `Enabled: pending` in `config/default.yml`. +- [ ] The cop is configured as `Enabled: true` in `.rubocop.yml`. +- [ ] The cop documents examples of good and bad code. +- [ ] The tests assert both that bad code is reported and that good code is not reported. +- [ ] Set `VersionAdded: "<>"` in `default/config.yml`. + +If you have modified an existing cop's configuration options: + +- [ ] Set `VersionChanged: "<>"` in `config/default.yml`. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..b18fd2935 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 000000000..3a7b82333 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,18 @@ +name: CodeSpell +on: + - pull_request +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +jobs: + codespell: + name: CodeSpell + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: CodeSpell + uses: codespell-project/actions-codespell@master + with: + check_filenames: true + check_hidden: true + ignore_words_file: .codespellignore diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 000000000..9876c082e --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,31 @@ +name: Linting +on: + - pull_request +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +jobs: + yamllint: + name: Yamllint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Yamllint + uses: karancode/yamllint-github-action@master + with: + yamllint_strict: true + yamllint_format: parsable + yamllint_comment: true + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + mdformat: + name: Mdformat + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Mdformat + uses: ydah/mdformat-action@main + with: + number: true + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..88f234e1c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,145 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + confirm_config_and_documentation: + runs-on: ubuntu-latest + name: Confirm config and documentation + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - run: bundle exec rake confirm_config documentation_syntax_check confirm_documentation + + main: + runs-on: ubuntu-latest + strategy: + matrix: + ruby: + - "2.7" + - "3.0" + - "3.1" + - "3.2" + - "3.3" + - "3.4" + - ruby-head + - jruby-9.4 + task: + - internal_investigation + - spec + name: "Ruby ${{ matrix.ruby }}: ${{ matrix.task }}" + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "${{ matrix.ruby }}" + bundler-cache: true + - run: NO_COVERAGE=true bundle exec rake ${{ matrix.task }} + + coverage: + runs-on: ubuntu-latest + name: "Test coverage" + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - run: bundle exec rake spec + + edge-rubocop: + runs-on: ubuntu-latest + strategy: + matrix: + task: + - internal_investigation + - spec + name: "Edge RuboCop: ${{ matrix.task }}" + steps: + - uses: actions/checkout@v4 + - name: Use latest RuboCop from `master` + run: | + echo "gem 'rubocop', github: 'rubocop/rubocop'" > Gemfile.local + cat Gemfile.local + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Show RuboCop version + run: grep '^ rubocop' Gemfile.lock | sort + - run: NO_COVERAGE=true bundle exec rake ${{ matrix.task }} + + oldest-rubocop: + runs-on: ubuntu-latest + strategy: + matrix: + task: + - spec + name: "Oldest RuboCop: ${{ matrix.task }}" + steps: + - uses: actions/checkout@v4 + - name: Use oldest RuboCop allowed by gemspec + run: | + sed -nr "s/ *spec.add_dependency 'rubocop'.*'>= ([0-9\.]+)'/gem 'rubocop', '= \1'/p" \ + rubocop-rspec.gemspec > Gemfile.local + cat Gemfile.local + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Show RuboCop version + run: grep '^ rubocop' Gemfile.lock | sort + - run: NO_COVERAGE=true bundle exec rake ${{ matrix.task }} + + rspec4: + runs-on: ubuntu-latest + name: RSpec 4 + steps: + - uses: actions/checkout@v4 + - name: Use latest RSpec 4 from `4-0-dev` branch + run: | + sed -e '/rspec/d' -i Gemfile + cat << EOF > Gemfile.local + gem 'rspec', github: 'rspec/rspec', branch: '4-0-dev' + gem 'rspec-core', github: 'rspec/rspec', branch: '4-0-dev' + gem 'rspec-expectations', github: 'rspec/rspec', branch: '4-0-dev' + gem 'rspec-mocks', github: 'rspec/rspec', branch: '4-0-dev' + gem 'rspec-support', github: 'rspec/rspec', branch: '4-0-dev' + EOF + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - run: NO_COVERAGE=true bundle exec rake spec + + prism: + runs-on: ubuntu-latest + name: Prism + steps: + - uses: actions/checkout@v4 + - name: Use prism parser + run: | + cat << EOF > Gemfile.local + gem 'prism' + EOF + - name: set up Ruby + uses: ruby/setup-ruby@v1 + with: + # Specify the minimum Ruby version 2.7 required for Prism to run. + ruby-version: "2.7" + bundler-cache: true + - env: + NO_COVERAGE: true + PARSER_ENGINE: parser_prism + run: bundle exec rake spec diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..4fe16c6dc --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,46 @@ +name: Publish +on: + push: + branches: master + paths: lib/rubocop/rspec/version.rb +jobs: + publish: + name: Publish to RubyGems + runs-on: ubuntu-latest + if: github.repository_owner == 'rubocop' + permissions: + actions: write + contents: write + id-token: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + ruby-version: ruby + - uses: rubygems/release-gem@v1 + - name: Create a GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + bundle exec rake create_release_notes + gh release create $(git tag --points-at @) \ + --title "RuboCop RSpec $(git tag --points-at @)" \ + --notes-file relnotes.md + - name: Replace version in Antora config + env: + GH_TOKEN: ${{ github.token }} + run: | + sed -i 's/version:.*$/version: ~/' docs/antora.yml + if ! git diff --exit-code docs/antora.yml; then + branch=switch-docs-version-$(git tag --points-at @) + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git checkout -b "$branch" + git add docs/antora.yml + git commit -m "Switch docs version back" + git push -u origin "$branch" + gh pr create --fill --head "$branch" + fi diff --git a/.gitignore b/.gitignore index 4970ee61e..9669bf146 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -# rcov generated -coverage -coverage.data - # rdoc generated rdoc @@ -21,3 +17,11 @@ pkg .ruby-gemset .ruby-version + +# simplecov generated +coverage + +# vscode generated +.vscode + +/relnotes.md diff --git a/.rspec b/.rspec index 4e33a322b..5be63fcb0 100644 --- a/.rspec +++ b/.rspec @@ -1,3 +1,2 @@ --require spec_helper ---color --format documentation diff --git a/.rubocop.yml b/.rubocop.yml index a0b9122b9..e8625243b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,50 +1,295 @@ inherit_from: .rubocop_todo.yml -require: - - rubocop-rspec - - rubocop/cop/internal_affairs + +plugins: + - rubocop-performance + - rubocop-rake + - rubocop-rspec + - rubocop-internal_affairs AllCops: DisplayCopNames: true - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.7 + NewCops: disable Exclude: - 'vendor/**/*' - 'spec/fixtures/**/*' - 'tmp/**/*' - 'spec/smoke_tests/**/*.rb' -# See https://github.com/rubocop-hq/rubocop/issues/6410 +InternalAffairs/OnSendWithoutOnCSend: + Enabled: false + Layout/HashAlignment: + EnforcedHashRocketStyle: + - key + - table + EnforcedColonStyle: + - key + - table + +Layout/LineLength: + Max: 80 # default: 120 + AllowedPatterns: + - '^\s*# .*https?:\/\/.+\[.+\]\.?$' # Allow long asciidoc links + +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented + +Layout/MultilineOperationIndentation: + EnforcedStyle: indented + +Lint/InterpolationCheck: + Exclude: + - spec/**/*.rb + +# When the `edge-rubocop` build is red, and we decide to disable the cop, +# the rest of the builds become red if the cop has not yet been released. +# Instead of waiting for RuboCop releases to make `edge-rubocop` green, +# we prefer keeping disable directives here and there and check if they +# are still needed once in a while. +Lint/RedundantCopDisableDirective: Enabled: false +Lint/UselessAccessModifier: + MethodCreatingMethods: + - def_node_matcher + - def_node_search + Metrics/BlockLength: Exclude: - rubocop-rspec.gemspec - Rakefile - '**/*.rake' - - spec/**/*.rb + +Metrics/MethodLength: + Max: 15 Naming/FileName: Exclude: - lib/rubocop-rspec.rb -Layout/MultilineMethodCallIndentation: - EnforcedStyle: indented +Naming/InclusiveLanguage: + Enabled: true + CheckStrings: true + FlaggedTerms: + ' a offense': + Suggestions: + - an offense + auto-correct: + Suggestions: + - autocorrect + auto_correct: + Suggestions: + - autocorrect + ' a violation': + Suggestions: + - an offense + behaviour: + Suggestions: + - behavior + offence: + Suggestions: + - offense + 'does not registers': + Suggestions: + - does not register + violation: + Suggestions: + - offense + 'register no offense': + Suggestions: + - registers no offense -Layout/MultilineOperationIndentation: - EnforcedStyle: indented - -Lint/BooleanSymbol: - Exclude: - - lib/rubocop/cop/rspec/expect_actual.rb - - lib/rubocop/cop/rspec/focus.rb +RSpec: + Language: + Expectations: + - expect_correction + - expect_no_offenses + - expect_offense -Lint/InterpolationCheck: +RSpec/DescribeClass: Exclude: - - spec/**/*.rb + - spec/project/**/*.rb RSpec/ExampleLength: - Max: 30 + CountAsOne: + - heredoc + Max: 11 -RSpec/DescribeClass: +RSpec/MultipleExpectations: + Max: 2 + +RSpec/SpecFilePathFormat: Exclude: - - spec/project/**/*.rb + - spec/rubocop/cop/rspec/mixin/**/*.rb + +# `expect_offense` does not use Kernel#format or String#% +Style/FormatStringToken: + Exclude: + - spec/rubocop/**/*.rb + +Style/NumberedParameters: + Enabled: true + EnforcedStyle: disallow + +Style/RequireOrder: + Enabled: true + +# Enable RuboCop's pending cops up to v1.72 + +Gemspec/AddRuntimeDependency: {Enabled: true} +Gemspec/DeprecatedAttributeAssignment: {Enabled: true} +Gemspec/DevelopmentDependencies: {Enabled: true} +Gemspec/RequireMFA: {Enabled: true} +Layout/LineContinuationLeadingSpace: {Enabled: true} +Layout/LineContinuationSpacing: {Enabled: true} +Layout/LineEndStringConcatenationIndentation: {Enabled: true} +Layout/SpaceBeforeBrackets: {Enabled: true} +Lint/AmbiguousAssignment: {Enabled: true} +Lint/AmbiguousOperatorPrecedence: {Enabled: true} +Lint/AmbiguousRange: {Enabled: true} +Lint/ArrayLiteralInRegexp: {Enabled: true} +Lint/ConstantOverwrittenInRescue: {Enabled: true} +Lint/ConstantReassignment: {Enabled: true} +Lint/CopDirectiveSyntax: {Enabled: true} +Lint/DeprecatedConstants: {Enabled: true} +Lint/DuplicateBranch: {Enabled: true} +Lint/DuplicateMagicComment: {Enabled: true} +Lint/DuplicateMatchPattern: {Enabled: true} +Lint/DuplicateRegexpCharacterClassElement: {Enabled: true} +Lint/DuplicateSetElement: {Enabled: true} +Lint/EmptyBlock: {Enabled: true} +Lint/EmptyClass: {Enabled: true} +Lint/EmptyInPattern: {Enabled: true} +Lint/HashNewWithKeywordArgumentsAsDefault: {Enabled: true} +Lint/IncompatibleIoSelectWithFiberScheduler: {Enabled: true} +Lint/ItWithoutArgumentsInBlock: {Enabled: true} +Lint/LambdaWithoutLiteralBlock: {Enabled: true} +Lint/LiteralAssignmentInCondition: {Enabled: true} +Lint/MixedCaseRange: {Enabled: true} +Lint/NonAtomicFileOperation: {Enabled: true} +Lint/NoReturnInBeginEndBlocks: {Enabled: true} +Lint/NumberedParameterAssignment: {Enabled: true} +Lint/NumericOperationWithConstantResult: {Enabled: true} +Lint/OrAssignmentToConstant: {Enabled: true} +Lint/RedundantDirGlobSort: {Enabled: true} +Lint/RedundantRegexpQuantifiers: {Enabled: true} +Lint/RedundantTypeConversion: {Enabled: true} +Lint/RefinementImportMethods: {Enabled: true} +Lint/RequireRangeParentheses: {Enabled: true} +Lint/RequireRelativeSelfPath: {Enabled: true} +Lint/SharedMutableDefault: {Enabled: true} +Lint/SuppressedExceptionInNumberConversion: {Enabled: true} +Lint/SymbolConversion: {Enabled: true} +Lint/ToEnumArguments: {Enabled: true} +Lint/TripleQuotes: {Enabled: true} +Lint/UnescapedBracketInRegexp: {Enabled: true} +Lint/UnexpectedBlockArity: {Enabled: true} +Lint/UnmodifiedReduceAccumulator: {Enabled: true} +Lint/UselessConstantScoping: {Enabled: true} +Lint/UselessDefined: {Enabled: true} +Lint/UselessNumericOperation: {Enabled: true} +Lint/UselessRescue: {Enabled: true} +Lint/UselessRuby2Keywords: {Enabled: true} +Metrics/CollectionLiteralLength: {Enabled: true} +Naming/BlockForwarding: {Enabled: true} +Security/CompoundHash: {Enabled: true} +Security/IoMethods: {Enabled: true} +Style/AmbiguousEndlessMethodDefinition: {Enabled: true} +Style/ArgumentsForwarding: {Enabled: true} +Style/ArrayIntersect: {Enabled: true} +Style/BitwisePredicate: {Enabled: true} +Style/CollectionCompact: {Enabled: true} +Style/CombinableDefined: {Enabled: true} +Style/ComparableClamp: {Enabled: true} +Style/ConcatArrayLiterals: {Enabled: true} +Style/DataInheritance: {Enabled: true} +Style/DigChain: {Enabled: true} +Style/DirEmpty: {Enabled: true} +Style/DocumentDynamicEvalDefinition: {Enabled: true} +Style/EmptyHeredoc: {Enabled: true} +Style/EndlessMethod: {Enabled: true} +Style/EnvHome: {Enabled: true} +Style/ExactRegexpMatch: {Enabled: true} +Style/FetchEnvVar: {Enabled: true} +Style/FileEmpty: {Enabled: true} +Style/FileNull: {Enabled: true} +Style/FileRead: {Enabled: true} +Style/FileTouch: {Enabled: true} +Style/FileWrite: {Enabled: true} +Style/HashConversion: {Enabled: true} +Style/HashExcept: {Enabled: true} +Style/HashSlice: {Enabled: true} +Style/IfWithBooleanLiteralBranches: {Enabled: true} +Style/InPatternThen: {Enabled: true} +Style/ItAssignment: {Enabled: true} +Style/KeywordArgumentsMerging: {Enabled: true} +Style/MagicCommentFormat: {Enabled: true} +Style/MapCompactWithConditionalBlock: {Enabled: true} +Style/MapIntoArray: {Enabled: true} +Style/MapToHash: {Enabled: true} +Style/MapToSet: {Enabled: true} +Style/MinMaxComparison: {Enabled: true} +Style/MultilineInPatternThen: {Enabled: true} +Style/NegatedIfElseCondition: {Enabled: true} +Style/NestedFileDirname: {Enabled: true} +Style/NilLambda: {Enabled: true} +Style/NumberedParametersLimit: {Enabled: true} +Style/ObjectThen: {Enabled: true} +Style/OpenStructUse: {Enabled: true} +Style/OperatorMethodCall: {Enabled: true} +Style/QuotedSymbols: {Enabled: true} +Style/RedundantArgument: {Enabled: true} +Style/RedundantArrayConstructor: {Enabled: true} +Style/RedundantConstantBase: {Enabled: true} +Style/RedundantCurrentDirectoryInPath: {Enabled: true} +Style/RedundantDoubleSplatHashBraces: {Enabled: true} +Style/RedundantEach: {Enabled: true} +Style/RedundantFilterChain: {Enabled: true} +Style/RedundantFormat: {Enabled: true} +Style/RedundantHeredocDelimiterQuotes: {Enabled: true} +Style/RedundantInitialize: {Enabled: true} +Style/RedundantInterpolationUnfreeze: {Enabled: true} +Style/RedundantLineContinuation: {Enabled: true} +Style/RedundantRegexpArgument: {Enabled: true} +Style/RedundantRegexpConstructor: {Enabled: true} +Style/RedundantSelfAssignmentBranch: {Enabled: true} +Style/RedundantStringEscape: {Enabled: true} +Style/ReturnNilInPredicateMethodDefinition: {Enabled: true} +Style/SafeNavigationChainLength: {Enabled: true} +Style/SelectByRegexp: {Enabled: true} +Style/SendWithLiteralMethodName: {Enabled: true} +Style/SingleLineDoEndBlock: {Enabled: true} +Style/StringChars: {Enabled: true} +Style/SuperArguments: {Enabled: true} +Style/SuperWithArgsParentheses: {Enabled: true} +Style/SwapValues: {Enabled: true} +Style/YAMLFileRead: {Enabled: true} + +# Enable RuboCop Performance's pending cops up to v1.24 + +Performance/AncestorsInclude: {Enabled: true} +Performance/BigDecimalWithNumericArgument: {Enabled: true} +Performance/BlockGivenWithExplicitBlock: {Enabled: true} +Performance/CollectionLiteralInLoop: {Enabled: true} +Performance/ConcurrentMonotonicTime: {Enabled: true} +Performance/ConstantRegexp: {Enabled: true} +Performance/MapCompact: {Enabled: true} +Performance/MapMethodChain: {Enabled: true} +Performance/MethodObjectAsBlock: {Enabled: true} +Performance/RedundantEqualityComparisonBlock: {Enabled: true} +Performance/RedundantSortBlock: {Enabled: true} +Performance/RedundantSplitRegexpArgument: {Enabled: true} +Performance/RedundantStringChars: {Enabled: true} +Performance/ReverseFirst: {Enabled: true} +Performance/SortReverse: {Enabled: true} +Performance/Squeeze: {Enabled: true} +Performance/StringBytesize: {Enabled: true} +Performance/StringIdentifierArgument: {Enabled: true} +Performance/StringInclude: {Enabled: true} +Performance/Sum: {Enabled: true} +Performance/ZipWithoutBlock: {Enabled: true} + +# Enable our own pending cops. + +RSpec/IncludeExamples: {Enabled: true} diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d6a65b650..fb7116182 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,17 +1,11 @@ # This configuration was generated by -# `rubocop --auto-gen-config` -# on 2019-12-12 15:15:34 +0200 using RuboCop version 0.77.0. +# `rubocop --auto-gen-config --no-offense-counts --no-auto-gen-timestamp` +# using RuboCop version 1.72.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 9 -# Cop supports --auto-correct. -InternalAffairs/MethodNameEqual: - Enabled: false - -# Offense count: 1 -# Configuration parameters: CountComments. -Metrics/ClassLength: - Max: 104 +Rake/MethodDefinitionInTask: + Exclude: + - 'tasks/cut_release.rake' diff --git a/.simplecov b/.simplecov new file mode 100644 index 000000000..2b29bd2ba --- /dev/null +++ b/.simplecov @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +SimpleCov.start do + enable_coverage :branch + minimum_coverage line: 100, branch: 100 + add_filter '/spec/' + add_filter '/vendor/bundle/' +end diff --git a/.yamllint b/.yamllint new file mode 100644 index 000000000..eae05bc43 --- /dev/null +++ b/.yamllint @@ -0,0 +1,11 @@ +extends: default + +rules: + comments: + min-spaces-from-content: 1 + document-start: disable + line-length: + allow-non-breakable-inline-mappings: true + max: 100 + truthy: + check-keys: false diff --git a/.yardopts b/.yardopts new file mode 100644 index 000000000..4419be7d6 --- /dev/null +++ b/.yardopts @@ -0,0 +1,3 @@ +--markup markdown +--hide-void-return +--tag safety:"Cop Safety Information" diff --git a/CHANGELOG.md b/CHANGELOG.md index b7897bccd..8d7652184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,203 +1,714 @@ -# Change log +# Changelog ## Master (Unreleased) +- Mark `RSpec/IncludeExamples` as `SafeAutoCorrect: false`. ([@yujideveloper]) +- Fix a false positive for `RSpec/LeakyConstantDeclaration` when defining constants in explicit namespaces. ([@naveg]) + +## 3.6.0 (2025-04-18) + +- Fix false positive in `RSpec/Pending`, where it would mark the default block `it` as an offense. ([@bquorning]) +- Fix issue when `Style/ContextWording` is configured with a Prefix being interpreted as a boolean, like `on`. ([@sakuro]) +- Add new `RSpec/IncludeExamples` cop to enforce using `it_behaves_like` over `include_examples`. ([@dvandersluis]) +- Change `RSpec/ScatteredSetup` to allow `around` hooks to be scattered. ([@ydah]) +- Fix an error `RSpec/ChangeByZero` cop when without expect block. ([@lee266]) +- Fix a false positive for `RSpec/DescribedClass` when `SkipBlocks` is true and numblocks are used. ([@earlopain]) + +## 3.5.0 (2025-02-16) + +- Don't let `RSpec/PredicateMatcher` replace `respond_to?` with two arguments with the RSpec `respond_to` matcher. ([@bquorning]) +- Fix `RSpec/PredicateMatcher` support for `eql` and `equal` matchers. ([@bquorning]) +- Pluginfy RuboCop RSpec. ([@koic]) + +## 3.4.0 (2025-01-20) + +- Fix `RSpec/SortMetadata` cop to limit sorting to trailing metadata arguments. ([@cbliard]) +- Replace `RSpec/StringAsInstanceDoubleConstant` with `RSpec/VerifiedDoubleReference` configured to only support constant class references. ([@corsonknowles]) +- Fix `RSpec/EmptyExampleGroup` cop false positive when a simple conditional is used inside an iterator. ([@lovro-bikic]) + +## 3.3.0 (2024-12-12) + +- Deprecate `top_level_group?` method from `TopLevelGroup` mixin as all of its callers were intentionally removed from `Rubocop/RSpec`. ([@corsonknowles]) +- Fix false positive for RSpec/EmptyMetadata for splat kwargs. ([@pirj]) + +## 3.2.0 (2024-10-26) + +- Fix `RSpec/VoidExpect` to only operate inside an example block. ([@corsonknowles]) +- Change `RSpec/ContextWording` cop to always report an offense when both `Prefixes` and `AllowedPatterns` are empty. ([@ydah]) +- Add support for `and` and `or` compound matchers to `RSpec/ChangeByZero` cop. ([@ydah]) + +## 3.1.0 (2024-10-01) + +- Add `RSpec/StringAsInstanceDoubleConstant` to check for and correct strings used as instance_doubles. ([@corsonknowles]) +- Fix false-positive for `RSpec/UnspecifiedException` when a method is literally named `raise_exception`. ([@aarestad]) +- Fix false-positive for `RSpec/UnspecifiedException` when `not_to raise_error` is used within a block. ([@aarestad], [@G-Rath]) + +## 3.0.5 (2024-09-07) + +- Fix false-negative and error for `RSpec/MetadataStyle` when non-literal args are used in metadata in `EnforceStyle: hash`. ([@cbliard]) +- Improve offense message for `RSpec/IndexedLet`. ([@earlopain]) + +## 3.0.4 (2024-08-05) + +- Fix false-negative for `UnspecifiedException` when matcher is chained. ([@r7kamura]) + +## 3.0.3 (2024-07-12) + +- Add support for Unicode RIGHT SINGLE QUOTATION MARK in `RSpec/ExampleWording`. ([@jdufresne]) +- Suppress deprecation warning for `RSpec/MultipleExpectations`, `RSpec/MultipleMemoizedHelpers`, and `RSpec/NestedGroups` cops. ([@koic]) + +## 3.0.2 (2024-07-02) + +- Fix wrong autocorrect for `RSpec/ScatteredSetup` when hook contains heredoc. ([@earlopain]) +- Fix false negative for `RSpec/PredicateMatcher` when expectation contains custom failure message. ([@earlopain]) +- Facilitate the 3.0 upgrade flow with proper extracted cop messages. ([@jeppester]) + +## 3.0.1 (2024-06-11) + +- Bump RuboCop requirement to +1.61. ([@ydah]) + +## 3.0.0 (2024-06-11) + +- Remove extracted cops in `Capybara`, `FactoryBot` and `Rails` departments. ([@ydah]) +- Remove `RuboCop::RSpec::Language::NodePattern`. ([@ydah]) +- Remove `RSpec/FilePath` cop. ([@ydah]) +- Remove `RSpec/Capybara/FeatureMethods` cop. If you are using this cop, change it to use `RSpec/Dialect`. ([@ydah]) +- Add new `RSpec/MissingExpectationTargetMethod` cop. ([@krororo]) +- Fix an error for `RSpec/ScatteredSetup` when one of the hooks is an empty block. ([@earlopain]) + +These previously pending cops are now enabled by default: `RSpec/BeEmpty`, `RSpec/BeEq`, `RSpec/BeNil`, `RSpec/ChangeByZero`, `RSpec/ClassCheck`, `RSpec/ContainExactly`, `RSpec/DuplicatedMetadata`, `RSpec/EmptyMetadata`, `RSpec/EmptyOutput`, `RSpec/Eq`, `RSpec/ExcessiveDocstringSpacing`, `RSpec/ExpectInLet`, `RSpec/IdenticalEqualityAssertion`, `RSpec/IndexedLet`, `RSpec/IsExpectedSpecify`, `RSpec/MatchArray`, `RSpec/MetadataStyle`, `RSpec/NoExpectationExample`, `RSpec/PendingWithoutReason`, `RSpec/ReceiveMessages`, `RSpec/RedundantAround`, `RSpec/RedundantPredicateMatcher`, `RSpec/RemoveConst`, `RSpec/RepeatedSubjectCall`, `RSpec/SkipBlockInsideExample`, `RSpec/SortMetadata`, `RSpec/SpecFilePathFormat`, `RSpec/SpecFilePathSuffix`, `RSpec/SubjectDeclaration`, `RSpec/UndescriptiveLiteralsDescription`, and `RSpec/VerifiedDoubleReference`. + +Read more about how to upgrade in https://docs.rubocop.org/rubocop-rspec/upgrade_to_version_3.html + +## 2.31.0 (2024-06-07) + +- Support `AutoCorrect: contextual` option for LSP. ([@ydah]) + +## 2.30.0 (2024-06-03) + +- Add new `RSpec/ExpectInLet` cop. ([@yasu551]) + +## 2.29.2 (2024-05-02) + +- Fix beginless and endless range bug for RepeatedIncludeExample cop. ([@hasghari]) +- Fix a false positive for `RSpec/RepeatedSubjectCall` when subject is used as argument to function call. ([@K-S-A]) + +## 2.29.1 (2024-04-05) + +- Fix an error in the default configuration. ([@ydah]) + +## 2.29.0 (2024-04-04) + +- Fix an autocorrect error for `RSpec/ExpectActual`. ([@bquorning]) +- Add new `RSpec/UndescriptiveLiteralsDescription` cop. ([@ydah]) +- Add new `RSpec/EmptyOutput` cop. ([@bquorning]) + +## 2.28.0 (2024-03-30) + +- Extract RSpec Rails cops to a separate repository, [`rubocop-rspec_rails`](https://github.com/rubocop/rubocop-rspec_rails). The `rubocop-rspec_rails` repository is a dependency of `rubocop-rspec` and the cops related to rspec-rails are aliased (`RSpec/Rails/Foo` == `RSpecRails/Foo`) until v3.0 is released, so the change will be invisible to users until then. ([@ydah]) + +## 2.27.1 (2024-03-03) + +- Fix a false positive for `RSpec/RepeatedSubjectCall` when `subject.method_call`. ([@ydah]) +- Add configuration option `OnlyStaticConstants` to `RSpec/DescribedClass`. ([@ydah]) + +## 2.27.0 (2024-03-01) + +- Add new `RSpec/IsExpectedSpecify` cop. ([@ydah]) +- Add new `RSpec/RepeatedSubjectCall` cop. ([@drcapulet]) +- Add support for `assert_true`, `assert_false`, `assert_not_equal`, `assert_not_nil`, `*_empty`, `*_predicate`, `*_kind_of`, `*_in_delta`, `*_match`, `*_instance_of` and `*_includes` assertions in `RSpec/Rails/MinitestAssertions`. ([@ydah], [@G-Rath]) +- Support asserts with messages in `RSpec/BeEmpty`. ([@G-Rath]) +- Fix a false positive for `RSpec/ExpectActual` when used with rspec-rails routing matchers. ([@naveg]) +- Add configuration option `ResponseMethods` to `RSpec/Rails/HaveHttpStatus`. ([@ydah]) +- Fix a false negative for `RSpec/DescribedClass` when class with constant. ([@ydah]) +- Fix a false positive for `RSpec/ExampleWithoutDescription` when `specify` with multi-line block and missing description. ([@ydah]) +- Fix an incorrect autocorrect for `RSpec/ChangeByZero` when compound expectations with line break before `.by(0)`. ([@ydah]) + +## 2.26.1 (2024-01-05) + +- Fix an error for `RSpec/SharedExamples` when using examples without argument. ([@ydah]) + +## 2.26.0 (2024-01-04) + +- Add new `RSpec/RedundantPredicateMatcher` cop. ([@ydah]) +- Add new `RSpec/RemoveConst` cop. ([@swelther]) +- Add support for correcting "it will" (future tense) for `RSpec/ExampleWording`. ([@jdufresne]) +- Add support for `symbol` style for `RSpec/SharedExamples`. ([@jessieay]) +- Ensure `PendingWithoutReason` can detect violations inside shared groups. ([@robinaugh]) + +## 2.25.0 (2023-10-27) + +- Add support single quoted string and percent string and heredoc for `RSpec/Rails/HttpStatus`. ([@ydah]) +- Change to be inline disable for `RSpec/SpecFilePathFormat` like `RSpec/FilePath`. ([@ydah]) +- Fix a false positive for `RSpec/MetadataStyle` with example groups having multiple string arguments. ([@franzliedke]) + +## 2.24.1 (2023-09-23) + +- Fix an error when using `RSpec/FilePath` and revert to enabled by default. If you have already moved to `RSpec/SpecFilePathSuffix` and `RSpec/SpecFilePathFormat`, disable `RSpec/FilePath` explicitly as `Enabled: false`. The `RSpec/FilePath` before migration and the `RSpec/SpecFilePathSuffix` and `RSpec/SpecFilePathFormat` as the target are available respectively. ([@ydah]) + +## 2.24.0 (2023-09-08) + +- Split `RSpec/FilePath` into `RSpec/SpecFilePathSuffix` and `RSpec/SpecFilePathFormat`. `RSpec/FilePath` cop is disabled by default and the two new cops are pending and need to be enabled explicitly. ([@ydah]) +- Add new `RSpec/Eq` cop. ([@ydah]) +- Add `RSpec/MetadataStyle` and `RSpec/EmptyMetadata` cops. ([@r7kamura]) +- Add support `RSpec/Rails/HttpStatus` when `have_http_status` with string argument. ([@ydah]) +- Fix an infinite loop error when `RSpec/ExcessiveDocstringSpacing` finds a description with non-ASCII leading/trailing whitespace. ([@bcgraham]) +- Fix an incorrect autocorrect for `RSpec/ReceiveMessages` when return values declared between stubs. ([@marocchino]) +- Fix a false positive `RSpec/Focus` when chained method call and inside define method. ([@ydah]) + +## 2.23.2 (2023-08-09) + +- Fix an incorrect autocorrect for `RSpec/ReceiveMessages` when method is only non-word character. ([@marocchino]) +- Fix a false positive for `RSpec/ReceiveMessages` when return with splat. ([@marocchino]) + +## 2.23.1 (2023-08-07) + +- Mark to `Safe: false` for `RSpec/Rails/NegationBeValid` cop. ([@ydah]) +- Declare autocorrect as unsafe for `RSpec/ReceiveMessages`. ([@bquorning]) + +## 2.23.0 (2023-07-30) + +- Add new `RSpec/Rails/NegationBeValid` cop. ([@ydah]) +- Fix a false negative for `RSpec/ExcessiveDocstringSpacing` when finds description with em space. ([@ydah]) +- Fix a false positive for `RSpec/EmptyExampleGroup` when example group with examples defined in `if` branch inside iterator. ([@ydah]) +- Update the message output of `RSpec/ExpectActual` to include the word 'value'. ([@corydiamand]) +- Fix a false negative for `RSpec/Pending` when `it` without body. ([@ydah]) +- Add new `RSpec/ReceiveMessages` cop. ([@ydah]) +- Change default.yml path to use `**/spec/*` instead of `spec/*`. ([@ydah]) +- Add `AllowedIdentifiers` and `AllowedPatterns` configuration option to `RSpec/IndexedLet`. ([@ydah]) +- Fix `RSpec/NamedSubject` when block has no body. ([@splattael]) +- Fix `RSpec/LetBeforeExamples` autocorrect incompatible with `RSpec/ScatteredLet` autocorrect. ([@ydah]) +- Update `RSpec/Focus` to support `shared_context` and `shared_examples`. ([@tmaier]) + +## 2.22.0 (2023-05-06) + +- Extract factory_bot cops to a separate repository, [`rubocop-factory_bot`](https://github.com/rubocop/rubocop-factory_bot). The `rubocop-factory_bot` repository is a dependency of `rubocop-rspec` and the factory_bot cops are aliased (`RSpec/FactoryBot/Foo` == `FactoryBot/Foo`) until v3.0 is released, so the change will be invisible to users until then. ([@ydah]) + +## 2.21.0 (2023-05-05) + +- Fix a false positive in `RSpec/IndexedLet` with suffixes after index-like numbers. ([@pirj]) +- Fix an error for `RSpec/Rails/HaveHttpStatus` with comparison with strings containing non-numeric characters. ([@ydah]) +- Fix an error for `RSpec/MatchArray` when `match_array` with no argument. ([@ydah]) +- Add support `a_block_changing` and `changing` for `RSpec/ChangeByZero`. ([@ydah]) +- Drop Ruby 2.6 support. ([@ydah]) + +## 2.20.0 (2023-04-18) + +- Add new `RSpec/IndexedLet` cop. ([@dmitrytsepelev]) +- Add new `RSpec/BeEmpty` cop. ([@ydah], [@bquorning]) +- Add autocorrect support for `RSpec/ScatteredSetup`. ([@ydah]) +- Add support `be_status` style for `RSpec/Rails/HttpStatus`. ([@ydah]) +- Add support for shared example groups to `RSpec/EmptyLineAfterExampleGroup`. ([@pirj]) +- Add support for `RSpec/HaveHttpStatus` when using `response.code`. ([@ydah]) +- Fix order of expected and actual in correction for `RSpec/Rails/MinitestAssertions`. ([@mvz]) +- Fix a false positive for `RSpec/DescribedClassModuleWrapping` when RSpec.describe numblock is nested within a module. ([@ydah]) +- Fix a false positive for `RSpec/FactoryBot/ConsistentParenthesesStyle` inside `&&`, `||` and `:?` when `omit_parentheses` is on. ([@dmitrytsepelev]) +- Fix a false positive for `RSpec/PendingWithoutReason` when pending/skip has a reason inside an example group. ([@ydah]) +- Fix a false negative for `RSpec/RedundantAround` when redundant numblock `around`. ([@ydah]) +- Change `RSpec/ContainExactly` to ignore calls with no arguments, and change `RSpec/MatchArray` to ignore calls with an empty array literal argument. ([@ydah], [@bquorning]) +- Make `RSpec/MatchArray` and `RSpec/ContainExactly` pending. ([@ydah]) + +## 2.19.0 (2023-03-06) + +- Fix a false positive for `RSpec/ContextWording` when context is interpolated string literal or execute string. ([@ydah]) +- Fix a false positive for `RSpec/DescribeMethod` when multi-line describe without `#` and `.` at the beginning. ([@ydah], [@pirj]) +- Fix a false positive for `RSpec/VariableName` when inside non-spec code. ([@ydah]) +- Fix a false positive for `RSpec/VariableDefinition` when inside non-spec code. ([@ydah]) +- Add new `RSpec/PendingBlockInsideExample` cop. ([@ydah]) +- Add `RSpec/RedundantAround` cop. ([@r7kamura]) +- Add `RSpec/Rails/TravelAround` cop. ([@r7kamura]) +- Add `RSpec/ContainExactly` and `RSpec/MatchArray` cops. ([@faucct]) +- Fix a false positive for `RSpec/PendingWithoutReason` when not inside example and pending/skip with block. ([@ydah], [@pirj]) +- Fix a false positive for `RSpec/PendingWithoutReason` when `skip` is passed a block inside example. ([@ydah], [@pirj]) +- Rename `RSpec/PendingBlockInsideExample` cop to `RSpec/SkipBlockInsideExample`. ([@pirj]) +- Deprecate `send_pattern`/`block_pattern`/`numblock_pattern` helpers in favour of using node pattern explicitly. ([@pirj], [@ydah]) +- Fix an incorrect autocorrect for `RSpec/VerifiedDoubleReference` when namespaced class. ([@ydah]) + +## 2.18.1 (2023-01-19) + +- Add `rubocop-capybara` version constraint to prevent sudden cop enabling when it hits 3.0. ([@pirj]) + +## 2.18.0 (2023-01-16) + +- Extract Capybara cops to a separate repository, [`rubocop-capybara`](https://github.com/rubocop/rubocop-capybara). The `rubocop-capybara` repository is a dependency of `rubocop-rspec` and the Capybara cops are aliased (`RSpec/Capybara/Foo` == `Capybara/Foo`) until v3.0 is released, so the change will be invisible to users until then. ([@pirj]) + +## 2.17.1 (2023-01-16) + +- Fix a false negative for `RSpec/Pending` when using skipped in metadata is multiline string. ([@ydah]) +- Fix a false positive for `RSpec/NoExpectationExample` when using skipped in metadata is multiline string. ([@ydah]) +- Fix a false positive for `RSpec/ContextMethod` when multi-line context with `#` at the beginning. ([@ydah]) +- Fix an incorrect autocorrect for `RSpec/PredicateMatcher` when multiline expect and predicate method with heredoc. ([@ydah]) +- Fix a false positive for `RSpec/PredicateMatcher` when `include` with multiple argument. ([@ydah]) + +## 2.17.0 (2023-01-13) + +- Fix a false positive for `RSpec/PendingWithoutReason` when pending/skip is argument of methods. ([@ydah]) +- Add new `RSpec/Capybara/MatchStyle` cop. ([@ydah]) +- Add new `RSpec/Rails/MinitestAssertions` cop. ([@ydah]) +- Fix a false positive for `RSpec/PendingWithoutReason` when not inside example. ([@ydah]) +- Fix a false negative for `RSpec/PredicateMatcher` when using `include` and `respond_to`. ([@ydah]) +- Fix a false positive for `RSpec/StubbedMock` when stubbed message expectation with a block and block parameter. ([@ydah]) + +## 2.16.0 (2022-12-13) + +- Add new `RSpec/FactoryBot/FactoryNameStyle` cop. ([@ydah]) +- Improved processing speed for `RSpec/Be`, `RSpec/ExpectActual`, `RSpec/ImplicitExpect`, `RSpec/MessageSpies`, `RSpec/PredicateMatcher` and `RSpec/Rails/HaveHttpStatus`. ([@ydah]) +- Fix wrong autocorrection in `n_times` style on `RSpec/FactoryBot/CreateList`. ([@r7kamura]) +- Fix a false positive for `RSpec/FactoryBot/ConsistentParenthesesStyle` when using `generate` with multiple arguments. ([@ydah]) +- Mark `RSpec/BeEq` as `Safe: false`. ([@r7kamura]) +- Add `RSpec/DuplicatedMetadata` cop. ([@r7kamura]) +- Mark `RSpec/BeEql` as `Safe: false`. ([@r7kamura]) +- Add `RSpec/PendingWithoutReason` cop. ([@r7kamura]) + +## 2.15.0 (2022-11-03) + +- Fix a false positive for `RSpec/RepeatedDescription` when different its block expectations are used. ([@ydah]) +- Add `named_only` style to `RSpec/NamedSubject`. ([@kuahyeow]) +- Fix `RSpec/FactoryBot/ConsistentParenthesesStyle` to ignore calls without the first positional argument. ([@pirj]) +- Fix `RSpec/FactoryBot/ConsistentParenthesesStyle` to ignore calls inside a Hash or an Array. ([@pirj]) +- Fix `RSpec/NestedGroups` to correctly use `AllowedGroups` config. ([@samrjenkins]) +- Remove `Runners` and `HookScopes` RSpec DSL elements from configuration. ([@pirj]) +- Add `with default RSpec/Language config` helper to `lib` (under `rubocop/rspec/shared_contexts/default_rspec_language_config_context`), to allow use for downstream cops based on `RuboCop::Cop::RSpec::Base`. ([@smcgivern]) + +## 2.14.2 (2022-10-25) + +- Fix an incorrect autocorrect for `FactoryBot/ConsistentParenthesesStyle` with `omit_parentheses` option when method name and first argument are not on same line. ([@ydah]) +- Fix autocorrection loop in `RSpec/ExampleWording` for insufficient example wording. ([@pirj]) +- Fix `RSpec/SortMetadata` not to reorder arguments of `include_`/`it_behaves_like`. ([@pirj]) +- Fix a false positive for `RSpec/NoExpectationExample` when allowed pattern methods with arguments. ([@ydah]) +- Change `RSpec/FilePath` so that it only checks suffix when path is under spec/routing or type is defined as routing. ([@r7kamura]) + +## 2.14.1 (2022-10-24) + +- Fix an error for `RSpec/Rails/InferredSpecType` with redundant type before other Hash metadata. ([@ydah]) + +## 2.14.0 (2022-10-23) + +- Add `require_implicit` style to `RSpec/ImplicitSubject`. ([@r7kamura]) +- Fix a false positive for `RSpec/Capybara/SpecificMatcher` when `have_css("a")` without attribute. ([@ydah]) +- Update `RSpec/ExampleWording` cop to raise error for insufficient descriptions. ([@akrox58]) +- Add new `RSpec/Capybara/NegationMatcher` cop. ([@ydah]) +- Add `AllowedPatterns` configuration option to `RSpec/NoExpectationExample`. ([@ydah]) +- Improve `RSpec/NoExpectationExample` cop to ignore examples skipped or pending via metadata. ([@pirj]) +- Add `RSpec/FactoryBot/ConsistentParenthesesStyle` cop. ([@Liberatys]) +- Add `RSpec/Rails/InferredSpecType` cop. ([@r7kamura]) +- Add new `RSpec/Capybara/SpecificActions` cop. ([@ydah]) +- Update `config/default.yml` removing deprecated option to make the config correctable by users. ([@ignaciovillaverde]) +- Do not attempt to auto-correct example groups with `include_examples` in `RSpec/LetBeforeExamples`. ([@pirj]) +- Add new `RSpec/SortMetadata` cop. ([@leoarnold]) +- Add support for subject! method to `RSpec/SubjectDeclaration`. ([@ydah]) + +## 2.13.2 (2022-09-23) + +- Fix an error for `RSpec/Capybara/SpecificFinders` with no parentheses. ([@ydah]) +- Fix a false positive for `RSpec/NoExpectationExample` with pending using `skip` or `pending` inside an example. ([@ydah]) +- Exclude `have_text` and `have_content` that raise `ArgumentError` with `RSpec/Capybara/VisibilityMatcher` where `:visible` is an invalid option. ([@ydah]) +- Fix a false negative for `RSpec/Capybara/VisibilityMatcher` with negative matchers. ([@ydah]) + +## 2.13.1 (2022-09-12) + +- Include config/obsoletion.yml in the gemspec. ([@hosamaly]) + +## 2.13.0 (2022-09-12) + +- Fix `RSpec/FilePath` cop missing mismatched expanded namespace. ([@sl4vr]) +- Add new `AllowConsecutiveOneLiners` (default true) option for `RSpec/EmptyLineAfterHook` cop. ([@ngouy]) +- Add autocorrect support for `RSpec/EmptyExampleGroup`. ([@r7kamura]) +- Fix `RSpec/ChangeByZero` with compound expressions using `&` or `|` operators. ([@BrianHawley]) +- Add `RSpec/NoExpectationExample`. ([@r7kamura]) +- Add some expectation methods to default configuration. ([@r7kamura]) +- Fix a false positive for `RSpec/Capybara/SpecificMatcher`. ([@ydah]) +- Fix a false negative for `RSpec/Capybara/SpecificMatcher` for `have_field`. ([@ydah]) +- Fix a false positive for `RSpec/Capybara/SpecificMatcher` when may not have a `href` by `have_link`. ([@ydah]) +- Add `NegatedMatcher` configuration option to `RSpec/ChangeByZero`. ([@ydah]) +- Add new `RSpec/Capybara/SpecificFinders` cop. ([@ydah]) +- Add support for numblocks to `RSpec/AroundBlock`, `RSpec/EmptyLineAfterHook`, `RSpec/ExpectInHook`, `RSpec/HookArgument`, `RSpec/HooksBeforeExamples`, `RSpec/IteratedExpectation`, and `RSpec/NoExpectationExample`. ([@ydah]) +- Fix incorrect documentation URLs when using `rubocop --show-docs-url`. ([@r7kamura]) +- Add `AllowedGroups` configuration option to `RSpec/NestedGroups`. ([@ydah]) +- Deprecate `IgnoredPatterns` option in favor of the `AllowedPatterns` options. ([@ydah]) +- Add `AllowedPatterns` configuration option to `RSpec/ContextWording`. ([@ydah]) +- Add `RSpec/ClassCheck` cop. ([@r7kamura]) +- Fix a false positive for `RSpec/Capybara/SpecificMatcher` when pseudo-classes. ([@ydah]) +- Fix a false negative for `RSpec/SubjectStub` when the subject is declared with the `subject!` method and called by name. ([@eikes]) +- Support `Array.new(n)` on `RSpec/FactoryBot/CreateList` cop. ([@r7kamura]) + +## 2.12.1 (2022-07-03) + +- Fix a false positive for `RSpec/Capybara/SpecificMatcher`. ([@ydah]) + +## 2.12.0 (2022-07-02) + +- Fix incorrect path suggested by `RSpec/FilePath` cop when second argument contains spaces. ([@tejasbubane]) +- Fix autocorrect for EmptyLineSeparation. ([@johnny-miyake]) +- Add new `RSpec/Capybara/SpecificMatcher` cop. ([@ydah]) +- Fixed false offense detection in `FactoryBot/CreateList` when a n.times block is including method calls in the factory create arguments. ([@ngouy]) +- Fix error in `RSpec/RSpec/FactoryBot/CreateList` cop for empty block. ([@tejasbubane]) +- Update `RSpec/MultipleExpectations` cop documentation with examples of aggregate_failures use. ([@edgibbs]) +- Declare autocorrect as unsafe for `RSpec/VerifiedDoubleReference`. ([@Drowze]) +- Add new `RSpec/Rails/HaveHttpStatus` cop. ([@akiomik]) + +## 2.11.1 (2022-05-18) + +- Fix a regression in `RSpec/ExpectChange` flagging chained method calls. ([@pirj]) + +## 2.11.0 (2022-05-18) + +- Drop Ruby 2.5 support. ([@ydah]) +- Add new `RSpec/ChangeByZero` cop. ([@ydah]) +- Improve `RSpec/ExpectChange` to detect namespaced and top-level constants. ([@M-Yamashita01]) +- Introduce an amendment to `Metrics/BlockLength` to exclude spec files. ([@luke-hill]) + +## 2.10.0 (2022-04-19) + +- Fix a false positive for `RSpec/EmptyExampleGroup` when expectations in case statement. ([@ydah]) +- Add `RSpec/VerifiedDoubleReference` cop. ([@t3h2mas]) +- Make `RSpec/BeNil` cop configurable with a `be_nil` style and a `be` style. ([@bquorning]) +- Fix `Capybara/CurrentPathExpectation` autocorrect incompatible with `Style/TrailingCommaInArguments` autocorrect. ([@ydah]) + +## 2.9.0 (2022-02-28) + +- Add new `RSpec/BeNil` cop. ([@bquorning]) +- Add new `RSpec/BeEq` cop. ([@bquorning]) + +## 2.8.0 (2022-01-24) + +- Fix `RSpec/FactoryBot/SyntaxMethods` and `RSpec/Capybara/FeatureMethods` to inspect shared groups. ([@pirj]) +- Fix `RSpec/LeadingSubject` failure in non-spec code. ([@pirj]) +- Add bad example to `RSpec/SubjectStub` cop. ([@oshiro3]) +- Replace non-styleguide cops `StyleGuide` attribute with `Reference`. ([@pirj]) +- Fix `RSpec/SubjectStub` to disallow stubbing of subjects defined in parent example groups. ([@pirj]) + +## 2.7.0 (2021-12-26) + +- Add new `RSpec/FactoryBot/SyntaxMethods` cop. ([@leoarnold]) +- Exclude `task` type specs from `RSpec/DescribeClass` cop. ([@harry-graham]) + +## 2.6.0 (2021-11-08) + +- Fix merging RSpec DSL configuration from third-party gems. ([@pirj]) +- Fix `RSpec/ExcessiveDocstringSpacing` false positive for multi-line indented strings. ([@G-Rath]) +- Fix `Include` configuration for sub-departments. ([@pirj]) +- Ignore heredocs in `RSpec/ExcessiveDocstringSpacing`. ([@G-Rath]) +- Stop `RSpec/ExampleWording` from trying to correct heredocs. ([@G-Rath]) +- Add autocorrect support for `RSpec/VariableDefinition`. ([@r7kamura]) + +## 2.5.0 (2021-09-21) + +- Declare autocorrect as unsafe for `ExpectChange`. ([@francois-ferrandis]) +- Fix each example for `RSpec/HookArgument`. ([@lokhi]) +- Exclude unrelated Rails directories from `RSpec/DescribeClass`. ([@MothOnMars]) +- Add `RSpec/ExcessiveDocstringSpacing` cop. ([@G-Rath]) +- Add `RSpec/SubjectDeclaration` cop. ([@dswij]) +- Fix excessive whitespace removal in `RSpec/EmptyHook` autocorrection. ([@pirj]) +- Bump RuboCop requirement to v1.19.0. ([@pirj]) +- Fix false positive in `RSpec/IteratedExpectation` when there is single, non-expectation statement in the block body. ([@Darhazer]) + +## 2.4.0 (2021-06-09) + +- Update `RSpec/FilePath` to check suffix when given a non-constant top-level node (e.g. features). ([@topalovic]) +- Add missing documentation for `single_statement_only` style of `RSpec/ImplicitSubject` cop. ([@tejasbubane]) +- Fix an exception in `DescribedClass` when accessing a constant on a variable in a spec that is nested in a namespace. ([@rrosenblum]) +- Add new `RSpec/IdenticalEqualityAssertion` cop. ([@tejasbubane]) +- Add `RSpec/Rails/AvoidSetupHook` cop. ([@paydaylight]) +- Fix false negative in `RSpec/ExpectChange` cop with block style and chained method call. ([@tejasbubane]) + +## 2.3.0 (2021-04-28) + +- Allow `RSpec/ContextWording` to accept multi-word prefixes. ([@hosamaly]) +- Drop support for ruby 2.4. ([@bquorning]) +- Add `CountAsOne` configuration option to `RSpec/ExampleLength`. ([@stephannv]) +- Fix a false positive for `RSpec/RepeatedExampleGroupBody` when `pending` or `skip` have argument(s). ([@Tietew]) + +## 2.2.0 (2021-02-02) + +- Fix `HooksBeforeExamples`, `LeadingSubject`, `LetBeforeExamples` and `ScatteredLet` autocorrection to take into account inline comments and comments immediately before the moved node. ([@Darhazer]) +- Improve rubocop-rspec performance. ([@Darhazer], [@bquorning]) +- Include `Enabled: true` to prevent a mismatched configuration parameter warning when `RSpec` cops are explicitly enabled in the user configuration. ([@pirj]) + +## 2.1.0 (2020-12-17) + +- Fix `RSpec/FilePath` false positive for relative file path runs with long namespaces. ([@ahukkanen]) +- Update `RSpec/Focus` to have auto-correction. ([@dvandersluis]) + +## 2.0.1 (2020-12-02) + +- Fixed infinite loop in `RSpec/ExpectActual` autocorrection when both expected and actual values are literals. ([@Darhazer]) + +## 2.0.0 (2020-11-06) + +- Remove deprecated class `::RuboCop::Cop::RSpec::Cop`. ([@bquorning]) +- Retire `RSpec/InvalidPredicateMatcher` cop. ([@pirj]) +- Remove the code responsible for filtering files to inspect. ([@pirj]) +- Make RSpec language elements configurable. ([@sl4vr]) +- Remove `CustomIncludeMethods` `RSpec/EmptyExampleGroup` option in favour of the new RSpec DSL configuration. ([@pirj]) +- Enabled pending cop (`RSpec/StubbedMock`). ([@pirj]) + +## 2.0.0.pre (2020-10-22) + +- Update RuboCop dependency to v1.0.0. ([@bquorning]) +- Change namespace of several cops (`Capybara/*` -> `RSpec/Capybara/*`, `FactoryBot/*` -> `RSpec/FactoryBot/*`, `Rails/*` -> `RSpec/Rails/*`). ([@pirj], [@bquorning]) + +## 1.44.1 (2020-10-20) + +- Relax `rubocop-ast` version constraint. ([@PhilCoggins]) + +## 1.44.0 (2020-10-20) + +- Move our documentation from rubocop-rspec.readthedocs.io to docs.rubocop.org/rubocop-rspec. ([@bquorning]) +- Add `RSpec/RepeatedIncludeExample` cop. ([@biinari]) +- Add `RSpec/StubbedMock` cop. ([@bquorning], [@pirj]) +- Add `IgnoredMetadata` configuration option to `RSpec/DescribeClass`. ([@Rafix02]) +- Fix false positives in `RSpec/EmptyExampleGroup`. ([@pirj]) +- Fix a false positive for `RSpec/EmptyExampleGroup` when example is defined in an `if` branch. ([@koic]) + +## 1.43.2 (2020-08-25) + +- Fix `RSpec/FilePath` when checking a file with a shared example. ([@pirj]) +- Fix subject nesting detection in `RSpec/LeadingSubject`. ([@pirj]) + +## 1.43.1 (2020-08-17) + +- Fix `RSpec/FilePath` when checking a file defining e.g. an empty class. ([@bquorning]) + +## 1.43.0 (2020-08-17) + +- Add a new base cop class `::RuboCop::Cop::RSpec::Base`. The old base class `::RuboCop::Cop::RSpec::Cop` is deprecated, and will be removed in the next major release. ([@bquorning]) +- Add support for subject detection after includes and example groups in `RSpec/LeadingSubject`. ([@pirj]) +- Ignore trailing punctuation in context description prefix. ([@elliterate]) +- Relax `RSpec/VariableDefinition` cop so interpolated and multiline strings are accepted even when configured to enforce the `symbol` style. ([@bquorning]) +- Fix `RSpec/EmptyExampleGroup` to flag example groups with examples in invalid scopes. ([@mlarraz]) +- Fix `RSpec/EmptyExampleGroup` to ignore examples groups with examples defined inside iterators. ([@pirj]) +- Improve `RSpec/NestedGroups`, `RSpec/FilePath`, `RSpec/DescribeMethod`, `RSpec/MultipleDescribes`, `RSpec/DescribeClass`'s top-level example group detection. ([@pirj]) +- Add detection of `let!` with a block-pass or a string literal to `RSpec/LetSetup`. ([@pirj]) +- Add `IgnoredPatterns` configuration option to `RSpec/VariableName`. ([@jtannas]) +- Add `RSpec/MultipleMemoizedHelpers` cop. ([@mockdeep]) + +## 1.42.0 (2020-07-09) + +- Update RuboCop dependency to 0.87.0 because of changes to internal APIs. ([@bquorning], [@Darhazer]) + +## 1.41.0 (2020-07-03) + +- Extend the list of Rails spec types for `RSpec/DescribeClass`. ([@pirj]) +- Fix `FactoryBot/AttributeDefinedStatically` to allow `#traits_for_enum` without a block. ([@harrylewis]) +- Improve the performance of `FactoryBot/AttributeDefinedStatically`, `RSpec/InstanceVariable`, `RSpec/LetSetup`, `RSpec/NestedGroups` and `RSpec/ReturnFromStub`. ([@andrykonchin]) + +## 1.40.0 (2020-06-11) + +- Add new `RSpec/VariableName` cop. ([@tejasbubane]) +- Add new `RSpec/VariableDefinition` cop. ([@tejasbubane]) +- Expand `Capybara/VisibilityMatcher` to support more than just `have_selector`. ([@twalpole]) +- Add new `SpecSuffixOnly` option to `RSpec/FilePath` cop. ([@zdennis]) +- Allow `RSpec/RepeatedExampleGroupBody` to differ only by described_class. ([@robotdana]) +- Fix `RSpec/FilePath` detection across sibling directories. ([@rolfschmidt]) +- Improve the performance of `RSpec/SubjectStub` by an order of magnitude. ([@andrykonchin]) + +## 1.39.0 (2020-05-01) + +- Fix `RSpec/FilePath` detection when absolute path includes test subject. ([@eitoball]) +- Add new `Capybara/VisibilityMatcher` cop. ([@aried3r]) +- Ignore String constants by `RSpec/Describe`. ([@AlexWayfer]) +- Drop support for ruby 2.3. ([@bquorning]) +- Fix multiple cops to detect `let` with proc argument. ([@tejasbubane]) +- Add autocorrect support for `RSpec/ScatteredLet`. ([@Darhazer]) +- Add new `RSpec/EmptyHook` cop. ([@tejasbubane]) + ## 1.38.1 (2020-02-15) -* Fix `RSpec/RepeatedDescription` to detect descriptions with interpolation and methods. ([@lazycoder9][]) +- Fix `RSpec/RepeatedDescription` to detect descriptions with interpolation and methods. ([@lazycoder9]) ## 1.38.0 (2020-02-11) -* Fix `RSpec/InstanceVariable` detection inside custom matchers. ([@pirj][]) -* Fix `RSpec/ScatteredSetup` to distinguish hooks with different metadata. ([@pirj][]) -* Add autocorrect support for `RSpec/ExpectActual` cop. ([@dduugg][], [@pirj][]) -* Add `RSpec/RepeatedExampleGroupBody` cop. ([@lazycoder9][]) -* Add `RSpec/RepeatedExampleGroupDescription` cop. ([@lazycoder9][]) -* Add block name and other lines to `RSpec/ScatteredSetup` message. ([@elebow][]) -* Fix `RSpec/RepeatedDescription` to take into account example metadata. ([@lazycoder9][]) +- Fix `RSpec/InstanceVariable` detection inside custom matchers. ([@pirj]) +- Fix `RSpec/ScatteredSetup` to distinguish hooks with different metadata. ([@pirj]) +- Add autocorrect support for `RSpec/ExpectActual` cop. ([@dduugg], [@pirj]) +- Add `RSpec/RepeatedExampleGroupBody` cop. ([@lazycoder9]) +- Add `RSpec/RepeatedExampleGroupDescription` cop. ([@lazycoder9]) +- Add block name and other lines to `RSpec/ScatteredSetup` message. ([@elebow]) +- Fix `RSpec/RepeatedDescription` to take into account example metadata. ([@lazycoder9]) ## 1.37.1 (2019-12-16) -* Improve message and description of `FactoryBot/FactoryClassName`. ([@ybiquitous][]) -* Fix `FactoryBot/FactoryClassName` to ignore `Hash` and `OpenStruct`. ([@jfragoulis][]) +- Improve message and description of `FactoryBot/FactoryClassName`. ([@ybiquitous]) +- Fix `FactoryBot/FactoryClassName` to ignore `Hash` and `OpenStruct`. ([@jfragoulis]) ## 1.37.0 (2019-11-25) -* Implement `RSpec/DescribedClassModuleWrapping` to disallow RSpec statements within a module. ([@kellysutton][]) -* Fix documentation rake task to support Rubocop 0.75. ([@nickcampbell18][]) -* Fix `RSpec/SubjectStub` to detect implicit subjects stubbed. ([@QQism][]) -* Fix `RSpec/Pending` not flagging `skip` with string values. ([@pirj][]) -* Add `AllowedExplicitMatchers` config option for `RSpec/PredicateMatcher`. ([@mkrawc][]) -* Add `FactoryBot/FactoryClassName` cop. ([@jfragoulis][]) +- Implement `RSpec/DescribedClassModuleWrapping` to disallow RSpec statements within a module. ([@kellysutton]) +- Fix documentation rake task to support RuboCop 0.75. ([@nickcampbell18]) +- Fix `RSpec/SubjectStub` to detect implicit subjects stubbed. ([@QQism]) +- Fix `RSpec/Pending` not flagging `skip` with string values. ([@pirj]) +- Add `AllowedExplicitMatchers` config option for `RSpec/PredicateMatcher`. ([@mkrawc]) +- Add `FactoryBot/FactoryClassName` cop. ([@jfragoulis]) ## 1.36.0 (2019-09-27) -* Fix `RSpec/DescribedClass`'s error when `described_class` is used as part of a constant. ([@pirj][]) -* Fix `RSpec/ExampleWording` autocorrect of multi-line docstrings. ([@pirj][]) -* Add `RSpec/ContextMethod` cop, to detect method names in `context`. ([@geniou][]) -* Update RuboCop dependency to 0.68.1 with support for children matching node pattern syntax. ([@pirj][]) -* Add `RSpec/EmptyLineAfterExample` cop to check that there is an empty line after example blocks. ([@pirj][]) -* Fix `Capybara/CurrentPathExpectation` auto-corrector, to include option `ignore_query: true`. ([@onumis][]) -* Fix `RSpec/Focus` detecting mixed array/hash metadata. ([@dgollahon][]) -* Fix `RSpec/Focus` to also detect `pending` examples. ([@dgollahon][]) +- Fix `RSpec/DescribedClass`'s error when `described_class` is used as part of a constant. ([@pirj]) +- Fix `RSpec/ExampleWording` autocorrect of multi-line docstrings. ([@pirj]) +- Add `RSpec/ContextMethod` cop, to detect method names in `context`. ([@geniou]) +- Update RuboCop dependency to 0.68.1 with support for children matching node pattern syntax. ([@pirj]) +- Add `RSpec/EmptyLineAfterExample` cop to check that there is an empty line after example blocks. ([@pirj]) +- Fix `Capybara/CurrentPathExpectation` auto-corrector, to include option `ignore_query: true`. ([@onumis]) +- Fix `RSpec/Focus` detecting mixed array/hash metadata. ([@dgollahon]) +- Fix `RSpec/Focus` to also detect `pending` examples. ([@dgollahon]) ## 1.35.0 (2019-08-02) -* Add `RSpec/ImplicitBlockExpectation` cop. ([@pirj][]) +- Add `RSpec/ImplicitBlockExpectation` cop. ([@pirj]) ## 1.34.1 (2019-07-31) -* Fix `RSpec/DescribedClass`'s error when a local variable is part of the namespace. ([@pirj][]) +- Fix `RSpec/DescribedClass`'s error when a local variable is part of the namespace. ([@pirj]) ## 1.34.0 (2019-07-23) -* Remove `AggregateFailuresByDefault` config option of `RSpec/MultipleExpectations`. ([@pirj][]) -* Add `RSpec/LeakyConstantDeclaration` cop. ([@jonatas][], [@pirj][]) -* Improve `aggregate_failures` metadata detection of `RSpec/MultipleExpectations`. ([@pirj][]) -* Improve `RSpec/SubjectStub` detection and message. ([@pirj][]) -* Change message of `RSpec/LetSetup` cop to be more descriptive. ([@foton][]) -* Improve `RSpec/ExampleWording` to handle interpolated example messages. ([@nc-holodakg][]) -* Improve detection by allowing the use of `RSpec` as a top-level constant. ([@pirj][]) -* Fix `RSpec/DescribedClass`'s incorrect detection. ([@pirj][]) -* Improve `RSpec/DescribedClass`'s ability to detect inside modules and classes. ([@pirj][]) +- Remove `AggregateFailuresByDefault` config option of `RSpec/MultipleExpectations`. ([@pirj]) +- Add `RSpec/LeakyConstantDeclaration` cop. ([@jonatas], [@pirj]) +- Improve `aggregate_failures` metadata detection of `RSpec/MultipleExpectations`. ([@pirj]) +- Improve `RSpec/SubjectStub` detection and message. ([@pirj]) +- Change message of `RSpec/LetSetup` cop to be more descriptive. ([@foton]) +- Improve `RSpec/ExampleWording` to handle interpolated example messages. ([@nc-holodakg]) +- Improve detection by allowing the use of `RSpec` as a top-level constant. ([@pirj]) +- Fix `RSpec/DescribedClass`'s incorrect detection. ([@pirj]) +- Improve `RSpec/DescribedClass`'s ability to detect inside modules and classes. ([@pirj]) ## 1.33.0 (2019-05-13) -* Let `RSpec/DescribedClass` pass `Struct` instantiation closures. ([@schmijos][]) -* Fixed `RSpec/ContextWording` missing `context`s with metadata. ([@pirj][]) -* Fix `FactoryBot/AttributeDefinedStatically` not working with an explicit receiver. ([@composerinteralia][]) -* Add `RSpec/Dialect` enforces custom RSpec dialects. ([@gsamokovarov][]) -* Fix redundant blank lines in `RSpec/MultipleSubjects`'s autocorrect. ([@pirj][]) -* Drop support for ruby `2.2`. ([@bquorning][]) +- Let `RSpec/DescribedClass` pass `Struct` instantiation closures. ([@schmijos]) +- Fixed `RSpec/ContextWording` missing `context`s with metadata. ([@pirj]) +- Fix `FactoryBot/AttributeDefinedStatically` not working with an explicit receiver. ([@composerinteralia]) +- Add `RSpec/Dialect` enforces custom RSpec dialects. ([@gsamokovarov]) +- Fix redundant blank lines in `RSpec/MultipleSubjects`'s autocorrect. ([@pirj]) +- Drop support for ruby `2.2`. ([@bquorning]) ## 1.32.0 (2019-01-27) -* Add `RSpec/Yield` cop, suggesting using the `and_yield` method when stubbing a method, accepting a block. ([@Darhazer][]) -* Fix `FactoryBot/CreateList` autocorrect crashing when the factory is called with a block=. ([@Darhazer][]) -* Fixed `RSpec/Focus` not flagging some cases of `RSpec.describe` with `focus: true`. ([@Darhazer][]) -* Fixed `RSpec/Pending` not flagging some cases of `RSpec.describe` with `:skip`. ([@Darhazer][]) -* Fix false positive in `RSpec/ReceiveCounts` when method name `exactly`, `at_least` or `at_most` is used along with `times`, without being an RSpec API. ([@Darhazer][]) +- Add `RSpec/Yield` cop, suggesting using the `and_yield` method when stubbing a method, accepting a block. ([@Darhazer]) +- Fix `FactoryBot/CreateList` autocorrect crashing when the factory is called with a block=. ([@Darhazer]) +- Fixed `RSpec/Focus` not flagging some cases of `RSpec.describe` with `focus: true`. ([@Darhazer]) +- Fixed `RSpec/Pending` not flagging some cases of `RSpec.describe` with `:skip`. ([@Darhazer]) +- Fix false positive in `RSpec/ReceiveCounts` when method name `exactly`, `at_least` or `at_most` is used along with `times`, without being an RSpec API. ([@Darhazer]) ## 1.31.0 (2019-01-02) -* Add `IgnoreSharedExamples` option for `RSpec/NamedSubject`. ([@RST-J][]) -* Add autocorrect support for `Capybara/CurrentPathExpectation` cop. ([@ypresto][]) -* Add support for built-in `exists` matcher for `RSpec/PredicateMatcher` cop. ([@mkenyon][]) -* `SingleArgumentMessageChain` no longer reports an array as it's only argument as an offense. ([@Darhazer][]) +- Add `IgnoreSharedExamples` option for `RSpec/NamedSubject`. ([@RST-J]) +- Add autocorrect support for `Capybara/CurrentPathExpectation` cop. ([@ypresto]) +- Add support for built-in `exists` matcher for `RSpec/PredicateMatcher` cop. ([@mkenyon]) +- `SingleArgumentMessageChain` no longer reports an array as it's only argument as an offense. ([@Darhazer]) ## 1.30.1 (2018-11-01) -* `FactoryBot/CreateList` now ignores `times` blocks with an argument. ([@Darhazer][]) +- `FactoryBot/CreateList` now ignores `times` blocks with an argument. ([@Darhazer]) ## 1.30.0 (2018-10-08) -* Add config to `RSpec/VerifiedDoubles` to enforcement of verification on unnamed doubles. ([@BrentWheeldon][]) -* Fix `FactoryBot/AttributeDefinedStatically` not working when there is a non-symbol key. ([@vzvu3k6k][]) -* Fix false positive in `RSpec/ImplicitSubject` when `is_expected` is used inside `its()` block. ([@Darhazer][]) -* Add `single_statement_only` style to `RSpec/ImplicitSubject` as a more relaxed alternative to `single_line_only`. ([@Darhazer][]) -* Add `RSpec/UnspecifiedException` as a default cop to encourage more-specific `expect{}.to raise_error(ExceptionType)`, or `raise_exception` style handling of exceptions. ([@daveworth][]) +- Add config to `RSpec/VerifiedDoubles` to enforcement of verification on unnamed doubles. ([@BrentWheeldon]) +- Fix `FactoryBot/AttributeDefinedStatically` not working when there is a non-symbol key. ([@vzvu3k6k]) +- Fix false positive in `RSpec/ImplicitSubject` when `is_expected` is used inside `its()` block. ([@Darhazer]) +- Add `single_statement_only` style to `RSpec/ImplicitSubject` as a more relaxed alternative to `single_line_only`. ([@Darhazer]) +- Add `RSpec/UnspecifiedException` as a default cop to encourage more-specific `expect{}.to raise_error(ExceptionType)`, or `raise_exception` style handling of exceptions. ([@daveworth]) ## 1.29.1 (2018-09-01) -* Fix false negative in `FactoryBot/AttributeDefinedStatically` when attribute is defined on `self`. ([@Darhazer][]) -* `RSpec/FactoryBot` cops will now also inspect the `spec/factories.rb` path by default. ([@bquorning][]) +- Fix false negative in `FactoryBot/AttributeDefinedStatically` when attribute is defined on `self`. ([@Darhazer]) +- `RSpec/FactoryBot` cops will now also inspect the `spec/factories.rb` path by default. ([@bquorning]) ## 1.29.0 (2018-08-25) -* `RSpec/InstanceVariable` - Recommend local variables in addition to `let`. ([@jaredbeck][]) -* Add `RSpec/ImplicitSubject` cop. ([@Darhazer][]) -* Add `RSpec/HooksBeforeExamples` cop. ([@Darhazer][]) +- `RSpec/InstanceVariable` - Recommend local variables in addition to `let`. ([@jaredbeck]) +- Add `RSpec/ImplicitSubject` cop. ([@Darhazer]) +- Add `RSpec/HooksBeforeExamples` cop. ([@Darhazer]) ## 1.28.0 (2018-08-14) -* Add `RSpec/ReceiveNever` cop enforcing usage of `not_to receive` instead of `never` matcher. ([@Darhazer][]) -* Fix false positive in `RSpec/EmptyLineAfterExampleGroup` cop when example is inside `if`. ([@Darhazer][]) -* Add `RSpec/MissingExampleGroupArgument` to enforce first argument for an example group. ([@geniou][]) -* Drop support for ruby `2.1`. ([@bquorning][]) -* Add `FactoryBot/AttributeDefinedStatically` cop to help FactoryBot users with the deprecation of static attributes. ([@composerinteralia][], [@seanpdoyle][]) -* Remove `FactoryBot/DynamicAttributeDefinedStatically` and `FactoryBot/StaticAttributeDefinedDynamically` cops. ([@composerinteralia][]) +- Add `RSpec/ReceiveNever` cop enforcing usage of `not_to receive` instead of `never` matcher. ([@Darhazer]) +- Fix false positive in `RSpec/EmptyLineAfterExampleGroup` cop when example is inside `if`. ([@Darhazer]) +- Add `RSpec/MissingExampleGroupArgument` to enforce first argument for an example group. ([@geniou]) +- Drop support for ruby `2.1`. ([@bquorning]) +- Add `FactoryBot/AttributeDefinedStatically` cop to help FactoryBot users with the deprecation of static attributes. ([@composerinteralia], [@seanpdoyle]) +- Remove `FactoryBot/DynamicAttributeDefinedStatically` and `FactoryBot/StaticAttributeDefinedDynamically` cops. ([@composerinteralia]) ## 1.27.0 (2018-06-14) -* `RSpec/LeadingSubject` now enforces subject to be before any examples, hooks or let declarations. ([@Darhazer][]) -* Fix `RSpec/NotToNot` to highlight only the selector (`not_to` or `to_not`), so it works also on `expect { ... }` blocks. ([@bquorning][]) -* Add `RSpec/EmptyLineAfterHook` cop. ([@bquorning][]) -* Add `RSpec/EmptyLineAfterExampleGroup` cop to check that there is an empty line after example group blocks. ([@bquorning][]) -* Fix `RSpec/DescribeClass` crashing on `RSpec.describe` without arguments. ([@Darhazer][]) -* Bump RuboCop requirement to v0.56.0. ([@bquorning][]) -* Fix `RSpec/OverwritingSetup` crashing if a variable is used as an argument for `let`. ([@Darhazer][]) +- `RSpec/LeadingSubject` now enforces subject to be before any examples, hooks or let declarations. ([@Darhazer]) +- Fix `RSpec/NotToNot` to highlight only the selector (`not_to` or `to_not`), so it works also on `expect { ... }` blocks. ([@bquorning]) +- Add `RSpec/EmptyLineAfterHook` cop. ([@bquorning]) +- Add `RSpec/EmptyLineAfterExampleGroup` cop to check that there is an empty line after example group blocks. ([@bquorning]) +- Fix `RSpec/DescribeClass` crashing on `RSpec.describe` without arguments. ([@Darhazer]) +- Bump RuboCop requirement to v0.56.0. ([@bquorning]) +- Fix `RSpec/OverwritingSetup` crashing if a variable is used as an argument for `let`. ([@Darhazer]) ## 1.26.0 (2018-06-06) -* Fix false positive in `RSpec/EmptyExampleGroup` cop when methods named like a RSpec method are used. ([@Darhazer][]) -* Fix `Capybara/FeatureMethods` not working when there is require before the spec. ([@Darhazer][]) -* Fix `RSpec/EmptyLineAfterFinalLet`: allow a comment to be placed after latest let, requiring empty line after the comment. ([@Darhazer][]) -* Add `RSpec/ReceiveCounts` cop to enforce usage of :once and :twice matchers. ([@Darhazer][]) +- Fix false positive in `RSpec/EmptyExampleGroup` cop when methods named like a RSpec method are used. ([@Darhazer]) +- Fix `Capybara/FeatureMethods` not working when there is require before the spec. ([@Darhazer]) +- Fix `RSpec/EmptyLineAfterFinalLet`: allow a comment to be placed after latest let, requiring empty line after the comment. ([@Darhazer]) +- Add `RSpec/ReceiveCounts` cop to enforce usage of :once and :twice matchers. ([@Darhazer]) ## 1.25.1 (2018-04-10) -* Fix false positive in `RSpec/Pending` cop when pending is used as a method name. ([@Darhazer][]) -* Fix `FactoryBot/DynamicAttributeDefinedStatically` false positive when using symbol proc argument for a sequence. ([@tdeo][]) +- Fix false positive in `RSpec/Pending` cop when pending is used as a method name. ([@Darhazer]) +- Fix `FactoryBot/DynamicAttributeDefinedStatically` false positive when using symbol proc argument for a sequence. ([@tdeo]) ## 1.25.0 (2018-04-07) -* Add `RSpec/SharedExamples` cop to enforce consistent usage of string to titleize shared examples. ([@anthony-robin][]) -* Add `RSpec/Be` cop to enforce passing argument to the generic `be` matcher. ([@Darhazer][]) -* Fix false positives in `StaticAttributeDefinedDynamically` and `ReturnFromStub` when a const is used in an array or hash. ([@Darhazer][]) -* Add `RSpec/Pending` cop to enforce no existing pending or skipped examples. This is disabled by default. ([@patrickomatic][]) -* Fix `RSpec/NestedGroups` cop support --auto-gen-config. ([@walf443][]) -* Fix false positives in `Capybara/FeatureMethods` when feature methods are used as property names in a factory. ([@Darhazer][]) -* Allow configuring enabled methods in `Capybara/FeatureMethods`. ([@Darhazer][]) -* Add `FactoryBot/CreateList` cop. ([@Darhazer][]) +- Add `RSpec/SharedExamples` cop to enforce consistent usage of string to titleize shared examples. ([@anthony-robin]) +- Add `RSpec/Be` cop to enforce passing argument to the generic `be` matcher. ([@Darhazer]) +- Fix false positives in `StaticAttributeDefinedDynamically` and `ReturnFromStub` when a const is used in an array or hash. ([@Darhazer]) +- Add `RSpec/Pending` cop to enforce no existing pending or skipped examples. This is disabled by default. ([@patrickomatic]) +- Fix `RSpec/NestedGroups` cop support --auto-gen-config. ([@walf443]) +- Fix false positives in `Capybara/FeatureMethods` when feature methods are used as property names in a factory. ([@Darhazer]) +- Allow configuring enabled methods in `Capybara/FeatureMethods`. ([@Darhazer]) +- Add `FactoryBot/CreateList` cop. ([@Darhazer]) ## 1.24.0 (2018-03-06) -* Compatibility with RuboCop v0.53.0. ([@bquorning][]) -* The `Rails/HttpStatus` cop is unavailable if the `rack` gem cannot be loaded. ([@bquorning][]) -* Fix `Rails/HttpStatus` not working with custom HTTP status codes. ([@bquorning][]) -* Fix `FactoryBot/StaticAttributeDefinedDynamically` to handle empty block. ([@abrom][]) -* Fix false positive in `FactoryBot/DynamicAttributeDefinedStatically` when a before/after callback has a symbol proc argument. ([@abrom][]) +- Compatibility with RuboCop v0.53.0. ([@bquorning]) +- The `Rails/HttpStatus` cop is unavailable if the `rack` gem cannot be loaded. ([@bquorning]) +- Fix `Rails/HttpStatus` not working with custom HTTP status codes. ([@bquorning]) +- Fix `FactoryBot/StaticAttributeDefinedDynamically` to handle empty block. ([@abrom]) +- Fix false positive in `FactoryBot/DynamicAttributeDefinedStatically` when a before/after callback has a symbol proc argument. ([@abrom]) ## 1.23.0 (2018-02-23) -* Add `RSpec/Rails/HttpStatus` cop to enforce consistent usage of the status format (numeric or symbolic). ([@anthony-robin][], [@jojos003][]) -* Fix false negative in `RSpec/ReturnFromStub` when a constant is being returned by the stub. ([@Darhazer][]) -* Fix `FactoryBot/DynamicAttributeDefinedStatically` to handle dynamic attributes inside arrays/hashes. ([@abrom][]) -* Add `FactoryBot/StaticAttributeDefinedDynamically` (based on dynamic attribute cop). ([@abrom][]) +- Add `RSpec/Rails/HttpStatus` cop to enforce consistent usage of the status format (numeric or symbolic). ([@anthony-robin], [@jojos003]) +- Fix false negative in `RSpec/ReturnFromStub` when a constant is being returned by the stub. ([@Darhazer]) +- Fix `FactoryBot/DynamicAttributeDefinedStatically` to handle dynamic attributes inside arrays/hashes. ([@abrom]) +- Add `FactoryBot/StaticAttributeDefinedDynamically` (based on dynamic attribute cop). ([@abrom]) ## 1.22.2 (2018-02-01) -* Fix error in `RSpec/DescribedClass` when working on an empty `describe` block. ([@bquorning][]) +- Fix error in `RSpec/DescribedClass` when working on an empty `describe` block. ([@bquorning]) ## 1.22.1 (2018-01-17) -* Fix false positives in `RSpec/ReturnFromStub`. ([@Darhazer][]) +- Fix false positives in `RSpec/ReturnFromStub`. ([@Darhazer]) ## 1.22.0 (2018-01-10) -* Updates `describe_class` to account for RSpecs `:system` wrapper of rails system tests. ([@EliseFitz15][]) -* Add `RSpec/ExpectChange` cop to enforce consistent usage of the change matcher. ([@Darhazer][]) -* Add autocorrect support to `RSpec/LetBeforeExamples`. ([@Darhazer][]) -* Fix `RSpec/InstanceVariable` flagging instance variables inside dynamically defined class. ([@Darhazer][]) -* Add autocorrect support for `RSpec/ReturnFromStub` cop. ([@bquorning][]) -* Add `RSpec/ExampleWithoutDescription` cop. ([@Darhazer][]) +- Updates `describe_class` to account for RSpecs `:system` wrapper of rails system tests. ([@EliseFitz15]) +- Add `RSpec/ExpectChange` cop to enforce consistent usage of the change matcher. ([@Darhazer]) +- Add autocorrect support to `RSpec/LetBeforeExamples`. ([@Darhazer]) +- Fix `RSpec/InstanceVariable` flagging instance variables inside dynamically defined class. ([@Darhazer]) +- Add autocorrect support for `RSpec/ReturnFromStub` cop. ([@bquorning]) +- Add `RSpec/ExampleWithoutDescription` cop. ([@Darhazer]) ## 1.21.0 (2017-12-13) -* Compatibility with RuboCop v0.52.0. ([@bquorning][]) -* Improve performance when user does not override default RSpec Pattern config. ([@walf443][]) -* Add `AggregateFailuresByDefault` configuration for `RSpec/MultipleExpectations` cop. ([@onk][]) +- Compatibility with RuboCop v0.52.0. ([@bquorning]) +- Improve performance when user does not override default RSpec Pattern config. ([@walf443]) +- Add `AggregateFailuresByDefault` configuration for `RSpec/MultipleExpectations` cop. ([@onk]) ## 1.20.1 (2017-11-15) -* Add "without" to list of default allowed prefixes for `RSpec/ContextWording`. ([@bquorning][]) +- Add "without" to list of default allowed prefixes for `RSpec/ContextWording`. ([@bquorning]) ## 1.20.0 (2017-11-09) -* Rename namespace `FactoryGirl` to `FactoryBot` following original library update. ([@walf443][]) -* Fix exception in `RSpec/ReturnFromStub` on empty block. ([@yevhene][]) -* Add `RSpec/ContextWording` cop. ([@pirj][], [@telmofcosta][]) -* Fix `RSpec/SubjectStub` cop matches receive message inside all matcher. ([@walf443][]) +- Rename namespace `FactoryGirl` to `FactoryBot` following original library update. ([@walf443]) +- Fix exception in `RSpec/ReturnFromStub` on empty block. ([@yevhene]) +- Add `RSpec/ContextWording` cop. ([@pirj], [@telmofcosta]) +- Fix `RSpec/SubjectStub` cop matches receive message inside all matcher. ([@walf443]) ## 1.19.0 (2017-10-18) @@ -205,287 +716,366 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features. ## 1.18.0 (2017-09-29) -* Fix false positive in `Capybara/FeatureMethods`. ([@Darhazer][]) -* Add `RSpec/Capybara/CurrentPathExpectation` cop for feature specs, disallowing setting expectations on `current_path`. ([@timrogers][]) -* Fix false positive in `RSpec/LetBeforeExamples` cop when example group contains single let. ([@Darhazer][]) +- Fix false positive in `Capybara/FeatureMethods`. ([@Darhazer]) +- Add `RSpec/Capybara/CurrentPathExpectation` cop for feature specs, disallowing setting expectations on `current_path`. ([@timrogers]) +- Fix false positive in `RSpec/LetBeforeExamples` cop when example group contains single let. ([@Darhazer]) ## 1.17.1 (2017-09-20) -* Improved `RSpec/ReturnFromStub` to handle string interpolation, hashes and do..end blocks. ([@Darhazer][]) -* Fixed compatibility with JRuby. ([@zverok][]) +- Improved `RSpec/ReturnFromStub` to handle string interpolation, hashes and do..end blocks. ([@Darhazer]) +- Fixed compatibility with JRuby. ([@zverok]) ## 1.17.0 (2017-09-14) -* Add `RSpec/Capybara` namespace including the first cop for feature specs: `Capybara/FeatureMethods`. ([@rspeicher][]) -* Update to RuboCop 0.50.0. ([@bquorning][]) +- Add `RSpec/Capybara` namespace including the first cop for feature specs: `Capybara/FeatureMethods`. ([@rspeicher]) +- Update to RuboCop 0.50.0. ([@bquorning]) ## 1.16.0 (2017-09-06) -* Add `RSpec/FactoryGirl` namespace including the first cop for factories: `FactoryGirl/DynamicAttributeDefinedStatically`. ([@jonatas][]) -* Add disabled by default `RSpec/AlignLeftLetBrace`. ([@backus][]) -* Add disabled by default `RSpec/AlignRightLetBrace`. ([@backus][]) -* Add `RSpec/LetBeforeExamples` cop. ([@Darhazer][]) -* Add `RSpec/MultipleSubjects` cop. ([@backus][]) -* Add `RSpec/ReturnFromStub` cop. ([@Darhazer][]) -* Add `RSpec/VoidExpect` cop. ([@pocke][]) -* Add `RSpec/InvalidPredicateMatcher` cop. ([@pocke][]) -* Change HookArgument cop to detect when hook has a receiver. ([@pocke][]) -* Add `RSpec/PredicateMatcher` cop. ([@pocke][]) -* Add `RSpec/ExpectInHook` cop. ([@pocke][]) -* `RSpec/MultipleExpectations` now detects usage of expect_any_instance_of. ([@Darhazer][]) -* `RSpec/MultipleExpectations` now detects usage of is_expected. ([@bmorrall][]) +- Add `RSpec/FactoryGirl` namespace including the first cop for factories: `FactoryGirl/DynamicAttributeDefinedStatically`. ([@jonatas]) +- Add disabled by default `RSpec/AlignLeftLetBrace`. ([@backus]) +- Add disabled by default `RSpec/AlignRightLetBrace`. ([@backus]) +- Add `RSpec/LetBeforeExamples` cop. ([@Darhazer]) +- Add `RSpec/MultipleSubjects` cop. ([@backus]) +- Add `RSpec/ReturnFromStub` cop. ([@Darhazer]) +- Add `RSpec/VoidExpect` cop. ([@pocke]) +- Add `RSpec/InvalidPredicateMatcher` cop. ([@pocke]) +- Change HookArgument cop to detect when hook has a receiver. ([@pocke]) +- Add `RSpec/PredicateMatcher` cop. ([@pocke]) +- Add `RSpec/ExpectInHook` cop. ([@pocke]) +- `RSpec/MultipleExpectations` now detects usage of expect_any_instance_of. ([@Darhazer]) +- `RSpec/MultipleExpectations` now detects usage of is_expected. ([@bmorrall]) ## 1.15.1 (2017-04-30) -* Fix the handling of various edge cases in the `RSpec/ExampleWording` cop, including one that would cause autocorrect to crash. ([@dgollahon][]) -* Fix `RSpec/IteratedExpectation` crashing when there is an assignment in the iteration. ([@Darhazer][]) -* Fix false positive in `RSpec/SingleArgumentMessageChain` cop when the single argument is a hash. ([@Darhazer][]) +- Fix the handling of various edge cases in the `RSpec/ExampleWording` cop, including one that would cause autocorrect to crash. ([@dgollahon]) +- Fix `RSpec/IteratedExpectation` crashing when there is an assignment in the iteration. ([@Darhazer]) +- Fix false positive in `RSpec/SingleArgumentMessageChain` cop when the single argument is a hash. ([@Darhazer]) ## 1.15.0 (2017-03-24) -* Add `RSpec/DescribeSymbol` cop. ([@rspeicher][]) -* Fix error when `RSpec/OverwritingSetup` and `RSpec/ScatteredLet` analyzed empty example groups. ([@backus][]) +- Add `RSpec/DescribeSymbol` cop. ([@rspeicher]) +- Fix error when `RSpec/OverwritingSetup` and `RSpec/ScatteredLet` analyzed empty example groups. ([@backus]) ## 1.14.0 (2017-03-24) -* Add `RSpec/OverwritingSetup` cop. ([@Darhazer][]) -* Add autocorrect support for `RSpec/LeadingSubject` cop. ([@Darhazer][]) -* Add `RSpec/ScatteredLet` cop. ([@Darhazer][]) -* Add `RSpec/IteratedExpectation` cop. ([@Darhazer][]) -* Add `RSpec/EmptyLineAfterSubject` cop. ([@Darhazer][]) -* Add `RSpec/EmptyLineAfterFinalLet` cop. ([@Darhazer][]) +- Add `RSpec/OverwritingSetup` cop. ([@Darhazer]) +- Add autocorrect support for `RSpec/LeadingSubject` cop. ([@Darhazer]) +- Add `RSpec/ScatteredLet` cop. ([@Darhazer]) +- Add `RSpec/IteratedExpectation` cop. ([@Darhazer]) +- Add `RSpec/EmptyLineAfterSubject` cop. ([@Darhazer]) +- Add `RSpec/EmptyLineAfterFinalLet` cop. ([@Darhazer]) ## 1.13.0 (2017-03-07) -* Add repeated 'it' detection to `RSpec/ExampleWording` cop. ([@dgollahon][]) -* Add [observed_nesting/max_nesting] info to `RSpec/NestedGroups` messages. ([@dgollahon][]) -* Add `RSpec/ItBehavesLike` cop. ([@dgollahon][]) -* Add `RSpec/SharedContext` cop. ([@Darhazer][]) -* `RSpec/MultipleExpectations`: Count aggregate_failures block as single expectation. ([@Darhazer][]) -* Fix `ExpectActual` cop flagging `rspec-rails` routing specs. ([@backus][]) -* Fix `FilePath` cop not registering offenses for files like `spec/blog/user.rb` when it should be `spec/blog/user_spec.rb`. ([@backus][]) +- Add repeated 'it' detection to `RSpec/ExampleWording` cop. ([@dgollahon]) +- Add [observed_nesting/max_nesting] info to `RSpec/NestedGroups` messages. ([@dgollahon]) +- Add `RSpec/ItBehavesLike` cop. ([@dgollahon]) +- Add `RSpec/SharedContext` cop. ([@Darhazer]) +- `RSpec/MultipleExpectations`: Count aggregate_failures block as single expectation. ([@Darhazer]) +- Fix `ExpectActual` cop flagging `rspec-rails` routing specs. ([@backus]) +- Fix `FilePath` cop not registering offenses for files like `spec/blog/user.rb` when it should be `spec/blog/user_spec.rb`. ([@backus]) ## 1.12.0 (2017-02-21) -* Add `RSpec/InstanceSpy` cop. ([@Darhazer][]) -* Add `RSpec/BeforeAfterAll` for avoiding leaky global test setup. ([@cfabianski][]) +- Add `RSpec/InstanceSpy` cop. ([@Darhazer]) +- Add `RSpec/BeforeAfterAll` for avoiding leaky global test setup. ([@cfabianski]) ## 1.11.0 (2017-02-16) -* Add `AroundBlock` cop. ([@Darhazer][]) -* Add `EnforcedStyle` configuration for `RSpec/DescribedClass` cop. ([@Darhazer][]) -* Fix false positive for `RSpec/RepeatedExample` cop. ([@redross][]) +- Add `AroundBlock` cop. ([@Darhazer]) +- Add `EnforcedStyle` configuration for `RSpec/DescribedClass` cop. ([@Darhazer]) +- Fix false positive for `RSpec/RepeatedExample` cop. ([@redross]) ## 1.10.0 (2017-01-15) -* Fix false negative for `RSpec/MessageSpies` cop. ([@onk][]) -* Fix internal dependencies on RuboCop to be compatible with 0.47 release. ([@backus][]) -* Add autocorrect support for `SingleArgumentMessageChain` cop. ([@bquorning][]) -* Rename `NestedGroups`' configuration key from `MaxNesting` to `Max` in order to be consistent with other cop configuration. ([@backus][]) -* Add `RepeatedExample` cop for detecting repeated examples within example groups. ([@backus][]) -* Add `ScatteredSetup` cop for enforcing that only one `before`, `around`, and `after` hook are used per example group scope. ([@backus][]) -* Add `ExpectOutput` cop for recommending `expect { ... }.to output(...).to_stdout`. ([@backus][]) +- Fix false negative for `RSpec/MessageSpies` cop. ([@onk]) +- Fix internal dependencies on RuboCop to be compatible with 0.47 release. ([@backus]) +- Add autocorrect support for `SingleArgumentMessageChain` cop. ([@bquorning]) +- Rename `NestedGroups`' configuration key from `MaxNesting` to `Max` in order to be consistent with other cop configuration. ([@backus]) +- Add `RepeatedExample` cop for detecting repeated examples within example groups. ([@backus]) +- Add `ScatteredSetup` cop for enforcing that only one `before`, `around`, and `after` hook are used per example group scope. ([@backus]) +- Add `ExpectOutput` cop for recommending `expect { ... }.to output(...).to_stdout`. ([@backus]) ## 1.9.1 (2017-01-02) -* Fix unintentional regression change in `NestedGroups` reported in #270. ([@backus][]) -* Change `MaxNesting` for `NestedGroups` from 2 to 3. ([@backus][]) +- Fix unintentional regression change in `NestedGroups` reported in #270. ([@backus]) +- Change `MaxNesting` for `NestedGroups` from 2 to 3. ([@backus]) ## 1.9.0 (2016-12-29) -* Add `MessageSpies` cop for enforcing consistent style of either `expect(...).to have_received` or `expect(...).to receive`, intended as a replacement for the `MessageExpectation` cop. ([@bquorning][]) -* Fix `DescribeClass` to not flag `describe` at the top of a block of shared examples. ([@clupprich][]) -* Add `SingleArgumentMessageChain` cop for recommending use of `receive` instead of `receive_message_chain` where possible. ([@bquorning][]) -* Add `RepeatedDescription` cop for detecting repeated example descriptions within example groups. ([@backus][]) +- Add `MessageSpies` cop for enforcing consistent style of either `expect(...).to have_received` or `expect(...).to receive`, intended as a replacement for the `MessageExpectation` cop. ([@bquorning]) +- Fix `DescribeClass` to not flag `describe` at the top of a block of shared examples. ([@clupprich]) +- Add `SingleArgumentMessageChain` cop for recommending use of `receive` instead of `receive_message_chain` where possible. ([@bquorning]) +- Add `RepeatedDescription` cop for detecting repeated example descriptions within example groups. ([@backus]) ## 1.8.0 (2016-10-27) -* Optionally ignore method names in the `describe` argument when running the `FilePath` cop. ([@bquorning][]) -* Fix regression in how `FilePath` converts alphanumeric class names into paths. ([@bquorning][]) -* Add `ImplicitExpect` cop for enforcing `should` vs. `is_expected.to`. ([@backus][]) -* Disable `MessageExpectation` cop in the default configuration. ([@bquorning][]) +- Optionally ignore method names in the `describe` argument when running the `FilePath` cop. ([@bquorning]) +- Fix regression in how `FilePath` converts alphanumeric class names into paths. ([@bquorning]) +- Add `ImplicitExpect` cop for enforcing `should` vs. `is_expected.to`. ([@backus]) +- Disable `MessageExpectation` cop in the default configuration. ([@bquorning]) ## 1.7.0 (2016-08-24) -* Add support for checking all example groups with `ExampleLength`. ([@backus][]) -* Add support for checking shared example groups for `DescribedClass`. ([@backus][]) -* Add support for checking `its` from [rspec-its](https://github.com/rspec/rspec-its). ([@backus][]) -* Add `EmptyExampleGroup` cop for detecting `describe`s and `context`s without any tests inside. ([@backus][]) -* Add `CustomIncludeMethods` configuration option for `EmptyExampleGroup`. ([@backus][]) -* Add `NestedGroups` cop for detecting excessive example group nesting. ([@backus][]) -* Add `MaxNesting` configuration option for `NestedGroups` cop. ([@backus][]) -* Add `ExpectActual` cop for detecting literal values within `expect(...)`. ([@backus][]) -* Add `MultipleExpectations` cop for detecting multiple `expect(...)` calls within one example. ([@backus][]) -* Add `Max` configuration option for `MultipleExpectations`. ([@backus][]) -* Add `SubjectStub` cop for testing stubbed test subjects. ([@backus][]) -* Add `LetSetup` cop for detecting cases where `let!` is used for test setup. ([@backus][]) -* Change all cops to only inspect files with names following rspec convention (`*/spec/*` and/or `_spec.rb`). ([@backus][]) -* Add `AllCops/RSpec` configuration option for specifying custom spec file patterns. ([@backus][]) -* Add `AssignmentOnly` configuration option for `RSpec/InstanceVariable` cop. ([@backus][]) -* Add `BeEql` cop which looks for expectations that can use `be(...)` instead of `eql(...)`. ([@backus][]) -* Add autocorrect support for `BeEql` cop. ([@backus][]) -* Add `MessageExpectation` cop for enforcing consistent style of either `expect(...).to receive` or `allow(...).to receive`. ([@backus][]) -* Add `MessageChain` cop. ([@bquorning][]) +- Add support for checking all example groups with `ExampleLength`. ([@backus]) +- Add support for checking shared example groups for `DescribedClass`. ([@backus]) +- Add support for checking `its` from [rspec-its](https://github.com/rspec/rspec-its). ([@backus]) +- Add `EmptyExampleGroup` cop for detecting `describe`s and `context`s without any tests inside. ([@backus]) +- Add `CustomIncludeMethods` configuration option for `EmptyExampleGroup`. ([@backus]) +- Add `NestedGroups` cop for detecting excessive example group nesting. ([@backus]) +- Add `MaxNesting` configuration option for `NestedGroups` cop. ([@backus]) +- Add `ExpectActual` cop for detecting literal values within `expect(...)`. ([@backus]) +- Add `MultipleExpectations` cop for detecting multiple `expect(...)` calls within one example. ([@backus]) +- Add `Max` configuration option for `MultipleExpectations`. ([@backus]) +- Add `SubjectStub` cop for testing stubbed test subjects. ([@backus]) +- Add `LetSetup` cop for detecting cases where `let!` is used for test setup. ([@backus]) +- Change all cops to only inspect files with names following rspec convention (`*/spec/*` and/or `_spec.rb`). ([@backus]) +- Add `AllCops/RSpec` configuration option for specifying custom spec file patterns. ([@backus]) +- Add `AssignmentOnly` configuration option for `RSpec/InstanceVariable` cop. ([@backus]) +- Add `BeEql` cop which looks for expectations that can use `be(...)` instead of `eql(...)`. ([@backus]) +- Add autocorrect support for `BeEql` cop. ([@backus]) +- Add `MessageExpectation` cop for enforcing consistent style of either `expect(...).to receive` or `allow(...).to receive`. ([@backus]) +- Add `MessageChain` cop. ([@bquorning]) ## 1.6.0 (2016-08-03) -* Add `SkipBlocks` option for `DescribedClass` cop. ([@backus][]) +- Add `SkipBlocks` option for `DescribedClass` cop. ([@backus]) ## 1.5.3 (2016-08-02) -* Add `RSpec/NamedSubject` cop. ([@backus][]) +- Add `RSpec/NamedSubject` cop. ([@backus]) ## 1.5.2 (2016-08-01) -* Drop support for ruby `2.0.0` and `2.1.0`. ([@backus][]) -* Internal refactorings and improved test coverage. ([@backus][]) +- Drop support for ruby `2.0.0` and `2.1.0`. ([@backus]) +- Internal refactorings and improved test coverage. ([@backus]) ## 1.5.1 (2016-07-20) -* Fix `unrecognized parameter RSpec/VerifiedDoubles:IgnoreSymbolicNames` warning. ([@jeffreyc][]) -* Update to rubocop 0.41.2. ([@backus][]) +- Fix `unrecognized parameter RSpec/VerifiedDoubles:IgnoreSymbolicNames` warning. ([@jeffreyc]) +- Update to rubocop 0.41.2. ([@backus]) ## 1.5.0 (2016-05-17) -* Expand `VerifiedDoubles` cop to check for `spy` as well as `double`. ([@andyw8][]) -* Enable `VerifiedDoubles` cop by default. ([@andyw8][]) -* Add `IgnoreSymbolicNames` option for `VerifiedDoubles` cop. ([@andyw8][]) -* Add `RSpec::ExampleLength` cop. ([@andyw8][]) -* Handle alphanumeric class names in `FilePath` cop. ([@andyw8][]) -* Skip `DescribeClass` cop for view specs. ([@andyw8][]) -* Skip `FilePath` cop for Rails routing specs. ([@andyw8][]) -* Add cop to check for focused specs. ([@renanborgescampos][], [@jaredmoody][]) -* Clean-up `RSpec::NotToNot` to use same configuration semantics as other Rubocop cops, add autocorrect support for `RSpec::NotToNot`. ([@baberthal][]) -* Update to rubocop 0.40.0. ([@nijikon][]) +- Expand `VerifiedDoubles` cop to check for `spy` as well as `double`. ([@andyw8]) +- Enable `VerifiedDoubles` cop by default. ([@andyw8]) +- Add `IgnoreSymbolicNames` option for `VerifiedDoubles` cop. ([@andyw8]) +- Add `RSpec::ExampleLength` cop. ([@andyw8]) +- Handle alphanumeric class names in `FilePath` cop. ([@andyw8]) +- Skip `DescribeClass` cop for view specs. ([@andyw8]) +- Skip `FilePath` cop for Rails routing specs. ([@andyw8]) +- Add cop to check for focused specs. ([@renanborgescampos], [@jaredmoody]) +- Clean-up `RSpec::NotToNot` to use same configuration semantics as other RuboCop cops, add autocorrect support for `RSpec::NotToNot`. ([@baberthal]) +- Update to rubocop 0.40.0. ([@nijikon]) ## 1.4.1 (2016-04-03) -* Ignore routing specs for DescribeClass cop. ([@nijikon][]) -* Move rubocop dependency to runtime. ([@nijikon][]) -* Update to rubocop 0.39.0. ([@nijikon][]) +- Ignore routing specs for DescribeClass cop. ([@nijikon]) +- Move rubocop dependency to runtime. ([@nijikon]) +- Update to rubocop 0.39.0. ([@nijikon]) ## 1.4.0 (2016-02-15) -* Update to rubocop 0.37.2. ([@nijikon][]) -* Update ruby versions we test against. ([@nijikon][]) -* Add `RSpec::NotToNot` cop. ([@miguelfteixeira][]) -* Add `RSpec/AnyInstance` cop. ([@mlarraz][]) +- Update to rubocop 0.37.2. ([@nijikon]) +- Update ruby versions we test against. ([@nijikon]) +- Add `RSpec::NotToNot` cop. ([@miguelfteixeira]) +- Add `RSpec/AnyInstance` cop. ([@mlarraz]) ## 1.3.1 -* Fix auto correction issue - syntax had changed in RuboCop v0.31. ([@bquorning][]) -* Add RuboCop clone to vendor folder - see #39 for details. ([@bquorning][]) +- Fix auto correction issue - syntax had changed in RuboCop v0.31. ([@bquorning]) +- Add RuboCop clone to vendor folder - see #39 for details. ([@bquorning]) ## 1.3.0 -* Ignore non string arguments for FilePathCop - thanks to @deivid-rodriguez. ([@geniou][]) -* Skip DescribeMethod cop for tagged specs. ([@deivid-rodriguez][]) -* Skip DescribeClass cop for feature/request specs. ([@deivid-rodriguez][]) +- Ignore non string arguments for FilePathCop - thanks to @deivid-rodriguez. ([@geniou]) +- Skip DescribeMethod cop for tagged specs. ([@deivid-rodriguez]) +- Skip DescribeClass cop for feature/request specs. ([@deivid-rodriguez]) ## 1.2.2 -* Make `RSpec::ExampleWording` case insensitive. ([@geniou][]) +- Make `RSpec::ExampleWording` case insensitive. ([@geniou]) ## 1.2.1 -* Add `RSpec::VerifiedDoubles` cop. ([@andyw8][]) +- Add `RSpec::VerifiedDoubles` cop. ([@andyw8]) ## 1.2.0 -* Drop support of ruby `1.9.2`. ([@geniou][]) -* Update to RuboCop `~> 0.24`. ([@geniou][]) -* Add `autocorrect` to `RSpec::ExampleWording`. This experimental - use with care and check the changes. ([@geniou][]) -* Fix config loader debug output. ([@geniou][]) -* Rename `FileName` cop to `FilePath` as a workaround - see [#19](https://github.com/nevir/rubocop-rspec/issues/19). ([@geniou][]) +- Drop support of ruby `1.9.2`. ([@geniou]) +- Update to RuboCop `~> 0.24`. ([@geniou]) +- Add `autocorrect` to `RSpec::ExampleWording`. This experimental - use with care and check the changes. ([@geniou]) +- Fix config loader debug output. ([@geniou]) +- Rename `FileName` cop to `FilePath` as a workaround - see [#19](https://github.com/nevir/rubocop-rspec/issues/19). ([@geniou]) ## 1.1.0 -* Add `autocorrect` to `RSpec::DescribedClass` cop. ([@geniou][]) +- Add `autocorrect` to `RSpec::DescribedClass` cop. ([@geniou]) ## 1.0.1 -* Add `config` folder to gemspec. ([@pstengel][]) +- Add `config` folder to gemspec. ([@pstengel]) ## 1.0.rc3 -* Update to RuboCop `>= 0.23`. ([@geniou][]) -* Add configuration option for `CustomTransformation` to `FileName` cop. ([@geniou][]) +- Update to RuboCop `>= 0.23`. ([@geniou]) +- Add configuration option for `CustomTransformation` to `FileName` cop. ([@geniou]) ## 1.0.rc2 -* Gem is no longer 20MB (sorry!). ([@nevir][]) -* `RspecFileName` cop allows for method specs to organized into directories by class and type. ([@nevir][]) +- Gem is no longer 20MB (sorry!). ([@nevir]) +- `RspecFileName` cop allows for method specs to organized into directories by class and type. ([@nevir]) ## 1.0.rc1 -* Update code to work with rubocop `>= 0.19`. ([@geniou][]) -* Split `UnitSpecNaming` cop into `RSpecDescribeClass`, `RSpecDescribeMethod` and `RSpecFileName` and enabled them all by default. ([@geniou][]) -* Add `RSpecExampleWording` cop to prevent to use of should at the beginning of the spec description. ([@geniou][]) -* Fix `RSpecFileName` cop for non-class specs. ([@geniou][]) -* Adapt `RSpecFileName` cop to commen naming convention and skip spec with multiple top level describes. ([@geniou][]) -* Add `RSpecMultipleDescribes` cop to check for multiple top level describes. ([@geniou][]) -* Add `RSpecDescribedClass` to promote the use of `described_class`. ([@geniou][]) -* Add `RSpecInstanceVariable` cop to check for the usage of instance variables. ([@geniou][]) +- Update code to work with rubocop `>= 0.19`. ([@geniou]) +- Split `UnitSpecNaming` cop into `RSpecDescribeClass`, `RSpecDescribeMethod` and `RSpecFileName` and enabled them all by default. ([@geniou]) +- Add `RSpecExampleWording` cop to prevent to use of should at the beginning of the spec description. ([@geniou]) +- Fix `RSpecFileName` cop for non-class specs. ([@geniou]) +- Adapt `RSpecFileName` cop to common naming convention and skip spec with multiple top level describes. ([@geniou]) +- Add `RSpecMultipleDescribes` cop to check for multiple top level describes. ([@geniou]) +- Add `RSpecDescribedClass` to promote the use of `described_class`. ([@geniou]) +- Add `RSpecInstanceVariable` cop to check for the usage of instance variables. ([@geniou]) - + +[@aarestad]: https://github.com/aarestad +[@abrom]: https://github.com/abrom +[@ahukkanen]: https://github.com/ahukkanen +[@akiomik]: https://github.com/akiomik +[@akrox58]: https://github.com/akrox58 +[@alexwayfer]: https://github.com/AlexWayfer +[@andrykonchin]: https://github.com/andrykonchin [@andyw8]: https://github.com/andyw8 +[@anthony-robin]: https://github.com/anthony-robin +[@aried3r]: https://github.com/aried3r +[@baberthal]: https://github.com/baberthal [@backus]: https://github.com/backus +[@bcgraham]: https://github.com/bcgraham +[@biinari]: https://github.com/biinari +[@bmorrall]: https://github.com/bmorrall [@bquorning]: https://github.com/bquorning +[@brentwheeldon]: https://github.com/BrentWheeldon +[@brianhawley]: https://github.com/BrianHawley +[@cbliard]: https://github.com/cbliard +[@cfabianski]: https://github.com/cfabianski +[@clupprich]: https://github.com/clupprich +[@composerinteralia]: https://github.com/composerinteralia +[@corsonknowles]: https://github.com/corsonknowles +[@corydiamand]: https://github.com/corydiamand +[@darhazer]: https://github.com/Darhazer +[@daveworth]: https://github.com/daveworth +[@dduugg]: https://github.com/dduugg [@deivid-rodriguez]: https://github.com/deivid-rodriguez +[@dgollahon]: https://github.com/dgollahon +[@dmitrytsepelev]: https://github.com/dmitrytsepelev +[@drcapulet]: https://github.com/drcapulet +[@drowze]: https://github.com/Drowze +[@dswij]: https://github.com/dswij +[@dvandersluis]: https://github.com/dvandersluis +[@earlopain]: https://github.com/earlopain +[@edgibbs]: https://github.com/edgibbs +[@eikes]: https://github.com/eikes +[@eitoball]: https://github.com/eitoball +[@elebow]: https://github.com/elebow +[@elisefitz15]: https://github.com/EliseFitz15 +[@elliterate]: https://github.com/elliterate +[@faucct]: https://github.com/faucct +[@foton]: https://github.com/foton +[@francois-ferrandis]: https://github.com/francois-ferrandis +[@franzliedke]: https://github.com/franzliedke +[@g-rath]: https://github.com/G-Rath [@geniou]: https://github.com/geniou +[@gsamokovarov]: https://github.com/gsamokovarov +[@harry-graham]: https://github.com/harry-graham +[@harrylewis]: https://github.com/harrylewis +[@hasghari]: https://github.com/hasghari +[@hosamaly]: https://github.com/hosamaly +[@ignaciovillaverde]: https://github.com/ignaciovillaverde [@jaredbeck]: https://github.com/jaredbeck -[@jawshooah]: https://github.com/jawshooah -[@nevir]: https://github.com/nevir -[@nijikon]: https://github.com/nijikon -[@pstengel]: https://github.com/pstengel -[@miguelfteixeira]: https://github.com/miguelfteixeira -[@mlarraz]: https://github.com/mlarraz -[@renanborgescampos]: https://github.com/renanborgescampos [@jaredmoody]: https://github.com/jaredmoody -[@baberthal]: https://github.com/baberthal +[@jdufresne]: https://github.com/jdufresne [@jeffreyc]: https://github.com/jeffreyc -[@clupprich]: https://github.com/clupprich +[@jeppester]: https://github.com/jeppester +[@jessieay]: https://github.com/jessieay +[@jfragoulis]: https://github.com/jfragoulis +[@johnny-miyake]: https://github.com/johnny-miyake +[@jojos003]: https://github.com/jojos003 +[@jonatas]: https://github.com/jonatas +[@jtannas]: https://github.com/jtannas +[@k-s-a]: https://github.com/K-S-A +[@kellysutton]: https://github.com/kellysutton +[@koic]: https://github.com/koic +[@krororo]: https://github.com/krororo +[@kuahyeow]: https://github.com/kuahyeow +[@lazycoder9]: https://github.com/lazycoder9 +[@lee266]: https://github.com/lee266 +[@leoarnold]: https://github.com/leoarnold +[@liberatys]: https://github.com/Liberatys +[@lokhi]: https://github.com/lokhi +[@lovro-bikic]: https://github.com/lovro-bikic +[@luke-hill]: https://github.com/luke-hill +[@m-yamashita01]: https://github.com/M-Yamashita01 +[@marocchino]: https://github.com/marocchino +[@miguelfteixeira]: https://github.com/miguelfteixeira +[@mkenyon]: https://github.com/mkenyon +[@mkrawc]: https://github.com/mkrawc +[@mlarraz]: https://github.com/mlarraz +[@mockdeep]: https://github.com/mockdeep +[@mothonmars]: https://github.com/MothOnMars +[@mvz]: https://github.com/mvz +[@naveg]: https://github.com/naveg +[@nc-holodakg]: https://github.com/nc-holodakg +[@nevir]: https://github.com/nevir +[@ngouy]: https://github.com/ngouy +[@nickcampbell18]: https://github.com/nickcampbell18 +[@nijikon]: https://github.com/nijikon [@onk]: https://github.com/onk -[@Darhazer]: https://github.com/Darhazer +[@onumis]: https://github.com/onumis +[@oshiro3]: https://github.com/oshiro3 +[@patrickomatic]: https://github.com/patrickomatic +[@paydaylight]: https://github.com/paydaylight +[@philcoggins]: https://github.com/PhilCoggins +[@pirj]: https://github.com/pirj +[@pocke]: https://github.com/pocke +[@pstengel]: https://github.com/pstengel +[@qqism]: https://github.com/QQism +[@r7kamura]: https://github.com/r7kamura +[@rafix02]: https://github.com/Rafix02 [@redross]: https://github.com/redross -[@cfabianski]: https://github.com/cfabianski -[@dgollahon]: https://github.com/dgollahon +[@renanborgescampos]: https://github.com/renanborgescampos +[@robinaugh]: https://github.com/robinaugh +[@robotdana]: https://github.com/robotdana +[@rolfschmidt]: https://github.com/rolfschmidt +[@rrosenblum]: https://github.com/rrosenblum [@rspeicher]: https://github.com/rspeicher -[@jonatas]: https://github.com/jonatas -[@pocke]: https://github.com/pocke -[@bmorrall]: https:/github.com/bmorrall -[@zverok]: https:/github.com/zverok -[@timrogers]: https://github.com/timrogers -[@yevhene]: https://github.com/yevhene -[@walf443]: https://github.com/walf443 -[@pirj]: https://github.com/pirj -[@telmofcosta]: https://github.com/telmofcosta -[@EliseFitz15]: https://github.com/EliseFitz15 -[@anthony-robin]: https://github.com/anthony-robin -[@jojos003]: https://github.com/jojos003 -[@abrom]: https://github.com/abrom -[@patrickomatic]: https://github.com/patrickomatic -[@tdeo]: https://github.com/tdeo -[@composerinteralia]: https://github.com/composerinteralia +[@rst-j]: https://github.com/RST-J +[@sakuro]: https://github.com/sakuro +[@samrjenkins]: https://github.com/samrjenkins +[@schmijos]: https://github.com/schmijos [@seanpdoyle]: https://github.com/seanpdoyle +[@sl4vr]: https://github.com/sl4vr +[@smcgivern]: https://github.com/smcgivern +[@splattael]: https://github.com/splattael +[@stephannv]: https://github.com/stephannv +[@swelther]: https://github.com/swelther +[@t3h2mas]: https://github.com/t3h2mas +[@tdeo]: https://github.com/tdeo +[@tejasbubane]: https://github.com/tejasbubane +[@telmofcosta]: https://github.com/telmofcosta +[@tietew]: https://github.com/Tietew +[@timrogers]: https://github.com/timrogers +[@tmaier]: https://github.com/tmaier +[@topalovic]: https://github.com/topalovic +[@twalpole]: https://github.com/twalpole [@vzvu3k6k]: https://github.com/vzvu3k6k -[@BrentWheeldon]: https://github.com/BrentWheeldon -[@daveworth]: https://github.com/daveworth -[@RST-J]: https://github.com/RST-J -[@ypresto]: https://github.com/ypresto -[@mkenyon]: https://github.com/mkenyon -[@gsamokovarov]: https://github.com/gsamokovarov -[@schmijos]: https://github.com/schmijos -[@foton]: https://github.com/foton -[@nc-holodakg]: https://github.com/nc-holodakg -[@onumis]: https://github.com/onumis -[@nickcampbell18]: https://github.com/nickcampbell18 -[@QQism]: https://github.com/QQism -[@kellysutton]: https://github.com/kellysutton -[@mkrawc]: https://github.com/mkrawc -[@jfragoulis]: https://github.com/jfragoulis +[@walf443]: https://github.com/walf443 +[@yasu551]: https://github.com/yasu551 [@ybiquitous]: https://github.com/ybiquitous -[@dduugg]: https://github.com/dduugg -[@lazycoder9]: https://github.com/lazycoder9 -[@elebow]: https://github.com/elebow +[@ydah]: https://github.com/ydah +[@yevhene]: https://github.com/yevhene +[@ypresto]: https://github.com/ypresto +[@yujideveloper]: https://github.com/yujideveloper +[@zdennis]: https://github.com/zdennis +[@zverok]: https://github.com/zverok diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..6e5a0ce2b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,17 @@ +# The RuboCop Community Code of Conduct + +**Note:** We have picked the following code of conduct based on [Ruby's own +code of conduct](https://www.ruby-lang.org/en/conduct/). + +This document provides a few simple community guidelines for a safe, respectful, +productive, and collaborative place for any person who is willing to contribute +to the RuboCop community. It applies to all "collaborative spaces", which are +defined as community communications channels (such as mailing lists, submitted +patches, commit comments, etc.). + +- Participants will be tolerant of opposing views. +- Participants must ensure that their language and actions are free of personal + attacks and disparaging personal remarks. +- When interpreting the words and actions of others, participants should always + assume good intentions. +- Behaviour which can be reasonably considered harassment will not be tolerated. diff --git a/Gemfile b/Gemfile index 515dec7c8..089cdf4e6 100644 --- a/Gemfile +++ b/Gemfile @@ -4,8 +4,14 @@ source 'https://rubygems.org' gemspec -local_gemfile = 'Gemfile.local' +gem 'bump' +gem 'rack' +gem 'rake' +gem 'rspec', '~> 3.11' +gem 'rubocop-performance', '~> 1.24' +gem 'rubocop-rake', '~> 0.7' +gem 'simplecov', '>= 0.19' +gem 'yard' -if File.exist?(local_gemfile) - eval(File.read(local_gemfile)) # rubocop:disable Security/Eval -end +local_gemfile = 'Gemfile.local' +eval_gemfile(local_gemfile) if File.exist?(local_gemfile) diff --git a/MIT-LICENSE.md b/MIT-LICENSE.md index c628b7a91..e32542a9c 100644 --- a/MIT-LICENSE.md +++ b/MIT-LICENSE.md @@ -1,5 +1,4 @@ -The MIT License (MIT) -===================== +# The MIT License (MIT) Copyright (c) 2014 Ian MacLeod diff --git a/README.md b/README.md index 50c2162f5..938a8f0d7 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,19 @@ [![Join the chat at https://gitter.im/rubocop-rspec/Lobby](https://badges.gitter.im/rubocop-rspec/Lobby.svg)](https://gitter.im/rubocop-rspec/Lobby) [![Gem Version](https://badge.fury.io/rb/rubocop-rspec.svg)](https://rubygems.org/gems/rubocop-rspec) -[![CircleCI](https://circleci.com/gh/rubocop-hq/rubocop-rspec.svg?style=svg)](https://circleci.com/gh/rubocop-hq/rubocop-rspec) -[![Test Coverage](https://api.codeclimate.com/v1/badges/8ffaabf633c968c22bdd/test_coverage)](https://codeclimate.com/github/rubocop-hq/rubocop-rspec/test_coverage) -[![Maintainability](https://api.codeclimate.com/v1/badges/8ffaabf633c968c22bdd/maintainability)](https://codeclimate.com/github/rubocop-hq/rubocop-rspec/maintainability) +![CI](https://github.com/rubocop/rubocop-rspec/workflows/CI/badge.svg) -RSpec-specific analysis for your projects, as an extension to -[RuboCop](https://github.com/rubocop-hq/rubocop). +[RSpec](https://rspec.info/)-specific analysis for your projects, as an extension to +[RuboCop](https://github.com/rubocop/rubocop). + +- [Installation](#installation) + - [Upgrading to RuboCop RSpec v3.x](#upgrading-to-rubocop-rspec-v3x) + - [Upgrading to RuboCop RSpec v2.x](#upgrading-to-rubocop-rspec-v2x) +- [Usage](#usage) +- [Documentation](#documentation) +- [The Cops](#the-cops) +- [Contributing](#contributing) +- [License](#license) ## Installation @@ -19,10 +26,18 @@ gem install rubocop-rspec or if you use bundler put this in your `Gemfile` -``` +```ruby gem 'rubocop-rspec', require: false ``` +### Upgrading to RuboCop RSpec v3.x + +Read all the details in our [Upgrade to Version 3.x](https://docs.rubocop.org/rubocop-rspec/3.0/upgrade_to_version_3.html) document. + +### Upgrading to RuboCop RSpec v2.x + +Read all the details in our [Upgrade to Version 2.x](https://docs.rubocop.org/rubocop-rspec/2.0/upgrade_to_version_2.html) document. + ## Usage You need to tell RuboCop to load the RSpec extension. There are three @@ -33,13 +48,13 @@ ways to do this: Put this into your `.rubocop.yml`. ```yaml -require: rubocop-rspec +plugins: rubocop-rspec ``` Alternatively, use the following array notation when specifying multiple extensions. ```yaml -require: +plugins: - rubocop-other-extension - rubocop-rspec ``` @@ -47,47 +62,30 @@ require: Now you can run `rubocop` and it will automatically load the RuboCop RSpec cops together with the standard cops. +> [!NOTE] +> The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. + ### Command line ```bash -rubocop --require rubocop-rspec +rubocop --plugin rubocop-rspec ``` ### Rake task ```ruby RuboCop::RakeTask.new do |task| - task.requires << 'rubocop-rspec' + task.plugins << 'rubocop-rspec' end ``` ### Code Climate -rubocop-rspec is available on Code Climate as part of the rubocop engine. [Learn More](https://codeclimate.com/changelog/55a433bbe30ba00852000fac). +rubocop-rspec is available on Code Climate as part of the rubocop engine. [Learn More](https://marketing.codeclimate.com/changelog/55a433bbe30ba00852000fac/). ## Documentation -You can read more about RuboCop-RSpec in its [official manual](http://rubocop-rspec.readthedocs.io). - -## Inspecting files that don't end with `_spec.rb` - -By default, `rubocop-rspec` only inspects code within paths ending in `_spec.rb` or including `spec/`. You can override this setting in your config file by specifying one or more patterns: - -```yaml -# Inspect all files -AllCops: - RSpec: - Patterns: - - '.+' -``` - -```yaml -# Inspect only files ending with `_test.rb` -AllCops: - RSpec: - Patterns: - - '_test.rb$' -``` +You can read more about RuboCop RSpec in its [official manual](https://docs.rubocop.org/rubocop-rspec). ## The Cops @@ -99,51 +97,11 @@ In your `.rubocop.yml`, you may treat the RSpec cops just like any other cop. For example: ```yaml -RSpec/FilePath: +RSpec/SpecFilePathFormat: Exclude: - spec/my_poorly_named_spec_file.rb ``` -## Non-goals of RuboCop RSpec - -### Enforcing `should` vs. `expect` syntax - -Enforcing - -```ruby -expect(calculator.compute(line_item)).to eq(5) -``` - -over - -```ruby -calculator.compute(line_item).should == 5 -``` - -is a feature of RSpec itself – you can read about it in the [RSpec Documentation](https://relishapp.com/rspec/rspec-expectations/docs/syntax-configuration#disable-should-syntax) - -### Enforcing an explicit RSpec receiver for top-level methods (disabling monkey patching) - -Enforcing - -```ruby -RSpec.describe MyClass do - ... -end -``` - -over - -```ruby -describe MyClass do - ... -end -``` - -can be achieved using RSpec's `disable_monkey_patching!` method, which you can read more about in the [RSpec Documentation](https://relishapp.com/rspec/rspec-core/v/3-7/docs/configuration/zero-monkey-patching-mode#monkey-patched-methods-are-undefined-with-%60disable-monkey-patching!%60). This will also prevent `should` from being defined on every object in your system. - -Before disabling `should` you will need all your specs to use the `expect` syntax. You can use [Transpec](http://yujinakayama.me/transpec/), which will do the conversion for you. - ## Contributing Checkout the [contribution guidelines](.github/CONTRIBUTING.md). diff --git a/Rakefile b/Rakefile index 4bea106c9..23d584ed0 100644 --- a/Rakefile +++ b/Rakefile @@ -14,6 +14,7 @@ rescue Bundler::BundlerError => e end require 'rspec/core/rake_task' +require 'rubocop/rake_task' Dir['tasks/**/*.rake'].each { |t| load t } @@ -21,20 +22,39 @@ RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] end -desc 'Run RSpec with code coverage' -task :coverage do - ENV['COVERAGE'] = 'true' - Rake::Task['spec'].execute -end - desc 'Run RuboCop over this gem' -task :internal_investigation do - sh('bundle exec rubocop --require rubocop-rspec') -end +RuboCop::RakeTask.new(:internal_investigation) desc 'Build config/default.yml' task :build_config do - sh('bin/build_config') + require 'yard' + + require 'rubocop-rspec' + require 'rubocop/rspec/config_formatter' + require 'rubocop/rspec/description_extractor' + + glob = File.join('lib', 'rubocop', 'cop', 'rspec', '*.rb') + # Due to YARD's sensitivity to file require order (as of 0.9.25), + # we have to prepend the list with our base cop, RuboCop::Cop::RSpec::Base. + # Otherwise, cop's parent class for cops loaded before our base cop class + # are detected as RuboCop::Cop::Base, and that complicates the detection + # of their relation with RuboCop RSpec. + rspec_cop_path = File.join('lib', 'rubocop', 'cop', 'rspec', 'base.rb') + YARD::Tags::Library.define_tag('Cop Safety Information', :safety) + YARD.parse(Dir[glob].prepend(rspec_cop_path), []) + + descriptions = + RuboCop::RSpec::DescriptionExtractor.new(YARD::Registry.all(:class)).to_h + current_config = if Psych::VERSION >= '4.0.0' # RUBY_VERSION >= '3.1.0' + YAML.unsafe_load_file('config/default.yml') + else + YAML.load_file('config/default.yml') + end + + File.write( + 'config/default.yml', + RuboCop::RSpec::ConfigFormatter.new(current_config, descriptions).dump + ) end desc 'Confirm config/default.yml is up to date' @@ -42,23 +62,29 @@ task confirm_config: :build_config do _, stdout, _, process = Open3.popen3('git diff --exit-code config/default.yml') - unless process.value.success? - raise "default.yml is out of sync:\n\n#{stdout.read}\nRun bin/build_config" - end + raise <<~ERROR unless process.value.success? + default.yml is out of sync: + + #{stdout.read} + Please run `rake build_config` + ERROR end desc 'Confirm documentation is up to date' task confirm_documentation: :generate_cops_documentation do - _, _, _, process = - Open3.popen3('git diff --exit-code manual/') - - unless process.value.success? - raise 'Please run `rake generate_cops_documentation` ' \ - 'and add manual/ to the commit.' + stdout, _stderr, status = + Open3.capture3('git diff --exit-code docs/') + + unless status.success? + warn 'Documentation is out of sync:' + warn stdout + warn 'Please run `rake generate_cops_documentation` ' \ + 'and add docs/ to the commit.' + exit 1 end end -task default: %i[build_config coverage +task default: %i[build_config spec internal_investigation confirm_config documentation_syntax_check @@ -67,21 +93,18 @@ task default: %i[build_config coverage desc 'Generate a new cop template' task :new_cop, [:cop] do |_task, args| require 'rubocop' + require_relative 'lib/rubocop/rspec/cop/generator' cop_name = args.fetch(:cop) do - warn 'usage: bundle exec rake new_cop[Department/Name]' + warn "usage: bundle exec rake 'new_cop[Department/Name]'" exit! end - github_user = `git config github.user`.chop - github_user = 'your_id' if github_user.empty? - - generator = RuboCop::Cop::Generator.new(cop_name, github_user) - + generator = RuboCop::RSpec::Cop::Generator.new(cop_name) generator.write_source generator.write_spec generator.inject_require(root_file_path: 'lib/rubocop/cop/rspec_cops.rb') - generator.inject_config(config_file_path: 'config/default.yml') + generator.inject_config puts generator.todo end diff --git a/bin/build_config b/bin/build_config deleted file mode 100755 index 35ec1641f..000000000 --- a/bin/build_config +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib')) - -require 'yard' - -require 'rubocop-rspec' -require 'rubocop/rspec/description_extractor' -require 'rubocop/rspec/config_formatter' - -glob = File.join(__dir__, '..', 'lib', 'rubocop', 'cop', 'rspec', - '{,capybara,factory_bot,rails}', '*.rb') -YARD.parse(Dir[glob], []) - -descriptions = RuboCop::RSpec::DescriptionExtractor.new(YARD::Registry.all).to_h -current_config = YAML.load_file('config/default.yml') - -File.write( - 'config/default.yml', - RuboCop::RSpec::ConfigFormatter.new(current_config, descriptions).dump -) diff --git a/config/default.yml b/config/default.yml index 739e3afd4..ca2f05531 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1,151 +1,410 @@ --- -AllCops: - RSpec: - Patterns: - - _spec.rb - - "(?:^|/)spec/" - RSpec/FactoryBot: - Patterns: - - spec/factories.rb - - spec/factories/**/*.rb - - features/support/factories/**/*.rb +RSpec: + Enabled: true + StyleGuideBaseURL: https://rspec.rubystyle.guide + DocumentationBaseURL: https://docs.rubocop.org/rubocop-rspec + Include: + - "**/*_spec.rb" + - "**/spec/**/*" + Language: + inherit_mode: + merge: + - Expectations + - Helpers + - Hooks + - Subjects + ExampleGroups: + inherit_mode: + merge: + - Regular + - Skipped + - Focused + Regular: + - describe + - context + - feature + - example_group + Skipped: + - xdescribe + - xcontext + - xfeature + Focused: + - fdescribe + - fcontext + - ffeature + Examples: + inherit_mode: + merge: + - Regular + - Skipped + - Focused + - Pending + Regular: + - it + - specify + - example + - scenario + - its + Focused: + - fit + - fspecify + - fexample + - fscenario + - focus + Skipped: + - xit + - xspecify + - xexample + - xscenario + - skip + Pending: + - pending + Expectations: + - are_expected + - expect + - expect_any_instance_of + - is_expected + - should + - should_not + - should_not_receive + - should_receive + Helpers: + - let + - let! + Hooks: + - prepend_before + - before + - append_before + - around + - prepend_after + - after + - append_after + Includes: + inherit_mode: + merge: + - Examples + - Context + Examples: + - it_behaves_like + - it_should_behave_like + - include_examples + Context: + - include_context + SharedGroups: + inherit_mode: + merge: + - Examples + - Context + Examples: + - shared_examples + - shared_examples_for + Context: + - shared_context + Subjects: + - subject + - subject! + +Metrics/BlockLength: + inherit_mode: + merge: + - Exclude + Exclude: + - "**/*_spec.rb" + - "**/spec/**/*" RSpec/AlignLeftLetBrace: Description: Checks that left braces for adjacent single line lets are aligned. Enabled: false - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AlignLeftLetBrace + VersionAdded: '1.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AlignLeftLetBrace RSpec/AlignRightLetBrace: Description: Checks that right braces for adjacent single line lets are aligned. Enabled: false - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AlignRightLetBrace + VersionAdded: '1.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AlignRightLetBrace RSpec/AnyInstance: Description: Check that instances are not being stubbed globally. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AnyInstance + VersionAdded: '1.4' + StyleGuide: https://rspec.rubystyle.guide/#any_instance_of + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AnyInstance RSpec/AroundBlock: Description: Checks that around blocks actually run the test. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AroundBlock + VersionAdded: '1.11' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AroundBlock RSpec/Be: Description: Check for expectations where `be` is used without argument. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Be + VersionAdded: '1.25' + StyleGuide: https://rspec.rubystyle.guide/#be-matcher + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Be + +RSpec/BeEmpty: + Description: Prefer using `be_empty` when checking for an empty array. + Enabled: true + AutoCorrect: contextual + VersionAdded: '2.20' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEmpty + +RSpec/BeEq: + Description: Check for expectations where `be(...)` can replace `eq(...)`. + Enabled: true + Safe: false + VersionAdded: 2.9.0 + VersionChanged: '2.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEq RSpec/BeEql: Description: Check for expectations where `be(...)` can replace `eql(...)`. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEql + Safe: false + VersionAdded: '1.7' + VersionChanged: '2.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEql + +RSpec/BeNil: + Description: Ensures a consistent style is used when matching `nil`. + Enabled: true + EnforcedStyle: be_nil + SupportedStyles: + - be + - be_nil + VersionAdded: 2.9.0 + VersionChanged: 2.10.0 + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeNil RSpec/BeforeAfterAll: - Description: Check that before/after(:all) isn't being used. + Description: Check that before/after(:all/:context) isn't being used. Enabled: true Exclude: - - spec/spec_helper.rb - - spec/rails_helper.rb - - spec/support/**/*.rb - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeforeAfterAll + - "**/spec/spec_helper.rb" + - "**/spec/rails_helper.rb" + - "**/spec/support/**/*.rb" + VersionAdded: '1.12' + VersionChanged: '2.23' + StyleGuide: https://rspec.rubystyle.guide/#avoid-hooks-with-context-scope + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeforeAfterAll + +RSpec/ChangeByZero: + Description: Prefer negated matchers over `to change.by(0)`. + Enabled: true + VersionAdded: '2.11' + VersionChanged: '2.14' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero + NegatedMatcher: ~ + +RSpec/ClassCheck: + Description: Enforces consistent use of `be_a` or `be_kind_of`. + StyleGuide: "#is-a-vs-kind-of" + Enabled: true + VersionAdded: '2.13' + EnforcedStyle: be_a + SupportedStyles: + - be_a + - be_kind_of + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ClassCheck + +RSpec/ContainExactly: + Description: Checks where `contain_exactly` is used. + Enabled: true + VersionAdded: '2.19' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContainExactly RSpec/ContextMethod: Description: "`context` should not be used for specifying methods." Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextMethod + VersionAdded: '1.36' + StyleGuide: https://rspec.rubystyle.guide/#example-group-naming + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextMethod RSpec/ContextWording: Description: Checks that `context` docstring starts with an allowed prefix. Enabled: true Prefixes: - - when - - with - - without - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextWording + - when + - with + - without + AllowedPatterns: [] + VersionAdded: '1.20' + VersionChanged: '2.13' + StyleGuide: https://rspec.rubystyle.guide/#context-descriptions + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextWording RSpec/DescribeClass: - Description: Check that the first argument to the top level describe is a constant. + Description: Check that the first argument to the top-level describe is a constant. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeClass + Exclude: + - "**/spec/features/**/*" + - "**/spec/requests/**/*" + - "**/spec/routing/**/*" + - "**/spec/system/**/*" + - "**/spec/views/**/*" + IgnoredMetadata: + type: + - channel + - controller + - helper + - job + - mailer + - model + - request + - routing + - view + - feature + - system + - mailbox + - aruba + - task + VersionAdded: '1.0' + VersionChanged: '2.7' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeClass RSpec/DescribeMethod: Description: Checks that the second argument to `describe` specifies a method. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeMethod + VersionAdded: '1.0' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeMethod RSpec/DescribeSymbol: Description: Avoid describing symbols. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeSymbol + VersionAdded: '1.15' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeSymbol RSpec/DescribedClass: Description: Checks that tests use `described_class`. - SkipBlocks: false Enabled: true + SkipBlocks: false EnforcedStyle: described_class SupportedStyles: - - described_class - - explicit - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribedClass + - described_class + - explicit + OnlyStaticConstants: true + SafeAutoCorrect: false + VersionAdded: '1.0' + VersionChanged: '2.27' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribedClass RSpec/DescribedClassModuleWrapping: Description: Avoid opening modules and defining specs within them. Enabled: false - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribedClassModuleWrapping + VersionAdded: '1.37' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribedClassModuleWrapping RSpec/Dialect: - Description: This cop enforces custom RSpec dialects. + Description: Enforces custom RSpec dialects. Enabled: false PreferredMethods: {} - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Dialect + VersionAdded: '1.33' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Dialect + +RSpec/DuplicatedMetadata: + Description: Avoid duplicated metadata. + Enabled: true + VersionAdded: '2.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DuplicatedMetadata RSpec/EmptyExampleGroup: Description: Checks if an example group does not include any tests. Enabled: true - CustomIncludeMethods: [] - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyExampleGroup + AutoCorrect: contextual + SafeAutoCorrect: false + VersionAdded: '1.7' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyExampleGroup + +RSpec/EmptyHook: + Description: Checks for empty before and after hooks. + Enabled: true + AutoCorrect: contextual + VersionAdded: '1.39' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyHook RSpec/EmptyLineAfterExample: Description: Checks if there is an empty line after example blocks. Enabled: true AllowConsecutiveOneLiners: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterExample + VersionAdded: '1.36' + StyleGuide: https://rspec.rubystyle.guide/#empty-lines-around-examples + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterExample RSpec/EmptyLineAfterExampleGroup: Description: Checks if there is an empty line after example group blocks. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterExampleGroup + VersionAdded: '1.27' + StyleGuide: https://rspec.rubystyle.guide/#empty-lines-between-describes + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterExampleGroup RSpec/EmptyLineAfterFinalLet: Description: Checks if there is an empty line after the last let block. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterFinalLet + VersionAdded: '1.14' + StyleGuide: https://rspec.rubystyle.guide/#empty-line-after-let + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterFinalLet RSpec/EmptyLineAfterHook: Description: Checks if there is an empty line after hook blocks. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterHook + VersionAdded: '1.27' + VersionChanged: '2.13' + StyleGuide: https://rspec.rubystyle.guide/#empty-line-after-let + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterHook + AllowConsecutiveOneLiners: true RSpec/EmptyLineAfterSubject: Description: Checks if there is an empty line after subject block. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterSubject + VersionAdded: '1.14' + StyleGuide: https://rspec.rubystyle.guide/#empty-line-after-let + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterSubject + +RSpec/EmptyMetadata: + Description: Avoid empty metadata hash. + Enabled: true + AutoCorrect: contextual + VersionAdded: '2.24' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyMetadata + +RSpec/EmptyOutput: + Description: Check that the `output` matcher is not called with an empty string. + Enabled: true + VersionAdded: '2.29' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyOutput + +RSpec/Eq: + Description: Use `eq` instead of `be ==` to compare objects. + Enabled: true + VersionAdded: '2.24' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Eq RSpec/ExampleLength: Description: Checks for long examples. Enabled: true Max: 5 - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleLength + CountAsOne: [] + VersionAdded: '1.5' + VersionChanged: '2.3' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleLength RSpec/ExampleWithoutDescription: Description: Checks for examples without a description. Enabled: true EnforcedStyle: always_allow SupportedStyles: - - always_allow - - single_line_only - - disallow - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWithoutDescription + - always_allow + - single_line_only + - disallow + VersionAdded: '1.22' + StyleGuide: https://rspec.rubystyle.guide/#it-and-specify + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWithoutDescription RSpec/ExampleWording: Description: Checks for common mistakes in example descriptions. @@ -156,212 +415,353 @@ RSpec/ExampleWording: have: has HAVE: HAS IgnoredWords: [] - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording + DisallowedExamples: + - works + VersionAdded: '1.0' + VersionChanged: '2.13' + StyleGuide: https://rspec.rubystyle.guide/#should-in-example-docstrings + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording + +RSpec/ExcessiveDocstringSpacing: + Description: Checks for excessive whitespace in example descriptions. + Enabled: true + VersionAdded: '2.5' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExcessiveDocstringSpacing RSpec/ExpectActual: Description: Checks for `expect(...)` calls containing literal values. Enabled: true Exclude: - - spec/routing/**/* - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectActual + - "**/spec/routing/**/*" + VersionAdded: '1.7' + VersionChanged: '2.23' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectActual RSpec/ExpectChange: Description: Checks for consistent style of change matcher. Enabled: true EnforcedStyle: method_call SupportedStyles: - - method_call - - block - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectChange + - method_call + - block + SafeAutoCorrect: false + VersionAdded: '1.22' + VersionChanged: '2.5' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectChange RSpec/ExpectInHook: - Enabled: true Description: Do not use `expect` in hooks such as `before`. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInHook + Enabled: true + VersionAdded: '1.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInHook -RSpec/ExpectOutput: - Description: Checks for opportunities to use `expect { ... }.to output`. +RSpec/ExpectInLet: + Description: Do not use `expect` in let. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectOutput + VersionAdded: '2.30' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInLet -RSpec/FilePath: - Description: Checks that spec file paths are consistent with the test subject. +RSpec/ExpectOutput: + Description: Checks for opportunities to use `expect { ... }.to output`. Enabled: true - CustomTransform: - RuboCop: rubocop - RSpec: rspec - IgnoreMethods: false - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FilePath + VersionAdded: '1.10' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectOutput RSpec/Focus: Description: Checks if examples are focused. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Focus + AutoCorrect: contextual + VersionAdded: '1.5' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Focus RSpec/HookArgument: Description: Checks the arguments passed to `before`, `around`, and `after`. Enabled: true EnforcedStyle: implicit SupportedStyles: - - implicit - - each - - example - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HookArgument + - implicit + - each + - example + VersionAdded: '1.7' + StyleGuide: https://rspec.rubystyle.guide/#redundant-beforeeach + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HookArgument RSpec/HooksBeforeExamples: - Enabled: true Description: Checks for before/around/after hooks that come after an example. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HooksBeforeExamples + Enabled: true + AutoCorrect: contextual + VersionAdded: '1.29' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HooksBeforeExamples + +RSpec/IdenticalEqualityAssertion: + Description: Checks for equality assertions with identical expressions on both sides. + Enabled: true + VersionAdded: '2.4' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IdenticalEqualityAssertion RSpec/ImplicitBlockExpectation: Description: Check that implicit block expectation syntax is not used. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitBlockExpectation + VersionAdded: '1.35' + StyleGuide: https://rspec.rubystyle.guide/#implicit-block-expectations + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitBlockExpectation RSpec/ImplicitExpect: Description: Check that a consistent implicit expectation style is used. Enabled: true EnforcedStyle: is_expected SupportedStyles: - - is_expected - - should - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitExpect + - is_expected + - should + VersionAdded: '1.8' + StyleGuide: https://rspec.rubystyle.guide/#use-expect + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitExpect RSpec/ImplicitSubject: - Enabled: true Description: Checks for usage of implicit subject (`is_expected` / `should`). + Enabled: true EnforcedStyle: single_line_only SupportedStyles: - - single_line_only - - single_statement_only - - disallow - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject + - single_line_only + - single_statement_only + - disallow + - require_implicit + VersionAdded: '1.29' + VersionChanged: '2.13' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject + +RSpec/IncludeExamples: + Description: Checks for usage of `include_examples`. + Enabled: pending + SafeAutoCorrect: false + VersionAdded: '3.6' + VersionChanged: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IncludeExamples + +RSpec/IndexedLet: + Description: Do not set up test data using indexes (e.g., `item_1`, `item_2`). + Enabled: true + VersionAdded: '2.20' + VersionChanged: '2.23' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IndexedLet + Max: 1 + AllowedIdentifiers: [] + AllowedPatterns: [] RSpec/InstanceSpy: Description: Checks for `instance_double` used with `have_received`. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceSpy + VersionAdded: '1.12' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceSpy RSpec/InstanceVariable: Description: Checks for instance variable usage in specs. - AssignmentOnly: false Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceVariable + AssignmentOnly: false + VersionAdded: '1.0' + VersionChanged: '1.7' + StyleGuide: https://rspec.rubystyle.guide/#instance-variables + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceVariable -RSpec/InvalidPredicateMatcher: - Description: Checks invalid usage for predicate matcher. +RSpec/IsExpectedSpecify: + Description: Check for `specify` with `is_expected` and one-liner expectations. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InvalidPredicateMatcher + VersionAdded: '2.27' + StyleGuide: https://rspec.rubystyle.guide/#it-and-specify + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IsExpectedSpecify RSpec/ItBehavesLike: Description: Checks that only one `it_behaves_like` style is used. Enabled: true EnforcedStyle: it_behaves_like SupportedStyles: - - it_behaves_like - - it_should_behave_like - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ItBehavesLike + - it_behaves_like + - it_should_behave_like + VersionAdded: '1.13' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ItBehavesLike RSpec/IteratedExpectation: Description: Check that `all` matcher is used instead of iterating over an array. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IteratedExpectation + VersionAdded: '1.14' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IteratedExpectation RSpec/LeadingSubject: Description: Enforce that subject is the first definition in the test. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeadingSubject + VersionAdded: '1.7' + VersionChanged: '1.14' + StyleGuide: https://rspec.rubystyle.guide/#leading-subject + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeadingSubject RSpec/LeakyConstantDeclaration: Description: Checks that no class, module, or constant is declared. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyConstantDeclaration + VersionAdded: '1.35' + StyleGuide: https://rspec.rubystyle.guide/#declare-constants + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyConstantDeclaration RSpec/LetBeforeExamples: Description: Checks for `let` definitions that come after an example. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetBeforeExamples + AutoCorrect: contextual + VersionAdded: '1.16' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetBeforeExamples RSpec/LetSetup: Description: Checks unreferenced `let!` calls being used for test setup. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetSetup + VersionAdded: '1.7' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetSetup + +RSpec/MatchArray: + Description: Checks where `match_array` is used. + Enabled: true + VersionAdded: '2.19' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchArray RSpec/MessageChain: Description: Check that chains of messages are not being stubbed. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageChain + VersionAdded: '1.7' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageChain RSpec/MessageExpectation: Description: Checks for consistent message expectation style. Enabled: false EnforcedStyle: allow SupportedStyles: - - allow - - expect - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageExpectation + - allow + - expect + VersionAdded: '1.7' + VersionChanged: '1.8' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageExpectation RSpec/MessageSpies: Description: Checks that message expectations are set using spies. Enabled: true EnforcedStyle: have_received SupportedStyles: - - have_received - - receive - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageSpies + - have_received + - receive + VersionAdded: '1.9' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageSpies + +RSpec/MetadataStyle: + Description: Use consistent metadata style. + Enabled: true + EnforcedStyle: symbol + SupportedStyles: + - hash + - symbol + VersionAdded: '2.24' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MetadataStyle RSpec/MissingExampleGroupArgument: Description: Checks that the first argument to an example group is not empty. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument + VersionAdded: '1.28' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument + +RSpec/MissingExpectationTargetMethod: + Description: Checks if `.to`, `not_to` or `to_not` are used. + Enabled: true + VersionAdded: '3.0' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExpectationTargetMethod RSpec/MultipleDescribes: - Description: Checks for multiple top level describes. + Description: Checks for multiple top-level example groups. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleDescribes + VersionAdded: '1.0' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleDescribes RSpec/MultipleExpectations: Description: Checks if examples contain too many `expect` calls. Enabled: true Max: 1 - AggregateFailuresByDefault: false - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleExpectations + VersionAdded: '1.7' + VersionChanged: '1.21' + StyleGuide: https://rspec.rubystyle.guide/#expectation-per-example + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleExpectations + +RSpec/MultipleMemoizedHelpers: + Description: Checks if example groups contain too many `let` and `subject` calls. + Enabled: true + AllowSubject: true + Max: 5 + VersionAdded: '1.43' + StyleGuide: https://rspec.rubystyle.guide/#let-blocks + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleMemoizedHelpers RSpec/MultipleSubjects: Description: Checks if an example group defines `subject` multiple times. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleSubjects + VersionAdded: '1.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleSubjects RSpec/NamedSubject: Description: Checks for explicitly referenced test subjects. Enabled: true + EnforcedStyle: always + SupportedStyles: + - always + - named_only IgnoreSharedExamples: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject + VersionAdded: 1.5.3 + VersionChanged: '2.15' + StyleGuide: https://rspec.rubystyle.guide/#use-subject + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject RSpec/NestedGroups: Description: Checks for nested example groups. Enabled: true Max: 3 - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups + AllowedGroups: [] + VersionAdded: '1.7' + VersionChanged: '2.13' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups + +RSpec/NoExpectationExample: + Description: Checks if an example contains any expectation. + Enabled: true + Safe: false + VersionAdded: '2.13' + VersionChanged: '2.14' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample + AllowedPatterns: + - "^expect_" + - "^assert_" RSpec/NotToNot: Description: Checks for consistent method usage for negating expectations. + Enabled: true EnforcedStyle: not_to SupportedStyles: - - not_to - - to_not - Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NotToNot + - not_to + - to_not + VersionAdded: '1.4' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NotToNot RSpec/OverwritingSetup: - Enabled: true Description: Checks if there is a let/subject that overwrites an existing one. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/OverwritingSetup + Enabled: true + VersionAdded: '1.14' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/OverwritingSetup RSpec/Pending: - Enabled: false Description: Checks for any pending or skipped examples. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Pending + Enabled: false + VersionAdded: '1.25' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Pending + +RSpec/PendingWithoutReason: + Description: Checks for pending or skipped examples without reason. + Enabled: true + VersionAdded: '2.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/PendingWithoutReason RSpec/PredicateMatcher: Description: Prefer using predicate matcher over using predicate method directly. @@ -370,136 +770,254 @@ RSpec/PredicateMatcher: EnforcedStyle: inflected AllowedExplicitMatchers: [] SupportedStyles: - - inflected - - explicit - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/PredicateMatcher + - inflected + - explicit + SafeAutoCorrect: false + VersionAdded: '1.16' + StyleGuide: https://rspec.rubystyle.guide/#predicate-matchers + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/PredicateMatcher RSpec/ReceiveCounts: - Enabled: true Description: Check for `once` and `twice` receive counts matchers usage. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveCounts + Enabled: true + VersionAdded: '1.26' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveCounts -RSpec/ReceiveNever: +RSpec/ReceiveMessages: + Description: Prefer `receive_messages` over multiple `receive`s on the same object. Enabled: true + SafeAutoCorrect: false + VersionAdded: '2.23' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveMessages + +RSpec/ReceiveNever: Description: Prefer `not_to receive(...)` over `receive(...).never`. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveNever + Enabled: true + VersionAdded: '1.28' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveNever -RSpec/RepeatedDescription: +RSpec/RedundantAround: + Description: Remove redundant `around` hook. Enabled: true + VersionAdded: '2.19' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantAround + +RSpec/RedundantPredicateMatcher: + Description: Checks for redundant predicate matcher. + Enabled: true + VersionAdded: '2.26' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantPredicateMatcher + +RSpec/RemoveConst: + Description: Checks that `remove_const` is not used in specs. + Enabled: true + VersionAdded: '2.26' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RemoveConst + +RSpec/RepeatedDescription: Description: Check for repeated description strings in example groups. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedDescription + Enabled: true + VersionAdded: '1.9' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedDescription RSpec/RepeatedExample: - Enabled: true Description: Check for repeated examples within example groups. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExample + Enabled: true + VersionAdded: '1.10' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExample RSpec/RepeatedExampleGroupBody: - Enabled: true Description: Check for repeated describe and context block body. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupBody + Enabled: true + VersionAdded: '1.38' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupBody RSpec/RepeatedExampleGroupDescription: - Enabled: true Description: Check for repeated example group descriptions. - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupDescription + Enabled: true + VersionAdded: '1.38' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupDescription -RSpec/ReturnFromStub: +RSpec/RepeatedIncludeExample: + Description: Check for repeated include of shared examples. + Enabled: true + VersionAdded: '1.44' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedIncludeExample + +RSpec/RepeatedSubjectCall: + Description: Checks for repeated calls to subject missing that it is memoized. Enabled: true + VersionAdded: '2.27' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedSubjectCall + +RSpec/ReturnFromStub: Description: Checks for consistent style of stub's return setting. + Enabled: true EnforcedStyle: and_return SupportedStyles: - - and_return - - block - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReturnFromStub + - and_return + - block + VersionAdded: '1.16' + VersionChanged: '1.22' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReturnFromStub RSpec/ScatteredLet: Description: Checks for let scattered across the example group. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ScatteredLet + AutoCorrect: contextual + VersionAdded: '1.14' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ScatteredLet RSpec/ScatteredSetup: Description: Checks for setup scattered across multiple hooks in an example group. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ScatteredSetup + AutoCorrect: contextual + VersionAdded: '1.10' + VersionChanged: '2.31' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ScatteredSetup RSpec/SharedContext: Description: Checks for proper shared_context and shared_examples usage. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedContext + VersionAdded: '1.13' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedContext RSpec/SharedExamples: - Description: Enforces use of string to titleize shared examples. + Description: Checks for consistent style for shared example names. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedExamples + EnforcedStyle: string + SupportedStyles: + - string + - symbol + VersionAdded: '1.25' + VersionChanged: '2.26' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedExamples RSpec/SingleArgumentMessageChain: Description: Checks that chains of messages contain more than one element. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain + VersionAdded: '1.9' + VersionChanged: '1.10' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain -RSpec/SubjectStub: - Description: Checks for stubbed test subjects. +RSpec/SkipBlockInsideExample: + Description: Checks for passing a block to `skip` within examples. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectStub + VersionAdded: '2.19' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SkipBlockInsideExample -RSpec/UnspecifiedException: - Description: Checks for a specified error in checking raised errors. +RSpec/SortMetadata: + Description: Sort RSpec metadata alphabetically. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UnspecifiedException + VersionAdded: '2.14' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SortMetadata -RSpec/VerifiedDoubles: - Description: Prefer using verifying doubles over normal doubles. +RSpec/SpecFilePathFormat: + Description: Checks that spec file paths are consistent and well-formed. Enabled: true - IgnoreNameless: true - IgnoreSymbolicNames: false - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VerifiedDoubles + Include: + - "**/*_spec.rb" + Exclude: + - "**/spec/routing/**/*" + CustomTransform: + RuboCop: rubocop + RSpec: rspec + IgnoreMethods: false + IgnoreMetadata: + type: routing + VersionAdded: '2.24' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathFormat -RSpec/VoidExpect: - Description: This cop checks void `expect()`. +RSpec/SpecFilePathSuffix: + Description: Checks that spec file paths suffix are consistent and well-formed. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VoidExpect + VersionAdded: '2.24' + Include: + - "**/*_spec*rb*" + - "**/spec/**/*" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathSuffix -RSpec/Yield: - Description: This cop checks for calling a block within a stub. +RSpec/StubbedMock: + Description: Checks that message expectations do not have a configured response. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Yield + VersionAdded: '1.44' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/StubbedMock -Capybara/CurrentPathExpectation: - Description: Checks that no expectations are set on Capybara's `current_path`. +RSpec/SubjectDeclaration: + Description: Ensure that subject is defined using subject helper. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/CurrentPathExpectation + VersionAdded: '2.5' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectDeclaration -Capybara/FeatureMethods: - Description: Checks for consistent method usage in feature specs. +RSpec/SubjectStub: + Description: Checks for stubbed test subjects. Enabled: true - EnabledMethods: [] - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/FeatureMethods + VersionAdded: '1.7' + VersionChanged: '2.8' + StyleGuide: https://rspec.rubystyle.guide/#dont-stub-subject + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectStub -FactoryBot/AttributeDefinedStatically: - Description: Always declare attribute values as blocks. +RSpec/UndescriptiveLiteralsDescription: + Description: Description should be descriptive. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically + VersionAdded: '2.29' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UndescriptiveLiteralsDescription -FactoryBot/CreateList: - Description: Checks for create_list usage. +RSpec/UnspecifiedException: + Description: Checks for a specified error in checking raised errors. Enabled: true - EnforcedStyle: create_list - SupportedStyles: - - create_list - - n_times - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/CreateList + VersionAdded: '1.30' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UnspecifiedException -FactoryBot/FactoryClassName: - Description: Use string value when setting the class attribute explicitly. +RSpec/VariableDefinition: + Description: Checks that memoized helpers names are symbols or strings. Enabled: true - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/FactoryClassName + EnforcedStyle: symbols + SupportedStyles: + - symbols + - strings + VersionAdded: '1.40' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableDefinition -Rails/HttpStatus: - Description: Enforces use of symbolic or numeric value to describe HTTP status. +RSpec/VariableName: + Description: Checks that memoized helper names use the configured style. Enabled: true - EnforcedStyle: symbolic + EnforcedStyle: snake_case SupportedStyles: - - numeric - - symbolic - StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HttpStatus + - snake_case + - camelCase + AllowedPatterns: [] + VersionAdded: '1.40' + VersionChanged: '2.13' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName + +RSpec/VerifiedDoubleReference: + Description: Checks for consistent verified double reference style. + Enabled: true + SafeAutoCorrect: false + VersionAdded: 2.10.0 + VersionChanged: '3.4' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VerifiedDoubleReference + +RSpec/VerifiedDoubles: + Description: Prefer using verifying doubles over normal doubles. + Enabled: true + IgnoreNameless: true + IgnoreSymbolicNames: false + VersionAdded: 1.2.1 + VersionChanged: '1.5' + StyleGuide: https://rspec.rubystyle.guide/#doubles + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VerifiedDoubles + +RSpec/VoidExpect: + Description: Checks void `expect()`. + Enabled: true + VersionAdded: '1.16' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VoidExpect + +RSpec/Yield: + Description: Checks for calling a block within a stub. + Enabled: true + VersionAdded: '1.32' + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Yield diff --git a/config/obsoletion.yml b/config/obsoletion.yml new file mode 100644 index 000000000..2c2442805 --- /dev/null +++ b/config/obsoletion.yml @@ -0,0 +1,35 @@ +# +# Configuration of obsolete/deprecated cops used by `ConfigObsoletion`. +# +# See: https://docs.rubocop.org/rubocop/extensions.html#config-obsoletions +# + +# Cop parameters that have been changed +# Can be treated as a warning instead of a failure with `severity: warning` +changed_parameters: + - cops: + - RSpec/VariableName + parameters: IgnoredPatterns + alternative: AllowedPatterns + severity: warning + - cops: RSpec/VerifiedDoubleReference + parameters: EnforcedStyle + reason: String references are not verifying unless the class is loaded. + +split: + RSpec/FilePath: + alternatives: + - RSpec/SpecFilePathFormat + - RSpec/SpecFilePathSuffix + +removed: + RSpec/Capybara/FeatureMethods: + reason: > + this cop has migrated to `RSpec/Dialect`. Please use `RSpec/Dialect` instead + RSpec/StringAsInstanceDoubleConstant: + reason: Please use `RSpec/VerifiedDoubleReference` instead + +extracted: + RSpec/Rails/*: rubocop-rspec_rails + RSpec/FactoryBot/*: rubocop-factory_bot + RSpec/Capybara/*: rubocop-capybara diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 000000000..51a533a15 --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,5 @@ +name: rubocop-rspec +title: RuboCop RSpec +version: ~ +nav: + - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc new file mode 100644 index 000000000..2ab1a89d1 --- /dev/null +++ b/docs/modules/ROOT/nav.adoc @@ -0,0 +1,10 @@ +* xref:index.adoc[Home] +* xref:installation.adoc[Installation] +* xref:usage.adoc[Usage] +* xref:cops.adoc[Cops] +* xref:upgrade_to_version_2.adoc[Upgrade to 2.x] +* xref:upgrade_to_version_3.adoc[Upgrade to 3.x] +* xref:third_party_rspec_syntax_extensions.adoc[RSpec syntax extensions in third-party gems] +* xref:development.adoc[Development] +* Cops Documentation +** xref:cops_rspec.adoc[RSpec] diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc new file mode 100644 index 000000000..08329092b --- /dev/null +++ b/docs/modules/ROOT/pages/cops.adoc @@ -0,0 +1,117 @@ +// START_COP_LIST + +=== Department xref:cops_rspec.adoc[RSpec] + +* xref:cops_rspec.adoc#rspecalignleftletbrace[RSpec/AlignLeftLetBrace] +* xref:cops_rspec.adoc#rspecalignrightletbrace[RSpec/AlignRightLetBrace] +* xref:cops_rspec.adoc#rspecanyinstance[RSpec/AnyInstance] +* xref:cops_rspec.adoc#rspecaroundblock[RSpec/AroundBlock] +* xref:cops_rspec.adoc#rspecbe[RSpec/Be] +* xref:cops_rspec.adoc#rspecbeempty[RSpec/BeEmpty] +* xref:cops_rspec.adoc#rspecbeeq[RSpec/BeEq] +* xref:cops_rspec.adoc#rspecbeeql[RSpec/BeEql] +* xref:cops_rspec.adoc#rspecbenil[RSpec/BeNil] +* xref:cops_rspec.adoc#rspecbeforeafterall[RSpec/BeforeAfterAll] +* xref:cops_rspec.adoc#rspecchangebyzero[RSpec/ChangeByZero] +* xref:cops_rspec.adoc#rspecclasscheck[RSpec/ClassCheck] +* xref:cops_rspec.adoc#rspeccontainexactly[RSpec/ContainExactly] +* xref:cops_rspec.adoc#rspeccontextmethod[RSpec/ContextMethod] +* xref:cops_rspec.adoc#rspeccontextwording[RSpec/ContextWording] +* xref:cops_rspec.adoc#rspecdescribeclass[RSpec/DescribeClass] +* xref:cops_rspec.adoc#rspecdescribemethod[RSpec/DescribeMethod] +* xref:cops_rspec.adoc#rspecdescribesymbol[RSpec/DescribeSymbol] +* xref:cops_rspec.adoc#rspecdescribedclass[RSpec/DescribedClass] +* xref:cops_rspec.adoc#rspecdescribedclassmodulewrapping[RSpec/DescribedClassModuleWrapping] +* xref:cops_rspec.adoc#rspecdialect[RSpec/Dialect] +* xref:cops_rspec.adoc#rspecduplicatedmetadata[RSpec/DuplicatedMetadata] +* xref:cops_rspec.adoc#rspecemptyexamplegroup[RSpec/EmptyExampleGroup] +* xref:cops_rspec.adoc#rspecemptyhook[RSpec/EmptyHook] +* xref:cops_rspec.adoc#rspecemptylineafterexample[RSpec/EmptyLineAfterExample] +* xref:cops_rspec.adoc#rspecemptylineafterexamplegroup[RSpec/EmptyLineAfterExampleGroup] +* xref:cops_rspec.adoc#rspecemptylineafterfinallet[RSpec/EmptyLineAfterFinalLet] +* xref:cops_rspec.adoc#rspecemptylineafterhook[RSpec/EmptyLineAfterHook] +* xref:cops_rspec.adoc#rspecemptylineaftersubject[RSpec/EmptyLineAfterSubject] +* xref:cops_rspec.adoc#rspecemptymetadata[RSpec/EmptyMetadata] +* xref:cops_rspec.adoc#rspecemptyoutput[RSpec/EmptyOutput] +* xref:cops_rspec.adoc#rspeceq[RSpec/Eq] +* xref:cops_rspec.adoc#rspecexamplelength[RSpec/ExampleLength] +* xref:cops_rspec.adoc#rspecexamplewithoutdescription[RSpec/ExampleWithoutDescription] +* xref:cops_rspec.adoc#rspecexamplewording[RSpec/ExampleWording] +* xref:cops_rspec.adoc#rspecexcessivedocstringspacing[RSpec/ExcessiveDocstringSpacing] +* xref:cops_rspec.adoc#rspecexpectactual[RSpec/ExpectActual] +* xref:cops_rspec.adoc#rspecexpectchange[RSpec/ExpectChange] +* xref:cops_rspec.adoc#rspecexpectinhook[RSpec/ExpectInHook] +* xref:cops_rspec.adoc#rspecexpectinlet[RSpec/ExpectInLet] +* xref:cops_rspec.adoc#rspecexpectoutput[RSpec/ExpectOutput] +* xref:cops_rspec.adoc#rspecfocus[RSpec/Focus] +* xref:cops_rspec.adoc#rspechookargument[RSpec/HookArgument] +* xref:cops_rspec.adoc#rspechooksbeforeexamples[RSpec/HooksBeforeExamples] +* xref:cops_rspec.adoc#rspecidenticalequalityassertion[RSpec/IdenticalEqualityAssertion] +* xref:cops_rspec.adoc#rspecimplicitblockexpectation[RSpec/ImplicitBlockExpectation] +* xref:cops_rspec.adoc#rspecimplicitexpect[RSpec/ImplicitExpect] +* xref:cops_rspec.adoc#rspecimplicitsubject[RSpec/ImplicitSubject] +* xref:cops_rspec.adoc#rspecincludeexamples[RSpec/IncludeExamples] +* xref:cops_rspec.adoc#rspecindexedlet[RSpec/IndexedLet] +* xref:cops_rspec.adoc#rspecinstancespy[RSpec/InstanceSpy] +* xref:cops_rspec.adoc#rspecinstancevariable[RSpec/InstanceVariable] +* xref:cops_rspec.adoc#rspecisexpectedspecify[RSpec/IsExpectedSpecify] +* xref:cops_rspec.adoc#rspecitbehaveslike[RSpec/ItBehavesLike] +* xref:cops_rspec.adoc#rspeciteratedexpectation[RSpec/IteratedExpectation] +* xref:cops_rspec.adoc#rspecleadingsubject[RSpec/LeadingSubject] +* xref:cops_rspec.adoc#rspecleakyconstantdeclaration[RSpec/LeakyConstantDeclaration] +* xref:cops_rspec.adoc#rspecletbeforeexamples[RSpec/LetBeforeExamples] +* xref:cops_rspec.adoc#rspecletsetup[RSpec/LetSetup] +* xref:cops_rspec.adoc#rspecmatcharray[RSpec/MatchArray] +* xref:cops_rspec.adoc#rspecmessagechain[RSpec/MessageChain] +* xref:cops_rspec.adoc#rspecmessageexpectation[RSpec/MessageExpectation] +* xref:cops_rspec.adoc#rspecmessagespies[RSpec/MessageSpies] +* xref:cops_rspec.adoc#rspecmetadatastyle[RSpec/MetadataStyle] +* xref:cops_rspec.adoc#rspecmissingexamplegroupargument[RSpec/MissingExampleGroupArgument] +* xref:cops_rspec.adoc#rspecmissingexpectationtargetmethod[RSpec/MissingExpectationTargetMethod] +* xref:cops_rspec.adoc#rspecmultipledescribes[RSpec/MultipleDescribes] +* xref:cops_rspec.adoc#rspecmultipleexpectations[RSpec/MultipleExpectations] +* xref:cops_rspec.adoc#rspecmultiplememoizedhelpers[RSpec/MultipleMemoizedHelpers] +* xref:cops_rspec.adoc#rspecmultiplesubjects[RSpec/MultipleSubjects] +* xref:cops_rspec.adoc#rspecnamedsubject[RSpec/NamedSubject] +* xref:cops_rspec.adoc#rspecnestedgroups[RSpec/NestedGroups] +* xref:cops_rspec.adoc#rspecnoexpectationexample[RSpec/NoExpectationExample] +* xref:cops_rspec.adoc#rspecnottonot[RSpec/NotToNot] +* xref:cops_rspec.adoc#rspecoverwritingsetup[RSpec/OverwritingSetup] +* xref:cops_rspec.adoc#rspecpending[RSpec/Pending] +* xref:cops_rspec.adoc#rspecpendingwithoutreason[RSpec/PendingWithoutReason] +* xref:cops_rspec.adoc#rspecpredicatematcher[RSpec/PredicateMatcher] +* xref:cops_rspec.adoc#rspecreceivecounts[RSpec/ReceiveCounts] +* xref:cops_rspec.adoc#rspecreceivemessages[RSpec/ReceiveMessages] +* xref:cops_rspec.adoc#rspecreceivenever[RSpec/ReceiveNever] +* xref:cops_rspec.adoc#rspecredundantaround[RSpec/RedundantAround] +* xref:cops_rspec.adoc#rspecredundantpredicatematcher[RSpec/RedundantPredicateMatcher] +* xref:cops_rspec.adoc#rspecremoveconst[RSpec/RemoveConst] +* xref:cops_rspec.adoc#rspecrepeateddescription[RSpec/RepeatedDescription] +* xref:cops_rspec.adoc#rspecrepeatedexample[RSpec/RepeatedExample] +* xref:cops_rspec.adoc#rspecrepeatedexamplegroupbody[RSpec/RepeatedExampleGroupBody] +* xref:cops_rspec.adoc#rspecrepeatedexamplegroupdescription[RSpec/RepeatedExampleGroupDescription] +* xref:cops_rspec.adoc#rspecrepeatedincludeexample[RSpec/RepeatedIncludeExample] +* xref:cops_rspec.adoc#rspecrepeatedsubjectcall[RSpec/RepeatedSubjectCall] +* xref:cops_rspec.adoc#rspecreturnfromstub[RSpec/ReturnFromStub] +* xref:cops_rspec.adoc#rspecscatteredlet[RSpec/ScatteredLet] +* xref:cops_rspec.adoc#rspecscatteredsetup[RSpec/ScatteredSetup] +* xref:cops_rspec.adoc#rspecsharedcontext[RSpec/SharedContext] +* xref:cops_rspec.adoc#rspecsharedexamples[RSpec/SharedExamples] +* xref:cops_rspec.adoc#rspecsingleargumentmessagechain[RSpec/SingleArgumentMessageChain] +* xref:cops_rspec.adoc#rspecskipblockinsideexample[RSpec/SkipBlockInsideExample] +* xref:cops_rspec.adoc#rspecsortmetadata[RSpec/SortMetadata] +* xref:cops_rspec.adoc#rspecspecfilepathformat[RSpec/SpecFilePathFormat] +* xref:cops_rspec.adoc#rspecspecfilepathsuffix[RSpec/SpecFilePathSuffix] +* xref:cops_rspec.adoc#rspecstubbedmock[RSpec/StubbedMock] +* xref:cops_rspec.adoc#rspecsubjectdeclaration[RSpec/SubjectDeclaration] +* xref:cops_rspec.adoc#rspecsubjectstub[RSpec/SubjectStub] +* xref:cops_rspec.adoc#rspecundescriptiveliteralsdescription[RSpec/UndescriptiveLiteralsDescription] +* xref:cops_rspec.adoc#rspecunspecifiedexception[RSpec/UnspecifiedException] +* xref:cops_rspec.adoc#rspecvariabledefinition[RSpec/VariableDefinition] +* xref:cops_rspec.adoc#rspecvariablename[RSpec/VariableName] +* xref:cops_rspec.adoc#rspecverifieddoublereference[RSpec/VerifiedDoubleReference] +* xref:cops_rspec.adoc#rspecverifieddoubles[RSpec/VerifiedDoubles] +* xref:cops_rspec.adoc#rspecvoidexpect[RSpec/VoidExpect] +* xref:cops_rspec.adoc#rspecyield[RSpec/Yield] + +// END_COP_LIST diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc new file mode 100644 index 000000000..40c7757ac --- /dev/null +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -0,0 +1,6610 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + += RSpec + +[#rspecalignleftletbrace] +== RSpec/AlignLeftLetBrace + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Disabled +| Yes +| Always +| 1.16 +| - +|=== + +Checks that left braces for adjacent single line lets are aligned. + +[#examples-rspecalignleftletbrace] +=== Examples + +[source,ruby] +---- +# bad +let(:foobar) { blahblah } +let(:baz) { bar } +let(:a) { b } + +# good +let(:foobar) { blahblah } +let(:baz) { bar } +let(:a) { b } +---- + +[#references-rspecalignleftletbrace] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AlignLeftLetBrace + +[#rspecalignrightletbrace] +== RSpec/AlignRightLetBrace + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Disabled +| Yes +| Always +| 1.16 +| - +|=== + +Checks that right braces for adjacent single line lets are aligned. + +[#examples-rspecalignrightletbrace] +=== Examples + +[source,ruby] +---- +# bad +let(:foobar) { blahblah } +let(:baz) { bar } +let(:a) { b } + +# good +let(:foobar) { blahblah } +let(:baz) { bar } +let(:a) { b } +---- + +[#references-rspecalignrightletbrace] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AlignRightLetBrace + +[#rspecanyinstance] +== RSpec/AnyInstance + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.4 +| - +|=== + +Check that instances are not being stubbed globally. + +Prefer instance doubles over stubbing any instance of a class + +[#examples-rspecanyinstance] +=== Examples + +[source,ruby] +---- +# bad +describe MyClass do + before { allow_any_instance_of(MyClass).to receive(:foo) } +end + +# good +describe MyClass do + let(:my_instance) { instance_double(MyClass) } + + before do + allow(MyClass).to receive(:new).and_return(my_instance) + allow(my_instance).to receive(:foo) + end +end +---- + +[#references-rspecanyinstance] +=== References + +* https://rspec.rubystyle.guide/#any_instance_of +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AnyInstance + +[#rspecaroundblock] +== RSpec/AroundBlock + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.11 +| - +|=== + +Checks that around blocks actually run the test. + +[#examples-rspecaroundblock] +=== Examples + +[source,ruby] +---- +# bad +around do + some_method +end + +around do |test| + some_method +end + +# good +around do |test| + some_method + test.call +end + +around do |test| + some_method + test.run +end +---- + +[#references-rspecaroundblock] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AroundBlock + +[#rspecbe] +== RSpec/Be + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.25 +| - +|=== + +Check for expectations where `be` is used without argument. + +The `be` matcher is too generic, as it pass on everything that is not +nil or false. If that is the exact intend, use `be_truthy`. In all other +cases it's better to specify what exactly is the expected value. + +[#examples-rspecbe] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to be + +# good +expect(foo).to be_truthy +expect(foo).to be 1.0 +expect(foo).to be(true) +---- + +[#references-rspecbe] +=== References + +* https://rspec.rubystyle.guide/#be-matcher +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Be + +[#rspecbeempty] +== RSpec/BeEmpty + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 2.20 +| 2.31 +|=== + +Prefer using `be_empty` when checking for an empty array. + +[#examples-rspecbeempty] +=== Examples + +[source,ruby] +---- +# bad +expect(array).to contain_exactly +expect(array).to match_array([]) + +# good +expect(array).to be_empty +---- + +[#references-rspecbeempty] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEmpty + +[#rspecbeeq] +== RSpec/BeEq + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| No +| Always (Unsafe) +| 2.9.0 +| 2.16 +|=== + +Check for expectations where `be(...)` can replace `eq(...)`. + +The `be` matcher compares by identity while the `eq` matcher compares +using `==`. Booleans and nil can be compared by identity and therefore +the `be` matcher is preferable as it is a more strict test. + +[#safety-rspecbeeq] +=== Safety + +This cop is unsafe because it changes how values are compared. + +[#examples-rspecbeeq] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to eq(true) +expect(foo).to eq(false) +expect(foo).to eq(nil) + +# good +expect(foo).to be(true) +expect(foo).to be(false) +expect(foo).to be(nil) +---- + +[#references-rspecbeeq] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEq + +[#rspecbeeql] +== RSpec/BeEql + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| No +| Always (Unsafe) +| 1.7 +| 2.16 +|=== + +Check for expectations where `be(...)` can replace `eql(...)`. + +The `be` matcher compares by identity while the `eql` matcher +compares using `eql?`. Integers, floats, booleans, symbols, and nil +can be compared by identity and therefore the `be` matcher is +preferable as it is a more strict test. + +This cop only looks for instances of `expect(...).to eql(...)`. We +do not check `to_not` or `not_to` since `!eql?` is more strict +than `!equal?`. We also do not try to flag `eq` because if +`a == b`, and `b` is comparable by identity, `a` is still not +necessarily the same type as `b` since the `#==` operator can +coerce objects for comparison. + +[#safety-rspecbeeql] +=== Safety + +This cop is unsafe because it changes how values are compared. + +[#examples-rspecbeeql] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to eql(1) +expect(foo).to eql(1.0) +expect(foo).to eql(true) +expect(foo).to eql(false) +expect(foo).to eql(:bar) +expect(foo).to eql(nil) + +# good +expect(foo).to be(1) +expect(foo).to be(1.0) +expect(foo).to be(true) +expect(foo).to be(false) +expect(foo).to be(:bar) +expect(foo).to be(nil) +---- + +[#references-rspecbeeql] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEql + +[#rspecbenil] +== RSpec/BeNil + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.9.0 +| 2.10.0 +|=== + +Ensures a consistent style is used when matching `nil`. + +You can either use the more specific `be_nil` matcher, or the more +generic `be` matcher with a `nil` argument. + +This cop can be configured using the `EnforcedStyle` option + +[#examples-rspecbenil] +=== Examples + +[#_enforcedstyle_-be_nil_-_default_-rspecbenil] +==== `EnforcedStyle: be_nil` (default) + +[source,ruby] +---- +# bad +expect(foo).to be(nil) + +# good +expect(foo).to be_nil +---- + +[#_enforcedstyle_-be_-rspecbenil] +==== `EnforcedStyle: be` + +[source,ruby] +---- +# bad +expect(foo).to be_nil + +# good +expect(foo).to be(nil) +---- + +[#configurable-attributes-rspecbenil] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `be_nil` +| `be`, `be_nil` +|=== + +[#references-rspecbenil] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeNil + +[#rspecbeforeafterall] +== RSpec/BeforeAfterAll + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.12 +| 2.23 +|=== + +Check that before/after(:all/:context) isn't being used. + +[#examples-rspecbeforeafterall] +=== Examples + +[source,ruby] +---- +# bad - Faster but risk of state leaking between examples +describe MyClass do + before(:all) { Widget.create } + after(:context) { Widget.delete_all } +end + +# good - Slower but examples are properly isolated +describe MyClass do + before(:each) { Widget.create } + after(:each) { Widget.delete_all } +end +---- + +[#configurable-attributes-rspecbeforeafterall] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Exclude +| `+**/spec/spec_helper.rb+`, `+**/spec/rails_helper.rb+`, `+**/spec/support/**/*.rb+` +| Array +|=== + +[#references-rspecbeforeafterall] +=== References + +* https://rspec.rubystyle.guide/#avoid-hooks-with-context-scope +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeforeAfterAll + +[#rspecchangebyzero] +== RSpec/ChangeByZero + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.11 +| 2.14 +|=== + +Prefer negated matchers over `to change.by(0)`. + +In the case of composite expectations, cop suggest using the +negation matchers of `RSpec::Matchers#change`. + +By default the cop does not support autocorrect of +compound expectations, but if you set the +negated matcher for `change`, e.g. `not_change` with +the `NegatedMatcher` option, the cop will perform the autocorrection. + +[#examples-rspecchangebyzero] +=== Examples + +[#negatedmatcher_-_-_default_-rspecchangebyzero] +==== NegatedMatcher: ~ (default) + +[source,ruby] +---- +# bad +expect { run }.to change(Foo, :bar).by(0) +expect { run }.to change { Foo.bar }.by(0) + +# bad - compound expectations (does not support autocorrection) +expect { run } + .to change(Foo, :bar).by(0) + .and change(Foo, :baz).by(0) +expect { run } + .to change { Foo.bar }.by(0) + .and change { Foo.baz }.by(0) + +# good +expect { run }.not_to change(Foo, :bar) +expect { run }.not_to change { Foo.bar } + +# good - compound expectations +define_negated_matcher :not_change, :change +expect { run } + .to not_change(Foo, :bar) + .and not_change(Foo, :baz) +expect { run } + .to not_change { Foo.bar } + .and not_change { Foo.baz } +---- + +[#negatedmatcher_-not_change-rspecchangebyzero] +==== NegatedMatcher: not_change + +[source,ruby] +---- +# bad (support autocorrection to good case) +expect { run } + .to change(Foo, :bar).by(0) + .and change(Foo, :baz).by(0) +expect { run } + .to change { Foo.bar }.by(0) + .and change { Foo.baz }.by(0) + +# good +define_negated_matcher :not_change, :change +expect { run } + .to not_change(Foo, :bar) + .and not_change(Foo, :baz) +expect { run } + .to not_change { Foo.bar } + .and not_change { Foo.baz } +---- + +[#configurable-attributes-rspecchangebyzero] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| NegatedMatcher +| `` +| +|=== + +[#references-rspecchangebyzero] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero + +[#rspecclasscheck] +== RSpec/ClassCheck + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.13 +| - +|=== + +Enforces consistent use of `be_a` or `be_kind_of`. + +[#examples-rspecclasscheck] +=== Examples + +[#enforcedstyle_-be_a-_default_-rspecclasscheck] +==== EnforcedStyle: be_a (default) + +[source,ruby] +---- +# bad +expect(object).to be_kind_of(String) +expect(object).to be_a_kind_of(String) + +# good +expect(object).to be_a(String) +expect(object).to be_an(String) +---- + +[#enforcedstyle_-be_kind_of-rspecclasscheck] +==== EnforcedStyle: be_kind_of + +[source,ruby] +---- +# bad +expect(object).to be_a(String) +expect(object).to be_an(String) + +# good +expect(object).to be_kind_of(String) +expect(object).to be_a_kind_of(String) +---- + +[#configurable-attributes-rspecclasscheck] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `be_a` +| `be_a`, `be_kind_of` +|=== + +[#references-rspecclasscheck] +=== References + +* https://rubystyle.guide#is-a-vs-kind-of +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ClassCheck + +[#rspeccontainexactly] +== RSpec/ContainExactly + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.19 +| - +|=== + +Checks where `contain_exactly` is used. + +This cop checks for the following: + +- Prefer `match_array` when matching array values. +- Prefer `be_empty` when using `contain_exactly` with no arguments. + +[#examples-rspeccontainexactly] +=== Examples + +[source,ruby] +---- +# bad +it { is_expected.to contain_exactly(*array1, *array2) } + +# good +it { is_expected.to match_array(array1 + array2) } + +# good +it { is_expected.to contain_exactly(content, *array) } +---- + +[#references-rspeccontainexactly] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContainExactly + +[#rspeccontextmethod] +== RSpec/ContextMethod + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.36 +| - +|=== + +`context` should not be used for specifying methods. + +[#examples-rspeccontextmethod] +=== Examples + +[source,ruby] +---- +# bad +context '#foo_bar' do + # ... +end + +context '.foo_bar' do + # ... +end + +# good +describe '#foo_bar' do + # ... +end + +describe '.foo_bar' do + # ... +end +---- + +[#references-rspeccontextmethod] +=== References + +* https://rspec.rubystyle.guide/#example-group-naming +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextMethod + +[#rspeccontextwording] +== RSpec/ContextWording + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.20 +| 2.13 +|=== + +Checks that `context` docstring starts with an allowed prefix. + +The default list of prefixes is minimal. Users are encouraged to tailor +the configuration to meet project needs. Other acceptable prefixes may +include `if`, `unless`, `for`, `before`, `after`, or `during`. +They may consist of multiple words if desired. + +If both `Prefixes` and `AllowedPatterns` are empty, this cop will always +report an offense. So you need to set at least one of them. + +This cop can be customized allowed context description pattern +with `AllowedPatterns`. By default, there are no checking by pattern. + +[#examples-rspeccontextwording] +=== Examples + +[#_prefixes_-configuration-rspeccontextwording] +==== `Prefixes` configuration + +[source,ruby] +---- +# .rubocop.yml +# RSpec/ContextWording: +# Prefixes: +# - when +# - with +# - without +# - if +# - unless +# - for +---- + +[source,ruby] +---- +# bad +context 'the display name not present' do + # ... +end + +# good +context 'when the display name is not present' do + # ... +end +---- + +[#_allowedpatterns_-configuration-rspeccontextwording] +==== `AllowedPatterns` configuration + +[source,ruby] +---- +# .rubocop.yml +# RSpec/ContextWording: +# AllowedPatterns: +# - とき$ +---- + +[source,ruby] +---- +# bad +context '条件を満たす' do + # ... +end + +# good +context '条件を満たすとき' do + # ... +end +---- + +[#configurable-attributes-rspeccontextwording] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Prefixes +| `when`, `with`, `without` +| Array + +| AllowedPatterns +| `[]` +| Array +|=== + +[#references-rspeccontextwording] +=== References + +* https://rspec.rubystyle.guide/#context-descriptions +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextWording +* http://www.betterspecs.org/#contexts + +[#rspecdescribeclass] +== RSpec/DescribeClass + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.0 +| 2.7 +|=== + +Check that the first argument to the top-level describe is a constant. + +It can be configured to ignore strings when certain metadata is passed. + +Ignores Rails and Aruba `type` metadata by default. + +[#examples-rspecdescribeclass] +=== Examples + +[#_ignoredmetadata_-configuration-rspecdescribeclass] +==== `IgnoredMetadata` configuration + +[source,ruby] +---- +# .rubocop.yml +# RSpec/DescribeClass: +# IgnoredMetadata: +# type: +# - request +# - controller +---- + +[source,ruby] +---- +# bad +describe 'Do something' do +end + +# good +describe TestedClass do + subject { described_class } +end + +describe 'TestedClass::VERSION' do + subject { Object.const_get(self.class.description) } +end + +describe "A feature example", type: :feature do +end +---- + +[#configurable-attributes-rspecdescribeclass] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Exclude +| `+**/spec/features/**/*+`, `+**/spec/requests/**/*+`, `+**/spec/routing/**/*+`, `+**/spec/system/**/*+`, `+**/spec/views/**/*+` +| Array + +| IgnoredMetadata +| `{"type" => ["channel", "controller", "helper", "job", "mailer", "model", "request", "routing", "view", "feature", "system", "mailbox", "aruba", "task"]}` +| +|=== + +[#references-rspecdescribeclass] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeClass + +[#rspecdescribemethod] +== RSpec/DescribeMethod + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.0 +| - +|=== + +Checks that the second argument to `describe` specifies a method. + +[#examples-rspecdescribemethod] +=== Examples + +[source,ruby] +---- +# bad +describe MyClass, 'do something' do +end + +# good +describe MyClass, '#my_instance_method' do +end + +describe MyClass, '.my_class_method' do +end +---- + +[#references-rspecdescribemethod] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeMethod + +[#rspecdescribesymbol] +== RSpec/DescribeSymbol + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.15 +| - +|=== + +Avoid describing symbols. + +[#examples-rspecdescribesymbol] +=== Examples + +[source,ruby] +---- +# bad +describe :my_method do + # ... +end + +# good +describe '#my_method' do + # ... +end +---- + +[#references-rspecdescribesymbol] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeSymbol +* https://github.com/rspec/rspec-core/issues/1610 + +[#rspecdescribedclass] +== RSpec/DescribedClass + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always (Unsafe) +| 1.0 +| 2.27 +|=== + +Checks that tests use `described_class`. + +If the first argument of describe is a class, the class is exposed to +each example via described_class. + +This cop can be configured using the `EnforcedStyle`, `SkipBlocks` +and `OnlyStaticConstants` options. +`OnlyStaticConstants` is only relevant when `EnforcedStyle` is +`described_class`. + +There's a known caveat with rspec-rails's `controller` helper that +runs its block in a different context, and `described_class` is not +available to it. `SkipBlocks` option excludes detection in all +non-RSpec related blocks. + +To narrow down this setting to only a specific directory, it is +possible to use an overriding configuration file local to that +directory. + +[#examples-rspecdescribedclass] +=== Examples + +[#_enforcedstyle_-described_class_-_default_-rspecdescribedclass] +==== `EnforcedStyle: described_class` (default) + +[source,ruby] +---- +# bad +describe MyClass do + subject { MyClass.do_something } +end + +# good +describe MyClass do + subject { described_class.do_something } +end +---- + +[#_onlystaticconstants_-true_-_default_-rspecdescribedclass] +==== `OnlyStaticConstants: true` (default) + +[source,ruby] +---- +# good +describe MyClass do + subject { MyClass::CONSTANT } +end +---- + +[#_onlystaticconstants_-false_-rspecdescribedclass] +==== `OnlyStaticConstants: false` + +[source,ruby] +---- +# bad +describe MyClass do + subject { MyClass::CONSTANT } +end +---- + +[#_enforcedstyle_-explicit_-rspecdescribedclass] +==== `EnforcedStyle: explicit` + +[source,ruby] +---- +# bad +describe MyClass do + subject { described_class.do_something } +end + +# good +describe MyClass do + subject { MyClass.do_something } +end +---- + +[#_skipblocks_-true_-rspecdescribedclass] +==== `SkipBlocks: true` + +[source,ruby] +---- +# spec/controllers/.rubocop.yml +# RSpec/DescribedClass: +# SkipBlocks: true + +# acceptable +describe MyConcern do + controller(ApplicationController) do + include MyConcern + end +end +---- + +[#configurable-attributes-rspecdescribedclass] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| SkipBlocks +| `false` +| Boolean + +| EnforcedStyle +| `described_class` +| `described_class`, `explicit` + +| OnlyStaticConstants +| `true` +| Boolean +|=== + +[#references-rspecdescribedclass] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribedClass + +[#rspecdescribedclassmodulewrapping] +== RSpec/DescribedClassModuleWrapping + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Disabled +| Yes +| No +| 1.37 +| - +|=== + +Avoid opening modules and defining specs within them. + +[#examples-rspecdescribedclassmodulewrapping] +=== Examples + +[source,ruby] +---- +# bad +module MyModule + RSpec.describe MyClass do + # ... + end +end + +# good +RSpec.describe MyModule::MyClass do + # ... +end +---- + +[#references-rspecdescribedclassmodulewrapping] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribedClassModuleWrapping +* https://github.com/rubocop/rubocop-rspec/issues/735 + +[#rspecdialect] +== RSpec/Dialect + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Disabled +| Yes +| Always +| 1.33 +| - +|=== + +Enforces custom RSpec dialects. + +A dialect can be based on the following RSpec methods: + +- describe, context, feature, example_group +- xdescribe, xcontext, xfeature +- fdescribe, fcontext, ffeature +- shared_examples, shared_examples_for, shared_context +- it, specify, example, scenario, its +- fit, fspecify, fexample, fscenario, focus +- xit, xspecify, xexample, xscenario, skip +- pending +- prepend_before, before, append_before, +- around +- prepend_after, after, append_after +- let, let! +- subject, subject! +- expect, is_expected, expect_any_instance_of + +By default all of the RSpec methods and aliases are allowed. By setting +a config like: + + RSpec/Dialect: + PreferredMethods: + context: describe + +If you were previously using the `RSpec/Capybara/FeatureMethods` cop and +want to keep disabling all Capybara-specific methods that have the same +native RSpec method (e.g. are just aliases), use the following config: + + RSpec/Dialect: + PreferredMethods: + background: :before + scenario: :it + xscenario: :xit + given: :let + given!: :let! + feature: :describe + +You can expect the following behavior: + +[#examples-rspecdialect] +=== Examples + +[source,ruby] +---- +# bad +context 'display name presence' do + # ... +end + +# good +describe 'display name presence' do + # ... +end +---- + +[#configurable-attributes-rspecdialect] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| PreferredMethods +| `{}` +| +|=== + +[#references-rspecdialect] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Dialect + +[#rspecduplicatedmetadata] +== RSpec/DuplicatedMetadata + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.16 +| - +|=== + +Avoid duplicated metadata. + +[#examples-rspecduplicatedmetadata] +=== Examples + +[source,ruby] +---- +# bad +describe 'Something', :a, :a + +# good +describe 'Something', :a +---- + +[#references-rspecduplicatedmetadata] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DuplicatedMetadata + +[#rspecemptyexamplegroup] +== RSpec/EmptyExampleGroup + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only (Unsafe) +| 1.7 +| 2.31 +|=== + +Checks if an example group does not include any tests. + +[#examples-rspecemptyexamplegroup] +=== Examples + +[#usage-rspecemptyexamplegroup] +==== usage + +[source,ruby] +---- +# bad +describe Bacon do + let(:bacon) { Bacon.new(chunkiness) } + let(:chunkiness) { false } + + context 'extra chunky' do # flagged by rubocop + let(:chunkiness) { true } + end + + it 'is chunky' do + expect(bacon.chunky?).to be_truthy + end +end + +# good +describe Bacon do + let(:bacon) { Bacon.new(chunkiness) } + let(:chunkiness) { false } + + it 'is chunky' do + expect(bacon.chunky?).to be_truthy + end +end + +# good +describe Bacon do + pending 'will add tests later' +end +---- + +[#references-rspecemptyexamplegroup] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyExampleGroup + +[#rspecemptyhook] +== RSpec/EmptyHook + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 1.39 +| 2.31 +|=== + +Checks for empty before and after hooks. + +[#examples-rspecemptyhook] +=== Examples + +[source,ruby] +---- +# bad +before {} +after do; end +before(:all) do +end +after(:all) { } + +# good +before { create_users } +after do + cleanup_users +end +before(:all) do + create_feed +end +after(:all) { cleanup_feed } +---- + +[#references-rspecemptyhook] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyHook + +[#rspecemptylineafterexample] +== RSpec/EmptyLineAfterExample + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.36 +| - +|=== + +Checks if there is an empty line after example blocks. + +[#examples-rspecemptylineafterexample] +=== Examples + +[source,ruby] +---- +# bad +RSpec.describe Foo do + it 'does this' do + end + it 'does that' do + end +end + +# good +RSpec.describe Foo do + it 'does this' do + end + + it 'does that' do + end +end + +# fair - it's ok to have non-separated one-liners +RSpec.describe Foo do + it { one } + it { two } +end +---- + +[#with-allowconsecutiveoneliners-configuration-rspecemptylineafterexample] +==== with AllowConsecutiveOneLiners configuration + +[source,ruby] +---- +# rubocop.yml +# RSpec/EmptyLineAfterExample: +# AllowConsecutiveOneLiners: false + +# bad +RSpec.describe Foo do + it { one } + it { two } +end +---- + +[#configurable-attributes-rspecemptylineafterexample] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| AllowConsecutiveOneLiners +| `true` +| Boolean +|=== + +[#references-rspecemptylineafterexample] +=== References + +* https://rspec.rubystyle.guide/#empty-lines-around-examples +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterExample + +[#rspecemptylineafterexamplegroup] +== RSpec/EmptyLineAfterExampleGroup + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.27 +| - +|=== + +Checks if there is an empty line after example group blocks. + +[#examples-rspecemptylineafterexamplegroup] +=== Examples + +[source,ruby] +---- +# bad +RSpec.describe Foo do + describe '#bar' do + end + describe '#baz' do + end +end + +# good +RSpec.describe Foo do + describe '#bar' do + end + + describe '#baz' do + end +end +---- + +[#references-rspecemptylineafterexamplegroup] +=== References + +* https://rspec.rubystyle.guide/#empty-lines-between-describes +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterExampleGroup + +[#rspecemptylineafterfinallet] +== RSpec/EmptyLineAfterFinalLet + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.14 +| - +|=== + +Checks if there is an empty line after the last let block. + +[#examples-rspecemptylineafterfinallet] +=== Examples + +[source,ruby] +---- +# bad +let(:foo) { bar } +let(:something) { other } +it { does_something } + +# good +let(:foo) { bar } +let(:something) { other } + +it { does_something } +---- + +[#references-rspecemptylineafterfinallet] +=== References + +* https://rspec.rubystyle.guide/#empty-line-after-let +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterFinalLet + +[#rspecemptylineafterhook] +== RSpec/EmptyLineAfterHook + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.27 +| 2.13 +|=== + +Checks if there is an empty line after hook blocks. + +`AllowConsecutiveOneLiners` configures whether adjacent +one-line definitions are considered an offense. + +[#examples-rspecemptylineafterhook] +=== Examples + +[source,ruby] +---- +# bad +before { do_something } +it { does_something } + +# bad +after { do_something } +it { does_something } + +# bad +around { |test| test.run } +it { does_something } + +# good +after { do_something } + +it { does_something } + +# fair - it's ok to have non-separated one-liners hooks +around { |test| test.run } +after { do_something } + +it { does_something } +---- + +[#with-allowconsecutiveoneliners-configuration-rspecemptylineafterhook] +==== with AllowConsecutiveOneLiners configuration + +[source,ruby] +---- +# rubocop.yml +# RSpec/EmptyLineAfterHook: +# AllowConsecutiveOneLiners: false + +# bad +around { |test| test.run } +after { do_something } + +it { does_something } + +# good +around { |test| test.run } + +after { do_something } + +it { does_something } +---- + +[#configurable-attributes-rspecemptylineafterhook] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| AllowConsecutiveOneLiners +| `true` +| Boolean +|=== + +[#references-rspecemptylineafterhook] +=== References + +* https://rspec.rubystyle.guide/#empty-line-after-let +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterHook + +[#rspecemptylineaftersubject] +== RSpec/EmptyLineAfterSubject + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.14 +| - +|=== + +Checks if there is an empty line after subject block. + +[#examples-rspecemptylineaftersubject] +=== Examples + +[source,ruby] +---- +# bad +subject(:obj) { described_class } +let(:foo) { bar } + +# good +subject(:obj) { described_class } + +let(:foo) { bar } +---- + +[#references-rspecemptylineaftersubject] +=== References + +* https://rspec.rubystyle.guide/#empty-line-after-let +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterSubject + +[#rspecemptymetadata] +== RSpec/EmptyMetadata + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 2.24 +| 2.31 +|=== + +Avoid empty metadata hash. + +[#examples-rspecemptymetadata] +=== Examples + +[#enforcedstyle_-symbol-_default_-rspecemptymetadata] +==== EnforcedStyle: symbol (default) + +[source,ruby] +---- +# bad +describe 'Something', {} + +# good +describe 'Something' +---- + +[#references-rspecemptymetadata] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyMetadata + +[#rspecemptyoutput] +== RSpec/EmptyOutput + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.29 +| - +|=== + +Check that the `output` matcher is not called with an empty string. + +[#examples-rspecemptyoutput] +=== Examples + +[source,ruby] +---- +# bad +expect { foo }.to output('').to_stdout +expect { bar }.not_to output('').to_stderr + +# good +expect { foo }.not_to output.to_stdout +expect { bar }.to output.to_stderr +---- + +[#references-rspecemptyoutput] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyOutput + +[#rspeceq] +== RSpec/Eq + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.24 +| - +|=== + +Use `eq` instead of `be ==` to compare objects. + +[#examples-rspeceq] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to be == 42 + +# good +expect(foo).to eq 42 +---- + +[#references-rspeceq] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Eq + +[#rspecexamplelength] +== RSpec/ExampleLength + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.5 +| 2.3 +|=== + +Checks for long examples. + +A long example is usually more difficult to understand. Consider +extracting out some behavior, e.g. with a `let` block, or a helper +method. + +You can set constructs you want to fold with `CountAsOne`. +Available are: 'array', 'hash', 'heredoc', and 'method_call'. +Each construct will be counted as one line regardless of +its actual size. + +[#examples-rspecexamplelength] +=== Examples + +[source,ruby] +---- +# bad +it do + service = described_class.new + more_setup + more_setup + result = service.call + expect(result).to be(true) +end + +# good +it do + service = described_class.new + result = service.call + expect(result).to be(true) +end +---- + +[#countasone_-__array__-_heredoc__-_method_call__-rspecexamplelength] +==== CountAsOne: ['array', 'heredoc', 'method_call'] + +[source,ruby] +---- +it do + array = [ # +1 + 1, + 2 + ] + + hash = { # +3 + key: 'value' + } + + msg = <<~HEREDOC # +1 + Heredoc + content. + HEREDOC + + foo( # +1 + 1, + 2 + ) +end # 6 points +---- + +[#configurable-attributes-rspecexamplelength] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Max +| `5` +| Integer + +| CountAsOne +| `[]` +| Array +|=== + +[#references-rspecexamplelength] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleLength + +[#rspecexamplewithoutdescription] +== RSpec/ExampleWithoutDescription + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.22 +| - +|=== + +Checks for examples without a description. + +RSpec allows for auto-generated example descriptions when there is no +description provided or the description is an empty one. +It is acceptable to use `specify` without a description + +This cop removes empty descriptions. +It also defines whether auto-generated description is allowed, based +on the configured style. + +This cop can be configured using the `EnforcedStyle` option + +[#examples-rspecexamplewithoutdescription] +=== Examples + +[source,ruby] +---- +# always good +specify do + result = service.call + expect(result).to be(true) +end +---- + +[#_enforcedstyle_-always_allow_-_default_-rspecexamplewithoutdescription] +==== `EnforcedStyle: always_allow` (default) + +[source,ruby] +---- +# bad +it('') { is_expected.to be_good } +specify '' do + result = service.call + expect(result).to be(true) +end + +# good +it { is_expected.to be_good } +specify do + result = service.call + expect(result).to be(true) +end +---- + +[#_enforcedstyle_-single_line_only_-rspecexamplewithoutdescription] +==== `EnforcedStyle: single_line_only` + +[source,ruby] +---- +# bad +it('') { is_expected.to be_good } +it do + result = service.call + expect(result).to be(true) +end + +# good +it { is_expected.to be_good } +---- + +[#_enforcedstyle_-disallow_-rspecexamplewithoutdescription] +==== `EnforcedStyle: disallow` + +[source,ruby] +---- +# bad +it { is_expected.to be_good } +it do + result = service.call + expect(result).to be(true) +end +---- + +[#configurable-attributes-rspecexamplewithoutdescription] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `always_allow` +| `always_allow`, `single_line_only`, `disallow` +|=== + +[#references-rspecexamplewithoutdescription] +=== References + +* https://rspec.rubystyle.guide/#it-and-specify +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWithoutDescription + +[#rspecexamplewording] +== RSpec/ExampleWording + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.0 +| 2.13 +|=== + +Checks for common mistakes in example descriptions. + +This cop will correct docstrings that begin with 'should' and 'it'. +This cop will also look for insufficient examples and call them out. + +The autocorrect is experimental - use with care! It can be configured +with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only). + +Use the DisallowedExamples setting to prevent unclear or insufficient +descriptions. Please note that this config will not be treated as +case sensitive. + +[#examples-rspecexamplewording] +=== Examples + +[source,ruby] +---- +# bad +it 'should find nothing' do +end + +it 'will find nothing' do +end + +# good +it 'finds nothing' do +end +---- + +[source,ruby] +---- +# bad +it 'it does things' do +end + +# good +it 'does things' do +end +---- + +[#_disallowedexamples_-__works___-_default_-rspecexamplewording] +==== `DisallowedExamples: ['works']` (default) + +[source,ruby] +---- +# bad +it 'works' do +end + +# good +it 'marks the task as done' do +end +---- + +[#configurable-attributes-rspecexamplewording] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| CustomTransform +| `{"be" => "is", "BE" => "IS", "have" => "has", "HAVE" => "HAS"}` +| + +| IgnoredWords +| `[]` +| Array + +| DisallowedExamples +| `works` +| Array +|=== + +[#references-rspecexamplewording] +=== References + +* https://rspec.rubystyle.guide/#should-in-example-docstrings +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording +* http://betterspecs.org/#should + +[#rspecexcessivedocstringspacing] +== RSpec/ExcessiveDocstringSpacing + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.5 +| - +|=== + +Checks for excessive whitespace in example descriptions. + +[#examples-rspecexcessivedocstringspacing] +=== Examples + +[source,ruby] +---- +# bad +it ' has excessive spacing ' do +end + +# good +it 'has excessive spacing' do +end +---- + +[source,ruby] +---- +# bad +context ' when a condition is met ' do +end + +# good +context 'when a condition is met' do +end +---- + +[#references-rspecexcessivedocstringspacing] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExcessiveDocstringSpacing + +[#rspecexpectactual] +== RSpec/ExpectActual + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.7 +| 2.23 +|=== + +Checks for `expect(...)` calls containing literal values. + +Autocorrection is performed when the expected is not a literal. + +[#examples-rspecexpectactual] +=== Examples + +[source,ruby] +---- +# bad +expect(5).to eq(price) +expect(/foo/).to eq(pattern) +expect("John").to eq(name) + +# good +expect(price).to eq(5) +expect(pattern).to eq(/foo/) +expect(name).to eq("John") + +# bad (not supported autocorrection) +expect(false).to eq(true) +---- + +[#configurable-attributes-rspecexpectactual] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Exclude +| `+**/spec/routing/**/*+` +| Array +|=== + +[#references-rspecexpectactual] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectActual + +[#rspecexpectchange] +== RSpec/ExpectChange + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always (Unsafe) +| 1.22 +| 2.5 +|=== + +Checks for consistent style of change matcher. + +Enforces either passing a receiver and message as method arguments, +or a block. + +This cop can be configured using the `EnforcedStyle` option. + +[#safety-rspecexpectchange] +=== Safety + +Autocorrection is unsafe because `method_call` style calls the +receiver *once* and sends the message to it before and after +calling the `expect` block, whereas `block` style calls the +expression *twice*, including the receiver. + +If your receiver is dynamic (e.g., the result of a method call) and +you expect it to be called before and after the `expect` block, +changing from `block` to `method_call` style may break your test. + +[source,ruby] +---- +expect { run }.to change { my_method.message } +# `my_method` is called before and after `run` + +expect { run }.to change(my_method, :message) +# `my_method` is called once, but `message` is called on it twice +---- + +[#examples-rspecexpectchange] +=== Examples + +[#_enforcedstyle_-method_call_-_default_-rspecexpectchange] +==== `EnforcedStyle: method_call` (default) + +[source,ruby] +---- +# bad +expect { run }.to change { Foo.bar } +expect { run }.to change { foo.baz } + +# good +expect { run }.to change(Foo, :bar) +expect { run }.to change(foo, :baz) +# also good when there are arguments or chained method calls +expect { run }.to change { Foo.bar(:count) } +expect { run }.to change { user.reload.name } +---- + +[#_enforcedstyle_-block_-rspecexpectchange] +==== `EnforcedStyle: block` + +[source,ruby] +---- +# bad +expect { run }.to change(Foo, :bar) + +# good +expect { run }.to change { Foo.bar } +---- + +[#configurable-attributes-rspecexpectchange] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `method_call` +| `method_call`, `block` +|=== + +[#references-rspecexpectchange] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectChange + +[#rspecexpectinhook] +== RSpec/ExpectInHook + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.16 +| - +|=== + +Do not use `expect` in hooks such as `before`. + +[#examples-rspecexpectinhook] +=== Examples + +[source,ruby] +---- +# bad +before do + expect(something).to eq 'foo' +end + +# bad +after do + expect_any_instance_of(Something).to receive(:foo) +end + +# good +it do + expect(something).to eq 'foo' +end +---- + +[#references-rspecexpectinhook] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInHook + +[#rspecexpectinlet] +== RSpec/ExpectInLet + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.30 +| - +|=== + +Do not use `expect` in let. + +[#examples-rspecexpectinlet] +=== Examples + +[source,ruby] +---- +# bad +let(:foo) do + expect(something).to eq 'foo' +end + +# good +it do + expect(something).to eq 'foo' +end +---- + +[#references-rspecexpectinlet] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectInLet + +[#rspecexpectoutput] +== RSpec/ExpectOutput + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.10 +| - +|=== + +Checks for opportunities to use `expect { ... }.to output`. + +[#examples-rspecexpectoutput] +=== Examples + +[source,ruby] +---- +# bad +$stdout = StringIO.new +my_app.print_report +$stdout = STDOUT +expect($stdout.string).to eq('Hello World') + +# good +expect { my_app.print_report }.to output('Hello World').to_stdout +---- + +[#references-rspecexpectoutput] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectOutput + +[#rspecfocus] +== RSpec/Focus + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 1.5 +| 2.31 +|=== + +Checks if examples are focused. + +This cop does not support autocorrection in some cases. + +[#examples-rspecfocus] +=== Examples + +[source,ruby] +---- +# bad +describe MyClass, focus: true do +end + +describe MyClass, :focus do +end + +fdescribe MyClass do +end + +# good +describe MyClass do +end + +# bad +fdescribe 'test' do; end + +# good +describe 'test' do; end + +# bad +shared_examples 'test', focus: true do; end + +# good +shared_examples 'test' do; end + +# bad +shared_context 'test', focus: true do; end + +# good +shared_context 'test' do; end + +# bad (does not support autocorrection) +focus 'test' do; end +---- + +[#references-rspecfocus] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Focus + +[#rspechookargument] +== RSpec/HookArgument + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.7 +| - +|=== + +Checks the arguments passed to `before`, `around`, and `after`. + +This cop checks for consistent style when specifying RSpec +hooks which run for each example. There are three supported +styles: "implicit", "each", and "example." All styles have +the same behavior. + +[#examples-rspechookargument] +=== Examples + +[#_enforcedstyle_-implicit_-_default_-rspechookargument] +==== `EnforcedStyle: implicit` (default) + +[source,ruby] +---- +# bad +before(:each) do + # ... +end + +# bad +before(:example) do + # ... +end + +# good +before do + # ... +end +---- + +[#_enforcedstyle_-each_-rspechookargument] +==== `EnforcedStyle: each` + +[source,ruby] +---- +# bad +before(:example) do + # ... +end + +# bad +before do + # ... +end + +# good +before(:each) do + # ... +end +---- + +[#_enforcedstyle_-example_-rspechookargument] +==== `EnforcedStyle: example` + +[source,ruby] +---- +# bad +before(:each) do + # ... +end + +# bad +before do + # ... +end + +# good +before(:example) do + # ... +end +---- + +[#configurable-attributes-rspechookargument] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `implicit` +| `implicit`, `each`, `example` +|=== + +[#references-rspechookargument] +=== References + +* https://rspec.rubystyle.guide/#redundant-beforeeach +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HookArgument + +[#rspechooksbeforeexamples] +== RSpec/HooksBeforeExamples + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 1.29 +| 2.31 +|=== + +Checks for before/around/after hooks that come after an example. + +[#examples-rspechooksbeforeexamples] +=== Examples + +[source,ruby] +---- +# bad +it 'checks what foo does' do + expect(foo).to be +end + +before { prepare } +after { clean_up } + +# good +before { prepare } +after { clean_up } + +it 'checks what foo does' do + expect(foo).to be +end +---- + +[#references-rspechooksbeforeexamples] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HooksBeforeExamples + +[#rspecidenticalequalityassertion] +== RSpec/IdenticalEqualityAssertion + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.4 +| - +|=== + +Checks for equality assertions with identical expressions on both sides. + +[#examples-rspecidenticalequalityassertion] +=== Examples + +[source,ruby] +---- +# bad +expect(foo.bar).to eq(foo.bar) +expect(foo.bar).to eql(foo.bar) + +# good +expect(foo.bar).to eq(2) +expect(foo.bar).to eql(2) +---- + +[#references-rspecidenticalequalityassertion] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IdenticalEqualityAssertion + +[#rspecimplicitblockexpectation] +== RSpec/ImplicitBlockExpectation + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.35 +| - +|=== + +Check that implicit block expectation syntax is not used. + +Prefer using explicit block expectations. + +[#examples-rspecimplicitblockexpectation] +=== Examples + +[source,ruby] +---- +# bad +subject { -> { do_something } } +it { is_expected.to change(something).to(new_value) } + +# good +it 'changes something to a new value' do + expect { do_something }.to change(something).to(new_value) +end +---- + +[#references-rspecimplicitblockexpectation] +=== References + +* https://rspec.rubystyle.guide/#implicit-block-expectations +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitBlockExpectation + +[#rspecimplicitexpect] +== RSpec/ImplicitExpect + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.8 +| - +|=== + +Check that a consistent implicit expectation style is used. + +This cop can be configured using the `EnforcedStyle` option +and supports the `--auto-gen-config` flag. + +[#examples-rspecimplicitexpect] +=== Examples + +[#_enforcedstyle_-is_expected_-_default_-rspecimplicitexpect] +==== `EnforcedStyle: is_expected` (default) + +[source,ruby] +---- +# bad +it { should be_truthy } + +# good +it { is_expected.to be_truthy } +---- + +[#_enforcedstyle_-should_-rspecimplicitexpect] +==== `EnforcedStyle: should` + +[source,ruby] +---- +# bad +it { is_expected.to be_truthy } + +# good +it { should be_truthy } +---- + +[#configurable-attributes-rspecimplicitexpect] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `is_expected` +| `is_expected`, `should` +|=== + +[#references-rspecimplicitexpect] +=== References + +* https://rspec.rubystyle.guide/#use-expect +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitExpect + +[#rspecimplicitsubject] +== RSpec/ImplicitSubject + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.29 +| 2.13 +|=== + +Checks for usage of implicit subject (`is_expected` / `should`). + +This cop can be configured using the `EnforcedStyle` option + +[#examples-rspecimplicitsubject] +=== Examples + +[#_enforcedstyle_-single_line_only_-_default_-rspecimplicitsubject] +==== `EnforcedStyle: single_line_only` (default) + +[source,ruby] +---- +# bad +it do + is_expected.to be_truthy +end + +# good +it { is_expected.to be_truthy } +it do + expect(subject).to be_truthy +end +---- + +[#_enforcedstyle_-single_statement_only_-rspecimplicitsubject] +==== `EnforcedStyle: single_statement_only` + +[source,ruby] +---- +# bad +it do + foo = 1 + is_expected.to be_truthy +end + +# good +it do + foo = 1 + expect(subject).to be_truthy +end +it do + is_expected.to be_truthy +end +---- + +[#_enforcedstyle_-disallow_-rspecimplicitsubject] +==== `EnforcedStyle: disallow` + +[source,ruby] +---- +# bad +it { is_expected.to be_truthy } + +# good +it { expect(subject).to be_truthy } +---- + +[#_enforcedstyle_-require_implicit_-rspecimplicitsubject] +==== `EnforcedStyle: require_implicit` + +[source,ruby] +---- +# bad +it { expect(subject).to be_truthy } + +# good +it { is_expected.to be_truthy } + +# bad +it do + expect(subject).to be_truthy +end + +# good +it do + is_expected.to be_truthy +end + +# good +it { expect(named_subject).to be_truthy } +---- + +[#configurable-attributes-rspecimplicitsubject] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `single_line_only` +| `single_line_only`, `single_statement_only`, `disallow`, `require_implicit` +|=== + +[#references-rspecimplicitsubject] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject + +[#rspecincludeexamples] +== RSpec/IncludeExamples + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Always (Unsafe) +| 3.6 +| <> +|=== + +Checks for usage of `include_examples`. + +`include_examples`, unlike `it_behaves_like`, does not create its +own context. As such, using `subject`, `let`, `before`/`after`, etc. +within shared examples included with `include_examples` can have +unexpected behavior and side effects. + +Prefer using `it_behaves_like` instead. + +---- + +[#safety-rspecincludeexamples] +=== Safety + +`include_examples` and `it_behaves_like` have different scoping +behaviors. +Changing `include_examples` to `it_behaves_like` creates a new +context, altering setup dependencies, which can lead to unexpected +test failures. +Specifically, the scope of hooks (`before`, `after`, `around`) +changes, which may prevent expected setup from being inherited +correctly. + +Additionally, `let` and `subject` are affected by scoping rules. +When `include_examples` is used, `let` and `subject` defined within +`shared_examples` are evaluated in the caller's context, allowing +access to their values. +In contrast, `it_behaves_like` creates a new context, preventing +access to `let` or `subject` values from the caller's context. + +[source,ruby] +---- +shared_examples "mock behavior" do + before do + allow(service).to receive(:call).and_return("mocked response") + end + + it "returns mocked response" do + expect(service.call).to eq "mocked response" + end +end + +context "working example with include_examples" do + let(:service) { double(:service) } + + include_examples "mock behavior" + + it "uses the mocked service" do + expect(service.call).to eq "mocked response" # Passes + end +end + +context "broken example with it_behaves_like" do + let(:service) { double(:service) } + + it_behaves_like "mock behavior" + + it "unexpectedly does not use the mocked service" do + # Fails because `it_behaves_like` does not apply the mock setup + expect(service.call).to eq "mocked response" + end +end + +[#examples-rspecincludeexamples] +=== Examples + +[source,ruby] +---- +# bad +include_examples 'examples' + +# good +it_behaves_like 'examples' +---- + +[#references-rspecincludeexamples] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IncludeExamples + +[#rspecindexedlet] +== RSpec/IndexedLet + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.20 +| 2.23 +|=== + +Do not set up test data using indexes (e.g., `item_1`, `item_2`). + +It makes reading the test harder because it's not clear what exactly +is tested by this particular example. + +The configurable options `AllowedIdentifiers` and `AllowedPatterns` +will also read those set in `Naming/VariableNumber`. + +[#examples-rspecindexedlet] +=== Examples + +[#_max_-1-_default__-rspecindexedlet] +==== `Max: 1 (default)` + +[source,ruby] +---- +# bad +let(:item_1) { create(:item) } +let(:item_2) { create(:item) } + +let(:item1) { create(:item) } +let(:item2) { create(:item) } + +# good + +let(:visible_item) { create(:item, visible: true) } +let(:invisible_item) { create(:item, visible: false) } +---- + +[#_max_-2_-rspecindexedlet] +==== `Max: 2` + +[source,ruby] +---- +# bad +let(:item_1) { create(:item) } +let(:item_2) { create(:item) } +let(:item_3) { create(:item) } + +# good +let(:item_1) { create(:item) } +let(:item_2) { create(:item) } +---- + +[#_allowedidentifiers_-__item_1__-_item_2___-rspecindexedlet] +==== `AllowedIdentifiers: ['item_1', 'item_2']` + +[source,ruby] +---- +# good +let(:item_1) { create(:item) } +let(:item_2) { create(:item) } +---- + +[#_allowedpatterns_-__item___-rspecindexedlet] +==== `AllowedPatterns: ['item']` + +[source,ruby] +---- +# good +let(:item_1) { create(:item) } +let(:item_2) { create(:item) } +---- + +[#configurable-attributes-rspecindexedlet] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Max +| `1` +| Integer + +| AllowedIdentifiers +| `[]` +| Array + +| AllowedPatterns +| `[]` +| Array +|=== + +[#references-rspecindexedlet] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IndexedLet + +[#rspecinstancespy] +== RSpec/InstanceSpy + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.12 +| - +|=== + +Checks for `instance_double` used with `have_received`. + +[#examples-rspecinstancespy] +=== Examples + +[source,ruby] +---- +# bad +it do + foo = instance_double(Foo).as_null_object + expect(foo).to have_received(:bar) +end + +# good +it do + foo = instance_spy(Foo) + expect(foo).to have_received(:bar) +end +---- + +[#references-rspecinstancespy] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceSpy + +[#rspecinstancevariable] +== RSpec/InstanceVariable + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.0 +| 1.7 +|=== + +Checks for instance variable usage in specs. + +This cop can be configured with the option `AssignmentOnly` which +will configure the cop to only register offenses on instance +variable usage if the instance variable is also assigned within +the spec + +[#examples-rspecinstancevariable] +=== Examples + +[source,ruby] +---- +# bad +describe MyClass do + before { @foo = [] } + it { expect(@foo).to be_empty } +end + +# good +describe MyClass do + let(:foo) { [] } + it { expect(foo).to be_empty } +end +---- + +[#with-assignmentonly-configuration-rspecinstancevariable] +==== with AssignmentOnly configuration + +[source,ruby] +---- +# rubocop.yml +# RSpec/InstanceVariable: +# AssignmentOnly: true + +# bad +describe MyClass do + before { @foo = [] } + it { expect(@foo).to be_empty } +end + +# allowed +describe MyClass do + it { expect(@foo).to be_empty } +end + +# good +describe MyClass do + let(:foo) { [] } + it { expect(foo).to be_empty } +end +---- + +[#configurable-attributes-rspecinstancevariable] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| AssignmentOnly +| `false` +| Boolean +|=== + +[#references-rspecinstancevariable] +=== References + +* https://rspec.rubystyle.guide/#instance-variables +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceVariable + +[#rspecisexpectedspecify] +== RSpec/IsExpectedSpecify + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.27 +| - +|=== + +Check for `specify` with `is_expected` and one-liner expectations. + +[#examples-rspecisexpectedspecify] +=== Examples + +[source,ruby] +---- +# bad +specify { is_expected.to be_truthy } + +# good +it { is_expected.to be_truthy } + +# good +specify do + # ... +end +specify { expect(sqrt(4)).to eq(2) } +---- + +[#references-rspecisexpectedspecify] +=== References + +* https://rspec.rubystyle.guide/#it-and-specify +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IsExpectedSpecify + +[#rspecitbehaveslike] +== RSpec/ItBehavesLike + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.13 +| - +|=== + +Checks that only one `it_behaves_like` style is used. + +[#examples-rspecitbehaveslike] +=== Examples + +[#_enforcedstyle_-it_behaves_like_-_default_-rspecitbehaveslike] +==== `EnforcedStyle: it_behaves_like` (default) + +[source,ruby] +---- +# bad +it_should_behave_like 'a foo' + +# good +it_behaves_like 'a foo' +---- + +[#_enforcedstyle_-it_should_behave_like_-rspecitbehaveslike] +==== `EnforcedStyle: it_should_behave_like` + +[source,ruby] +---- +# bad +it_behaves_like 'a foo' + +# good +it_should_behave_like 'a foo' +---- + +[#configurable-attributes-rspecitbehaveslike] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `it_behaves_like` +| `it_behaves_like`, `it_should_behave_like` +|=== + +[#references-rspecitbehaveslike] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ItBehavesLike + +[#rspeciteratedexpectation] +== RSpec/IteratedExpectation + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.14 +| - +|=== + +Check that `all` matcher is used instead of iterating over an array. + +[#examples-rspeciteratedexpectation] +=== Examples + +[source,ruby] +---- +# bad +it 'validates users' do + [user1, user2, user3].each { |user| expect(user).to be_valid } +end + +# good +it 'validates users' do + expect([user1, user2, user3]).to all(be_valid) +end +---- + +[#references-rspeciteratedexpectation] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IteratedExpectation + +[#rspecleadingsubject] +== RSpec/LeadingSubject + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.7 +| 1.14 +|=== + +Enforce that subject is the first definition in the test. + +[#examples-rspecleadingsubject] +=== Examples + +[source,ruby] +---- +# bad +let(:params) { blah } +subject { described_class.new(params) } + +before { do_something } +subject { described_class.new(params) } + +it { expect_something } +subject { described_class.new(params) } +it { expect_something_else } + +# good +subject { described_class.new(params) } +let(:params) { blah } + +# good +subject { described_class.new(params) } +before { do_something } + +# good +subject { described_class.new(params) } +it { expect_something } +it { expect_something_else } +---- + +[#references-rspecleadingsubject] +=== References + +* https://rspec.rubystyle.guide/#leading-subject +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeadingSubject + +[#rspecleakyconstantdeclaration] +== RSpec/LeakyConstantDeclaration + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.35 +| - +|=== + +Checks that no class, module, or constant is declared. + +Constants, including classes and modules, when declared in a block +scope, are defined in global namespace, and leak between examples. + +If several examples may define a `DummyClass`, instead of being a +blank slate class as it will be in the first example, subsequent +examples will be reopening it and modifying its behavior in +unpredictable ways. +Even worse when a class that exists in the codebase is reopened. + +Anonymous classes are fine, since they don't result in global +namespace name clashes. + +[#examples-rspecleakyconstantdeclaration] +=== Examples + +[#constants-leak-between-examples-rspecleakyconstantdeclaration] +==== Constants leak between examples + +[source,ruby] +---- +# bad +describe SomeClass do + OtherClass = Struct.new + CONSTANT_HERE = 'I leak into global namespace' +end + +# good +describe SomeClass do + before do + stub_const('OtherClass', Struct.new) + stub_const('CONSTANT_HERE', 'I only exist during this example') + end +end +---- + +[source,ruby] +---- +# bad +describe SomeClass do + class FooClass < described_class + def double_that + some_base_method * 2 + end + end + + it { expect(FooClass.new.double_that).to eq(4) } +end + +# good - anonymous class, no constant needs to be defined +describe SomeClass do + let(:foo_class) do + Class.new(described_class) do + def double_that + some_base_method * 2 + end + end + end + + it { expect(foo_class.new.double_that).to eq(4) } +end + +# good - constant is stubbed +describe SomeClass do + before do + foo_class = Class.new(described_class) do + def do_something + end + end + stub_const('FooClass', foo_class) + end + + it { expect(FooClass.new.double_that).to eq(4) } +end +---- + +[source,ruby] +---- +# bad +describe SomeClass do + module SomeModule + class SomeClass + def do_something + end + end + end +end + +# good +describe SomeClass do + before do + foo_class = Class.new(described_class) do + def do_something + end + end + stub_const('SomeModule::SomeClass', foo_class) + end +end +---- + +[#references-rspecleakyconstantdeclaration] +=== References + +* https://rspec.rubystyle.guide/#declare-constants +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyConstantDeclaration +* https://rspec.info/features/3-12/rspec-mocks/mutating-constants + +[#rspecletbeforeexamples] +== RSpec/LetBeforeExamples + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 1.16 +| 2.31 +|=== + +Checks for `let` definitions that come after an example. + +[#examples-rspecletbeforeexamples] +=== Examples + +[source,ruby] +---- +# bad +let(:foo) { bar } + +it 'checks what foo does' do + expect(foo).to be +end + +let(:some) { other } + +it 'checks what some does' do + expect(some).to be +end + +# good +let(:foo) { bar } +let(:some) { other } + +it 'checks what foo does' do + expect(foo).to be +end + +it 'checks what some does' do + expect(some).to be +end +---- + +[#references-rspecletbeforeexamples] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetBeforeExamples + +[#rspecletsetup] +== RSpec/LetSetup + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.7 +| - +|=== + +Checks unreferenced `let!` calls being used for test setup. + +[#examples-rspecletsetup] +=== Examples + +[source,ruby] +---- +# bad +let!(:my_widget) { create(:widget) } + +it 'counts widgets' do + expect(Widget.count).to eq(1) +end + +# good +it 'counts widgets' do + create(:widget) + expect(Widget.count).to eq(1) +end + +# good +before { create(:widget) } + +it 'counts widgets' do + expect(Widget.count).to eq(1) +end +---- + +[#references-rspecletsetup] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetSetup + +[#rspecmatcharray] +== RSpec/MatchArray + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.19 +| - +|=== + +Checks where `match_array` is used. + +This cop checks for the following: + +- Prefer `contain_exactly` when matching an array with values. +- Prefer `eq` when using `match_array` with an empty array literal. + +[#examples-rspecmatcharray] +=== Examples + +[source,ruby] +---- +# bad +it { is_expected.to match_array([content1, content2]) } + +# good +it { is_expected.to contain_exactly(content1, content2) } + +# good +it { is_expected.to match_array([content] + array) } + +# good +it { is_expected.to match_array(%w(tremble in fear foolish mortals)) } +---- + +[#references-rspecmatcharray] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchArray + +[#rspecmessagechain] +== RSpec/MessageChain + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.7 +| - +|=== + +Check that chains of messages are not being stubbed. + +[#examples-rspecmessagechain] +=== Examples + +[source,ruby] +---- +# bad +allow(foo).to receive_message_chain(:bar, :baz).and_return(42) + +# good +thing = Thing.new(baz: 42) +allow(foo).to receive(:bar).and_return(thing) +---- + +[#references-rspecmessagechain] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageChain + +[#rspecmessageexpectation] +== RSpec/MessageExpectation + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Disabled +| Yes +| No +| 1.7 +| 1.8 +|=== + +Checks for consistent message expectation style. + +This cop can be configured in your configuration using the +`EnforcedStyle` option and supports `--auto-gen-config`. + +[#examples-rspecmessageexpectation] +=== Examples + +[#_enforcedstyle_-allow_-_default_-rspecmessageexpectation] +==== `EnforcedStyle: allow` (default) + +[source,ruby] +---- +# bad +expect(foo).to receive(:bar) + +# good +allow(foo).to receive(:bar) +---- + +[#_enforcedstyle_-expect_-rspecmessageexpectation] +==== `EnforcedStyle: expect` + +[source,ruby] +---- +# bad +allow(foo).to receive(:bar) + +# good +expect(foo).to receive(:bar) +---- + +[#configurable-attributes-rspecmessageexpectation] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `allow` +| `allow`, `expect` +|=== + +[#references-rspecmessageexpectation] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageExpectation + +[#rspecmessagespies] +== RSpec/MessageSpies + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.9 +| - +|=== + +Checks that message expectations are set using spies. + +This cop can be configured in your configuration using the +`EnforcedStyle` option and supports `--auto-gen-config`. + +[#examples-rspecmessagespies] +=== Examples + +[#_enforcedstyle_-have_received_-_default_-rspecmessagespies] +==== `EnforcedStyle: have_received` (default) + +[source,ruby] +---- +# bad +expect(foo).to receive(:bar) +do_something + +# good +allow(foo).to receive(:bar) # or use instance_spy +do_something +expect(foo).to have_received(:bar) +---- + +[#_enforcedstyle_-receive_-rspecmessagespies] +==== `EnforcedStyle: receive` + +[source,ruby] +---- +# bad +allow(foo).to receive(:bar) +do_something +expect(foo).to have_received(:bar) + +# good +expect(foo).to receive(:bar) +do_something +---- + +[#configurable-attributes-rspecmessagespies] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `have_received` +| `have_received`, `receive` +|=== + +[#references-rspecmessagespies] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MessageSpies + +[#rspecmetadatastyle] +== RSpec/MetadataStyle + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.24 +| - +|=== + +Use consistent metadata style. + +This cop does not support autocorrection in the case of +`EnforcedStyle: hash` where the trailing metadata type is ambiguous. +(e.g. `describe 'Something', :a, b`) + +[#examples-rspecmetadatastyle] +=== Examples + +[#enforcedstyle_-symbol-_default_-rspecmetadatastyle] +==== EnforcedStyle: symbol (default) + +[source,ruby] +---- +# bad +describe 'Something', a: true + +# good +describe 'Something', :a +---- + +[#enforcedstyle_-hash-rspecmetadatastyle] +==== EnforcedStyle: hash + +[source,ruby] +---- +# bad +describe 'Something', :a + +# good +describe 'Something', a: true +---- + +[#configurable-attributes-rspecmetadatastyle] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `symbol` +| `hash`, `symbol` +|=== + +[#references-rspecmetadatastyle] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MetadataStyle + +[#rspecmissingexamplegroupargument] +== RSpec/MissingExampleGroupArgument + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.28 +| - +|=== + +Checks that the first argument to an example group is not empty. + +[#examples-rspecmissingexamplegroupargument] +=== Examples + +[source,ruby] +---- +# bad +describe do +end + +RSpec.describe do +end + +# good +describe TestedClass do +end + +describe "A feature example" do +end +---- + +[#references-rspecmissingexamplegroupargument] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument + +[#rspecmissingexpectationtargetmethod] +== RSpec/MissingExpectationTargetMethod + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 3.0 +| - +|=== + +Checks if `.to`, `not_to` or `to_not` are used. + +The RSpec::Expectations::ExpectationTarget must use `to`, `not_to` or +`to_not` to run. Therefore, this cop checks if other methods are used. + +[#examples-rspecmissingexpectationtargetmethod] +=== Examples + +[source,ruby] +---- +# bad +expect(something).kind_of? Foo +is_expected == 42 +expect{something}.eq? BarError + +# good +expect(something).to be_a Foo +is_expected.to eq 42 +expect{something}.to raise_error BarError +---- + +[#references-rspecmissingexpectationtargetmethod] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExpectationTargetMethod + +[#rspecmultipledescribes] +== RSpec/MultipleDescribes + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.0 +| - +|=== + +Checks for multiple top-level example groups. + +Multiple descriptions for the same class or module should either +be nested or separated into different test files. + +[#examples-rspecmultipledescribes] +=== Examples + +[source,ruby] +---- +# bad +describe MyClass, '.do_something' do +end +describe MyClass, '.do_something_else' do +end + +# good +describe MyClass do + describe '.do_something' do + end + describe '.do_something_else' do + end +end +---- + +[#references-rspecmultipledescribes] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleDescribes + +[#rspecmultipleexpectations] +== RSpec/MultipleExpectations + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.7 +| 1.21 +|=== + +Checks if examples contain too many `expect` calls. + +This cop is configurable using the `Max` option +and works with `--auto-gen-config`. + +[#examples-rspecmultipleexpectations] +=== Examples + +[source,ruby] +---- +# bad +describe UserCreator do + it 'builds a user' do + expect(user.name).to eq("John") + expect(user.age).to eq(22) + end +end + +# good +describe UserCreator do + it 'sets the users name' do + expect(user.name).to eq("John") + end + + it 'sets the users age' do + expect(user.age).to eq(22) + end +end +---- + +[#_aggregate_failures_-true_-_default_-rspecmultipleexpectations] +==== `aggregate_failures: true` (default) + +[source,ruby] +---- +# good - the cop ignores when RSpec aggregates failures +describe UserCreator do + it 'builds a user', :aggregate_failures do + expect(user.name).to eq("John") + expect(user.age).to eq(22) + end +end +---- + +[#_aggregate_failures_-false_-rspecmultipleexpectations] +==== `aggregate_failures: false` + +[source,ruby] +---- +# Detected as an offense +describe UserCreator do + it 'builds a user', aggregate_failures: false do + expect(user.name).to eq("John") + expect(user.age).to eq(22) + end +end +---- + +[#_max_-1_-_default_-rspecmultipleexpectations] +==== `Max: 1` (default) + +[source,ruby] +---- +# bad +describe UserCreator do + it 'builds a user' do + expect(user.name).to eq("John") + expect(user.age).to eq(22) + end +end +---- + +[#_max_-2_-rspecmultipleexpectations] +==== `Max: 2` + +[source,ruby] +---- +# good +describe UserCreator do + it 'builds a user' do + expect(user.name).to eq("John") + expect(user.age).to eq(22) + end +end +---- + +[#configurable-attributes-rspecmultipleexpectations] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Max +| `1` +| Integer +|=== + +[#references-rspecmultipleexpectations] +=== References + +* https://rspec.rubystyle.guide/#expectation-per-example +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleExpectations +* http://betterspecs.org/#single + +[#rspecmultiplememoizedhelpers] +== RSpec/MultipleMemoizedHelpers + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.43 +| - +|=== + +Checks if example groups contain too many `let` and `subject` calls. + +This cop is configurable using the `Max` option and the `AllowSubject` +which will configure the cop to only register offenses on calls to +`let` and not calls to `subject`. + +[#examples-rspecmultiplememoizedhelpers] +=== Examples + +[source,ruby] +---- +# bad +describe MyClass do + let(:foo) { [] } + let(:bar) { [] } + let!(:baz) { [] } + let(:qux) { [] } + let(:quux) { [] } + let(:quuz) { {} } +end + +describe MyClass do + let(:foo) { [] } + let(:bar) { [] } + let!(:baz) { [] } + + context 'when stuff' do + let(:qux) { [] } + let(:quux) { [] } + let(:quuz) { {} } + end +end + +# good +describe MyClass do + let(:bar) { [] } + let!(:baz) { [] } + let(:qux) { [] } + let(:quux) { [] } + let(:quuz) { {} } +end + +describe MyClass do + context 'when stuff' do + let(:foo) { [] } + let(:bar) { [] } + let!(:booger) { [] } + end + + context 'when other stuff' do + let(:qux) { [] } + let(:quux) { [] } + let(:quuz) { {} } + end +end +---- + +[#when-disabling-allowsubject-configuration-rspecmultiplememoizedhelpers] +==== when disabling AllowSubject configuration + +[source,ruby] +---- +# rubocop.yml +# RSpec/MultipleMemoizedHelpers: +# AllowSubject: false + +# bad - `subject` counts towards memoized helpers +describe MyClass do + subject { {} } + let(:foo) { [] } + let(:bar) { [] } + let!(:baz) { [] } + let(:qux) { [] } + let(:quux) { [] } +end +---- + +[#with-max-configuration-rspecmultiplememoizedhelpers] +==== with Max configuration + +[source,ruby] +---- +# rubocop.yml +# RSpec/MultipleMemoizedHelpers: +# Max: 1 + +# bad +describe MyClass do + let(:foo) { [] } + let(:bar) { [] } +end +---- + +[#configurable-attributes-rspecmultiplememoizedhelpers] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| AllowSubject +| `true` +| Boolean + +| Max +| `5` +| Integer +|=== + +[#references-rspecmultiplememoizedhelpers] +=== References + +* https://rspec.rubystyle.guide/#let-blocks +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleMemoizedHelpers + +[#rspecmultiplesubjects] +== RSpec/MultipleSubjects + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.16 +| - +|=== + +Checks if an example group defines `subject` multiple times. + +This cop does not support autocorrection in some cases. +The autocorrect behavior for this cop depends on the type of +duplication: + + - If multiple named subjects are defined then this probably indicates + that the overwritten subjects (all subjects except the last + definition) are effectively being used to define helpers. In this + case they are replaced with `let`. + + - If multiple unnamed subjects are defined though then this can *only* + be dead code and we remove the overwritten subject definitions. + + - If subjects are defined with `subject!` then we don't autocorrect. + This is enough of an edge case that people can just move this to + a `before` hook on their own + +[#examples-rspecmultiplesubjects] +=== Examples + +[source,ruby] +---- +# bad +describe Foo do + subject(:user) { User.new } + subject(:post) { Post.new } +end + +# good +describe Foo do + let(:user) { User.new } + subject(:post) { Post.new } +end + +# bad (does not support autocorrection) +describe Foo do + subject!(:user) { User.new } + subject!(:post) { Post.new } +end + +# good +describe Foo do + before do + User.new + Post.new + end +end +---- + +[#references-rspecmultiplesubjects] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleSubjects + +[#rspecnamedsubject] +== RSpec/NamedSubject + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.5.3 +| 2.15 +|=== + +Checks for explicitly referenced test subjects. + +RSpec lets you declare an "implicit subject" using `subject { ... }` +which allows for tests like `it { is_expected.to be_valid }`. +If you need to reference your test subject you should explicitly +name it using `subject(:your_subject_name) { ... }`. Your test subjects +should be the most important object in your tests so they deserve +a descriptive name. + +This cop can be configured in your configuration using `EnforcedStyle`, +and `IgnoreSharedExamples` which will not report offenses for implicit +subjects in shared example groups. + +[#examples-rspecnamedsubject] +=== Examples + +[#_enforcedstyle_-always_-_default_-rspecnamedsubject] +==== `EnforcedStyle: always` (default) + +[source,ruby] +---- +# bad +RSpec.describe User do + subject { described_class.new } + + it 'is valid' do + expect(subject.valid?).to be(true) + end +end + +# good +RSpec.describe User do + subject(:user) { described_class.new } + + it 'is valid' do + expect(user.valid?).to be(true) + end +end + +# also good +RSpec.describe User do + subject(:user) { described_class.new } + + it { is_expected.to be_valid } +end +---- + +[#_enforcedstyle_-named_only_-rspecnamedsubject] +==== `EnforcedStyle: named_only` + +[source,ruby] +---- +# bad +RSpec.describe User do + subject(:user) { described_class.new } + + it 'is valid' do + expect(subject.valid?).to be(true) + end +end + +# good +RSpec.describe User do + subject(:user) { described_class.new } + + it 'is valid' do + expect(user.valid?).to be(true) + end +end + +# also good +RSpec.describe User do + subject { described_class.new } + + it { is_expected.to be_valid } +end + +# acceptable +RSpec.describe User do + subject { described_class.new } + + it 'is valid' do + expect(subject.valid?).to be(true) + end +end +---- + +[#configurable-attributes-rspecnamedsubject] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `always` +| `always`, `named_only` + +| IgnoreSharedExamples +| `true` +| Boolean +|=== + +[#references-rspecnamedsubject] +=== References + +* https://rspec.rubystyle.guide/#use-subject +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject + +[#rspecnestedgroups] +== RSpec/NestedGroups + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.7 +| 2.13 +|=== + +Checks for nested example groups. + +This cop is configurable using the `Max` option +and supports `--auto-gen-config`. + +[#examples-rspecnestedgroups] +=== Examples + +[source,ruby] +---- +# bad +context 'when using some feature' do + let(:some) { :various } + let(:feature) { :setup } + + context 'when user is signed in' do # flagged by rubocop + let(:user) do + UserCreate.call(user_attributes) + end + + let(:user_attributes) do + { + name: 'John', + age: 22, + role: role + } + end + + context 'when user is an admin' do # flagged by rubocop + let(:role) { 'admin' } + + it 'blah blah' + it 'yada yada' + end + end +end + +# good +context 'using some feature as an admin' do + let(:some) { :various } + let(:feature) { :setup } + + let(:user) do + UserCreate.call( + name: 'John', + age: 22, + role: 'admin' + ) + end + + it 'blah blah' + it 'yada yada' +end +---- + +[#_max_-3_-_default_-rspecnestedgroups] +==== `Max: 3` (default) + +[source,ruby] +---- +# bad +describe Foo do + context 'foo' do + context 'bar' do + context 'baz' do # flagged by rubocop + end + end + end +end +---- + +[#_max_-2_-rspecnestedgroups] +==== `Max: 2` + +[source,ruby] +---- +# bad +describe Foo do + context 'foo' do + context 'bar' do # flagged by rubocop + context 'baz' do # flagged by rubocop + end + end + end +end +---- + +[#_allowedgroups_-__-_default__-rspecnestedgroups] +==== `AllowedGroups: [] (default)` + +[source,ruby] +---- +describe Foo do # <-- nested groups 1 + context 'foo' do # <-- nested groups 2 + context 'bar' do # <-- nested groups 3 + end + end +end +---- + +[#_allowedgroups_-_path__-rspecnestedgroups] +==== `AllowedGroups: [path]` + +[source,ruby] +---- +describe Foo do # <-- nested groups 1 + path '/foo' do # <-- nested groups 1 (not counted) + context 'bar' do # <-- nested groups 2 + end + end +end +---- + +[#configurable-attributes-rspecnestedgroups] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Max +| `3` +| Integer + +| AllowedGroups +| `[]` +| Array +|=== + +[#references-rspecnestedgroups] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups + +[#rspecnoexpectationexample] +== RSpec/NoExpectationExample + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| No +| No +| 2.13 +| 2.14 +|=== + +Checks if an example contains any expectation. + +All RSpec's example and expectation methods are covered by default. +If you are using your own custom methods, +add the following configuration: + + RSpec: + Language: + Examples: + Regular: + - custom_it + Expectations: + - custom_expect + +This cop can be customized with an allowed expectation methods pattern +with an `AllowedPatterns` option. ^expect_ and ^assert_ are allowed +by default. + +[#examples-rspecnoexpectationexample] +=== Examples + +[source,ruby] +---- +# bad +it do + a? +end + +# good +it do + expect(a?).to be(true) +end +---- + +[#_allowedpatterns_-configuration-rspecnoexpectationexample] +==== `AllowedPatterns` configuration + +[source,ruby] +---- +# .rubocop.yml +# RSpec/NoExpectationExample: +# AllowedPatterns: +# - ^expect_ +# - ^assert_ +---- + +[source,ruby] +---- +# bad +it do + not_expect_something +end + +# good +it do + expect_something +end + +it do + assert_something +end +---- + +[#configurable-attributes-rspecnoexpectationexample] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| AllowedPatterns +| `^expect_`, `^assert_` +| Array +|=== + +[#references-rspecnoexpectationexample] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample + +[#rspecnottonot] +== RSpec/NotToNot + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.4 +| - +|=== + +Checks for consistent method usage for negating expectations. + +[#examples-rspecnottonot] +=== Examples + +[#_enforcedstyle_-not_to_-_default_-rspecnottonot] +==== `EnforcedStyle: not_to` (default) + +[source,ruby] +---- +# bad +it '...' do + expect(false).to_not be_true +end + +# good +it '...' do + expect(false).not_to be_true +end +---- + +[#_enforcedstyle_-to_not_-rspecnottonot] +==== `EnforcedStyle: to_not` + +[source,ruby] +---- +# bad +it '...' do + expect(false).not_to be_true +end + +# good +it '...' do + expect(false).to_not be_true +end +---- + +[#configurable-attributes-rspecnottonot] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `not_to` +| `not_to`, `to_not` +|=== + +[#references-rspecnottonot] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NotToNot + +[#rspecoverwritingsetup] +== RSpec/OverwritingSetup + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.14 +| - +|=== + +Checks if there is a let/subject that overwrites an existing one. + +[#examples-rspecoverwritingsetup] +=== Examples + +[source,ruby] +---- +# bad +let(:foo) { bar } +let(:foo) { baz } + +subject(:foo) { bar } +let(:foo) { baz } + +let(:foo) { bar } +let!(:foo) { baz } + +# good +subject(:test) { something } +let(:foo) { bar } +let(:baz) { baz } +let!(:other) { other } +---- + +[#references-rspecoverwritingsetup] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/OverwritingSetup + +[#rspecpending] +== RSpec/Pending + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Disabled +| Yes +| No +| 1.25 +| - +|=== + +Checks for any pending or skipped examples. + +[#examples-rspecpending] +=== Examples + +[source,ruby] +---- +# bad +describe MyClass do + it "should be true" +end + +describe MyClass do + it "should be true", skip: true do + expect(1).to eq(2) + end +end + +describe MyClass do + it "should be true" do + pending + end +end + +describe MyClass do + xit "should be true" do + end +end + +# good +describe MyClass do +end +---- + +[#references-rspecpending] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Pending + +[#rspecpendingwithoutreason] +== RSpec/PendingWithoutReason + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.16 +| - +|=== + +Checks for pending or skipped examples without reason. + +[#examples-rspecpendingwithoutreason] +=== Examples + +[source,ruby] +---- +# bad +pending 'does something' do +end + +# bad +it 'does something', :pending do +end + +# bad +it 'does something' do + pending +end + +# bad +xdescribe 'something' do +end + +# bad +skip 'does something' do +end + +# bad +it 'does something', :skip do +end + +# bad +it 'does something' do + skip +end + +# bad +it 'does something' + +# good +it 'does something' do + pending 'reason' +end + +# good +it 'does something' do + skip 'reason' +end + +# good +it 'does something', pending: 'reason' do +end + +# good +it 'does something', skip: 'reason' do +end +---- + +[#references-rspecpendingwithoutreason] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/PendingWithoutReason + +[#rspecpredicatematcher] +== RSpec/PredicateMatcher + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always (Unsafe) +| 1.16 +| - +|=== + +Prefer using predicate matcher over using predicate method directly. + +RSpec defines magic matchers for predicate methods. +This cop recommends to use the predicate matcher instead of using +predicate method directly. + +[#examples-rspecpredicatematcher] +=== Examples + +[#strict_-true_-enforcedstyle_-inflected-_default_-rspecpredicatematcher] +==== Strict: true, EnforcedStyle: inflected (default) + +[source,ruby] +---- +# bad +expect(foo.something?).to be_truthy + +# good +expect(foo).to be_something + +# also good - It checks "true" strictly. +expect(foo.something?).to be(true) +---- + +[#strict_-false_-enforcedstyle_-inflected-rspecpredicatematcher] +==== Strict: false, EnforcedStyle: inflected + +[source,ruby] +---- +# bad +expect(foo.something?).to be_truthy +expect(foo.something?).to be(true) + +# good +expect(foo).to be_something +---- + +[#strict_-true_-enforcedstyle_-explicit-rspecpredicatematcher] +==== Strict: true, EnforcedStyle: explicit + +[source,ruby] +---- +# bad +expect(foo).to be_something + +# good - the above code is rewritten to it by this cop +expect(foo.something?).to be(true) + +# bad - no autocorrect +expect(foo) + .to be_something(<<~TEXT) + bar + TEXT + +# good +expect(foo.something?(<<~TEXT)).to be(true) + bar +TEXT +---- + +[#strict_-false_-enforcedstyle_-explicit-rspecpredicatematcher] +==== Strict: false, EnforcedStyle: explicit + +[source,ruby] +---- +# bad +expect(foo).to be_something + +# good - the above code is rewritten to it by this cop +expect(foo.something?).to be_truthy +---- + +[#configurable-attributes-rspecpredicatematcher] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Strict +| `true` +| Boolean + +| EnforcedStyle +| `inflected` +| `inflected`, `explicit` + +| AllowedExplicitMatchers +| `[]` +| Array +|=== + +[#references-rspecpredicatematcher] +=== References + +* https://rspec.rubystyle.guide/#predicate-matchers +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/PredicateMatcher + +[#rspecreceivecounts] +== RSpec/ReceiveCounts + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.26 +| - +|=== + +Check for `once` and `twice` receive counts matchers usage. + +[#examples-rspecreceivecounts] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to receive(:bar).exactly(1).times +expect(foo).to receive(:bar).exactly(2).times +expect(foo).to receive(:bar).at_least(1).times +expect(foo).to receive(:bar).at_least(2).times +expect(foo).to receive(:bar).at_most(1).times +expect(foo).to receive(:bar).at_most(2).times + +# good +expect(foo).to receive(:bar).once +expect(foo).to receive(:bar).twice +expect(foo).to receive(:bar).at_least(:once) +expect(foo).to receive(:bar).at_least(:twice) +expect(foo).to receive(:bar).at_most(:once) +expect(foo).to receive(:bar).at_most(:twice).times +---- + +[#references-rspecreceivecounts] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveCounts + +[#rspecreceivemessages] +== RSpec/ReceiveMessages + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always (Unsafe) +| 2.23 +| - +|=== + +Prefer `receive_messages` over multiple `receive`s on the same object. + +[#safety-rspecreceivemessages] +=== Safety + +The autocorrection is marked as unsafe, because it may change the +order of stubs. This in turn may cause e.g. variables to be called +before they are defined. + +[#examples-rspecreceivemessages] +=== Examples + +[source,ruby] +---- +# bad +before do + allow(Service).to receive(:foo).and_return(bar) + allow(Service).to receive(:baz).and_return(qux) +end + +# good +before do + allow(Service).to receive_messages(foo: bar, baz: qux) +end + +# good - ignore same message +before do + allow(Service).to receive(:foo).and_return(bar) + allow(Service).to receive(:foo).and_return(qux) +end +---- + +[#references-rspecreceivemessages] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveMessages + +[#rspecreceivenever] +== RSpec/ReceiveNever + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.28 +| - +|=== + +Prefer `not_to receive(...)` over `receive(...).never`. + +[#examples-rspecreceivenever] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to receive(:bar).never + +# good +expect(foo).not_to receive(:bar) +---- + +[#references-rspecreceivenever] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveNever + +[#rspecredundantaround] +== RSpec/RedundantAround + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.19 +| - +|=== + +Remove redundant `around` hook. + +[#examples-rspecredundantaround] +=== Examples + +[source,ruby] +---- +# bad +around do |example| + example.run +end + +# good +---- + +[#references-rspecredundantaround] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantAround + +[#rspecredundantpredicatematcher] +== RSpec/RedundantPredicateMatcher + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.26 +| - +|=== + +Checks for redundant predicate matcher. + +[#examples-rspecredundantpredicatematcher] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to be_exist(bar) +expect(foo).not_to be_include(bar) +expect(foo).to be_all(bar) + +# good +expect(foo).to exist(bar) +expect(foo).not_to include(bar) +expect(foo).to all be(bar) +---- + +[#references-rspecredundantpredicatematcher] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RedundantPredicateMatcher + +[#rspecremoveconst] +== RSpec/RemoveConst + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.26 +| - +|=== + +Checks that `remove_const` is not used in specs. + +[#examples-rspecremoveconst] +=== Examples + +[source,ruby] +---- +# bad +it 'does something' do + Object.send(:remove_const, :SomeConstant) +end + +before do + SomeClass.send(:remove_const, :SomeConstant) +end +---- + +[#references-rspecremoveconst] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RemoveConst + +[#rspecrepeateddescription] +== RSpec/RepeatedDescription + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.9 +| - +|=== + +Check for repeated description strings in example groups. + +[#examples-rspecrepeateddescription] +=== Examples + +[source,ruby] +---- +# bad +RSpec.describe User do + it 'is valid' do + # ... + end + + it 'is valid' do + # ... + end +end + +# good +RSpec.describe User do + it 'is valid when first and last name are present' do + # ... + end + + it 'is valid when last name only is present' do + # ... + end +end + +# good +RSpec.describe User do + it 'is valid' do + # ... + end + + it 'is valid', :flag do + # ... + end +end +---- + +[#references-rspecrepeateddescription] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedDescription + +[#rspecrepeatedexample] +== RSpec/RepeatedExample + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.10 +| - +|=== + +Check for repeated examples within example groups. + +[#examples-rspecrepeatedexample] +=== Examples + +[source,ruby] +---- +it 'is valid' do + expect(user).to be_valid +end + +it 'validates the user' do + expect(user).to be_valid +end +---- + +[#references-rspecrepeatedexample] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExample + +[#rspecrepeatedexamplegroupbody] +== RSpec/RepeatedExampleGroupBody + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.38 +| - +|=== + +Check for repeated describe and context block body. + +[#examples-rspecrepeatedexamplegroupbody] +=== Examples + +[source,ruby] +---- +# bad +describe 'cool feature x' do + it { cool_predicate } +end + +describe 'cool feature y' do + it { cool_predicate } +end + +# good +describe 'cool feature' do + it { cool_predicate } +end + +describe 'another cool feature' do + it { another_predicate } +end + +# good +context 'when case x', :tag do + it { cool_predicate } +end + +context 'when case y' do + it { cool_predicate } +end + +# good +context Array do + it { is_expected.to respond_to :each } +end + +context Hash do + it { is_expected.to respond_to :each } +end +---- + +[#references-rspecrepeatedexamplegroupbody] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupBody + +[#rspecrepeatedexamplegroupdescription] +== RSpec/RepeatedExampleGroupDescription + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.38 +| - +|=== + +Check for repeated example group descriptions. + +[#examples-rspecrepeatedexamplegroupdescription] +=== Examples + +[source,ruby] +---- +# bad +describe 'cool feature' do + # example group +end + +describe 'cool feature' do + # example group +end + +# bad +context 'when case x' do + # example group +end + +describe 'when case x' do + # example group +end + +# good +describe 'cool feature' do + # example group +end + +describe 'another cool feature' do + # example group +end + +# good +context 'when case x' do + # example group +end + +context 'when another case' do + # example group +end +---- + +[#references-rspecrepeatedexamplegroupdescription] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupDescription + +[#rspecrepeatedincludeexample] +== RSpec/RepeatedIncludeExample + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.44 +| - +|=== + +Check for repeated include of shared examples. + +[#examples-rspecrepeatedincludeexample] +=== Examples + +[source,ruby] +---- +# bad +describe 'foo' do + include_examples 'cool stuff' + include_examples 'cool stuff' +end + +# bad +describe 'foo' do + it_behaves_like 'a cool', 'thing' + it_behaves_like 'a cool', 'thing' +end + +# bad +context 'foo' do + it_should_behave_like 'a duck' + it_should_behave_like 'a duck' +end + +# good +describe 'foo' do + include_examples 'cool stuff' +end + +describe 'bar' do + include_examples 'cool stuff' +end + +# good +describe 'foo' do + it_behaves_like 'a cool', 'thing' + it_behaves_like 'a cool', 'person' +end + +# good +context 'foo' do + it_should_behave_like 'a duck' + it_should_behave_like 'a goose' +end +---- + +[#references-rspecrepeatedincludeexample] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedIncludeExample + +[#rspecrepeatedsubjectcall] +== RSpec/RepeatedSubjectCall + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.27 +| - +|=== + +Checks for repeated calls to subject missing that it is memoized. + +[#examples-rspecrepeatedsubjectcall] +=== Examples + +[source,ruby] +---- +# bad +it do + subject + expect { subject }.to not_change { A.count } +end + +it do + expect { subject }.to change { A.count } + expect { subject }.to not_change { A.count } +end + +# good +it do + expect { my_method }.to change { A.count } + expect { my_method }.to not_change { A.count } +end + +# also good +it do + expect { subject.a }.to change { A.count } + expect { subject.b }.to not_change { A.count } +end +---- + +[#references-rspecrepeatedsubjectcall] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedSubjectCall + +[#rspecreturnfromstub] +== RSpec/ReturnFromStub + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.16 +| 1.22 +|=== + +Checks for consistent style of stub's return setting. + +Enforces either `and_return` or block-style return in the cases +where the returned value is constant. Ignores dynamic returned values +are the result would be different + +This cop can be configured using the `EnforcedStyle` option + +[#examples-rspecreturnfromstub] +=== Examples + +[#_enforcedstyle_-and_return_-_default_-rspecreturnfromstub] +==== `EnforcedStyle: and_return` (default) + +[source,ruby] +---- +# bad +allow(Foo).to receive(:bar) { "baz" } +expect(Foo).to receive(:bar) { "baz" } + +# good +allow(Foo).to receive(:bar).and_return("baz") +expect(Foo).to receive(:bar).and_return("baz") +# also good as the returned value is dynamic +allow(Foo).to receive(:bar) { bar.baz } +---- + +[#_enforcedstyle_-block_-rspecreturnfromstub] +==== `EnforcedStyle: block` + +[source,ruby] +---- +# bad +allow(Foo).to receive(:bar).and_return("baz") +expect(Foo).to receive(:bar).and_return("baz") + +# good +allow(Foo).to receive(:bar) { "baz" } +expect(Foo).to receive(:bar) { "baz" } +# also good as the returned value is dynamic +allow(Foo).to receive(:bar).and_return(bar.baz) +---- + +[#configurable-attributes-rspecreturnfromstub] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `and_return` +| `and_return`, `block` +|=== + +[#references-rspecreturnfromstub] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReturnFromStub + +[#rspecscatteredlet] +== RSpec/ScatteredLet + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 1.14 +| 2.31 +|=== + +Checks for let scattered across the example group. + +Group lets together + +[#examples-rspecscatteredlet] +=== Examples + +[source,ruby] +---- +# bad +describe Foo do + let(:foo) { 1 } + subject { Foo } + let(:bar) { 2 } + before { prepare } + let!(:baz) { 3 } +end + +# good +describe Foo do + subject { Foo } + before { prepare } + let(:foo) { 1 } + let(:bar) { 2 } + let!(:baz) { 3 } +end +---- + +[#references-rspecscatteredlet] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ScatteredLet + +[#rspecscatteredsetup] +== RSpec/ScatteredSetup + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Command-line only +| 1.10 +| 2.31 +|=== + +Checks for setup scattered across multiple hooks in an example group. + +Unify `before` and `after` hooks when possible. +However, `around` hooks are allowed to be defined multiple times, +as unifying them would typically make the code harder to read. + +[#examples-rspecscatteredsetup] +=== Examples + +[source,ruby] +---- +# bad +describe Foo do + before { setup1 } + before { setup2 } +end + +# good +describe Foo do + before do + setup1 + setup2 + end +end + +# good +describe Foo do + around { |example| before1; example.call; after1 } + around { |example| before2; example.call; after2 } +end +---- + +[#references-rspecscatteredsetup] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ScatteredSetup + +[#rspecsharedcontext] +== RSpec/SharedContext + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.13 +| - +|=== + +Checks for proper shared_context and shared_examples usage. + +If there are no examples defined, use shared_context. +If there is no setup defined, use shared_examples. + +[#examples-rspecsharedcontext] +=== Examples + +[source,ruby] +---- +# bad +RSpec.shared_context 'only examples here' do + it 'does x' do + end + + it 'does y' do + end +end + +# good +RSpec.shared_examples 'only examples here' do + it 'does x' do + end + + it 'does y' do + end +end +---- + +[source,ruby] +---- +# bad +RSpec.shared_examples 'only setup here' do + subject(:foo) { :bar } + + let(:baz) { :bazz } + + before do + something + end +end + +# good +RSpec.shared_context 'only setup here' do + subject(:foo) { :bar } + + let(:baz) { :bazz } + + before do + something + end +end +---- + +[#references-rspecsharedcontext] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedContext + +[#rspecsharedexamples] +== RSpec/SharedExamples + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.25 +| 2.26 +|=== + +Checks for consistent style for shared example names. + +Enforces either `string` or `symbol` for shared example names. + +This cop can be configured using the `EnforcedStyle` option + +[#examples-rspecsharedexamples] +=== Examples + +[#_enforcedstyle_-string_-_default_-rspecsharedexamples] +==== `EnforcedStyle: string` (default) + +[source,ruby] +---- +# bad +it_behaves_like :foo_bar_baz +it_should_behave_like :foo_bar_baz +shared_examples :foo_bar_baz +shared_examples_for :foo_bar_baz +include_examples :foo_bar_baz + +# good +it_behaves_like 'foo bar baz' +it_should_behave_like 'foo bar baz' +shared_examples 'foo bar baz' +shared_examples_for 'foo bar baz' +include_examples 'foo bar baz' +---- + +[#_enforcedstyle_-symbol_-rspecsharedexamples] +==== `EnforcedStyle: symbol` + +[source,ruby] +---- +# bad +it_behaves_like 'foo bar baz' +it_should_behave_like 'foo bar baz' +shared_examples 'foo bar baz' +shared_examples_for 'foo bar baz' +include_examples 'foo bar baz' + +# good +it_behaves_like :foo_bar_baz +it_should_behave_like :foo_bar_baz +shared_examples :foo_bar_baz +shared_examples_for :foo_bar_baz +include_examples :foo_bar_baz +---- + +[#configurable-attributes-rspecsharedexamples] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `string` +| `string`, `symbol` +|=== + +[#references-rspecsharedexamples] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedExamples + +[#rspecsingleargumentmessagechain] +== RSpec/SingleArgumentMessageChain + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.9 +| 1.10 +|=== + +Checks that chains of messages contain more than one element. + +[#examples-rspecsingleargumentmessagechain] +=== Examples + +[source,ruby] +---- +# bad +allow(foo).to receive_message_chain(:bar).and_return(42) + +# good +allow(foo).to receive(:bar).and_return(42) + +# also good +allow(foo).to receive(:bar, :baz) +allow(foo).to receive("bar.baz") +---- + +[#references-rspecsingleargumentmessagechain] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain + +[#rspecskipblockinsideexample] +== RSpec/SkipBlockInsideExample + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.19 +| - +|=== + +Checks for passing a block to `skip` within examples. + +[#examples-rspecskipblockinsideexample] +=== Examples + +[source,ruby] +---- +# bad +it 'does something' do + skip 'not yet implemented' do + do_something + end +end + +# good +it 'does something' do + skip 'not yet implemented' + do_something +end + +# good - when outside example +skip 'not yet implemented' do +end +---- + +[#references-rspecskipblockinsideexample] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SkipBlockInsideExample + +[#rspecsortmetadata] +== RSpec/SortMetadata + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 2.14 +| - +|=== + +Sort RSpec metadata alphabetically. + +Only the trailing metadata is sorted. + +[#examples-rspecsortmetadata] +=== Examples + +[source,ruby] +---- +# bad +describe 'Something', :b, :a +context 'Something', foo: 'bar', baz: true +it 'works', :b, :a, foo: 'bar', baz: true + +# good +describe 'Something', :a, :b +context 'Something', baz: true, foo: 'bar' +it 'works', :a, :b, baz: true, foo: 'bar' + +# good, trailing metadata is sorted +describe 'Something', 'description', :a, :b, :z +context 'Something', :z, variable, :a, :b +---- + +[#references-rspecsortmetadata] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SortMetadata + +[#rspecspecfilepathformat] +== RSpec/SpecFilePathFormat + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.24 +| - +|=== + +Checks that spec file paths are consistent and well-formed. + +[#examples-rspecspecfilepathformat] +=== Examples + +[source,ruby] +---- +# bad +whatever_spec.rb # describe MyClass +my_class_spec.rb # describe MyClass, '#method' + +# good +my_class_spec.rb # describe MyClass +my_class_method_spec.rb # describe MyClass, '#method' +my_class/method_spec.rb # describe MyClass, '#method' +---- + +[#_customtransform_-_rubocop__rubocop_-rspec__rspec__-_default_-rspecspecfilepathformat] +==== `CustomTransform: {RuboCop=>rubocop, RSpec=>rspec}` (default) + +[source,ruby] +---- +# good +rubocop_spec.rb # describe RuboCop +rspec_spec.rb # describe RSpec +---- + +[#_ignoremethods_-false_-_default_-rspecspecfilepathformat] +==== `IgnoreMethods: false` (default) + +[source,ruby] +---- +# bad +my_class_spec.rb # describe MyClass, '#method' +---- + +[#_ignoremethods_-true_-rspecspecfilepathformat] +==== `IgnoreMethods: true` + +[source,ruby] +---- +# good +my_class_spec.rb # describe MyClass, '#method' +---- + +[#_ignoremetadata_-_type__routing__-_default_-rspecspecfilepathformat] +==== `IgnoreMetadata: {type=>routing}` (default) + +[source,ruby] +---- +# good +whatever_spec.rb # describe MyClass, type: :routing do; end +---- + +[#configurable-attributes-rspecspecfilepathformat] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Include +| `+**/*_spec.rb+` +| Array + +| Exclude +| `+**/spec/routing/**/*+` +| Array + +| CustomTransform +| `{"RuboCop" => "rubocop", "RSpec" => "rspec"}` +| + +| IgnoreMethods +| `false` +| Boolean + +| IgnoreMetadata +| `{"type" => "routing"}` +| +|=== + +[#references-rspecspecfilepathformat] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathFormat + +[#rspecspecfilepathsuffix] +== RSpec/SpecFilePathSuffix + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.24 +| - +|=== + +Checks that spec file paths suffix are consistent and well-formed. + +[#examples-rspecspecfilepathsuffix] +=== Examples + +[source,ruby] +---- +# bad +my_class/foo_specorb.rb # describe MyClass +spec/models/user.rb # describe User +spec/models/user_specxrb # describe User + +# good +my_class_spec.rb # describe MyClass + +# good - shared examples are allowed +spec/models/user.rb # shared_examples_for 'foo' +---- + +[#configurable-attributes-rspecspecfilepathsuffix] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Include +| `+**/*_spec*rb*+`, `+**/spec/**/*+` +| Array +|=== + +[#references-rspecspecfilepathsuffix] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathSuffix + +[#rspecstubbedmock] +== RSpec/StubbedMock + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.44 +| - +|=== + +Checks that message expectations do not have a configured response. + +[#examples-rspecstubbedmock] +=== Examples + +[source,ruby] +---- +# bad +expect(foo).to receive(:bar).with(42).and_return("hello world") + +# good (without spies) +allow(foo).to receive(:bar).with(42).and_return("hello world") +expect(foo).to receive(:bar).with(42) +---- + +[#references-rspecstubbedmock] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/StubbedMock + +[#rspecsubjectdeclaration] +== RSpec/SubjectDeclaration + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.5 +| - +|=== + +Ensure that subject is defined using subject helper. + +[#examples-rspecsubjectdeclaration] +=== Examples + +[source,ruby] +---- +# bad +let(:subject) { foo } +let!(:subject) { foo } +subject(:subject) { foo } +subject!(:subject) { foo } + +# bad +block = -> {} +let(:subject, &block) + +# good +subject(:test_subject) { foo } +---- + +[#references-rspecsubjectdeclaration] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectDeclaration + +[#rspecsubjectstub] +== RSpec/SubjectStub + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.7 +| 2.8 +|=== + +Checks for stubbed test subjects. + +Checks nested subject stubs for innermost subject definition +when subject is also defined in parent example groups. + +[#examples-rspecsubjectstub] +=== Examples + +[source,ruby] +---- +# bad +describe Article do + subject(:article) { Article.new } + + it 'indicates that the author is unknown' do + allow(article).to receive(:author).and_return(nil) + expect(article.description).to include('by an unknown author') + end +end + +# bad +describe Article do + subject(:foo) { Article.new } + + context 'nested subject' do + subject(:article) { Article.new } + + it 'indicates that the author is unknown' do + allow(article).to receive(:author).and_return(nil) + expect(article.description).to include('by an unknown author') + end + end +end + +# good +describe Article do + subject(:article) { Article.new(author: nil) } + + it 'indicates that the author is unknown' do + expect(article.description).to include('by an unknown author') + end +end +---- + +[#references-rspecsubjectstub] +=== References + +* https://rspec.rubystyle.guide/#dont-stub-subject +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectStub +* https://robots.thoughtbot.com/don-t-stub-the-system-under-test +* https://penelope.zone/2015/12/27/introducing-rspec-smells-and-where-to-find-them.html#smell-1-stubjec + +[#rspecundescriptiveliteralsdescription] +== RSpec/UndescriptiveLiteralsDescription + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 2.29 +| - +|=== + +Description should be descriptive. + +If example group or example contains only `execute string`, numbers +and regular expressions, the description is not clear. + +[#examples-rspecundescriptiveliteralsdescription] +=== Examples + +[source,ruby] +---- +# bad +describe `time` do + # ... +end + +# bad +context /when foo/ do + # ... +end + +# bad +it 10000 do + # ... +end + +# good +describe Foo do + # ... +end + +# good +describe '#foo' do + # ... +end + +# good +context "when #{foo} is bar" do + # ... +end + +# good +it 'does something' do + # ... +end +---- + +[#references-rspecundescriptiveliteralsdescription] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UndescriptiveLiteralsDescription + +[#rspecunspecifiedexception] +== RSpec/UnspecifiedException + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.30 +| - +|=== + +Checks for a specified error in checking raised errors. + +Enforces one of an Exception type, a string, or a regular +expression to match against the exception message as a parameter +to `raise_error` + +[#examples-rspecunspecifiedexception] +=== Examples + +[source,ruby] +---- +# bad +expect { + raise StandardError.new('error') +}.to raise_error + +# good +expect { + raise StandardError.new('error') +}.to raise_error(StandardError) + +expect { + raise StandardError.new('error') +}.to raise_error('error') + +expect { + raise StandardError.new('error') +}.to raise_error(/err/) + +expect { do_something }.not_to raise_error +---- + +[#references-rspecunspecifiedexception] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UnspecifiedException + +[#rspecvariabledefinition] +== RSpec/VariableDefinition + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.40 +| - +|=== + +Checks that memoized helpers names are symbols or strings. + +[#examples-rspecvariabledefinition] +=== Examples + +[#enforcedstyle_-symbols-_default_-rspecvariabledefinition] +==== EnforcedStyle: symbols (default) + +[source,ruby] +---- +# bad +subject('user') { create_user } +let('user_name') { 'Adam' } + +# good +subject(:user) { create_user } +let(:user_name) { 'Adam' } +---- + +[#enforcedstyle_-strings-rspecvariabledefinition] +==== EnforcedStyle: strings + +[source,ruby] +---- +# bad +subject(:user) { create_user } +let(:user_name) { 'Adam' } + +# good +subject('user') { create_user } +let('user_name') { 'Adam' } +---- + +[#configurable-attributes-rspecvariabledefinition] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `symbols` +| `symbols`, `strings` +|=== + +[#references-rspecvariabledefinition] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableDefinition + +[#rspecvariablename] +== RSpec/VariableName + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.40 +| 2.13 +|=== + +Checks that memoized helper names use the configured style. + +Variables can be excluded from checking using the `AllowedPatterns` +option. + +[#examples-rspecvariablename] +=== Examples + +[#enforcedstyle_-snake_case-_default_-rspecvariablename] +==== EnforcedStyle: snake_case (default) + +[source,ruby] +---- +# bad +subject(:userName1) { 'Adam' } +let(:userName2) { 'Adam' } + +# good +subject(:user_name_1) { 'Adam' } +let(:user_name_2) { 'Adam' } +---- + +[#enforcedstyle_-camelcase-rspecvariablename] +==== EnforcedStyle: camelCase + +[source,ruby] +---- +# bad +subject(:user_name_1) { 'Adam' } +let(:user_name_2) { 'Adam' } + +# good +subject(:userName1) { 'Adam' } +let(:userName2) { 'Adam' } +---- + +[#allowedpatterns-configuration-rspecvariablename] +==== AllowedPatterns configuration + +[source,ruby] +---- +# rubocop.yml +# RSpec/VariableName: +# EnforcedStyle: snake_case +# AllowedPatterns: +# - ^userFood +---- + +[source,ruby] +---- +# okay because it matches the `^userFood` regex in `AllowedPatterns` +subject(:userFood_1) { 'spaghetti' } +let(:userFood_2) { 'fettuccine' } +---- + +[#configurable-attributes-rspecvariablename] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| EnforcedStyle +| `snake_case` +| `snake_case`, `camelCase` + +| AllowedPatterns +| `[]` +| Array +|=== + +[#references-rspecvariablename] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName + +[#rspecverifieddoublereference] +== RSpec/VerifiedDoubleReference + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always (Unsafe) +| 2.10.0 +| 3.4 +|=== + +Checks for consistent verified double reference style. + +[#safety-rspecverifieddoublereference] +=== Safety + +This cop is unsafe because the correction requires loading the class. +Loading before stubbing causes RSpec to only allow instance methods +to be stubbed. + +[#examples-rspecverifieddoublereference] +=== Examples + +[source,ruby] +---- +# bad +let(:foo) do + instance_double('ClassName', method_name: 'returned_value') +end + +# good +let(:foo) do + instance_double(ClassName, method_name: 'returned_value') +end +---- + +[#reference-is-any-dynamic-variable_-no-enforcement-rspecverifieddoublereference] +==== Reference is any dynamic variable. No enforcement + +[source,ruby] +---- +# good +let(:foo) do + instance_double(@klass, method_name: 'returned_value') +end +---- + +[#references-rspecverifieddoublereference] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VerifiedDoubleReference +* https://rspec.info/features/3-12/rspec-mocks/verifying-doubles + +[#rspecverifieddoubles] +== RSpec/VerifiedDoubles + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.2.1 +| 1.5 +|=== + +Prefer using verifying doubles over normal doubles. + +[#examples-rspecverifieddoubles] +=== Examples + +[source,ruby] +---- +# bad +let(:foo) do + double(method_name: 'returned value') +end + +# bad +let(:foo) do + double("ClassName", method_name: 'returned value') +end + +# good +let(:foo) do + instance_double("ClassName", method_name: 'returned value') +end +---- + +[#configurable-attributes-rspecverifieddoubles] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| IgnoreNameless +| `true` +| Boolean + +| IgnoreSymbolicNames +| `false` +| Boolean +|=== + +[#references-rspecverifieddoubles] +=== References + +* https://rspec.rubystyle.guide/#doubles +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VerifiedDoubles +* https://rspec.info/features/3-12/rspec-mocks/verifying-doubles + +[#rspecvoidexpect] +== RSpec/VoidExpect + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.16 +| - +|=== + +Checks void `expect()`. + +[#examples-rspecvoidexpect] +=== Examples + +[source,ruby] +---- +# bad +expect(something) + +# good +expect(something).to be(1) +---- + +[#references-rspecvoidexpect] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VoidExpect + +[#rspecyield] +== RSpec/Yield + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| Always +| 1.32 +| - +|=== + +Checks for calling a block within a stub. + +[#examples-rspecyield] +=== Examples + +[source,ruby] +---- +# bad +allow(foo).to receive(:bar) { |&block| block.call(1) } + +# good +expect(foo).to receive(:bar).and_yield(1) +---- + +[#references-rspecyield] +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Yield diff --git a/docs/modules/ROOT/pages/development.adoc b/docs/modules/ROOT/pages/development.adoc new file mode 100644 index 000000000..308980d8b --- /dev/null +++ b/docs/modules/ROOT/pages/development.adoc @@ -0,0 +1,73 @@ += Development + +This page describes considerations when developing RSpec-specific cops. It is intended to be a complement to the general https://docs.rubocop.org/rubocop/development.html[RuboCop development documentation]. + +== Create a new cop + +NOTE: Clone the repository and run `bundle install` if not done yet. +The following rake task can only be run inside the rubocop project directory itself. + +Use the bundled rake task `new_cop` to generate a cop template: + +[source,sh] +---- +$ bundle exec rake 'new_cop[RSpec/CopName]' +[create] lib/rubocop/cop/rspec/cop_name.rb +[create] spec/rubocop/cop/rspec/cop_name_spec.rb +[modify] lib/rubocop/cop/rspec_cops.rb - `require_relative 'rspec/cop_name'` was injected. +[modify] A configuration for the cop is added into config/default.yml. +Do 4 steps: + 1. Modify the description of RSpec/CopName in config/default.yml + 2. Implement your new cop in the generated file! + 3. Add an entry about new cop to CHANGELOG.md + 4. Commit your new cop with a message such as + e.g. "Add new `#{badge}` cop" +---- + +=== Choose a Name + +Use the following rules to give the new cop a name: + +* Pick a department. See the xref:cops.adoc[list of existing departments] +* The name is self-explanatory +* The name explains the offense the cop detects, e.g. `ExtraSpacing` +* The name starts with a noun instead of a verb, e.g. `ArrayAlignment` instead of `AlignArray` +* The name is easy to understand, e.g. `IndentationStyle` instead of just `Tab` +* The name is specific, e.g. `DuplicateHashKey` instead of just `DuplicateKey` +* The name is neutral when possible and accommodates multiple styles when feasible, e.g. `EmptyLineBeforeBegin`. +* The name uses commonly-used terms, e.g. `RedundantPercentI` instead of `RedundantPercentSymbolArray` +* The name uses correct terms, e.g. arguments in a method call, and parameters in a method signature +* Lines with no symbols are called "empty", not "blank", e.g. `LeadingEmptyLines` instead of `LeadingBlankLines` +* Prefer "redundant" to "unneeded", e.g. `RedundantSelf` instead of `UnneededSelf` + +See the https://github.com/rubocop/rubocop/blob/12fd014e255617a08b7b42aa5df0745e7382af88/config/obsoletion.yml#L4["renamed" section of `config/obsoletion.yml`] +for good and bad examples (old name is on the left, new name on the right). + +== Base class + +The `RuboCop::Cop::RSpec::Base` class includes convenient https://docs.rubocop.org/rubocop-ast/node_pattern.html[node pattern DSL] matchers that will automatically account for any xref:usage.adoc#rspec-dsl-configuration[custom RSpec DSL configuration]. + +For example, if the project defines https://github.com/test-prof/test-prof/blob/master/docs/recipes/let_it_be.md[`let_it_be`] as a `Helper`, then all cops will find `let_it_be` when using the `let?` matcher. + +== Writing specs + +When working on RSpec-specific cops, ensure that the https://github.com/rubocop/rubocop-rspec/blob/master/config/default.yml[default language config] is loaded for all RSpec specs. For example: + +[source,ruby] +---- +require 'rubocop/rspec/shared_contexts/default_rspec_language_config_context' + +RSpec.config do |config| + # Set metadata on all cop specs + config.define_derived_metadata(file_path: %r{/spec/rubocop/cop/}) do |meta| + meta[:type] = :cop_spec + end + + # Include RuboCop's config shared context for all cop specs + config.define_derived_metadata(type: :cop_spec) do |meta| + meta[:config] = true + end + + config.include_context 'with default RSpec/Language config', :config +end +---- diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 000000000..e6a27ef5c --- /dev/null +++ b/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,59 @@ += RuboCop RSpec + +https://rspec.info/[RSpec]-specific analysis for your projects, as an extension to +https://github.com/rubocop/rubocop[RuboCop]. + +RuboCop RSpec follows the https://docs.rubocop.org/rubocop/versioning.html[RuboCop versioning guide]. +In a nutshell, between major versions new cops are introduced in a special `pending` status. +That means that they won't be run unless explicitly told otherwise. +RuboCop will warn on start that certain cops are neither explicitly enabled and disabled. +On a major version release, all `pending` cops are enabled. + +== Project Goals + +* Enforce the guidelines and best practices outlined in the community https://rspec.rubystyle.guide[RSpec style guide] +* Simplify the process of adopting new RSpec functionality + +== Non-goals of RuboCop RSpec + +=== Enforcing `should` vs. `expect` syntax + +Enforcing + +[source,ruby] +---- +expect(calculator.compute(line_item)).to eq(5) +---- + +over + +[source,ruby] +---- +calculator.compute(line_item).should == 5 +---- + +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]. + +=== Enforcing an explicit RSpec receiver for top-level methods (disabling monkey patching) + +Enforcing + +[source,ruby] +---- +RSpec.describe MyClass do + ... +end +---- + +over + +[source,ruby] +---- +describe MyClass do + ... +end +---- + +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. + +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. diff --git a/manual/installation.md b/docs/modules/ROOT/pages/installation.adoc similarity index 67% rename from manual/installation.md rename to docs/modules/ROOT/pages/installation.adoc index 5c26d5ce4..dda8f545d 100644 --- a/manual/installation.md +++ b/docs/modules/ROOT/pages/installation.adoc @@ -1,11 +1,15 @@ += Installation + Just install the `rubocop-rspec` gem -```bash +[source,bash] +---- gem install rubocop-rspec -``` +---- or if you use bundler put this in your `Gemfile` -```ruby +[source,ruby] +---- gem 'rubocop-rspec' -``` +---- diff --git a/docs/modules/ROOT/pages/third_party_rspec_syntax_extensions.adoc b/docs/modules/ROOT/pages/third_party_rspec_syntax_extensions.adoc new file mode 100644 index 000000000..bb6825eba --- /dev/null +++ b/docs/modules/ROOT/pages/third_party_rspec_syntax_extensions.adoc @@ -0,0 +1,28 @@ += RSpec syntax extensions in third-party gems + +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. + +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. +It is possible for the gems to provide configuration for RuboCop RSpec to allow proper detection of RSpec elements. +RuboCop https://docs.rubocop.org/rubocop/configuration.html#inheriting-configuration-from-a-dependency-gem[provides third-party gems with an ability to configure RuboCop]. + +== Packaging configuration for RuboCop RSpec + +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. + +For a third-party gem, it's sufficient to follow three steps: + +1. Provide a configuration file (e.g. `.rubocop_rspec_alias_config.yml` or `config/rubocop-rspec.yml`). +Please check with the xref:usage.adoc#rspec-dsl-configuration[RSpec DSL configuration] how different elements of RSpec syntax can be configured. + +2. Add a section to their documentation how users can benefit from using RuboCop RSpec with full detection of their syntax extensions. + Example: + + ## Usage with RuboCop RSpec + Please add the following to your `.rubocop.yml` to make RuboCop RSpec aware of our cool syntax extensions: + inherit_gem: + third-party-gem: .rubocop_rspec_alias_config.yml + +3. Include the configuration file to their package by updating their `gemspec`'s `spec.files` to include the aforementioned configuration file. + +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. diff --git a/docs/modules/ROOT/pages/upgrade_to_version_2.adoc b/docs/modules/ROOT/pages/upgrade_to_version_2.adoc new file mode 100644 index 000000000..2d91caa6e --- /dev/null +++ b/docs/modules/ROOT/pages/upgrade_to_version_2.adoc @@ -0,0 +1,273 @@ += Upgrade to Version 2.x +:doctype: book + +== Configuration File Update + +In version 2.x: + + - `RSpec/InvalidPredicateMatcher` cop is removed + - `CustomIncludeMethods` configuration option for `RSpec/EmptyExampleGroup` is removed + - cop departments are nested for cops with a department that doesn't match the extension name (`Capybara`, `FactoryBot`, `Rails`) + - `AllCops/RSpec/Patterns`/`AllCops/FactoryBot/Patterns` options are removed + - Calling `super` from `#on_new_investigation` defined in a cop is mandatory now + - In specs, do not define `cop` + +[discrete] +=== Adjust the configuration of `RSpec/EmptyExampleGroup` + +[source,yaml] +---- +# .rubocop.yml + +# Before +RSpec/EmptyExampleGroup: + CustomIncludeMethods: + - include_tests + +# After +RSpec: + Language: + Includes: + Examples: + - include_tests +---- + +=== Add a top-level `RSpec` department + +RuboCop extensions had cops with clashing names and departments, e.g. both `rspec-rails` and `rubocop-rspec` had `Rails::HttpStatus` cops. +To avoid issues, e.g. inability to disable just one of the cops, each extension now has its own uber-department. +Expectedly, RuboCop RSpec's uber-department name is `RSpec`. +Changes are only applied to cops that don't already have the department set to `RSpec`, i.e. `Capybara`, `FactoryBot` and `Rails`. + +[source,yaml] +---- +# .rubocop.yml + +# Before +Capybara/CurrentPathExpectation: + Enabled: false + +FactoryBot/AttributeDefinedStatically: + Enabled: false + +# remains the same +RSpec/EmptyExampleGroup: + Enabled: false + +# After +RSpec/Capybara/CurrentPathExpectation: + Enabled: false + +RSpec/FactoryBot/AttributeDefinedStatically: + Enabled: false + +# remains the same +RSpec/EmptyExampleGroup: + Enabled: false +---- + +https://github.com/rubocop/rubocop/pull/8490[Learn more about this change]. + + +=== Use the RuboCop standard `Include` option to filter inspected files + +`Patterns` was a RuboCop RSpec-specific option, and RuboCop has a standard replacement. + +[source,yaml] +---- +# .rubocop.yml + +# Before +AllCops: + RSpec/FactoryBot: + Patterns: + - spec/factories/**/*.rb + - property/factories/**/*.rb + +# After +RSpec/FactoryBot: + Include: + - spec/factories/**/*.rb + - property/factories/**/*.rb +---- + +NOTE: Please keep in mind that merge mode for `Include` is set to override the default settings, so if you intend to add a path while keeping the default paths, you should include the default `Include` paths in your configuration. + +https://github.com/rubocop/rubocop-rspec/pull/1063[Learn more about this change]. + +== Custom Cop Update Guide + +Due to significant API changes, custom cops may break. +Here is the summary of the changes: + +1. The base class for cops is now `RuboCop::Cop::RSpec::Base` instead of `RuboCop::Cop::RSpec::Cop`. + +2. The module `RuboCop::Cop::RSpec::TopLevelDescribe` is replaced with a more generic `RuboCop::Cop::RSpec::TopLevelGroup`. + +3. `RuboCop::RSpec::Language` has been completely rewritten to support dynamic RSpec DSL aliases and negated matchers to fully support third-party libraries such as RSpec Rails, Pundit, Action Policy and many others. + +4. RuboCop RSpec updated the dependency of RuboCop to 1.0+. + +Below are the necessary steps to update custom cops to work with `rubocop-rspec` version 2.x. + + +=== Change the Parent Class + +Change the parent class of the custom cops from `RuboCop::Cop::RSpec::Cop` to `RuboCop::Cop::RSpec::Base`. + +[source,ruby] +---- +# Before +module RuboCop + module Cop + module RSpec + class FightPowerty < Cop + +# After +module RuboCop + module Cop + module RSpec + class FightPowerty < Base +---- + +https://github.com/rubocop/rubocop-rspec/pull/962[Example pull request]. + + +=== Replace `TopLevelDescribe` + +`TopLevelDescribe` was incomplete, had poor performance and did not distinguish between example groups and shared example groups. + +`TopLevelGroup` provides a similar interface, but instead of a single `on_top_level_describe` hook there are two, `on_top_level_example_group` and `on_top_level_group`. +There's no need yet for `on_top_level_shared_group` for RuboCop core cops, but if your custom cop needs such a hook, please feel free to send a pull request. + +Additionally, `single_top_level_describe?` is removed with no direct replacement. +You may use `top_level_groups` query method instead, e.g. `top_level_groups.one?`. + +Example pull requests to replace `TopLevelDescribe` with `TopLevelGroup` [https://github.com/rubocop/rubocop-rspec/pull/978[1], https://github.com/rubocop/rubocop-rspec/pull/932[2], https://github.com/rubocop/rubocop-rspec/pull/977[3]]. + + +=== Change the `Language` Module Usages + +To allow for lazy initialization, and for loading of the language configuration after the class are loaded, a https://docs.rubocop.org/rubocop-ast/node_pattern.html#to-call-functions[function call feature of RuboCop AST] is used. + +The `RuboCop::RSpec::Language` is completely different now. + +`Hooks::ALL` and alike, and their accompanying helpers work differently. + +[source,ruby] +---- +# Before +def_node_matcher :shared_context, + SharedGroups::CONTEXT.block_pattern + +# After +def_node_matcher :shared_context, + '(block (send #rspec? #SharedGroups.context ...) ...)' +---- + +[source,ruby] +---- +# Before +def_node_search :examples?, + (Includes::EXAMPLES + Examples::ALL).send_pattern + +# After +def_node_search :examples?, + '(send nil? {#Includes.examples #Examples.all} ...)' +---- + +[source,ruby] +---- +# Before +def_node_search :find_rspec_blocks, + ExampleGroups::ALL.block_pattern + +# After +def_node_search :find_rspec_blocks, + '(block (send #rspec? #ExampleGroups.all ...) ...)' +---- + +If you were calling Language elements directly, you have to make the same adjustments: + +[source,ruby] +---- +# Before +node&.sym_type? && Hooks::Scopes::ALL.include?(node.value) + +# After +node&.sym_type? && Language::HookScopes.all(node.value) +---- + +You may see a common pattern in the change. +There is a small exception, though: + +[source,ruby] +---- +# Before +ExampleGroups::GROUPS + +# After +ExampleGroups.regular + +# Before +Examples::EXAMPLES + +# After +Examples.regular +---- + +https://github.com/rubocop/rubocop-rspec/pull/956[Pull request with more examples]. + +=== Always call `super` from `on_new_investigation` in your cops + +`on_new_investigation` is now used for internal purposes, and not calling `super` from your cop involves a risk of configuration not being properly loaded, and dynamic RSpec DSL matchers won't work. + +NOTE: You don't have to define `on_new_investigation` in your cops unless you need to. + +[source,ruby] +---- +module RuboCop + module Cop + module RSpec + class MultipleMemoizedHelpers < Base + def on_new_investigation + super # Always call `super` + @example_group_memoized_helpers = {} + end + end + end + end +end +---- + +https://github.com/rubocop/rubocop-rspec/pull/956[Pull request with more examples]. + +=== Use `:config` RSpec metadata in cop specs + +`:config` metadata should be added to the top-level example group of your cop spec. +Doing otherwise will not pass configuration to the cop, and dynamic RSpec DSL matchers might not work. + +[source,ruby] +---- +# Before +RSpec.describe 'MyMightyCop' do + let(:cop) { described_class.new } + # ... +end + +# After +RSpec.describe 'MyMightyCop', :config do + # `cop` is defined for you by RuboCop's shared context that is included + # to example groups with :config metadata + + # ... +end +---- + +https://github.com/rubocop/rubocop/blob/51ff1d7e29c985732fe129082c98d66c531a2611/lib/rubocop/rspec/shared_contexts.rb#L56[RuboCop takes care of defining everything for your cop specs]. + +=== Conform with RuboCop API Changes + +The parent project, RuboCop, has API changes. +While they won't result in cop breakages, it is recommended to update cops to use new API's. +Follow the https://docs.rubocop.org/rubocop/v1_upgrade_notes[RuboCop v1 update guide] to adjust custom cops' use of RuboCop's auto-correction API. diff --git a/docs/modules/ROOT/pages/upgrade_to_version_3.adoc b/docs/modules/ROOT/pages/upgrade_to_version_3.adoc new file mode 100644 index 000000000..29de466d4 --- /dev/null +++ b/docs/modules/ROOT/pages/upgrade_to_version_3.adoc @@ -0,0 +1,60 @@ += Upgrade to Version 3.x +:doctype: book + +== Configuration File Update + +In version 3.x: + + - cop departments are extracted to another gem. (`Capybara`, `FactoryBot`, `Rails`) + +[discrete] +=== Extraction of cop departments. (`Capybara`, `FactoryBot`, `Rails`) + +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: + +* Capybara: `rubocop-capybara` +* FactoryBot: `rubocop-factory_bot` +* Rails: `rubocop-rspec_rails` + +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: + +[source,ruby] +---- +# Gemfile +group :test do + gem 'rubocop-rspec' + gem 'rubocop-capybara' +end +---- + +[source,yaml] +---- +require: + - rubocop-rspec + - rubocop-capybara +---- + +And you need to remove the old department in your `.rubocop.yml` file: + +[source,yaml] +---- +RSpec/Capybara: + Enabled: false +---- + +For another example, if you are not using these departments, you don't need to do anything. +And when you update to RuboCop RSpec v3.0.0, you need to remove the old departments from your `.rubocop.yml` file, e.g.: + +[source,yaml] +---- +RSpec/Capybara: + Enabled: false +RSpec/FactoryBot: + Enabled: false +RSpec/Rails: + Enabled: false +---- + +== Troubleshooting + +If you're seeing `cannot load such file` when running rubocop, even after installing the gems, restart the server with `rubocop --restart-server`. diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc new file mode 100644 index 000000000..d0a276420 --- /dev/null +++ b/docs/modules/ROOT/pages/usage.adoc @@ -0,0 +1,119 @@ += Usage + +You need to tell RuboCop to load the RSpec extension. +There are three ways to do this: + +== RuboCop configuration file + +Put this into your `.rubocop.yml`: + +---- +plugins: rubocop-rspec +---- + +or, if you are using several extensions: + +---- +plugins: + - rubocop-rspec + - rubocop-performance +---- + +Now you can run `rubocop` and it will automatically load the RuboCop RSpec +cops together with the standard cops. + +NOTE: The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. + +=== RSpec DSL configuration + +In case you https://github.com/rspec/rspec-core/blob/b0d0843a285693c64cdbe0c85726db155b46047e/lib/rspec/core/configuration.rb#L1122[define aliases for RSpec DSL], i.e. examples, example groups, hooks, or include example statements, you need to configure it so those elements are properly detected by RuboCop RSpec. + +[source,ruby] +---- +# spec/spec_helper.rb +RSpec.configure do |c| + c.alias_example_group_to :detail, :detailed => true +end + +# spec/detail_spec.rb +RSpec.detail "a detail" do + it "can do some less important stuff" do + end +end +---- + +[source,yaml] +---- +# .rubocop.yml +RSpec: + Language: + ExampleGroups: + Regular: + - detail +---- + +Some libraries extensively define RSpec DSL aliases (e.g. Pundit, Action Policy) or augment existing elements providing the same semantics (e.g. `let_it_be` from `test-prof`). +Those libraries can provide necessary configuration, but won't necessarily do so. +If they do, their README will mention that you have to explicitly require their configuration from your `.rubocop.yml` file. + +[source,yaml] +---- +# .rubocop.yml + +require: + - rubocop-rspec + - test-prof + +# or + +RSpec: + Language: + Helpers: + - let_it_be +---- + +NOTE: the default merge mode is to inherit, so you won't remove any of the default settings. + +RuboCop RSpec's https://github.com/rubocop/rubocop-rspec/blob/a43424527c09fae2e6ddb133f4b2988f6c46bb2e/config/default.yml#L6[default configuration] is a good source of information on what can be configured. + +== Command line + +[source,bash] +---- +$ rubocop --plugin rubocop-rspec +---- + +== Rake task + +[source,ruby] +---- +RuboCop::RakeTask.new do |task| + task.plugins << 'rubocop-rspec' +end +---- + +== Code Climate + +`rubocop-rspec` is available on Code Climate as part of the rubocop engine. https://codeclimate.com/changelog/55a433bbe30ba00852000fac[Learn More]. + +== Inspecting files that don't end with `_spec.rb` + +By default, `rubocop-rspec` only inspects code within paths ending in `_spec.rb` or including `spec/`. You can override this setting in your config file by setting `Include`: + +[source,yaml] +---- +# Inspect files in `test/` directory +RSpec: + Include: + - '**/test/**/*' +---- + +[source,yaml] +---- +# Inspect only files ending with `_test.rb` +RSpec: + Include: + - '**/*_test.rb' +---- + +NOTE: Please keep in mind that merge mode for `Include` is set to override the default settings, so if you intend to add a path while keeping the default paths, you should include the default `Include` paths in your configuration. diff --git a/lib/rubocop-rspec.rb b/lib/rubocop-rspec.rb index 840b3a5a6..3de03a7ae 100644 --- a/lib/rubocop-rspec.rb +++ b/lib/rubocop-rspec.rb @@ -6,40 +6,46 @@ require 'rubocop' require_relative 'rubocop/rspec' -require_relative 'rubocop/rspec/version' -require_relative 'rubocop/rspec/inject' +require_relative 'rubocop/rspec/language' require_relative 'rubocop/rspec/node' -require_relative 'rubocop/rspec/top_level_describe' +require_relative 'rubocop/rspec/plugin' +require_relative 'rubocop/rspec/version' require_relative 'rubocop/rspec/wording' -require_relative 'rubocop/rspec/language' -require_relative 'rubocop/rspec/language/node_pattern' + +require_relative 'rubocop/cop/rspec/mixin/file_help' +require_relative 'rubocop/cop/rspec/mixin/final_end_location' +require_relative 'rubocop/cop/rspec/mixin/inside_example_group' +require_relative 'rubocop/cop/rspec/mixin/location_help' +require_relative 'rubocop/cop/rspec/mixin/metadata' +require_relative 'rubocop/cop/rspec/mixin/namespace' +require_relative 'rubocop/cop/rspec/mixin/skip_or_pending' +require_relative 'rubocop/cop/rspec/mixin/top_level_group' +require_relative 'rubocop/cop/rspec/mixin/variable' + +# Dependent on `RuboCop::Cop::RSpec::FinalEndLocation`. +require_relative 'rubocop/cop/rspec/mixin/comments_help' +require_relative 'rubocop/cop/rspec/mixin/empty_line_separation' + +require_relative 'rubocop/cop/rspec/base' +require_relative 'rubocop/rspec/align_let_brace' require_relative 'rubocop/rspec/concept' -require_relative 'rubocop/rspec/example_group' +require_relative 'rubocop/rspec/corrector/move_node' require_relative 'rubocop/rspec/example' +require_relative 'rubocop/rspec/example_group' require_relative 'rubocop/rspec/hook' -require_relative 'rubocop/cop/rspec/cop' -require_relative 'rubocop/rspec/align_let_brace' -require_relative 'rubocop/rspec/factory_bot' -require_relative 'rubocop/rspec/final_end_location' -require_relative 'rubocop/rspec/blank_line_separation' - -RuboCop::RSpec::Inject.defaults! require_relative 'rubocop/cop/rspec_cops' -# We have to register our autocorrect incompatibilies in RuboCop's cops as well -# so we do not hit infinite loops - -module RuboCop - module Cop - module Layout - class ExtraSpacing # rubocop:disable Style/Documentation - def self.autocorrect_incompatible_with - [RSpec::AlignLeftLetBrace, RSpec::AlignRightLetBrace] - end - end +# We have to register our autocorrect incompatibilities in RuboCop's cops +# as well so we do not hit infinite loops + +RuboCop::Cop::Layout::ExtraSpacing.singleton_class.prepend( + Module.new do + def autocorrect_incompatible_with + super.push(RuboCop::Cop::RSpec::AlignLeftLetBrace) + .push(RuboCop::Cop::RSpec::AlignRightLetBrace) end end -end +) RuboCop::AST::Node.include(RuboCop::RSpec::Node) diff --git a/lib/rubocop/cop/rspec/align_left_let_brace.rb b/lib/rubocop/cop/rspec/align_left_let_brace.rb index 08669f3aa..990222a68 100644 --- a/lib/rubocop/cop/rspec/align_left_let_brace.rb +++ b/lib/rubocop/cop/rspec/align_left_let_brace.rb @@ -6,46 +6,42 @@ module RSpec # Checks that left braces for adjacent single line lets are aligned. # # @example + # # bad + # let(:foobar) { blahblah } + # let(:baz) { bar } + # let(:a) { b } # - # # bad - # let(:foobar) { blahblah } - # let(:baz) { bar } - # let(:a) { b } + # # good + # let(:foobar) { blahblah } + # let(:baz) { bar } + # let(:a) { b } # - # # good - # let(:foobar) { blahblah } - # let(:baz) { bar } - # let(:a) { b } - # - class AlignLeftLetBrace < Cop + class AlignLeftLetBrace < Base + extend AutoCorrector + MSG = 'Align left let brace' def self.autocorrect_incompatible_with [Layout::ExtraSpacing] end - def investigate(_processed_source) + def on_new_investigation + super return if processed_source.blank? token_aligner.offending_tokens.each do |let| - add_offense(let, location: :begin) - end - end - - def autocorrect(let) - lambda do |corrector| - corrector.insert_before( - let.loc.begin, - token_aligner.indent_for(let) - ) + add_offense(let.loc.begin) do |corrector| + corrector.insert_before( + let.loc.begin, token_aligner.indent_for(let) + ) + end end end private def token_aligner - @token_aligner ||= - RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin) + RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin) end end end diff --git a/lib/rubocop/cop/rspec/align_right_let_brace.rb b/lib/rubocop/cop/rspec/align_right_let_brace.rb index 0526a09ac..cd33e6d8e 100644 --- a/lib/rubocop/cop/rspec/align_right_let_brace.rb +++ b/lib/rubocop/cop/rspec/align_right_let_brace.rb @@ -6,46 +6,42 @@ module RSpec # Checks that right braces for adjacent single line lets are aligned. # # @example + # # bad + # let(:foobar) { blahblah } + # let(:baz) { bar } + # let(:a) { b } # - # # bad - # let(:foobar) { blahblah } - # let(:baz) { bar } - # let(:a) { b } + # # good + # let(:foobar) { blahblah } + # let(:baz) { bar } + # let(:a) { b } # - # # good - # let(:foobar) { blahblah } - # let(:baz) { bar } - # let(:a) { b } - # - class AlignRightLetBrace < Cop + class AlignRightLetBrace < Base + extend AutoCorrector + MSG = 'Align right let brace' def self.autocorrect_incompatible_with [Layout::ExtraSpacing] end - def investigate(_processed_source) + def on_new_investigation + super return if processed_source.blank? token_aligner.offending_tokens.each do |let| - add_offense(let, location: :end) - end - end - - def autocorrect(let) - lambda do |corrector| - corrector.insert_before( - let.loc.end, - token_aligner.indent_for(let) - ) + add_offense(let.loc.end) do |corrector| + corrector.insert_before( + let.loc.end, token_aligner.indent_for(let) + ) + end end end private def token_aligner - @token_aligner ||= - RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end) + RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end) end end end diff --git a/lib/rubocop/cop/rspec/any_instance.rb b/lib/rubocop/cop/rspec/any_instance.rb index 08c92a867..6c0e5bb8e 100644 --- a/lib/rubocop/cop/rspec/any_instance.rb +++ b/lib/rubocop/cop/rspec/any_instance.rb @@ -22,20 +22,17 @@ module RSpec # allow(my_instance).to receive(:foo) # end # end - class AnyInstance < Cop + # + class AnyInstance < Base MSG = 'Avoid stubbing using `%s`.' - - def_node_matcher :disallowed_stub, <<-PATTERN - (send _ ${:any_instance :allow_any_instance_of :expect_any_instance_of} ...) - PATTERN + RESTRICT_ON_SEND = %i[ + any_instance + allow_any_instance_of + expect_any_instance_of + ].freeze def on_send(node) - disallowed_stub(node) do |method| - add_offense( - node, - message: format(MSG, method: method) - ) - end + add_offense(node, message: format(MSG, method: node.method_name)) end end end diff --git a/lib/rubocop/cop/rspec/around_block.rb b/lib/rubocop/cop/rspec/around_block.rb index 0dc006364..c10d8f183 100644 --- a/lib/rubocop/cop/rspec/around_block.rb +++ b/lib/rubocop/cop/rspec/around_block.rb @@ -25,21 +25,29 @@ module RSpec # some_method # test.run # end - class AroundBlock < Cop + # + class AroundBlock < Base MSG_NO_ARG = 'Test object should be passed to around block.' - MSG_UNUSED_ARG = 'You should call `%s.call` '\ + MSG_UNUSED_ARG = 'You should call `%s.call` ' \ 'or `%s.run`.' - def_node_matcher :hook, <<-PATTERN - (block {(send nil? :around) (send nil? :around sym)} (args $...) ...) + # @!method hook_block(node) + def_node_matcher :hook_block, <<~PATTERN + (block (send nil? :around sym ?) (args $...) ...) + PATTERN + + # @!method hook_numblock(node) + def_node_matcher :hook_numblock, <<~PATTERN + (numblock (send nil? :around sym ?) ...) PATTERN - def_node_search :find_arg_usage, <<-PATTERN + # @!method find_arg_usage(node) + def_node_search :find_arg_usage, <<~PATTERN {(send $... {:call :run}) (send _ _ $...) (yield $...) (block-pass $...)} PATTERN def on_block(node) - hook(node) do |(example_proxy)| + hook_block(node) do |(example_proxy)| if example_proxy.nil? add_no_arg_offense(node) else @@ -48,6 +56,12 @@ def on_block(node) end end + def on_numblock(node) + hook_numblock(node) do + check_for_numblock(node) + end + end + private def add_no_arg_offense(node) @@ -55,15 +69,24 @@ def add_no_arg_offense(node) end def check_for_unused_proxy(block, proxy) - name, = *proxy - find_arg_usage(block) do |usage| - return if usage.include?(s(:lvar, name)) + return if usage.include?(s(:lvar, proxy.name)) end add_offense( proxy, - message: format(MSG_UNUSED_ARG, arg: name) + message: format(MSG_UNUSED_ARG, arg: proxy.name) + ) + end + + def check_for_numblock(block) + find_arg_usage(block) do |usage| + return if usage.include?(s(:lvar, :_1)) + end + + add_offense( + block.children.last, + message: format(MSG_UNUSED_ARG, arg: :_1) ) end end diff --git a/lib/rubocop/cop/rspec/base.rb b/lib/rubocop/cop/rspec/base.rb new file mode 100644 index 000000000..0152f108f --- /dev/null +++ b/lib/rubocop/cop/rspec/base.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # @abstract parent class to RSpec cops + class Base < ::RuboCop::Cop::Base + include RuboCop::RSpec::Language + + exclude_from_registry + + # Invoke the original inherited hook so our cops are recognized + def self.inherited(subclass) # rubocop:disable Lint/MissingSuper + RuboCop::Cop::Base.inherited(subclass) + end + + # Set the config for dynamic DSL configuration-aware helpers + # that have no other means of accessing the configuration. + def on_new_investigation + super + RuboCop::RSpec::Language.config = config['RSpec']['Language'] + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/be.rb b/lib/rubocop/cop/rspec/be.rb index 503648e12..6536389dc 100644 --- a/lib/rubocop/cop/rspec/be.rb +++ b/lib/rubocop/cop/rspec/be.rb @@ -10,7 +10,6 @@ module RSpec # cases it's better to specify what exactly is the expected value. # # @example - # # # bad # expect(foo).to be # @@ -19,16 +18,19 @@ module RSpec # expect(foo).to be 1.0 # expect(foo).to be(true) # - class Be < Cop - MSG = 'Don\'t use `be` without an argument.' + class Be < Base + MSG = "Don't use `be` without an argument." + + RESTRICT_ON_SEND = Runners.all - def_node_matcher :be_without_args, <<-PATTERN - (send _ #{Runners::ALL.node_pattern_union} $(send nil? :be)) + # @!method be_without_args(node) + def_node_matcher :be_without_args, <<~PATTERN + (send _ #Runners.all $(send nil? :be)) PATTERN def on_send(node) be_without_args(node) do |matcher| - add_offense(matcher, location: :selector) + add_offense(matcher.loc.selector) end end end diff --git a/lib/rubocop/cop/rspec/be_empty.rb b/lib/rubocop/cop/rspec/be_empty.rb new file mode 100644 index 000000000..fbd79fdb0 --- /dev/null +++ b/lib/rubocop/cop/rspec/be_empty.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Prefer using `be_empty` when checking for an empty array. + # + # @example + # # bad + # expect(array).to contain_exactly + # expect(array).to match_array([]) + # + # # good + # expect(array).to be_empty + # + class BeEmpty < Base + extend AutoCorrector + + MSG = 'Use `be_empty` matchers for checking an empty array.' + RESTRICT_ON_SEND = %i[contain_exactly match_array].freeze + + # @!method expect_array_matcher?(node) + def_node_matcher :expect_array_matcher?, <<~PATTERN + (send + (send nil? :expect _) + #Runners.all + ${ + (send nil? :match_array (array)) + (send nil? :contain_exactly) + } + _? + ) + PATTERN + + def on_send(node) + expect_array_matcher?(node.parent) do |expect| + add_offense(expect) do |corrector| + corrector.replace(expect, 'be_empty') + end + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/be_eq.rb b/lib/rubocop/cop/rspec/be_eq.rb new file mode 100644 index 000000000..dfdcd7c5a --- /dev/null +++ b/lib/rubocop/cop/rspec/be_eq.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Check for expectations where `be(...)` can replace `eq(...)`. + # + # The `be` matcher compares by identity while the `eq` matcher compares + # using `==`. Booleans and nil can be compared by identity and therefore + # the `be` matcher is preferable as it is a more strict test. + # + # @safety + # This cop is unsafe because it changes how values are compared. + # + # @example + # # bad + # expect(foo).to eq(true) + # expect(foo).to eq(false) + # expect(foo).to eq(nil) + # + # # good + # expect(foo).to be(true) + # expect(foo).to be(false) + # expect(foo).to be(nil) + # + class BeEq < Base + extend AutoCorrector + + MSG = 'Prefer `be` over `eq`.' + RESTRICT_ON_SEND = %i[eq].freeze + + # @!method eq_type_with_identity?(node) + def_node_matcher :eq_type_with_identity?, <<~PATTERN + (send nil? :eq {boolean nil}) + PATTERN + + def on_send(node) + return unless eq_type_with_identity?(node) + + add_offense(node.loc.selector) do |corrector| + corrector.replace(node.loc.selector, 'be') + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/be_eql.rb b/lib/rubocop/cop/rspec/be_eql.rb index 0721c8c53..f49a69231 100644 --- a/lib/rubocop/cop/rspec/be_eql.rb +++ b/lib/rubocop/cop/rspec/be_eql.rb @@ -10,8 +10,10 @@ module RSpec # can be compared by identity and therefore the `be` matcher is # preferable as it is a more strict test. # - # @example + # @safety + # This cop is unsafe because it changes how values are compared. # + # @example # # bad # expect(foo).to eql(1) # expect(foo).to eql(1.0) @@ -35,22 +37,24 @@ module RSpec # necessarily the same type as `b` since the `#==` operator can # coerce objects for comparison. # - class BeEql < Cop + class BeEql < Base + extend AutoCorrector + MSG = 'Prefer `be` over `eql`.' + RESTRICT_ON_SEND = %i[to].freeze - def_node_matcher :eql_type_with_identity, <<-PATTERN - (send _ :to $(send nil? :eql {true false int float sym nil_type?})) + # @!method eql_type_with_identity(node) + def_node_matcher :eql_type_with_identity, <<~PATTERN + (send _ :to $(send nil? :eql {boolean int float sym nil})) PATTERN def on_send(node) eql_type_with_identity(node) do |eql| - add_offense(eql, location: :selector) + add_offense(eql.loc.selector) do |corrector| + corrector.replace(eql.loc.selector, 'be') + end end end - - def autocorrect(node) - ->(corrector) { corrector.replace(node.loc.selector, 'be') } - end end end end diff --git a/lib/rubocop/cop/rspec/be_nil.rb b/lib/rubocop/cop/rspec/be_nil.rb new file mode 100644 index 000000000..1a755ba64 --- /dev/null +++ b/lib/rubocop/cop/rspec/be_nil.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Ensures a consistent style is used when matching `nil`. + # + # You can either use the more specific `be_nil` matcher, or the more + # generic `be` matcher with a `nil` argument. + # + # This cop can be configured using the `EnforcedStyle` option + # + # @example `EnforcedStyle: be_nil` (default) + # # bad + # expect(foo).to be(nil) + # + # # good + # expect(foo).to be_nil + # + # @example `EnforcedStyle: be` + # # bad + # expect(foo).to be_nil + # + # # good + # expect(foo).to be(nil) + # + class BeNil < Base + extend AutoCorrector + include ConfigurableEnforcedStyle + + BE_MSG = 'Prefer `be(nil)` over `be_nil`.' + BE_NIL_MSG = 'Prefer `be_nil` over `be(nil)`.' + RESTRICT_ON_SEND = %i[be be_nil].freeze + + # @!method be_nil_matcher?(node) + def_node_matcher :be_nil_matcher?, <<~PATTERN + (send nil? :be_nil) + PATTERN + + # @!method nil_value_expectation?(node) + def_node_matcher :nil_value_expectation?, <<~PATTERN + (send nil? :be nil) + PATTERN + + def on_send(node) + case style + when :be + check_be_style(node) + when :be_nil + check_be_nil_style(node) + else + # :nocov: + :noop + # :nocov: + end + end + + private + + def check_be_style(node) + return unless be_nil_matcher?(node) + + add_offense(node, message: BE_MSG) do |corrector| + corrector.replace(node, 'be(nil)') + end + end + + def check_be_nil_style(node) + return unless nil_value_expectation?(node) + + add_offense(node, message: BE_NIL_MSG) do |corrector| + corrector.replace(node, 'be_nil') + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/before_after_all.rb b/lib/rubocop/cop/rspec/before_after_all.rb index e4809ff93..53f7b9ae6 100644 --- a/lib/rubocop/cop/rspec/before_after_all.rb +++ b/lib/rubocop/cop/rspec/before_after_all.rb @@ -3,34 +3,32 @@ module RuboCop module Cop module RSpec - # Check that before/after(:all) isn't being used. + # Check that before/after(:all/:context) isn't being used. # # @example - # # bad - # # - # # Faster but risk of state leaking between examples - # # + # # bad - Faster but risk of state leaking between examples # describe MyClass do # before(:all) { Widget.create } - # after(:all) { Widget.delete_all } + # after(:context) { Widget.delete_all } # end # - # # good - # # - # # Slower but examples are properly isolated - # # + # # good - Slower but examples are properly isolated # describe MyClass do # before(:each) { Widget.create } # after(:each) { Widget.delete_all } # end - class BeforeAfterAll < Cop - MSG = 'Beware of using `%s` as it may cause state to leak '\ - 'between tests. If you are using `rspec-rails`, and '\ - '`use_transactional_fixtures` is enabled, then records created '\ + # + class BeforeAfterAll < Base + MSG = 'Beware of using `%s` as it may cause state to leak ' \ + 'between tests. If you are using `rspec-rails`, and ' \ + '`use_transactional_fixtures` is enabled, then records created ' \ 'in `%s` are not automatically rolled back.' - def_node_matcher :before_or_after_all, <<-PATTERN - $(send _ {:before :after} (sym {:all :context})) + RESTRICT_ON_SEND = Set[:before, :after].freeze + + # @!method before_or_after_all(node) + def_node_matcher :before_or_after_all, <<~PATTERN + $(send _ RESTRICT_ON_SEND (sym {:all :context})) PATTERN def on_send(node) diff --git a/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb b/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb deleted file mode 100644 index c69303161..000000000 --- a/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module RSpec - module Capybara - # Checks that no expectations are set on Capybara's `current_path`. - # - # The `have_current_path` matcher (https://www.rubydoc.info/github/ - # teamcapybara/capybara/master/Capybara/RSpecMatchers#have_current_path- - # instance_method) should be used on `page` to set expectations on - # Capybara's current path, since it uses Capybara's waiting - # functionality (https://github.com/teamcapybara/capybara/blob/master/ - # README.md#asynchronous-javascript-ajax-and-friends) which ensures that - # preceding actions (like `click_link`) have completed. - # - # @example - # # bad - # expect(current_path).to eq('/callback') - # expect(page.current_path).to match(/widgets/) - # - # # good - # expect(page).to have_current_path("/callback") - # expect(page).to have_current_path(/widgets/) - # - class CurrentPathExpectation < Cop - MSG = 'Do not set an RSpec expectation on `current_path` in ' \ - 'Capybara feature specs - instead, use the ' \ - '`have_current_path` matcher on `page`' - - def_node_matcher :expectation_set_on_current_path, <<-PATTERN - (send nil? :expect (send {(send nil? :page) nil?} :current_path)) - PATTERN - - # Supported matchers: eq(...) / match(/regexp/) / match('regexp') - def_node_matcher :as_is_matcher, <<-PATTERN - (send - #expectation_set_on_current_path $#{Runners::ALL.node_pattern_union} - ${(send nil? :eq ...) (send nil? :match (regexp ...))}) - PATTERN - - def_node_matcher :regexp_str_matcher, <<-PATTERN - (send - #expectation_set_on_current_path $#{Runners::ALL.node_pattern_union} - $(send nil? :match (str $_))) - PATTERN - - def on_send(node) - expectation_set_on_current_path(node) do - add_offense(node, location: :selector) - end - end - - def autocorrect(node) - lambda do |corrector| - return unless node.chained? - - as_is_matcher(node.parent) do |to_sym, matcher_node| - rewrite_expectation(corrector, node, to_sym, matcher_node) - end - - regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp| - rewrite_expectation(corrector, node, to_sym, matcher_node) - convert_regexp_str_to_literal(corrector, matcher_node, regexp) - end - end - end - - private - - def rewrite_expectation(corrector, node, to_symbol, matcher_node) - current_path_node = node.first_argument - corrector.replace(current_path_node.loc.expression, 'page') - corrector.replace(node.parent.loc.selector, 'to') - matcher_method = if to_symbol == :to - 'have_current_path' - else - 'have_no_current_path' - end - corrector.replace(matcher_node.loc.selector, matcher_method) - add_ignore_query_options(corrector, node) - end - - def convert_regexp_str_to_literal(corrector, matcher_node, regexp_str) - str_node = matcher_node.first_argument - regexp_expr = Regexp.new(regexp_str).inspect - corrector.replace(str_node.loc.expression, regexp_expr) - end - - # `have_current_path` with no options will include the querystring - # while `page.current_path` does not. - # This ensures the option `ignore_query: true` is added - # except when the expectation is a regexp or string - def add_ignore_query_options(corrector, node) - expectation_node = node.parent.last_argument - expectation_last_child = expectation_node.children.last - return if %i[regexp str].include?(expectation_last_child.type) - - corrector.insert_after( - expectation_last_child.loc.expression, - ', ignore_query: true' - ) - end - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/capybara/feature_methods.rb b/lib/rubocop/cop/rspec/capybara/feature_methods.rb deleted file mode 100644 index f6681fe28..000000000 --- a/lib/rubocop/cop/rspec/capybara/feature_methods.rb +++ /dev/null @@ -1,118 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module RSpec - module Capybara - # Checks for consistent method usage in feature specs. - # - # By default, the cop disables all Capybara-specific methods that have - # the same native RSpec method (e.g. are just aliases). Some teams - # however may prefer using some of the Capybara methods (like `feature`) - # to make it obvious that the test uses Capybara, while still disable - # the rest of the methods, like `given` (alias for `let`), `background` - # (alias for `before`), etc. You can configure which of the methods to - # be enabled by using the EnabledMethods configuration option. - # - # @example - # # bad - # feature 'User logs in' do - # given(:user) { User.new } - # - # background do - # visit new_session_path - # end - # - # scenario 'with OAuth' do - # # ... - # end - # end - # - # # good - # describe 'User logs in' do - # let(:user) { User.new } - # - # before do - # visit new_session_path - # end - # - # it 'with OAuth' do - # # ... - # end - # end - class FeatureMethods < Cop - MSG = 'Use `%s` instead of `%s`.' - - # https://git.io/v7Kwr - MAP = { - background: :before, - scenario: :it, - xscenario: :xit, - given: :let, - given!: :let!, - feature: :describe - }.freeze - - def_node_matcher :spec?, <<-PATTERN - (block - (send #{RSPEC} {:describe :feature} ...) - ...) - PATTERN - - def_node_matcher :feature_method, <<-PATTERN - (block - $(send #{RSPEC} ${#{MAP.keys.map(&:inspect).join(' ')}} ...) - ...) - PATTERN - - def on_block(node) - return unless inside_spec?(node) - - feature_method(node) do |send_node, match| - next if enabled?(match) - - add_offense( - send_node, - location: :selector, - message: format(MSG, method: match, replacement: MAP[match]) - ) - end - end - - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.loc.selector, MAP[node.method_name].to_s) - end - end - - private - - def inside_spec?(node) - return spec?(node) if root_node?(node) - - root = node.ancestors.find { |parent| root_node?(parent) } - spec?(root) - end - - def root_node?(node) - node.parent.nil? || root_with_siblings?(node.parent) - end - - def root_with_siblings?(node) - node.begin_type? && node.parent.nil? - end - - def enabled?(method_name) - enabled_methods.include?(method_name) - end - - def enabled_methods - cop_config - .fetch('EnabledMethods', []) - .map(&:to_sym) - end - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/change_by_zero.rb b/lib/rubocop/cop/rspec/change_by_zero.rb new file mode 100644 index 000000000..64c5df4b2 --- /dev/null +++ b/lib/rubocop/cop/rspec/change_by_zero.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Prefer negated matchers over `to change.by(0)`. + # + # In the case of composite expectations, cop suggest using the + # negation matchers of `RSpec::Matchers#change`. + # + # By default the cop does not support autocorrect of + # compound expectations, but if you set the + # negated matcher for `change`, e.g. `not_change` with + # the `NegatedMatcher` option, the cop will perform the autocorrection. + # + # @example NegatedMatcher: ~ (default) + # # bad + # expect { run }.to change(Foo, :bar).by(0) + # expect { run }.to change { Foo.bar }.by(0) + # + # # bad - compound expectations (does not support autocorrection) + # expect { run } + # .to change(Foo, :bar).by(0) + # .and change(Foo, :baz).by(0) + # expect { run } + # .to change { Foo.bar }.by(0) + # .and change { Foo.baz }.by(0) + # + # # good + # expect { run }.not_to change(Foo, :bar) + # expect { run }.not_to change { Foo.bar } + # + # # good - compound expectations + # define_negated_matcher :not_change, :change + # expect { run } + # .to not_change(Foo, :bar) + # .and not_change(Foo, :baz) + # expect { run } + # .to not_change { Foo.bar } + # .and not_change { Foo.baz } + # + # @example NegatedMatcher: not_change + # # bad (support autocorrection to good case) + # expect { run } + # .to change(Foo, :bar).by(0) + # .and change(Foo, :baz).by(0) + # expect { run } + # .to change { Foo.bar }.by(0) + # .and change { Foo.baz }.by(0) + # + # # good + # define_negated_matcher :not_change, :change + # expect { run } + # .to not_change(Foo, :bar) + # .and not_change(Foo, :baz) + # expect { run } + # .to not_change { Foo.bar } + # .and not_change { Foo.baz } + # + class ChangeByZero < Base + extend AutoCorrector + include RangeHelp + + MSG = 'Prefer `not_to change` over `to %s.by(0)`.' + MSG_COMPOUND = 'Prefer %s with compound expectations ' \ + 'over `%s.by(0)`.' + CHANGE_METHODS = Set[:change, :a_block_changing, :changing].freeze + RESTRICT_ON_SEND = CHANGE_METHODS.freeze + + # @!method expect_change_with_arguments(node) + def_node_matcher :expect_change_with_arguments, <<~PATTERN + (send + $(send nil? CHANGE_METHODS ...) :by + (int 0)) + PATTERN + + # @!method expect_change_with_block(node) + def_node_matcher :expect_change_with_block, <<~PATTERN + (send + (block + $(send nil? CHANGE_METHODS) + (args) + (send (...) _)) :by + (int 0)) + PATTERN + + # @!method change_nodes(node) + def_node_search :change_nodes, <<~PATTERN + $(send nil? CHANGE_METHODS ...) + PATTERN + + def on_send(node) + expect_change_with_arguments(node.parent) do |change| + register_offense(node.parent, change) + end + + expect_change_with_block(node.parent.parent) do |change| + register_offense(node.parent.parent, change) + end + end + + private + + def register_offense(node, change_node) + return unless node.parent.send_type? + + if compound_expectations?(node) + add_offense(node, + message: message_compound(change_node)) do |corrector| + autocorrect_compound(corrector, node) + end + else + add_offense(node, + message: message(change_node)) do |corrector| + autocorrect(corrector, node, change_node) + end + end + end + + def compound_expectations?(node) + %i[and or & |].include?(node.parent.method_name) + end + + def message(change_node) + format(MSG, method: change_node.method_name) + end + + def message_compound(change_node) + format(MSG_COMPOUND, preferred: preferred_method, + method: change_node.method_name) + end + + def autocorrect(corrector, node, change_node) + corrector.replace(node.parent.loc.selector, 'not_to') + corrector.replace(change_node.loc.selector, 'change') + range = node.loc.dot.with(end_pos: node.source_range.end_pos) + corrector.remove(range) + end + + def autocorrect_compound(corrector, node) + return unless negated_matcher + + change_nodes(node) do |change_node| + corrector.replace(change_node.loc.selector, negated_matcher) + insert_operator(corrector, node, change_node) + remove_by_zero(corrector, node, change_node) + end + end + + def insert_operator(corrector, node, change_node) + operator = node.right_siblings.first + return unless %i[& |].include?(operator) + + corrector.insert_after( + replace_node(node, change_node), " #{operator}" + ) + end + + def replace_node(node, change_node) + expect_change_with_arguments(node) ? change_node : change_node.parent + end + + def remove_by_zero(corrector, node, change_node) + range = node.loc.dot.with(end_pos: node.source_range.end_pos) + if change_node.loc.line == range.line + corrector.remove(range) + else + corrector.remove( + range_by_whole_lines(range, include_final_newline: true) + ) + end + end + + def negated_matcher + cop_config['NegatedMatcher'] + end + + def preferred_method + negated_matcher ? "`#{negated_matcher}`" : 'negated matchers' + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/class_check.rb b/lib/rubocop/cop/rspec/class_check.rb new file mode 100644 index 000000000..5e426d5d2 --- /dev/null +++ b/lib/rubocop/cop/rspec/class_check.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Enforces consistent use of `be_a` or `be_kind_of`. + # + # @example EnforcedStyle: be_a (default) + # # bad + # expect(object).to be_kind_of(String) + # expect(object).to be_a_kind_of(String) + # + # # good + # expect(object).to be_a(String) + # expect(object).to be_an(String) + # + # @example EnforcedStyle: be_kind_of + # # bad + # expect(object).to be_a(String) + # expect(object).to be_an(String) + # + # # good + # expect(object).to be_kind_of(String) + # expect(object).to be_a_kind_of(String) + # + class ClassCheck < Base + extend AutoCorrector + include ConfigurableEnforcedStyle + + MSG = 'Prefer `%s` over `%s`.' + + METHOD_NAMES_FOR_BE_A = ::Set[ + :be_a, + :be_an + ].freeze + + METHOD_NAMES_FOR_KIND_OF = ::Set[ + :be_a_kind_of, + :be_kind_of + ].freeze + + PREFERRED_METHOD_NAME_BY_STYLE = { + be_a: :be_a, + be_kind_of: :be_kind_of + }.freeze + + RESTRICT_ON_SEND = %i[ + be_a + be_a_kind_of + be_an + be_kind_of + ].freeze + + def on_send(node) + return unless offending?(node) + + add_offense( + node.loc.selector, + message: format_message(node) + ) do |corrector| + autocorrect(corrector, node) + end + end + + private + + def autocorrect(corrector, node) + corrector.replace(node.loc.selector, preferred_method_name) + end + + def format_message(node) + format( + MSG, + current: node.method_name, + preferred: preferred_method_name + ) + end + + def offending?(node) + !node.receiver && !preferred_method_name?(node.method_name) + end + + def preferred_method_name?(method_name) + preferred_method_names.include?(method_name) + end + + def preferred_method_name + PREFERRED_METHOD_NAME_BY_STYLE[style] + end + + def preferred_method_names + if style == :be_a + METHOD_NAMES_FOR_BE_A + else + METHOD_NAMES_FOR_KIND_OF + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/contain_exactly.rb b/lib/rubocop/cop/rspec/contain_exactly.rb new file mode 100644 index 000000000..bdb44ed8d --- /dev/null +++ b/lib/rubocop/cop/rspec/contain_exactly.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Checks where `contain_exactly` is used. + # + # This cop checks for the following: + # + # - Prefer `match_array` when matching array values. + # - Prefer `be_empty` when using `contain_exactly` with no arguments. + # + # @example + # # bad + # it { is_expected.to contain_exactly(*array1, *array2) } + # + # # good + # it { is_expected.to match_array(array1 + array2) } + # + # # good + # it { is_expected.to contain_exactly(content, *array) } + # + class ContainExactly < Base + extend AutoCorrector + + MSG = 'Prefer `match_array` when matching array values.' + RESTRICT_ON_SEND = %i[contain_exactly].freeze + + def on_send(node) + return if node.arguments.empty? + + check_populated_collection(node) + end + + private + + def check_populated_collection(node) + return unless node.each_child_node.all?(&:splat_type?) + + add_offense(node) do |corrector| + autocorrect_for_populated_array(node, corrector) + end + end + + def autocorrect_for_populated_array(node, corrector) + arrays = node.arguments.map do |splat_node| + splat_node.children.first + end + corrector.replace( + node, + "match_array(#{arrays.map(&:source).join(' + ')})" + ) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/context_method.rb b/lib/rubocop/cop/rspec/context_method.rb index 23061ad3f..1e28e8b18 100644 --- a/lib/rubocop/cop/rspec/context_method.rb +++ b/lib/rubocop/cop/rspec/context_method.rb @@ -23,22 +23,26 @@ module RSpec # describe '.foo_bar' do # # ... # end - class ContextMethod < Cop + # + class ContextMethod < Base + extend AutoCorrector + MSG = 'Use `describe` for testing methods.' - def_node_matcher :context_method, <<-PATTERN - (block (send #{RSPEC} :context $(str #method_name?) ...) ...) + # @!method context_method(node) + def_node_matcher :context_method, <<~PATTERN + (block + (send #rspec? :context + ${(str #method_name?) (dstr (str #method_name?) ...)} + ...) + ...) PATTERN - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler context_method(node) do |context| - add_offense(context) - end - end - - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.parent.loc.selector, 'describe') + add_offense(context) do |corrector| + corrector.replace(node.send_node.loc.selector, 'describe') + end end end diff --git a/lib/rubocop/cop/rspec/context_wording.rb b/lib/rubocop/cop/rspec/context_wording.rb index 285cf263a..96c40a76e 100644 --- a/lib/rubocop/cop/rspec/context_wording.rb +++ b/lib/rubocop/cop/rspec/context_wording.rb @@ -8,12 +8,14 @@ module RSpec # The default list of prefixes is minimal. Users are encouraged to tailor # the configuration to meet project needs. Other acceptable prefixes may # include `if`, `unless`, `for`, `before`, `after`, or `during`. + # They may consist of multiple words if desired. # - # @see https://rspec.rubystyle.guide/#context-descriptions # @see http://www.betterspecs.org/#contexts # - # @example `Prefixes` configuration + # If both `Prefixes` and `AllowedPatterns` are empty, this cop will always + # report an offense. So you need to set at least one of them. # + # @example `Prefixes` configuration # # .rubocop.yml # # RSpec/ContextWording: # # Prefixes: @@ -34,36 +36,91 @@ module RSpec # context 'when the display name is not present' do # # ... # end - class ContextWording < Cop - MSG = 'Start context description with %s.' + # + # This cop can be customized allowed context description pattern + # with `AllowedPatterns`. By default, there are no checking by pattern. + # + # @example `AllowedPatterns` configuration + # + # # .rubocop.yml + # # RSpec/ContextWording: + # # AllowedPatterns: + # # - とき$ + # + # @example + # # bad + # context '条件を満たす' do + # # ... + # end + # + # # good + # context '条件を満たすとき' do + # # ... + # end + # + class ContextWording < Base + include AllowedPattern + + MSG_MATCH = 'Context description should match %s.' + MSG_ALWAYS = 'Current settings will always report an offense. Please ' \ + 'add allowed words to `Prefixes` or `AllowedPatterns`.' - def_node_matcher :context_wording, <<-PATTERN - (block (send #{RSPEC} { :context :shared_context } $(str #bad_prefix?) ...) ...) + # @!method context_wording(node) + def_node_matcher :context_wording, <<~PATTERN + (block (send #rspec? { :context :shared_context } $({str dstr xstr} ...) ...) ...) PATTERN - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler context_wording(node) do |context| - add_offense(context, - message: format(MSG, prefixes: joined_prefixes)) + unless matches_allowed_pattern?(description(context)) + add_offense(context, message: message) + end end end private - def bad_prefix?(description) - !prefixes.include?(description.split.first) + def allowed_patterns + super + prefix_regexes + end + + def prefix_regexes + @prefix_regexes ||= prefixes.map { |pre| /^#{Regexp.escape(pre)}\b/ } + end + + def description(context) + if context.xstr_type? + context.value.value + else + context.value + end end - def joined_prefixes - quoted = prefixes.map { |prefix| "'#{prefix}'" } - return quoted.first if quoted.size == 1 + def message + if allowed_patterns.empty? + MSG_ALWAYS + else + format(MSG_MATCH, patterns: expect_patterns) + end + end - quoted << "or #{quoted.pop}" - quoted.join(', ') + def expect_patterns + inspected = allowed_patterns.map do |pattern| + pattern.inspect.gsub(/\A"|"\z/, '/') + end + return inspected.first if inspected.size == 1 + + inspected << "or #{inspected.pop}" + inspected.join(', ') end def prefixes - cop_config['Prefixes'] || [] + Array(cop_config.fetch('Prefixes', [])).tap do |prefixes| + non_strings = prefixes.reject { |pre| pre.is_a?(String) } + unless non_strings.empty? + raise "Non-string prefixes #{non_strings.inspect} detected." + end + end end end end diff --git a/lib/rubocop/cop/rspec/cop.rb b/lib/rubocop/cop/rspec/cop.rb deleted file mode 100644 index 636333212..000000000 --- a/lib/rubocop/cop/rspec/cop.rb +++ /dev/null @@ -1,94 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop # rubocop:disable Style/Documentation - WorkaroundCop = Cop.dup - - # Clone of the the normal RuboCop::Cop::Cop class so we can rewrite - # the inherited method without breaking functionality - class WorkaroundCop - # Remove the Cop.inherited method to be a noop. Our RSpec::Cop - # class will invoke the inherited hook instead - class << self - undef inherited - def inherited(*) end - end - - # Special case `Module#<` so that the rspec support rubocop exports - # is compatible with our subclass - def self.<(other) - other.equal?(RuboCop::Cop::Cop) || super - end - end - private_constant(:WorkaroundCop) - - module RSpec - # @abstract parent class to rspec cops - # - # The criteria for whether rubocop-rspec analyzes a certain ruby file - # is configured via `AllCops/RSpec`. For example, if you want to - # customize your project to scan all files within a `test/` directory - # then you could add this to your configuration: - # - # @example configuring analyzed paths - # - # AllCops: - # RSpec: - # Patterns: - # - '_test.rb$' - # - '(?:^|/)test/' - class Cop < WorkaroundCop - include RuboCop::RSpec::Language - include RuboCop::RSpec::Language::NodePattern - - DEFAULT_CONFIGURATION = - RuboCop::RSpec::CONFIG.fetch('AllCops').fetch('RSpec') - - DEFAULT_PATTERN_RE = Regexp.union( - DEFAULT_CONFIGURATION.fetch('Patterns') - .map(&Regexp.public_method(:new)) - ) - - # Invoke the original inherited hook so our cops are recognized - def self.inherited(subclass) - RuboCop::Cop::Cop.inherited(subclass) - end - - def relevant_file?(file) - relevant_rubocop_rspec_file?(file) && super - end - - private - - def relevant_rubocop_rspec_file?(file) - rspec_pattern =~ file - end - - def rspec_pattern - if rspec_pattern_config? - Regexp.union(rspec_pattern_config.map(&Regexp.public_method(:new))) - else - DEFAULT_PATTERN_RE - end - end - - def all_cops_config - config - .for_all_cops - end - - def rspec_pattern_config? - return unless all_cops_config.key?('RSpec') - - all_cops_config.fetch('RSpec').key?('Patterns') - end - - def rspec_pattern_config - all_cops_config - .fetch('RSpec', DEFAULT_CONFIGURATION) - .fetch('Patterns') - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/describe_class.rb b/lib/rubocop/cop/rspec/describe_class.rb index 101fabecb..ab358678a 100644 --- a/lib/rubocop/cop/rspec/describe_class.rb +++ b/lib/rubocop/cop/rspec/describe_class.rb @@ -3,7 +3,19 @@ module RuboCop module Cop module RSpec - # Check that the first argument to the top level describe is a constant. + # Check that the first argument to the top-level describe is a constant. + # + # It can be configured to ignore strings when certain metadata is passed. + # + # Ignores Rails and Aruba `type` metadata by default. + # + # @example `IgnoredMetadata` configuration + # # .rubocop.yml + # # RSpec/DescribeClass: + # # IgnoredMetadata: + # # type: + # # - request + # # - controller # # @example # # bad @@ -12,44 +24,60 @@ module RSpec # # # good # describe TestedClass do + # subject { described_class } + # end + # + # describe 'TestedClass::VERSION' do + # subject { Object.const_get(self.class.description) } # end # # describe "A feature example", type: :feature do # end - class DescribeClass < Cop - include RuboCop::RSpec::TopLevelDescribe + # + class DescribeClass < Base + include TopLevelGroup - MSG = 'The first argument to describe should be '\ + MSG = 'The first argument to describe should be ' \ 'the class or module being tested.' - def_node_matcher :valid_describe?, <<-PATTERN - { - (send #{RSPEC} :describe const ...) - (send #{RSPEC} :describe) - } + # @!method example_group_with_ignored_metadata?(node) + def_node_matcher :example_group_with_ignored_metadata?, <<~PATTERN + (send #rspec? :describe ... (hash <#ignored_metadata? ...>)) PATTERN - def_node_matcher :describe_with_rails_metadata?, <<-PATTERN - (send #{RSPEC} :describe !const ... - (hash <#rails_metadata? ...>) - ) + # @!method not_a_const_described(node) + def_node_matcher :not_a_const_described, <<~PATTERN + (send #rspec? :describe $[!const !#string_constant?] ...) PATTERN - def_node_matcher :rails_metadata?, <<-PATTERN - (pair - (sym :type) - (sym {:request :feature :system :routing :view}) - ) + # @!method sym_pair(node) + def_node_matcher :sym_pair, <<~PATTERN + (pair $sym $sym) PATTERN - def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern + def on_top_level_group(node) + return if example_group_with_ignored_metadata?(node.send_node) - def on_top_level_describe(node, args) - return if shared_group?(root_node) - return if valid_describe?(node) - return if describe_with_rails_metadata?(node) + not_a_const_described(node.send_node) do |described| + add_offense(described) + end + end + + private + + def ignored_metadata?(node) + sym_pair(node) do |key, value| + ignored_metadata[key.value.to_s].to_a.include?(value.value.to_s) + end + end + + def string_constant?(described) + described.str_type? && + described.value.match?(/^(?:(?:::)?[A-Z]\w*)+$/) + end - add_offense(args.first) + def ignored_metadata + cop_config['IgnoredMetadata'] || {} end end end diff --git a/lib/rubocop/cop/rspec/describe_method.rb b/lib/rubocop/cop/rspec/describe_method.rb index 7a32739e9..eecc763af 100644 --- a/lib/rubocop/cop/rspec/describe_method.rb +++ b/lib/rubocop/cop/rspec/describe_method.rb @@ -16,17 +16,35 @@ module RSpec # # describe MyClass, '.my_class_method' do # end - class DescribeMethod < Cop - include RuboCop::RSpec::TopLevelDescribe + # + class DescribeMethod < Base + include TopLevelGroup - MSG = 'The second argument to describe should be the method '\ + MSG = 'The second argument to describe should be the method ' \ "being tested. '#instance' or '.class'." - def on_top_level_describe(_node, (_, second_arg)) - return unless second_arg&.str_type? - return if second_arg.str_content.start_with?('#', '.') + # @!method second_string_literal_argument(node) + def_node_matcher :second_string_literal_argument, <<~PATTERN + (block + (send #rspec? :describe _first_argument ${str dstr} ...) + ...) + PATTERN + + # @!method method_name?(node) + def_node_matcher :method_name?, <<~PATTERN + {(str #method_name_prefix?) (dstr (str #method_name_prefix?) ...)} + PATTERN + + def on_top_level_group(node) + second_string_literal_argument(node) do |argument| + add_offense(argument) unless method_name?(argument) + end + end + + private - add_offense(second_arg) + def method_name_prefix?(description) + description.start_with?('.', '#') end end end diff --git a/lib/rubocop/cop/rspec/describe_symbol.rb b/lib/rubocop/cop/rspec/describe_symbol.rb index 4bffa63fe..44a0b7f15 100644 --- a/lib/rubocop/cop/rspec/describe_symbol.rb +++ b/lib/rubocop/cop/rspec/describe_symbol.rb @@ -17,11 +17,13 @@ module RSpec # end # # @see https://github.com/rspec/rspec-core/issues/1610 - class DescribeSymbol < Cop + class DescribeSymbol < Base MSG = 'Avoid describing symbols.' + RESTRICT_ON_SEND = %i[describe].freeze - def_node_matcher :describe_symbol?, <<-PATTERN - (send #{RSPEC} :describe $sym ...) + # @!method describe_symbol?(node) + def_node_matcher :describe_symbol?, <<~PATTERN + (send #rspec? :describe $sym ...) PATTERN def on_send(node) diff --git a/lib/rubocop/cop/rspec/described_class.rb b/lib/rubocop/cop/rspec/described_class.rb index cbe453858..598c2f975 100644 --- a/lib/rubocop/cop/rspec/described_class.rb +++ b/lib/rubocop/cop/rspec/described_class.rb @@ -8,10 +8,12 @@ module RSpec # If the first argument of describe is a class, the class is exposed to # each example via described_class. # - # This cop can be configured using the `EnforcedStyle` and `SkipBlocks` - # options. + # This cop can be configured using the `EnforcedStyle`, `SkipBlocks` + # and `OnlyStaticConstants` options. + # `OnlyStaticConstants` is only relevant when `EnforcedStyle` is + # `described_class`. # - # @example `EnforcedStyle: described_class` + # @example `EnforcedStyle: described_class` (default) # # bad # describe MyClass do # subject { MyClass.do_something } @@ -22,6 +24,18 @@ module RSpec # subject { described_class.do_something } # end # + # @example `OnlyStaticConstants: true` (default) + # # good + # describe MyClass do + # subject { MyClass::CONSTANT } + # end + # + # @example `OnlyStaticConstants: false` + # # bad + # describe MyClass do + # subject { MyClass::CONSTANT } + # end + # # @example `EnforcedStyle: explicit` # # bad # describe MyClass do @@ -54,30 +68,36 @@ module RSpec # end # end # - class DescribedClass < Cop + class DescribedClass < Base # rubocop:disable Metrics/ClassLength + extend AutoCorrector include ConfigurableEnforcedStyle + include Namespace DESCRIBED_CLASS = 'described_class' MSG = 'Use `%s` instead of `%s`.' - def_node_matcher :common_instance_exec_closure?, <<-PATTERN + # @!method common_instance_exec_closure?(node) + def_node_matcher :common_instance_exec_closure?, <<~PATTERN (block (send (const nil? {:Class :Module :Struct}) :new ...) ...) PATTERN + # @!method rspec_block?(node) def_node_matcher :rspec_block?, - RuboCop::RSpec::Language::ALL.block_pattern + '(any_block (send #rspec? #ALL.all ...) ...)' + # @!method scope_changing_syntax?(node) def_node_matcher :scope_changing_syntax?, '{def class module}' - def_node_matcher :described_constant, <<-PATTERN + # @!method described_constant(node) + def_node_matcher :described_constant, <<~PATTERN (block (send _ :describe $(const ...) ...) (args) $_) PATTERN - def_node_search :contains_described_class?, <<-PATTERN - (send nil? :described_class) - PATTERN + # @!method contains_described_class?(node) + def_node_search :contains_described_class?, + '(send nil? :described_class)' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler # In case the explicit style is used, we need to remember what's # being described. @described_class, body = described_constant(node) @@ -85,33 +105,38 @@ def on_block(node) return unless body find_usage(body) do |match| - add_offense(match, message: message(match.const_name)) + msg = message(match.const_name) + add_offense(match, message: msg) do |corrector| + autocorrect(corrector, match) + end end end - def autocorrect(node) + private + + def autocorrect(corrector, match) replacement = if style == :described_class DESCRIBED_CLASS else @described_class.const_name end - lambda do |corrector| - corrector.replace(node.loc.expression, replacement) - end - end - private + corrector.replace(match, replacement) + end def find_usage(node, &block) yield(node) if offensive?(node) - - return if scope_change?(node) || node.const_type? + return if scope_change?(node) || allowed?(node) node.each_child_node do |child| find_usage(child, &block) end end + def allowed?(node) + node.const_type? && only_static_constants? + end + def message(offense) if style == :described_class format(MSG, replacement: DESCRIBED_CLASS, src: offense) @@ -122,42 +147,46 @@ def message(offense) end def scope_change?(node) - scope_changing_syntax?(node) || + scope_changing_syntax?(node) || common_instance_exec_closure?(node) || skippable_block?(node) end def skippable_block?(node) - node.block_type? && !rspec_block?(node) && skip_blocks? + return false unless cop_config['SkipBlocks'] + + node.any_block_type? && !rspec_block?(node) end - def skip_blocks? - cop_config['SkipBlocks'] + def only_static_constants? + cop_config.fetch('OnlyStaticConstants', true) end def offensive?(node) if style == :described_class offensive_described_class?(node) else - node.send_type? && node.method_name == :described_class + node.send_type? && node.method?(:described_class) end end def offensive_described_class?(node) - return unless node.const_type? + return false unless node.const_type? + # E.g. `described_class::CONSTANT` - return if contains_described_class?(node) + return false if contains_described_class?(node) nearest_described_class, = node.each_ancestor(:block) .map { |ancestor| described_constant(ancestor) }.find(&:itself) - return if nearest_described_class.equal?(node) + return false if nearest_described_class.equal?(node) full_const_name(nearest_described_class) == full_const_name(node) end def full_const_name(node) - collapse_namespace(namespace(node), const_name(node)) + symbolized_namespace = namespace(node).map(&:to_sym) + collapse_namespace(symbolized_namespace, const_name(node)) end # @param namespace [Array] @@ -165,14 +194,13 @@ def full_const_name(node) # @return [Array] # @example # # nil represents base constant - # collapse_namespace([], :C) # => [:C] - # collapse_namespace([:A, :B], [:C) # => [:A, :B, :C] - # collapse_namespace([:A, :B], [:B, :C) # => [:A, :B, :C] - # collapse_namespace([:A, :B], [nil, :C) # => [nil, :C] - # collapse_namespace([:A, :B], [nil, :B, :C) # => [nil, :B, :C] + # collapse_namespace([], [:C]) # => [:C] + # collapse_namespace([:A, :B], [:C]) # => [:A, :B, :C] + # collapse_namespace([:A, :B], [:B, :C]) # => [:A, :B, :C] + # collapse_namespace([:A, :B], [nil, :C]) # => [nil, :C] + # collapse_namespace([:A, :B], [nil, :B, :C]) # => [nil, :B, :C] def collapse_namespace(namespace, const) - return const if namespace.empty? - return const if const.first.nil? + return const if namespace.empty? || const.first.nil? start = [0, (namespace.length - const.length)].max max = namespace.length @@ -189,29 +217,16 @@ def collapse_namespace(namespace, const) # const_name(s(:const, s(:const, nil, :M), :C)) # => [:M, :C] # const_name(s(:const, s(:cbase), :C)) # => [nil, :C] def const_name(node) - # rubocop:disable InternalAffairs/NodeDestructuring - namespace, name = *node - # rubocop:enable InternalAffairs/NodeDestructuring + namespace = node.namespace + name = node.short_name if !namespace [name] elsif namespace.const_type? [*const_name(namespace), name] - elsif namespace.lvar_type? || namespace.cbase_type? + elsif %i[lvar cbase send].include?(namespace.type) [nil, name] end end - - # @param node [RuboCop::AST::Node] - # @return [Array] - # @example - # namespace(node) # => [:A, :B, :C] - def namespace(node) - node - .each_ancestor(:class, :module) - .reverse_each - .flat_map { |ancestor| ancestor.defined_module_name.split('::') } - .map(&:to_sym) - end end end end diff --git a/lib/rubocop/cop/rspec/described_class_module_wrapping.rb b/lib/rubocop/cop/rspec/described_class_module_wrapping.rb index e1d548ce5..9ea0a786d 100644 --- a/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +++ b/lib/rubocop/cop/rspec/described_class_module_wrapping.rb @@ -18,17 +18,19 @@ module RSpec # # ... # end # - # @see https://github.com/rubocop-hq/rubocop-rspec/issues/735 - class DescribedClassModuleWrapping < Cop + # @see https://github.com/rubocop/rubocop-rspec/issues/735 + class DescribedClassModuleWrapping < Base MSG = 'Avoid opening modules and defining specs within them.' - def_node_search :find_rspec_blocks, - ExampleGroups::ALL.block_pattern + # @!method include_rspec_blocks?(node) + def_node_search :include_rspec_blocks?, <<~PATTERN + (any_block (send #explicit_rspec? #ExampleGroups.all ...) ...) + PATTERN def on_module(node) - find_rspec_blocks(node) do - add_offense(node) - end + return unless include_rspec_blocks?(node) + + add_offense(node) end end end diff --git a/lib/rubocop/cop/rspec/dialect.rb b/lib/rubocop/cop/rspec/dialect.rb index 137359483..5bc91f0cc 100644 --- a/lib/rubocop/cop/rspec/dialect.rb +++ b/lib/rubocop/cop/rspec/dialect.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module RSpec - # This cop enforces custom RSpec dialects. + # Enforces custom RSpec dialects. # # A dialect can be based on the following RSpec methods: # @@ -29,6 +29,19 @@ module RSpec # PreferredMethods: # context: describe # + # If you were previously using the `RSpec/Capybara/FeatureMethods` cop and + # want to keep disabling all Capybara-specific methods that have the same + # native RSpec method (e.g. are just aliases), use the following config: + # + # RSpec/Dialect: + # PreferredMethods: + # background: :before + # scenario: :it + # xscenario: :xit + # given: :let + # given!: :let! + # feature: :describe + # # You can expect the following behavior: # # @example @@ -41,35 +54,30 @@ module RSpec # describe 'display name presence' do # # ... # end - class Dialect < Cop + # + class Dialect < Base + extend AutoCorrector include MethodPreference MSG = 'Prefer `%s` over `%s`.' - def_node_matcher :rspec_method?, ALL.send_pattern + # @!method rspec_method?(node) + def_node_matcher :rspec_method?, '(send #rspec? #ALL.all ...)' def on_send(node) return unless rspec_method?(node) return unless preferred_methods[node.method_name] - add_offense(node) - end + msg = format(MSG, prefer: preferred_method(node.method_name), + current: node.method_name) - def autocorrect(node) - lambda do |corrector| + add_offense(node, message: msg) do |corrector| current = node.loc.selector preferred = preferred_method(current.source) corrector.replace(current, preferred) end end - - private - - def message(node) - format(MSG, prefer: preferred_method(node.method_name), - current: node.method_name) - end end end end diff --git a/lib/rubocop/cop/rspec/duplicated_metadata.rb b/lib/rubocop/cop/rspec/duplicated_metadata.rb new file mode 100644 index 000000000..7363946eb --- /dev/null +++ b/lib/rubocop/cop/rspec/duplicated_metadata.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Avoid duplicated metadata. + # + # @example + # # bad + # describe 'Something', :a, :a + # + # # good + # describe 'Something', :a + class DuplicatedMetadata < Base + extend AutoCorrector + + include Metadata + include RangeHelp + + MSG = 'Avoid duplicated metadata.' + + def on_metadata(symbols, _hash) + symbols.each do |symbol| + on_metadata_symbol(symbol) + end + end + + private + + def on_metadata_symbol(node) + return unless duplicated?(node) + + add_offense(node) do |corrector| + autocorrect(corrector, node) + end + end + + def autocorrect(corrector, node) + corrector.remove( + range_with_surrounding_comma( + range_with_surrounding_space( + node.source_range, + side: :left + ), + :left + ) + ) + end + + def duplicated?(node) + node.left_siblings.any? do |sibling| + sibling.eql?(node) + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/empty_example_group.rb b/lib/rubocop/cop/rspec/empty_example_group.rb index 2d8bb24f4..3e0d7878e 100644 --- a/lib/rubocop/cop/rspec/empty_example_group.rb +++ b/lib/rubocop/cop/rspec/empty_example_group.rb @@ -5,10 +5,7 @@ module Cop module RSpec # Checks if an example group does not include any tests. # - # This cop is configurable using the `CustomIncludeMethods` option - # # @example usage - # # # bad # describe Bacon do # let(:bacon) { Bacon.new(chunkiness) } @@ -33,56 +30,158 @@ module RSpec # end # end # - # @example configuration - # - # # .rubocop.yml - # # RSpec/EmptyExampleGroup: - # # CustomIncludeMethods: - # # - include_tests - # - # # spec_helper.rb - # RSpec.configure do |config| - # config.alias_it_behaves_like_to(:include_tests) - # end - # - # # bacon_spec.rb + # # good # describe Bacon do - # let(:bacon) { Bacon.new(chunkiness) } - # let(:chunkiness) { false } - # - # context 'extra chunky' do # not flagged by rubocop - # let(:chunkiness) { true } - # - # include_tests 'shared tests' - # end + # pending 'will add tests later' # end # - class EmptyExampleGroup < Cop + class EmptyExampleGroup < Base + extend AutoCorrector + + include RangeHelp + MSG = 'Empty example group detected.' - def_node_search :contains_example?, <<-PATTERN + # @!method example_group_body(node) + # Match example group blocks and yield their body + # + # @example source that matches + # describe 'example group' do + # it { is_expected.to be } + # end + # + # @param node [RuboCop::AST::Node] + # @yield [RuboCop::AST::Node] example group body + def_node_matcher :example_group_body, <<~PATTERN + (block (send #rspec? #ExampleGroups.all ...) args $_) + PATTERN + + # @!method example_or_group_or_include?(node) + # Match examples, example groups and includes + # + # @example source that matches + # it { is_expected.to fly } + # describe('non-empty example groups too') { } + # it_behaves_like 'an animal' + # it_behaves_like('a cat') { let(:food) { 'milk' } } + # it_has_root_access + # skip + # it 'will be implemented later' + # + # @param node [RuboCop::AST::Node] + # @return [Array] matching nodes + def_node_matcher :example_or_group_or_include?, <<~PATTERN { - #{(Examples::ALL + Includes::ALL).send_pattern} - (send _ #custom_include? ...) + (block + (send #rspec? {#Examples.all #ExampleGroups.all #Includes.all} ...) + ...) + (send nil? {#Examples.all #Includes.all} ...) } PATTERN - def on_block(node) - return unless example_group?(node) && !contains_example?(node) + # @!method examples_inside_block?(node) + # Match examples defined inside a block which is not a hook + # + # @example source that matches + # %w(r g b).each do |color| + # it { is_expected.to have_color(color) } + # end + # + # @example source that does not match + # before do + # it { is_expected.to fall_into_oblivion } + # end + # + # @param node [RuboCop::AST::Node] + # @return [Array] matching nodes + def_node_matcher :examples_inside_block?, <<~PATTERN + (block !(send nil? #Hooks.all ...) _ #examples?) + PATTERN - add_offense(node.send_node) + # @!method examples_directly_or_in_block?(node) + # Match examples or examples inside blocks + # + # @example source that matches + # it { expect(drink).to be_cold } + # context('when winter') { it { expect(drink).to be_hot } } + # (1..5).each { |divisor| it { is_expected.to divide_by(divisor) } } + # + # @param node [RuboCop::AST::Node] + # @return [Array] matching nodes + def_node_matcher :examples_directly_or_in_block?, <<~PATTERN + { + #example_or_group_or_include? + #examples_inside_block? + } + PATTERN + + # @!method examples?(node) + # Matches examples defined in scopes where they could run + # + # @example source that matches + # it { expect(myself).to be_run } + # describe { it { i_run_as_well } } + # + # @example source that does not match + # before { it { whatever here won't run anyway } } + # + # @param node [RuboCop::AST::Node] + # @return [Array] matching nodes + def_node_matcher :examples?, <<~PATTERN + { + #examples_directly_or_in_block? + #examples_in_branches? + (begin <#examples_directly_or_in_block? ...>) + (begin <#examples_in_branches? ...>) + } + PATTERN + + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return if node.each_ancestor(:any_def).any? + return if node.each_ancestor(:block).any? { |block| example?(block) } + + example_group_body(node) do |body| + next unless offensive?(body) + + add_offense(node.send_node) do |corrector| + corrector.remove(removed_range(node)) + end + end end private - def custom_include?(method_name) - custom_include_methods.include?(method_name) + def offensive?(body) + return true unless body + return false if conditionals_with_examples?(body) + + if body.type?(:if, :case) + !examples_in_branches?(body) + else + !examples?(body) + end + end + + def conditionals_with_examples?(body) + return false unless body.type?(:begin, :case) + + body.each_descendant(:if, :case).any? do |condition_node| + examples_in_branches?(condition_node) + end + end + + def examples_in_branches?(condition_node) + return false unless condition_node + return false unless condition_node.type?(:if, :case) + + condition_node.branches.any? { |branch| examples?(branch) } end - def custom_include_methods - cop_config - .fetch('CustomIncludeMethods', []) - .map(&:to_sym) + def removed_range(node) + range_by_whole_lines( + node.source_range, + include_final_newline: true + ) end end end diff --git a/lib/rubocop/cop/rspec/empty_hook.rb b/lib/rubocop/cop/rspec/empty_hook.rb new file mode 100644 index 000000000..6dae31aea --- /dev/null +++ b/lib/rubocop/cop/rspec/empty_hook.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Checks for empty before and after hooks. + # + # @example + # # bad + # before {} + # after do; end + # before(:all) do + # end + # after(:all) { } + # + # # good + # before { create_users } + # after do + # cleanup_users + # end + # before(:all) do + # create_feed + # end + # after(:all) { cleanup_feed } + # + class EmptyHook < Base + extend AutoCorrector + include RuboCop::Cop::RangeHelp + + MSG = 'Empty hook detected.' + + # @!method empty_hook?(node) + def_node_matcher :empty_hook?, <<~PATTERN + (block $(send nil? #Hooks.all ...) _ nil?) + PATTERN + + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + empty_hook?(node) do |hook| + add_offense(hook) do |corrector| + corrector.remove( + range_with_surrounding_space(node.source_range, side: :left) + ) + end + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/empty_line_after_example.rb b/lib/rubocop/cop/rspec/empty_line_after_example.rb index 7ddd05632..5603f1463 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_example.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_example.rb @@ -30,7 +30,6 @@ module RSpec # end # # @example with AllowConsecutiveOneLiners configuration - # # # rubocop.yml # # RSpec/EmptyLineAfterExample: # # AllowConsecutiveOneLiners: false @@ -41,23 +40,23 @@ module RSpec # it { two } # end # - class EmptyLineAfterExample < Cop - include RuboCop::RSpec::BlankLineSeparation + class EmptyLineAfterExample < Base + extend AutoCorrector + include EmptyLineSeparation MSG = 'Add an empty line after `%s`.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless example?(node) - return if last_child?(node) return if allowed_one_liner?(node) - missing_separating_line(node) do |location| - add_offense(node, - location: location, - message: format(MSG, example: node.method_name)) + missing_separating_line_offense(node) do |method| + format(MSG, example: method) end end + private + def allowed_one_liner?(node) consecutive_one_liner?(node) && allow_consecutive_one_liners? end @@ -67,19 +66,15 @@ def allow_consecutive_one_liners? end def consecutive_one_liner?(node) - node.line_count == 1 && next_one_line_example?(node) + node.single_line? && next_one_line_example?(node) end def next_one_line_example?(node) - next_sibling = next_sibling(node) - return unless next_sibling - return unless example?(next_sibling) - - next_sibling.line_count == 1 - end + next_sibling = node.right_sibling + return false unless next_sibling + return false unless example?(next_sibling) - def next_sibling(node) - node.parent.children[node.sibling_index + 1] + next_sibling.single_line? end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_example_group.rb b/lib/rubocop/cop/rspec/empty_line_after_example_group.rb index 7b87add04..d7dadc35a 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_example_group.rb @@ -23,21 +23,17 @@ module RSpec # end # end # - class EmptyLineAfterExampleGroup < Cop - include RuboCop::RSpec::BlankLineSeparation + class EmptyLineAfterExampleGroup < Base + extend AutoCorrector + include EmptyLineSeparation MSG = 'Add an empty line after `%s`.' - def on_block(node) - return unless example_group?(node) - return if last_child?(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless spec_group?(node) - missing_separating_line(node) do |location| - add_offense( - node, - location: location, - message: format(MSG, example_group: node.method_name) - ) + missing_separating_line_offense(node) do |method| + format(MSG, example_group: method) end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_final_let.rb b/lib/rubocop/cop/rspec/empty_line_after_final_let.rb index a059433ae..72f0fc185 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_final_let.rb @@ -16,21 +16,22 @@ module RSpec # let(:something) { other } # # it { does_something } - class EmptyLineAfterFinalLet < Cop - include RuboCop::RSpec::BlankLineSeparation + # + class EmptyLineAfterFinalLet < Base + extend AutoCorrector + include EmptyLineSeparation - MSG = 'Add an empty line after the last `let` block.' + MSG = 'Add an empty line after the last `%s`.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless example_group_with_body?(node) - latest_let = node.body.child_nodes.select { |child| let?(child) }.last + final_let = node.body.child_nodes.reverse.find { |child| let?(child) } - return if latest_let.nil? - return if last_child?(latest_let) + return if final_let.nil? - missing_separating_line(latest_let) do |location| - add_offense(latest_let, location: location) + missing_separating_line_offense(final_let) do |method| + format(MSG, let: method) end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_hook.rb b/lib/rubocop/cop/rspec/empty_line_after_hook.rb index 678534317..cfcd8d30a 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_hook.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_hook.rb @@ -5,6 +5,9 @@ module Cop module RSpec # Checks if there is an empty line after hook blocks. # + # `AllowConsecutiveOneLiners` configures whether adjacent + # one-line definitions are considered an offense. + # # @example # # bad # before { do_something } @@ -19,11 +22,23 @@ module RSpec # it { does_something } # # # good - # before { do_something } + # after { do_something } # # it { does_something } # - # # good + # # fair - it's ok to have non-separated one-liners hooks + # around { |test| test.run } + # after { do_something } + # + # it { does_something } + # + # @example with AllowConsecutiveOneLiners configuration + # # rubocop.yml + # # RSpec/EmptyLineAfterHook: + # # AllowConsecutiveOneLiners: false + # + # # bad + # around { |test| test.run } # after { do_something } # # it { does_something } @@ -31,25 +46,36 @@ module RSpec # # good # around { |test| test.run } # + # after { do_something } + # # it { does_something } # - class EmptyLineAfterHook < Cop - include RuboCop::RSpec::BlankLineSeparation + class EmptyLineAfterHook < Base + extend AutoCorrector + include ConfigurableEnforcedStyle + include EmptyLineSeparation MSG = 'Add an empty line after `%s`.' def on_block(node) return unless hook?(node) - return if last_child?(node) - - missing_separating_line(node) do |location| - add_offense( - node, - location: location, - message: format(MSG, hook: node.method_name) - ) + return if cop_config['AllowConsecutiveOneLiners'] && + chained_single_line_hooks?(node) + + missing_separating_line_offense(node) do |method| + format(MSG, hook: method) end end + + alias on_numblock on_block + + private + + def chained_single_line_hooks?(node) + next_node = node.right_sibling + + hook?(next_node) && node.single_line? && next_node.single_line? + end end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_subject.rb b/lib/rubocop/cop/rspec/empty_line_after_subject.rb index a0f6ed0f5..f232e3fa5 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_subject.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_subject.rb @@ -14,25 +14,20 @@ module RSpec # subject(:obj) { described_class } # # let(:foo) { bar } - class EmptyLineAfterSubject < Cop - include RuboCop::RSpec::BlankLineSeparation - - MSG = 'Add empty line after `subject`.' - - def on_block(node) - return unless subject?(node) && !in_spec_block?(node) - return if last_child?(node) + # + class EmptyLineAfterSubject < Base + extend AutoCorrector + include EmptyLineSeparation + include InsideExampleGroup - missing_separating_line(node) do |location| - add_offense(node, location: location) - end - end + MSG = 'Add an empty line after `%s`.' - private + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless subject?(node) + return unless inside_example_group?(node) - def in_spec_block?(node) - node.each_ancestor(:block).any? do |ancestor| - Examples::ALL.include?(ancestor.method_name) + missing_separating_line_offense(node) do |method| + format(MSG, subject: method) end end end diff --git a/lib/rubocop/cop/rspec/empty_metadata.rb b/lib/rubocop/cop/rspec/empty_metadata.rb new file mode 100644 index 000000000..390059c31 --- /dev/null +++ b/lib/rubocop/cop/rspec/empty_metadata.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Avoid empty metadata hash. + # + # @example EnforcedStyle: symbol (default) + # # bad + # describe 'Something', {} + # + # # good + # describe 'Something' + class EmptyMetadata < Base + extend AutoCorrector + + include Metadata + include RangeHelp + + MSG = 'Avoid empty metadata hash.' + + def on_metadata(_symbols, hash) + return unless hash&.pairs&.empty? + return if hash.children.any?(&:kwsplat_type?) + + add_offense(hash) do |corrector| + remove_empty_metadata(corrector, hash) + end + end + + private + + def remove_empty_metadata(corrector, node) + corrector.remove( + range_with_surrounding_comma( + range_with_surrounding_space( + node.source_range, + side: :left + ), + :left + ) + ) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/empty_output.rb b/lib/rubocop/cop/rspec/empty_output.rb new file mode 100644 index 000000000..f70d3de21 --- /dev/null +++ b/lib/rubocop/cop/rspec/empty_output.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Check that the `output` matcher is not called with an empty string. + # + # @example + # # bad + # expect { foo }.to output('').to_stdout + # expect { bar }.not_to output('').to_stderr + # + # # good + # expect { foo }.not_to output.to_stdout + # expect { bar }.to output.to_stderr + # + class EmptyOutput < Base + extend AutoCorrector + + MSG = 'Use `%s` instead of matching on an empty output.' + RESTRICT_ON_SEND = Runners.all + + # @!method matching_empty_output(node) + def_node_matcher :matching_empty_output, <<~PATTERN + (send + (block + (send nil? :expect) ... + ) + #Runners.all + (send $(send nil? :output (str empty?)) ...) + ) + PATTERN + + def on_send(send_node) + matching_empty_output(send_node) do |node| + runner = send_node.method?(:to) ? 'not_to' : 'to' + message = format(MSG, runner: runner) + add_offense(node, message: message) do |corrector| + corrector.replace(send_node.loc.selector, runner) + corrector.replace(node, 'output') + end + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/eq.rb b/lib/rubocop/cop/rspec/eq.rb new file mode 100644 index 000000000..f56176548 --- /dev/null +++ b/lib/rubocop/cop/rspec/eq.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Use `eq` instead of `be ==` to compare objects. + # + # @example + # # bad + # expect(foo).to be == 42 + # + # # good + # expect(foo).to eq 42 + # + class Eq < Base + extend AutoCorrector + include RangeHelp + + MSG = 'Use `eq` instead of `be ==` to compare objects.' + RESTRICT_ON_SEND = Runners.all + + # @!method be_equals(node) + def_node_matcher :be_equals, <<~PATTERN + (send _ #Runners.all $(send (send nil? :be) :== _)) + PATTERN + + def on_send(node) + be_equals(node) do |matcher| + range = offense_range(matcher) + add_offense(range) do |corrector| + corrector.replace(range, 'eq') + end + end + end + + private + + def offense_range(matcher) + range_between( + matcher.source_range.begin_pos, + matcher.loc.selector.end_pos + ) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/example_length.rb b/lib/rubocop/cop/rspec/example_length.rb index 1bfb2dde3..880030935 100644 --- a/lib/rubocop/cop/rspec/example_length.rb +++ b/lib/rubocop/cop/rspec/example_length.rb @@ -6,7 +6,7 @@ module RSpec # Checks for long examples. # # A long example is usually more difficult to understand. Consider - # extracting out some behaviour, e.g. with a `let` block, or a helper + # extracting out some behavior, e.g. with a `let` block, or a helper # method. # # @example @@ -25,29 +25,50 @@ module RSpec # result = service.call # expect(result).to be(true) # end - class ExampleLength < Cop + # + # You can set constructs you want to fold with `CountAsOne`. + # Available are: 'array', 'hash', 'heredoc', and 'method_call'. + # Each construct will be counted as one line regardless of + # its actual size. + # + # @example CountAsOne: ['array', 'heredoc', 'method_call'] + # + # it do + # array = [ # +1 + # 1, + # 2 + # ] + # + # hash = { # +3 + # key: 'value' + # } + # + # msg = <<~HEREDOC # +1 + # Heredoc + # content. + # HEREDOC + # + # foo( # +1 + # 1, + # 2 + # ) + # end # 6 points + # + class ExampleLength < Base include CodeLength - MSG = 'Example has too many lines [%d/%d].' + LABEL = 'Example' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless example?(node) - length = code_length(node) - - return unless length > max_length - - add_offense(node, message: message(length)) + check_code_length(node) end private - def code_length(node) - node.source.lines[1..-2].count { |line| !irrelevant_line(line) } - end - - def message(length) - format(MSG, total: length, max: max_length) + def cop_label + LABEL end end end diff --git a/lib/rubocop/cop/rspec/example_without_description.rb b/lib/rubocop/cop/rspec/example_without_description.rb index 8d4ace983..e53d574b1 100644 --- a/lib/rubocop/cop/rspec/example_without_description.rb +++ b/lib/rubocop/cop/rspec/example_without_description.rb @@ -7,6 +7,7 @@ module RSpec # # RSpec allows for auto-generated example descriptions when there is no # description provided or the description is an empty one. + # It is acceptable to use `specify` without a description # # This cop removes empty descriptions. # It also defines whether auto-generated description is allowed, based @@ -14,17 +15,24 @@ module RSpec # # This cop can be configured using the `EnforcedStyle` option # - # @example `EnforcedStyle: always_allow` + # @example + # # always good + # specify do + # result = service.call + # expect(result).to be(true) + # end + # + # @example `EnforcedStyle: always_allow` (default) # # bad # it('') { is_expected.to be_good } - # it '' do + # specify '' do # result = service.call # expect(result).to be(true) # end # # # good # it { is_expected.to be_good } - # it do + # specify do # result = service.call # expect(result).to be(true) # end @@ -47,16 +55,18 @@ module RSpec # result = service.call # expect(result).to be(true) # end - class ExampleWithoutDescription < Cop + # + class ExampleWithoutDescription < Base include ConfigurableEnforcedStyle MSG_DEFAULT_ARGUMENT = 'Omit the argument when you want to ' \ 'have auto-generated description.' MSG_ADD_DESCRIPTION = 'Add a description.' + # @!method example_description(node) def_node_matcher :example_description, '(send nil? _ $(str $_))' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless example?(node) check_example_without_description(node.send_node) @@ -73,6 +83,7 @@ def on_block(node) def check_example_without_description(node) return if node.arguments? return unless disallow_empty_description?(node) + return if node.method?(:specify) && node.parent.multiline? add_offense(node, message: MSG_ADD_DESCRIPTION) end diff --git a/lib/rubocop/cop/rspec/example_wording.rb b/lib/rubocop/cop/rspec/example_wording.rb index 5407544e9..dfbccb744 100644 --- a/lib/rubocop/cop/rspec/example_wording.rb +++ b/lib/rubocop/cop/rspec/example_wording.rb @@ -6,17 +6,25 @@ module RSpec # Checks for common mistakes in example descriptions. # # This cop will correct docstrings that begin with 'should' and 'it'. + # This cop will also look for insufficient examples and call them out. # # @see http://betterspecs.org/#should # # The autocorrect is experimental - use with care! It can be configured # with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only). # + # Use the DisallowedExamples setting to prevent unclear or insufficient + # descriptions. Please note that this config will not be treated as + # case sensitive. + # # @example # # bad # it 'should find nothing' do # end # + # it 'will find nothing' do + # end + # # # good # it 'finds nothing' do # end @@ -29,44 +37,65 @@ module RSpec # # good # it 'does things' do # end - class ExampleWording < Cop + # + # @example `DisallowedExamples: ['works']` (default) + # # bad + # it 'works' do + # end + # + # # good + # it 'marks the task as done' do + # end + class ExampleWording < Base + extend AutoCorrector + MSG_SHOULD = 'Do not use should when describing your tests.' + MSG_WILL = 'Do not use the future tense when describing your tests.' MSG_IT = "Do not repeat 'it' when describing your tests." + MSG_INSUFFICIENT_DESCRIPTION = 'Your example description is ' \ + 'insufficient.' - SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze + SHOULD_PREFIX = /\Ashould(?:n't|n’t)?\b/i.freeze + WILL_PREFIX = /\A(?:will|won't|won’t)\b/i.freeze IT_PREFIX = /\Ait /i.freeze - def_node_matcher :it_description, <<-PATTERN + # @!method it_description(node) + def_node_matcher :it_description, <<~PATTERN (block (send _ :it ${ (str $_) (dstr (str $_ ) ...) } ...) ...) PATTERN - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler it_description(node) do |description_node, message| - if message =~ SHOULD_PREFIX + if message.match?(SHOULD_PREFIX) add_wording_offense(description_node, MSG_SHOULD) - elsif message =~ IT_PREFIX + elsif message.match?(WILL_PREFIX) + add_wording_offense(description_node, MSG_WILL) + elsif message.match?(IT_PREFIX) add_wording_offense(description_node, MSG_IT) + elsif insufficient_docstring?(description_node) + add_offense(docstring(description_node), + message: MSG_INSUFFICIENT_DESCRIPTION) end end end - def autocorrect(node) - lambda do |corrector| - corrector.replace(docstring(node), replacement_text(node)) - end - end - private def add_wording_offense(node, message) - add_offense(node, location: docstring(node), message: message) + docstring = docstring(node) + + add_offense(docstring, message: message) do |corrector| + next if node.heredoc? + + corrector.replace(docstring, replacement_text(node)) + end end def docstring(node) - expr = node.loc.expression + expr = node.source_range Parser::Source::Range.new( expr.source_buffer, @@ -78,7 +107,7 @@ def docstring(node) def replacement_text(node) text = text(node) - if text =~ SHOULD_PREFIX + if text.match?(SHOULD_PREFIX) || text.match?(WILL_PREFIX) RuboCop::RSpec::Wording.new( text, ignore: ignored_words, @@ -97,7 +126,7 @@ def text(node) node.node_parts.map { |child_node| text(child_node) }.join when :str node.value - when :begin + else node.source end end @@ -109,6 +138,19 @@ def custom_transform def ignored_words cop_config.fetch('IgnoredWords', []) end + + def insufficient_docstring?(description_node) + insufficient_examples.include?(preprocess(text(description_node))) + end + + def insufficient_examples + examples = cop_config.fetch('DisallowedExamples', []) + examples.map! { |example| preprocess(example) } + end + + def preprocess(message) + message.strip.squeeze(' ').downcase + end end end end diff --git a/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb b/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb new file mode 100644 index 000000000..c0aada43e --- /dev/null +++ b/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Checks for excessive whitespace in example descriptions. + # + # @example + # # bad + # it ' has excessive spacing ' do + # end + # + # # good + # it 'has excessive spacing' do + # end + # + # @example + # # bad + # context ' when a condition is met ' do + # end + # + # # good + # context 'when a condition is met' do + # end + # + class ExcessiveDocstringSpacing < Base + extend AutoCorrector + + MSG = 'Excessive whitespace.' + + # @!method example_description(node) + def_node_matcher :example_description, <<~PATTERN + (send _ {#Examples.all #ExampleGroups.all} ${ + $str + $(dstr ({str dstr `sym} ...) ...) + } ...) + PATTERN + + def on_send(node) + example_description(node) do |description_node, message| + return if description_node.heredoc? + + text = text(message) + + return unless excessive_whitespace?(text) + + add_whitespace_offense(description_node, text) + end + end + + private + + # @param text [String] + def excessive_whitespace?(text) + text.match?(/ + # Leading space + \A[[:blank:]] + | + # Trailing space + [[:blank:]]\z + | + # Two or more consecutive spaces, except if they are leading spaces + [^[[:space:]]][[:blank:]]{2,}[^[[:blank:]]] + /x) + end + + # @param text [String] + def strip_excessive_whitespace(text) + text + .gsub(/[[:blank:]]{2,}/, ' ') + .gsub(/\A[[:blank:]]|[[:blank:]]\z/, '') + end + + # @param node [RuboCop::AST::Node] + # @param text [String] + def add_whitespace_offense(node, text) + docstring = docstring(node) + corrected = strip_excessive_whitespace(text) + + add_offense(docstring) do |corrector| + corrector.replace(docstring, corrected) + end + end + + def docstring(node) + expr = node.source_range + + Parser::Source::Range.new( + expr.source_buffer, + expr.begin_pos + 1, + expr.end_pos - 1 + ) + end + + # Recursive processing is required to process nested dstr nodes + # that is the case for \-separated multiline strings with interpolation. + def text(node) + case node.type + when :dstr + node.node_parts.map { |child_node| text(child_node) }.join + when :str, :sym + node.value + else + node.source + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/expect_actual.rb b/lib/rubocop/cop/rspec/expect_actual.rb index 3bffe2179..7cef8e289 100644 --- a/lib/rubocop/cop/rspec/expect_actual.rb +++ b/lib/rubocop/cop/rspec/expect_actual.rb @@ -5,6 +5,8 @@ module Cop module RSpec # Checks for `expect(...)` calls containing literal values. # + # Autocorrection is performed when the expected is not a literal. + # # @example # # bad # expect(5).to eq(price) @@ -16,8 +18,15 @@ module RSpec # expect(pattern).to eq(/foo/) # expect(name).to eq("John") # - class ExpectActual < Cop - MSG = 'Provide the actual you are testing to `expect(...)`.' + # # bad (not supported autocorrection) + # expect(false).to eq(true) + # + class ExpectActual < Base + extend AutoCorrector + + MSG = 'Provide the actual value you are testing to `expect(...)`.' + + RESTRICT_ON_SEND = Runners.all SIMPLE_LITERALS = %i[ true @@ -41,13 +50,15 @@ class ExpectActual < Cop regexp ].freeze - SUPPORTED_MATCHERS = %i[eq eql equal be].freeze + SKIPPED_MATCHERS = %i[route_to be_routable].freeze + CORRECTABLE_MATCHERS = %i[eq eql equal be].freeze + # @!method expect_literal(node) def_node_matcher :expect_literal, <<~PATTERN (send (send nil? :expect $#literal?) - #{Runners::ALL.node_pattern_union} - { + #Runners.all + ${ (send (send nil? $:be) :== $_) (send nil? $_ $_ ...) } @@ -55,23 +66,26 @@ class ExpectActual < Cop PATTERN def on_send(node) - expect_literal(node) do |argument| - add_offense(node, location: argument.source_range) - end - end + expect_literal(node) do |actual, send_node, matcher, expected| + next if SKIPPED_MATCHERS.include?(matcher) - def autocorrect(node) - actual, matcher, expected = expect_literal(node) - lambda do |corrector| - return unless SUPPORTED_MATCHERS.include?(matcher) + add_offense(actual) do |corrector| + next unless CORRECTABLE_MATCHERS.include?(matcher) + next if literal?(expected) - swap(corrector, actual, expected) + corrector.replace(actual, expected.source) + if matcher == :be + corrector.replace(expected, actual.source) + else + corrector.replace(send_node, "#{matcher}(#{actual.source})") + end + end end end private - # This is not implement using a NodePattern because it seems + # This is not implemented using a NodePattern because it seems # to not be able to match against an explicit (nil) sexp def literal?(node) node && (simple_literal?(node) || complex_literal?(node)) @@ -83,12 +97,7 @@ def simple_literal?(node) def complex_literal?(node) COMPLEX_LITERALS.include?(node.type) && - node.each_child_node.all?(&method(:literal?)) - end - - def swap(corrector, actual, expected) - corrector.replace(actual.source_range, expected.source) - corrector.replace(expected.source_range, actual.source) + node.each_child_node.all? { |child_node| literal?(child_node) } end end end diff --git a/lib/rubocop/cop/rspec/expect_change.rb b/lib/rubocop/cop/rspec/expect_change.rb index e2afd909a..12f339e2b 100644 --- a/lib/rubocop/cop/rspec/expect_change.rb +++ b/lib/rubocop/cop/rspec/expect_change.rb @@ -5,19 +5,31 @@ module Cop module RSpec # Checks for consistent style of change matcher. # - # Enforces either passing object and attribute as arguments to the matcher - # or passing a block that reads the attribute value. + # Enforces either passing a receiver and message as method arguments, + # or a block. # # This cop can be configured using the `EnforcedStyle` option. # - # @example `EnforcedStyle: block` - # # bad - # expect { run }.to change(Foo, :bar) + # @safety + # Autocorrection is unsafe because `method_call` style calls the + # receiver *once* and sends the message to it before and after + # calling the `expect` block, whereas `block` style calls the + # expression *twice*, including the receiver. # - # # good - # expect { run }.to change { Foo.bar } + # If your receiver is dynamic (e.g., the result of a method call) and + # you expect it to be called before and after the `expect` block, + # changing from `block` to `method_call` style may break your test. + # + # [source,ruby] + # ---- + # expect { run }.to change { my_method.message } + # # `my_method` is called before and after `run` # - # @example `EnforcedStyle: method_call` + # expect { run }.to change(my_method, :message) + # # `my_method` is called once, but `message` is called on it twice + # ---- + # + # @example `EnforcedStyle: method_call` (default) # # bad # expect { run }.to change { Foo.bar } # expect { run }.to change { foo.baz } @@ -29,21 +41,38 @@ module RSpec # expect { run }.to change { Foo.bar(:count) } # expect { run }.to change { user.reload.name } # - class ExpectChange < Cop + # @example `EnforcedStyle: block` + # # bad + # expect { run }.to change(Foo, :bar) + # + # # good + # expect { run }.to change { Foo.bar } + # + class ExpectChange < Base + extend AutoCorrector include ConfigurableEnforcedStyle MSG_BLOCK = 'Prefer `change(%s, :%s)`.' MSG_CALL = 'Prefer `change { %s.%s }`.' + RESTRICT_ON_SEND = %i[change].freeze - def_node_matcher :expect_change_with_arguments, <<-PATTERN - (send nil? :change ({const send} nil? $_) (sym $_)) + # @!method expect_change_with_arguments(node) + def_node_matcher :expect_change_with_arguments, <<~PATTERN + (send nil? :change $_ ({sym str} $_)) PATTERN - def_node_matcher :expect_change_with_block, <<-PATTERN + # @!method expect_change_with_block(node) + def_node_matcher :expect_change_with_block, <<~PATTERN (block (send nil? :change) (args) - (send ({const send} nil? $_) $_) + (send + ${ + (send nil? _) # change { user.name } + const # change { User.count } + } + $_ + ) ) PATTERN @@ -51,48 +80,22 @@ def on_send(node) return unless style == :block expect_change_with_arguments(node) do |receiver, message| - add_offense( - node, - message: format(MSG_CALL, obj: receiver, attr: message) - ) + msg = format(MSG_CALL, obj: receiver.source, attr: message) + add_offense(node, message: msg) do |corrector| + replacement = "change { #{receiver.source}.#{message} }" + corrector.replace(node, replacement) + end end end - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless style == :method_call expect_change_with_block(node) do |receiver, message| - add_offense( - node, - message: format(MSG_BLOCK, obj: receiver, attr: message) - ) - end - end - - def autocorrect(node) - if style == :block - autocorrect_method_call_to_block(node) - else - autocorrect_block_to_method_call(node) - end - end - - private - - def autocorrect_method_call_to_block(node) - lambda do |corrector| - expect_change_with_arguments(node) do |receiver, message| - replacement = "change { #{receiver}.#{message} }" - corrector.replace(node.loc.expression, replacement) - end - end - end - - def autocorrect_block_to_method_call(node) - lambda do |corrector| - expect_change_with_block(node) do |receiver, message| - replacement = "change(#{receiver}, :#{message})" - corrector.replace(node.loc.expression, replacement) + msg = format(MSG_BLOCK, obj: receiver.source, attr: message) + add_offense(node, message: msg) do |corrector| + replacement = "change(#{receiver.source}, :#{message})" + corrector.replace(node, replacement) end end end diff --git a/lib/rubocop/cop/rspec/expect_in_hook.rb b/lib/rubocop/cop/rspec/expect_in_hook.rb index b0cd8e69b..4474c707b 100644 --- a/lib/rubocop/cop/rspec/expect_in_hook.rb +++ b/lib/rubocop/cop/rspec/expect_in_hook.rb @@ -20,21 +20,25 @@ module RSpec # it do # expect(something).to eq 'foo' # end - class ExpectInHook < Cop + # + class ExpectInHook < Base MSG = 'Do not use `%s` in `%s` hook' - def_node_search :expectation, Expectations::ALL.send_pattern + # @!method expectation(node) + def_node_search :expectation, '(send nil? #Expectations.all ...)' def on_block(node) return unless hook?(node) return if node.body.nil? expectation(node.body) do |expect| - add_offense(expect, location: :selector, - message: message(expect, node)) + add_offense(expect.loc.selector, + message: message(expect, node)) end end + alias on_numblock on_block + private def message(expect, hook) diff --git a/lib/rubocop/cop/rspec/expect_in_let.rb b/lib/rubocop/cop/rspec/expect_in_let.rb new file mode 100644 index 000000000..d74e4a65a --- /dev/null +++ b/lib/rubocop/cop/rspec/expect_in_let.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Do not use `expect` in let. + # + # @example + # # bad + # let(:foo) do + # expect(something).to eq 'foo' + # end + # + # # good + # it do + # expect(something).to eq 'foo' + # end + # + class ExpectInLet < Base + MSG = 'Do not use `%s` in let' + + # @!method expectation(node) + def_node_search :expectation, '(send nil? #Expectations.all ...)' + + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless let?(node) + return if node.body.nil? + + expectation(node.body) do |expect| + add_offense(expect.loc.selector, message: message(expect)) + end + end + + private + + def message(expect) + format(MSG, expect: expect.method_name) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/expect_output.rb b/lib/rubocop/cop/rspec/expect_output.rb index 9d2e37153..7a0ff63dd 100644 --- a/lib/rubocop/cop/rspec/expect_output.rb +++ b/lib/rubocop/cop/rspec/expect_output.rb @@ -14,20 +14,18 @@ module RSpec # # # good # expect { my_app.print_report }.to output('Hello World').to_stdout - class ExpectOutput < Cop - MSG = 'Use `expect { ... }.to output(...).to_%s` '\ + # + class ExpectOutput < Base + MSG = 'Use `expect { ... }.to output(...).to_%s` ' \ 'instead of mutating $%s.' def on_gvasgn(node) return unless inside_example_scope?(node) - # rubocop:disable InternalAffairs/NodeDestructuring - variable_name, _rhs = *node - # rubocop:enable InternalAffairs/NodeDestructuring - name = variable_name[1..-1] + name = node.name[1..] return unless name.eql?('stdout') || name.eql?('stderr') - add_offense(node, location: :name, message: format(MSG, name: name)) + add_offense(node.loc.name, message: format(MSG, name: name)) end private diff --git a/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb b/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb deleted file mode 100644 index 863eb401f..000000000 --- a/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +++ /dev/null @@ -1,121 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module RSpec - module FactoryBot - # Always declare attribute values as blocks. - # - # @example - # # bad - # kind [:active, :rejected].sample - # - # # good - # kind { [:active, :rejected].sample } - # - # # bad - # closed_at 1.day.from_now - # - # # good - # closed_at { 1.day.from_now } - # - # # bad - # count 1 - # - # # good - # count { 1 } - class AttributeDefinedStatically < Cop - MSG = 'Use a block to declare attribute values.' - - def_node_matcher :value_matcher, <<-PATTERN - (send _ !#reserved_method? $...) - PATTERN - - def_node_search :factory_attributes, <<-PATTERN - (block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } ) - PATTERN - - def on_block(node) - factory_attributes(node).to_a.flatten.each do |attribute| - next unless offensive_receiver?(attribute.receiver, node) - next if proc?(attribute) || association?(attribute.first_argument) - - add_offense(attribute) - end - end - - def autocorrect(node) - if node.parenthesized? - autocorrect_replacing_parens(node) - else - autocorrect_without_parens(node) - end - end - - private - - def offensive_receiver?(receiver, node) - receiver.nil? || - receiver.self_type? || - receiver_matches_first_block_argument?(receiver, node) - end - - def receiver_matches_first_block_argument?(receiver, node) - first_block_argument = node.arguments.first - - !first_block_argument.nil? && - receiver.lvar_type? && - receiver.node_parts == first_block_argument.node_parts - end - - def proc?(attribute) - value_matcher(attribute).to_a.all?(&:block_pass_type?) - end - - def_node_matcher :association?, '(hash <(pair (sym :factory) _) ...>)' - - def autocorrect_replacing_parens(node) - left_braces, right_braces = braces(node) - - lambda do |corrector| - corrector.replace(node.location.begin, ' ' + left_braces) - corrector.replace(node.location.end, right_braces) - end - end - - def autocorrect_without_parens(node) - left_braces, right_braces = braces(node) - - lambda do |corrector| - argument = node.first_argument - expression = argument.location.expression - corrector.insert_before(expression, left_braces) - corrector.insert_after(expression, right_braces) - end - end - - def braces(node) - if value_hash_without_braces?(node.first_argument) - ['{ { ', ' } }'] - else - ['{ ', ' }'] - end - end - - def value_hash_without_braces?(node) - node.hash_type? && !node.braces? - end - - def reserved_method?(method_name) - RuboCop::RSpec::FactoryBot.reserved_methods.include?(method_name) - end - - def attribute_defining_method?(method_name) - RuboCop::RSpec::FactoryBot.attribute_defining_methods - .include?(method_name) - end - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/factory_bot/create_list.rb b/lib/rubocop/cop/rspec/factory_bot/create_list.rb deleted file mode 100644 index 11addc723..000000000 --- a/lib/rubocop/cop/rspec/factory_bot/create_list.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module RSpec - module FactoryBot - # Checks for create_list usage. - # - # This cop can be configured using the `EnforcedStyle` option - # - # @example `EnforcedStyle: create_list` - # # bad - # 3.times { create :user } - # - # # good - # create_list :user, 3 - # - # # good - # 3.times { |n| create :user, created_at: n.months.ago } - # - # @example `EnforcedStyle: n_times` - # # bad - # create_list :user, 3 - # - # # good - # 3.times { create :user } - class CreateList < Cop - include ConfigurableEnforcedStyle - - MSG_CREATE_LIST = 'Prefer create_list.' - MSG_N_TIMES = 'Prefer %s.times.' - - def_node_matcher :n_times_block_without_arg?, <<-PATTERN - (block - (send (int _) :times) - (args) - ... - ) - PATTERN - - def_node_matcher :factory_call, <<-PATTERN - (send ${(const nil? {:FactoryGirl :FactoryBot}) nil?} :create (sym $_) $...) - PATTERN - - def_node_matcher :factory_list_call, <<-PATTERN - (send ${(const nil? {:FactoryGirl :FactoryBot}) nil?} :create_list (sym $_) (int $_) $...) - PATTERN - - def on_block(node) - return unless style == :create_list - return unless n_times_block_without_arg?(node) - return unless contains_only_factory?(node.body) - - add_offense(node.send_node, message: MSG_CREATE_LIST) - end - - def on_send(node) - return unless style == :n_times - - factory_list_call(node) do |_receiver, _factory, count, _| - add_offense( - node, - location: :selector, - message: format(MSG_N_TIMES, number: count) - ) - end - end - - def autocorrect(node) - if style == :create_list - CreateListCorrector.new(node) - else - TimesCorrector.new(node) - end - end - - private - - def contains_only_factory?(node) - if node.block_type? - factory_call(node.send_node) - else - factory_call(node) - end - end - - # :nodoc - class Corrector - private - - def build_options_string(options) - options.map(&:source).join(', ') - end - - def format_method_call(node, method, arguments) - if node.block_type? || node.parenthesized? - "#{method}(#{arguments})" - else - "#{method} #{arguments}" - end - end - - def format_receiver(receiver) - return '' unless receiver - - "#{receiver.source}." - end - end - - # :nodoc - class TimesCorrector < Corrector - def initialize(node) - @node = node - end - - def call(corrector) - replacement = generate_n_times_block(node) - corrector.replace(node.loc.expression, replacement) - end - - private - - attr_reader :node - - def generate_n_times_block(node) - factory, count, *options = node.arguments - - arguments = factory.source - options = build_options_string(options) - arguments += ", #{options}" unless options.empty? - - replacement = format_receiver(node.receiver) - replacement += format_method_call(node, 'create', arguments) - "#{count.source}.times { #{replacement} }" - end - end - - # :nodoc: - class CreateListCorrector < Corrector - def initialize(node) - @node = node.parent - end - - def call(corrector) - replacement = if node.body.block_type? - call_with_block_replacement(node) - else - call_replacement(node) - end - - corrector.replace(node.loc.expression, replacement) - end - - private - - attr_reader :node - - def call_with_block_replacement(node) - block = node.body - arguments = build_arguments(block, node.receiver.source) - replacement = format_receiver(block.send_node.receiver) - replacement += format_method_call(block, 'create_list', arguments) - replacement += format_block(block) - replacement - end - - def build_arguments(node, count) - factory, *options = *node.send_node.arguments - - arguments = ":#{factory.value}, #{count}" - options = build_options_string(options) - arguments += ", #{options}" unless options.empty? - arguments - end - - def call_replacement(node) - block = node.body - factory, *options = *block.arguments - - arguments = "#{factory.source}, #{node.receiver.source}" - options = build_options_string(options) - arguments += ", #{options}" unless options.empty? - - replacement = format_receiver(block.receiver) - replacement += format_method_call(block, 'create_list', arguments) - replacement - end - - def format_block(node) - if node.body.begin_type? - format_multiline_block(node) - else - format_singeline_block(node) - end - end - - def format_multiline_block(node) - indent = ' ' * node.body.loc.column - indent_end = ' ' * node.parent.loc.column - " do #{node.arguments.source}\n" \ - "#{indent}#{node.body.source}\n" \ - "#{indent_end}end" - end - - def format_singeline_block(node) - " { #{node.arguments.source} #{node.body.source} }" - end - end - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb b/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb deleted file mode 100644 index 3a49f33d9..000000000 --- a/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module RSpec - module FactoryBot - # Use string value when setting the class attribute explicitly. - # - # This cop would promote faster tests by lazy-loading of - # application files. Also, this could help you suppress potential bugs - # in combination with external libraries by avoiding a preload of - # application files from the factory files. - # - # @example - # # bad - # factory :foo, class: Foo do - # end - # - # # good - # factory :foo, class: 'Foo' do - # end - class FactoryClassName < Cop - MSG = "Pass '%s' string instead of `%s` " \ - 'constant.' - ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze - - def_node_matcher :class_name, <<~PATTERN - (send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>)) - PATTERN - - def on_send(node) - class_name(node) do |cn| - next if allowed?(cn.const_name) - - add_offense(cn, message: format(MSG, class_name: cn.const_name)) - end - end - - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.loc.expression, "'#{node.source}'") - end - end - - private - - def allowed?(const_name) - ALLOWED_CONSTANTS.include?(const_name) - end - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/file_path.rb b/lib/rubocop/cop/rspec/file_path.rb deleted file mode 100644 index b4d81df62..000000000 --- a/lib/rubocop/cop/rspec/file_path.rb +++ /dev/null @@ -1,115 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module RSpec - # Checks that spec file paths are consistent with the test subject. - # - # Checks the path of the spec file and enforces that it reflects the - # described class/module and its optionally called out method. - # - # With the configuration option `IgnoreMethods` the called out method will - # be ignored when determining the enforced path. - # - # With the configuration option `CustomTransform` modules or classes can - # be specified that should not as usual be transformed from CamelCase to - # snake_case (e.g. 'RuboCop' => 'rubocop' ). - # - # @example - # # bad - # whatever_spec.rb # describe MyClass - # - # # bad - # my_class_spec.rb # describe MyClass, '#method' - # - # # good - # my_class_spec.rb # describe MyClass - # - # # good - # my_class_method_spec.rb # describe MyClass, '#method' - # - # # good - # my_class/method_spec.rb # describe MyClass, '#method' - # - # @example when configuration is `IgnoreMethods: true` - # # bad - # whatever_spec.rb # describe MyClass - # - # # good - # my_class_spec.rb # describe MyClass - # - # # good - # my_class_spec.rb # describe MyClass, '#method' - # - class FilePath < Cop - include RuboCop::RSpec::TopLevelDescribe - - MSG = 'Spec path should end with `%s`.' - - def_node_search :const_described?, '(send _ :describe (const ...) ...)' - def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))' - - def on_top_level_describe(node, args) - return unless const_described?(node) && single_top_level_describe? - return if routing_spec?(args) - - glob = glob_for(args) - - return if filename_ends_with?(glob) - - add_offense( - node, - message: format(MSG, suffix: glob) - ) - end - - private - - def routing_spec?(args) - args.any?(&method(:routing_metadata?)) - end - - def glob_for((described_class, method_name)) - "#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb" - end - - def name_glob(name) - return unless name&.str_type? - - "*#{name.str_content.gsub(/\W/, '')}" unless ignore_methods? - end - - def expected_path(constant) - File.join( - constant.const_name.split('::').map do |name| - custom_transform.fetch(name) { camel_to_snake_case(name) } - end - ) - end - - def camel_to_snake_case(string) - string - .gsub(/([^A-Z])([A-Z]+)/, '\1_\2') - .gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2') - .downcase - end - - def custom_transform - cop_config.fetch('CustomTransform', {}) - end - - def ignore_methods? - cop_config['IgnoreMethods'] - end - - def filename_ends_with?(glob) - File.fnmatch?("*#{glob}", processed_source.buffer.name) - end - - def relevant_rubocop_rspec_file?(_file) - true - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/focus.rb b/lib/rubocop/cop/rspec/focus.rb index 11413d4e5..5d247490b 100644 --- a/lib/rubocop/cop/rspec/focus.rb +++ b/lib/rubocop/cop/rspec/focus.rb @@ -5,6 +5,8 @@ module Cop module RSpec # Checks if examples are focused. # + # This cop does not support autocorrection in some cases. + # # @example # # bad # describe MyClass, focus: true do @@ -19,39 +21,99 @@ module RSpec # # good # describe MyClass do # end - class Focus < Cop - MSG = 'Focused spec found.' - - focusable = - ExampleGroups::GROUPS + - ExampleGroups::SKIPPED + - Examples::EXAMPLES + - Examples::SKIPPED + - Examples::PENDING + # + # # bad + # fdescribe 'test' do; end + # + # # good + # describe 'test' do; end + # + # # bad + # shared_examples 'test', focus: true do; end + # + # # good + # shared_examples 'test' do; end + # + # # bad + # shared_context 'test', focus: true do; end + # + # # good + # shared_context 'test' do; end + # + # # bad (does not support autocorrection) + # focus 'test' do; end + # + class Focus < Base + extend AutoCorrector + include RangeHelp - focused = ExampleGroups::FOCUSED + Examples::FOCUSED + MSG = 'Focused spec found.' - FOCUSABLE_SELECTORS = focusable.node_pattern_union + # @!method focusable_selector?(node) + def_node_matcher :focusable_selector?, <<~PATTERN + { + #ExampleGroups.regular + #ExampleGroups.skipped + #Examples.regular + #Examples.skipped + #Examples.pending + #SharedGroups.all + } + PATTERN - def_node_matcher :metadata, <<-PATTERN - {(send #{RSPEC} #{FOCUSABLE_SELECTORS} <$(sym :focus) ...>) - (send #{RSPEC} #{FOCUSABLE_SELECTORS} ... (hash <$(pair (sym :focus) true) ...>))} + # @!method metadata(node) + def_node_matcher :metadata, <<~PATTERN + {(send #rspec? #focusable_selector? <$(sym :focus) ...>) + (send #rspec? #focusable_selector? ... (hash <$(pair (sym :focus) true) ...>))} PATTERN - def_node_matcher :focused_block?, focused.send_pattern + # @!method focused_block?(node) + def_node_matcher :focused_block?, <<~PATTERN + (send #rspec? {#ExampleGroups.focused #Examples.focused} ...) + PATTERN def on_send(node) - focus_metadata(node) do |focus| - add_offense(focus) + return if node.chained? || node.each_ancestor(:any_def).any? + + if focused_block?(node) + on_focused_block(node) + else + metadata(node) do |focus| + on_metadata(focus) + end end end private - def focus_metadata(node, &block) - yield(node) if focused_block?(node) + def on_focused_block(node) + add_offense(node) do |corrector| + correct_send(corrector, node) + end + end + + def on_metadata(node) + add_offense(node) do |corrector| + corrector.remove(with_surrounding(node)) + end + end + + def with_surrounding(focus) + range_with_space = + range_with_surrounding_space(focus.source_range, side: :left) + + range_with_surrounding_comma(range_with_space, :left) + end + + def correct_send(corrector, focus) + range = focus.loc.selector + unfocused = focus.method_name.to_s.sub(/^f/, '') + unless Examples.regular(unfocused) || ExampleGroups.regular(unfocused) + return + end - metadata(node, &block) + corrector.replace(range, + range.source.sub(focus.method_name.to_s, unfocused)) end end end diff --git a/lib/rubocop/cop/rspec/hook_argument.rb b/lib/rubocop/cop/rspec/hook_argument.rb index 99411cd90..654dd036f 100644 --- a/lib/rubocop/cop/rspec/hook_argument.rb +++ b/lib/rubocop/cop/rspec/hook_argument.rb @@ -10,7 +10,7 @@ module RSpec # styles: "implicit", "each", and "example." All styles have # the same behavior. # - # @example when configuration is `EnforcedStyle: implicit` + # @example `EnforcedStyle: implicit` (default) # # bad # before(:each) do # # ... @@ -26,13 +26,13 @@ module RSpec # # ... # end # - # @example when configuration is `EnforcedStyle: each` + # @example `EnforcedStyle: each` # # bad # before(:example) do # # ... # end # - # # good + # # bad # before do # # ... # end @@ -42,7 +42,7 @@ module RSpec # # ... # end # - # @example when configuration is `EnforcedStyle: example` + # @example `EnforcedStyle: example` # # bad # before(:each) do # # ... @@ -57,20 +57,23 @@ module RSpec # before(:example) do # # ... # end - class HookArgument < Cop + # + class HookArgument < Base + extend AutoCorrector include ConfigurableEnforcedStyle - IMPLICIT_MSG = 'Omit the default `%p` ' \ - 'argument for RSpec hooks.' + IMPLICIT_MSG = 'Omit the default `%p` argument for RSpec hooks.' EXPLICIT_MSG = 'Use `%p` for RSpec hooks.' - HOOKS = Hooks::ALL.node_pattern_union.freeze - - def_node_matcher :scoped_hook, <<-PATTERN - (block $(send _ #{HOOKS} (sym ${:each :example})) ...) + # @!method scoped_hook(node) + def_node_matcher :scoped_hook, <<~PATTERN + (any_block $(send _ #Hooks.all (sym ${:each :example})) ...) PATTERN - def_node_matcher :unscoped_hook, "(block $(send _ #{HOOKS}) ...)" + # @!method unscoped_hook(node) + def_node_matcher :unscoped_hook, <<~PATTERN + (any_block $(send _ #Hooks.all) ...) + PATTERN def on_block(node) hook(node) do |method_send, scope_name| @@ -78,32 +81,36 @@ def on_block(node) return check_implicit(method_send) unless scope_name style_detected(scope_name) - add_offense( - method_send, - message: explicit_message(scope_name) - ) + msg = explicit_message(scope_name) + add_offense(method_send, message: msg) do |corrector| + autocorrect(corrector, node, method_send) + end end end - def autocorrect(node) - scope = implicit_style? ? '' : "(#{style.inspect})" - - lambda do |corrector| - corrector.replace(argument_range(node), scope) - end - end + alias on_numblock on_block private + def autocorrect(corrector, _node, method_send) + scope = implicit_style? ? '' : "(#{style.inspect})" + corrector.replace( + LocationHelp.arguments_with_whitespace(method_send), scope + ) + end + def check_implicit(method_send) style_detected(:implicit) return if implicit_style? - add_offense( - method_send, - location: :selector, - message: format(EXPLICIT_MSG, scope: style) - ) + msg = explicit_message(nil) + add_offense(method_send.loc.selector, message: msg) do |corrector| + scope = "(#{style.inspect})" + corrector.replace( + LocationHelp.arguments_with_whitespace(method_send), + scope + ) + end end def explicit_message(scope) @@ -121,12 +128,6 @@ def implicit_style? def hook(node, &block) scoped_hook(node, &block) || unscoped_hook(node, &block) end - - def argument_range(send_node) - send_node.loc.selector.end.with( - end_pos: send_node.loc.expression.end_pos - ) - end end end end diff --git a/lib/rubocop/cop/rspec/hooks_before_examples.rb b/lib/rubocop/cop/rspec/hooks_before_examples.rb index 0d4b94500..e8aeeebb5 100644 --- a/lib/rubocop/cop/rspec/hooks_before_examples.rb +++ b/lib/rubocop/cop/rspec/hooks_before_examples.rb @@ -6,8 +6,7 @@ module RSpec # Checks for before/around/after hooks that come after an example. # # @example - # # Bad - # + # # bad # it 'checks what foo does' do # expect(foo).to be # end @@ -15,7 +14,7 @@ module RSpec # before { prepare } # after { clean_up } # - # # Good + # # good # before { prepare } # after { clean_up } # @@ -23,16 +22,19 @@ module RSpec # expect(foo).to be # end # - class HooksBeforeExamples < Cop - include RangeHelp - include RuboCop::RSpec::FinalEndLocation + class HooksBeforeExamples < Base + extend AutoCorrector MSG = 'Move `%s` above the examples in the group.' - def_node_matcher :example_or_group?, <<-PATTERN + # @!method example_or_group?(node) + def_node_matcher :example_or_group?, <<~PATTERN { - #{(Examples::ALL + ExampleGroups::ALL).block_pattern} - #{Includes::EXAMPLES.send_pattern} + (any_block { + (send #rspec? #ExampleGroups.all ...) + (send nil? #Examples.all ...) + } ...) + (send nil? #Includes.examples ...) } PATTERN @@ -42,16 +44,7 @@ def on_block(node) check_hooks(node.body) if multiline_block?(node.body) end - def autocorrect(node) - lambda do |corrector| - first_example = find_first_example(node.parent) - first_example_pos = first_example.loc.expression - indent = "\n" + ' ' * first_example.loc.column - - corrector.insert_before(first_example_pos, source(node) + indent) - corrector.remove(node_range_with_surrounding_space(node)) - end - end + alias on_numblock on_block private @@ -63,14 +56,13 @@ def check_hooks(node) first_example = find_first_example(node) return unless first_example - node.each_child_node do |child| - next if child.sibling_index < first_example.sibling_index - next unless hook?(child) + first_example.right_siblings.each do |sibling| + next unless hook?(sibling) - add_offense( - child, - message: format(MSG, hook: child.method_name) - ) + msg = format(MSG, hook: sibling.method_name) + add_offense(sibling, message: msg) do |corrector| + autocorrect(corrector, sibling, first_example) + end end end @@ -78,17 +70,10 @@ def find_first_example(node) node.children.find { |sibling| example_or_group?(sibling) } end - def node_range_with_surrounding_space(node) - range = node_range(node) - range_by_whole_lines(range, include_final_newline: true) - end - - def source(node) - node_range(node).source - end - - def node_range(node) - node.loc.expression.with(end_pos: final_end_location(node).end_pos) + def autocorrect(corrector, node, first_example) + RuboCop::RSpec::Corrector::MoveNode.new( + node, corrector, processed_source + ).move_before(first_example) end end end diff --git a/lib/rubocop/cop/rspec/identical_equality_assertion.rb b/lib/rubocop/cop/rspec/identical_equality_assertion.rb new file mode 100644 index 000000000..7298415c9 --- /dev/null +++ b/lib/rubocop/cop/rspec/identical_equality_assertion.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Checks for equality assertions with identical expressions on both sides. + # + # @example + # # bad + # expect(foo.bar).to eq(foo.bar) + # expect(foo.bar).to eql(foo.bar) + # + # # good + # expect(foo.bar).to eq(2) + # expect(foo.bar).to eql(2) + # + class IdenticalEqualityAssertion < Base + MSG = 'Identical expressions on both sides of the equality ' \ + 'may indicate a flawed test.' + RESTRICT_ON_SEND = %i[to].freeze + + # @!method equality_check?(node) + def_node_matcher :equality_check?, <<~PATTERN + (send (send nil? :expect $_) :to + {(send nil? {:eql :eq :be} $_) + (send (send nil? :be) :== $_)}) + PATTERN + + def on_send(node) + equality_check?(node) do |left, right| + add_offense(node) if left == right + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/implicit_block_expectation.rb b/lib/rubocop/cop/rspec/implicit_block_expectation.rb index d77680029..55a00fc61 100644 --- a/lib/rubocop/cop/rspec/implicit_block_expectation.rb +++ b/lib/rubocop/cop/rspec/implicit_block_expectation.rb @@ -16,20 +16,24 @@ module RSpec # it 'changes something to a new value' do # expect { do_something }.to change(something).to(new_value) # end - class ImplicitBlockExpectation < Cop + # + class ImplicitBlockExpectation < Base MSG = 'Avoid implicit block expectations.' + RESTRICT_ON_SEND = %i[is_expected should should_not].freeze - def_node_matcher :lambda?, <<-PATTERN + # @!method lambda?(node) + def_node_matcher :lambda?, <<~PATTERN { (send (const nil? :Proc) :new) - (send nil? :proc) - (send nil? :lambda) + (send nil? {:proc :lambda}) } PATTERN + # @!method lambda_subject?(node) def_node_matcher :lambda_subject?, '(block #lambda? ...)' - def_node_matcher :implicit_expect, <<-PATTERN + # @!method implicit_expect(node) + def_node_matcher :implicit_expect, <<~PATTERN $(send nil? {:is_expected :should :should_not} ...) PATTERN diff --git a/lib/rubocop/cop/rspec/implicit_expect.rb b/lib/rubocop/cop/rspec/implicit_expect.rb index ca3184bf8..1c46715df 100644 --- a/lib/rubocop/cop/rspec/implicit_expect.rb +++ b/lib/rubocop/cop/rspec/implicit_expect.rb @@ -8,8 +8,7 @@ module RSpec # This cop can be configured using the `EnforcedStyle` option # and supports the `--auto-gen-config` flag. # - # @example `EnforcedStyle: is_expected` - # + # @example `EnforcedStyle: is_expected` (default) # # bad # it { should be_truthy } # @@ -17,22 +16,25 @@ module RSpec # it { is_expected.to be_truthy } # # @example `EnforcedStyle: should` - # # # bad # it { is_expected.to be_truthy } # # # good # it { should be_truthy } # - class ImplicitExpect < Cop + class ImplicitExpect < Base + extend AutoCorrector include ConfigurableEnforcedStyle MSG = 'Prefer `%s` over `%s`.' - def_node_matcher :implicit_expect, <<-PATTERN + RESTRICT_ON_SEND = Runners.all + %i[should should_not] + + # @!method implicit_expect(node) + def_node_matcher :implicit_expect, <<~PATTERN { (send nil? ${:should :should_not} ...) - (send (send nil? $:is_expected) #{Runners::ALL.node_pattern_union} ...) + (send (send nil? $:is_expected) #Runners.all ...) } PATTERN @@ -44,7 +46,7 @@ class ImplicitExpect < Cop ENFORCED_REPLACEMENTS = alternatives.merge(alternatives.invert).freeze - def on_send(node) # rubocop:disable Metrics/MethodLength + def on_send(node) return unless (source_range = offending_expect(node)) expectation_source = source_range.source @@ -54,20 +56,11 @@ def on_send(node) # rubocop:disable Metrics/MethodLength else opposite_style_detected - add_offense( - node, - location: source_range, - message: offense_message(expectation_source) - ) - end - end - - def autocorrect(node) - lambda do |corrector| - offense = offending_expect(node) - replacement = replacement_source(offense.source) - - corrector.replace(offense, replacement) + msg = offense_message(expectation_source) + add_offense(source_range, message: msg) do |corrector| + replacement = replacement_source(expectation_source) + corrector.replace(source_range, replacement) + end end end @@ -76,13 +69,13 @@ def autocorrect(node) def offending_expect(node) case implicit_expect(node) when :is_expected - is_expected_range(node.loc) + range_for_is_expected(node.loc) when :should, :should_not node.loc.selector end end - def is_expected_range(source_map) # rubocop:disable Naming/PredicateName + def range_for_is_expected(source_map) Parser::Source::Range.new( source_map.expression.source_buffer, source_map.expression.begin_pos, diff --git a/lib/rubocop/cop/rspec/implicit_subject.rb b/lib/rubocop/cop/rspec/implicit_subject.rb index b3deba158..f9395ab1e 100644 --- a/lib/rubocop/cop/rspec/implicit_subject.rb +++ b/lib/rubocop/cop/rspec/implicit_subject.rb @@ -7,7 +7,7 @@ module RSpec # # This cop can be configured using the `EnforcedStyle` option # - # @example `EnforcedStyle: single_line_only` + # @example `EnforcedStyle: single_line_only` (default) # # bad # it do # is_expected.to be_truthy @@ -19,6 +19,22 @@ module RSpec # expect(subject).to be_truthy # end # + # @example `EnforcedStyle: single_statement_only` + # # bad + # it do + # foo = 1 + # is_expected.to be_truthy + # end + # + # # good + # it do + # foo = 1 + # expect(subject).to be_truthy + # end + # it do + # is_expected.to be_truthy + # end + # # @example `EnforcedStyle: disallow` # # bad # it { is_expected.to be_truthy } @@ -26,49 +42,131 @@ module RSpec # # good # it { expect(subject).to be_truthy } # - class ImplicitSubject < Cop + # @example `EnforcedStyle: require_implicit` + # # bad + # it { expect(subject).to be_truthy } + # + # # good + # it { is_expected.to be_truthy } + # + # # bad + # it do + # expect(subject).to be_truthy + # end + # + # # good + # it do + # is_expected.to be_truthy + # end + # + # # good + # it { expect(named_subject).to be_truthy } + # + class ImplicitSubject < Base + extend AutoCorrector include ConfigurableEnforcedStyle - MSG = "Don't use implicit subject." + MSG_REQUIRE_EXPLICIT = "Don't use implicit subject." + + MSG_REQUIRE_IMPLICIT = "Don't use explicit subject." + + RESTRICT_ON_SEND = %i[ + expect + is_expected + should + should_not + ].freeze + + # @!method explicit_unnamed_subject?(node) + def_node_matcher :explicit_unnamed_subject?, <<~PATTERN + (send nil? :expect (send nil? :subject)) + PATTERN - def_node_matcher :implicit_subject?, <<-PATTERN + # @!method implicit_subject?(node) + def_node_matcher :implicit_subject?, <<~PATTERN (send nil? {:should :should_not :is_expected} ...) PATTERN def on_send(node) - return unless implicit_subject?(node) - return if valid_usage?(node) + return unless invalid?(node) + + add_offense(node) do |corrector| + autocorrect(corrector, node) + end + end + + private + + def autocorrect(corrector, node) + case node.method_name + when :expect + corrector.replace(node, 'is_expected') + when :is_expected + corrector.replace(node.location.selector, 'expect(subject)') + when :should + corrector.replace(node.location.selector, 'expect(subject).to') + when :should_not + corrector.replace(node.location.selector, 'expect(subject).not_to') + else + # :nocov: + :noop + # :nocov: + end + end - add_offense(node) + def message(_node) + case style + when :require_implicit + MSG_REQUIRE_IMPLICIT + else + MSG_REQUIRE_EXPLICIT + end end - def autocorrect(node) - replacement = 'expect(subject)' - if node.method_name == :should - replacement += '.to' - elsif node.method_name == :should_not - replacement += '.not_to' + def invalid?(node) + case style + when :require_implicit + explicit_unnamed_subject?(node) + when :disallow + implicit_subject_in_non_its?(node) + when :single_line_only + implicit_subject_in_non_its_and_non_single_line?(node) + when :single_statement_only + implicit_subject_in_non_its_and_non_single_statement?(node) + else + # :nocov: + :noop + # :nocov: end + end - ->(corrector) { corrector.replace(node.loc.selector, replacement) } + def implicit_subject_in_non_its?(node) + implicit_subject?(node) && !its?(node) end - private + def implicit_subject_in_non_its_and_non_single_line?(node) + implicit_subject_in_non_its?(node) && !single_line?(node) + end - def valid_usage?(node) - example = node.ancestors.find { |parent| example?(parent) } - return false if example.nil? + def implicit_subject_in_non_its_and_non_single_statement?(node) + implicit_subject_in_non_its?(node) && !single_statement?(node) + end - example.method_name == :its || allowed_by_style?(example) + def its?(node) + example_of(node)&.method?(:its) end - def allowed_by_style?(example) - if style == :single_line_only - example.single_line? - elsif style == :single_statement_only - !example.body.begin_type? - else - false + def single_line?(node) + example_of(node)&.single_line? + end + + def single_statement?(node) + !example_of(node)&.body&.begin_type? + end + + def example_of(node) + node.each_ancestor.find do |ancestor| + example?(ancestor) end end end diff --git a/lib/rubocop/cop/rspec/include_examples.rb b/lib/rubocop/cop/rspec/include_examples.rb new file mode 100644 index 000000000..81b44ccf3 --- /dev/null +++ b/lib/rubocop/cop/rspec/include_examples.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Checks for usage of `include_examples`. + # + # `include_examples`, unlike `it_behaves_like`, does not create its + # own context. As such, using `subject`, `let`, `before`/`after`, etc. + # within shared examples included with `include_examples` can have + # unexpected behavior and side effects. + # + # Prefer using `it_behaves_like` instead. + # + # @safety + # `include_examples` and `it_behaves_like` have different scoping + # behaviors. + # Changing `include_examples` to `it_behaves_like` creates a new + # context, altering setup dependencies, which can lead to unexpected + # test failures. + # Specifically, the scope of hooks (`before`, `after`, `around`) + # changes, which may prevent expected setup from being inherited + # correctly. + # + # Additionally, `let` and `subject` are affected by scoping rules. + # When `include_examples` is used, `let` and `subject` defined within + # `shared_examples` are evaluated in the caller's context, allowing + # access to their values. + # In contrast, `it_behaves_like` creates a new context, preventing + # access to `let` or `subject` values from the caller's context. + # + # [source,ruby] + # ---- + # shared_examples "mock behavior" do + # before do + # allow(service).to receive(:call).and_return("mocked response") + # end + # + # it "returns mocked response" do + # expect(service.call).to eq "mocked response" + # end + # end + # + # context "working example with include_examples" do + # let(:service) { double(:service) } + # + # include_examples "mock behavior" + # + # it "uses the mocked service" do + # expect(service.call).to eq "mocked response" # Passes + # end + # end + # + # context "broken example with it_behaves_like" do + # let(:service) { double(:service) } + # + # it_behaves_like "mock behavior" + # + # it "unexpectedly does not use the mocked service" do + # # Fails because `it_behaves_like` does not apply the mock setup + # expect(service.call).to eq "mocked response" + # end + # end + # ---- + # + # @example + # # bad + # include_examples 'examples' + # + # # good + # it_behaves_like 'examples' + # + class IncludeExamples < Base + extend AutoCorrector + + MSG = 'Prefer `it_behaves_like` over `include_examples`.' + + RESTRICT_ON_SEND = %i[include_examples].freeze + + def on_send(node) + selector = node.loc.selector + + add_offense(selector) do |corrector| + corrector.replace(selector, 'it_behaves_like') + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/indexed_let.rb b/lib/rubocop/cop/rspec/indexed_let.rb new file mode 100644 index 000000000..1afc46112 --- /dev/null +++ b/lib/rubocop/cop/rspec/indexed_let.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Do not set up test data using indexes (e.g., `item_1`, `item_2`). + # + # It makes reading the test harder because it's not clear what exactly + # is tested by this particular example. + # + # The configurable options `AllowedIdentifiers` and `AllowedPatterns` + # will also read those set in `Naming/VariableNumber`. + # + # @example `Max: 1 (default)` + # # bad + # let(:item_1) { create(:item) } + # let(:item_2) { create(:item) } + # + # let(:item1) { create(:item) } + # let(:item2) { create(:item) } + # + # # good + # + # let(:visible_item) { create(:item, visible: true) } + # let(:invisible_item) { create(:item, visible: false) } + # + # @example `Max: 2` + # # bad + # let(:item_1) { create(:item) } + # let(:item_2) { create(:item) } + # let(:item_3) { create(:item) } + # + # # good + # let(:item_1) { create(:item) } + # let(:item_2) { create(:item) } + # + # @example `AllowedIdentifiers: ['item_1', 'item_2']` + # # good + # let(:item_1) { create(:item) } + # let(:item_2) { create(:item) } + # + # @example `AllowedPatterns: ['item']` + # # good + # let(:item_1) { create(:item) } + # let(:item_2) { create(:item) } + # + class IndexedLet < Base + include AllowedIdentifiers + include AllowedPattern + + MSG = 'This `let` statement uses `%s` in its name. ' \ + 'Please give it a meaningful name.' + + # @!method let_name(node) + def_node_matcher :let_name, <<~PATTERN + { + (block (send nil? #Helpers.all ({str sym} $_) ...) ...) + (send nil? #Helpers.all ({str sym} $_) block_pass) + } + PATTERN + + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless spec_group?(node) + + children = node.body&.child_nodes + return unless children + + filter_indexed_lets(children).each do |let_node| + index = let_name(let_node)[INDEX_REGEX] + add_offense(let_node, message: format(MSG, index: index)) + end + end + + private + + SUFFIX_INDEX_REGEX = /_?\d+$/.freeze + private_constant :SUFFIX_INDEX_REGEX + INDEX_REGEX = /\d+/.freeze + private_constant :INDEX_REGEX + + def filter_indexed_lets(candidates) + candidates + .filter { |node| indexed_let?(node) } + .group_by { |node| let_name_stripped_index(node) } + .values + .filter { |lets| lets.length > cop_config['Max'] } + .flatten + end + + def indexed_let?(node) + let?(node) && + SUFFIX_INDEX_REGEX.match?(let_name(node)) && + !allowed_identifier?(let_name(node).to_s) && + !matches_allowed_pattern?(let_name(node).to_s) + end + + def let_name_stripped_index(node) + let_name(node).to_s.gsub(INDEX_REGEX, '') + end + + def cop_config_patterns_values + Array(config.for_cop('Naming/VariableNumber') + .fetch('AllowedPatterns', [])) + + Array(cop_config.fetch('AllowedPatterns', [])) + end + + def allowed_identifiers + Array(config.for_cop('Naming/VariableNumber') + .fetch('AllowedIdentifiers', [])) + + Array(cop_config.fetch('AllowedIdentifiers', [])) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/instance_spy.rb b/lib/rubocop/cop/rspec/instance_spy.rb index 7b80c167a..cca5a1e6a 100644 --- a/lib/rubocop/cop/rspec/instance_spy.rb +++ b/lib/rubocop/cop/rspec/instance_spy.rb @@ -18,18 +18,22 @@ module RSpec # expect(foo).to have_received(:bar) # end # - class InstanceSpy < Cop - MSG = 'Use `instance_spy` when you check your double '\ + class InstanceSpy < Base + extend AutoCorrector + + MSG = 'Use `instance_spy` when you check your double ' \ 'with `have_received`.' - def_node_search :null_double, <<-PATTERN + # @!method null_double(node) + def_node_search :null_double, <<~PATTERN (lvasgn $_ (send $(send nil? :instance_double ...) :as_null_object)) PATTERN - def_node_search :have_received_usage, <<-PATTERN + # @!method have_received_usage(node) + def_node_search :have_received_usage, <<~PATTERN (send (send nil? :expect (lvar $_)) :to @@ -38,27 +42,31 @@ class InstanceSpy < Cop ...) PATTERN - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless example?(node) null_double(node) do |var, receiver| have_received_usage(node) do |expected| - add_offense(receiver) if expected == var + next if expected != var + + add_offense(receiver) do |corrector| + autocorrect(corrector, receiver) + end end end end - def autocorrect(node) - lambda do |corrector| - replacement = 'instance_spy' - corrector.replace(node.loc.selector, replacement) + private - double_source_map = node.parent.loc - as_null_object_range = double_source_map - .dot - .join(double_source_map.selector) - corrector.remove(as_null_object_range) - end + def autocorrect(corrector, node) + replacement = 'instance_spy' + corrector.replace(node.loc.selector, replacement) + + double_source_map = node.parent.loc + as_null_object_range = double_source_map + .dot + .join(double_source_map.selector) + corrector.remove(as_null_object_range) end end end diff --git a/lib/rubocop/cop/rspec/instance_variable.rb b/lib/rubocop/cop/rspec/instance_variable.rb index 3dfec9859..181619b83 100644 --- a/lib/rubocop/cop/rspec/instance_variable.rb +++ b/lib/rubocop/cop/rspec/instance_variable.rb @@ -24,10 +24,9 @@ module RSpec # end # # @example with AssignmentOnly configuration - # # # rubocop.yml # # RSpec/InstanceVariable: - # # AssignmentOnly: false + # # AssignmentOnly: true # # # bad # describe MyClass do @@ -46,32 +45,32 @@ module RSpec # it { expect(foo).to be_empty } # end # - class InstanceVariable < Cop - MSG = 'Avoid instance variables – use let, ' \ - 'a method call, or a local variable (if possible).' - - EXAMPLE_GROUP_METHODS = ExampleGroups::ALL + SharedGroups::ALL + class InstanceVariable < Base + include TopLevelGroup - def_node_matcher :spec_group?, EXAMPLE_GROUP_METHODS.block_pattern + MSG = 'Avoid instance variables - use let, ' \ + 'a method call, or a local variable (if possible).' - def_node_matcher :dynamic_class?, <<-PATTERN + # @!method dynamic_class?(node) + def_node_matcher :dynamic_class?, <<~PATTERN (block (send (const nil? :Class) :new ...) ...) PATTERN - def_node_matcher :custom_matcher?, <<-PATTERN + # @!method custom_matcher?(node) + def_node_matcher :custom_matcher?, <<~PATTERN (block { (send nil? :matcher sym) (send (const (const nil? :RSpec) :Matchers) :define sym) } ...) PATTERN + # @!method ivar_usage(node) def_node_search :ivar_usage, '$(ivar $_)' + # @!method ivar_assigned?(node) def_node_search :ivar_assigned?, '(ivasgn % ...)' - def on_block(node) - return unless spec_group?(node) - + def on_top_level_group(node) ivar_usage(node) do |ivar, name| next if valid_usage?(ivar) next if assignment_only? && !ivar_assigned?(node, name) diff --git a/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb b/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb deleted file mode 100644 index c0944de78..000000000 --- a/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module RSpec - # Checks invalid usage for predicate matcher. - # - # Predicate matcher does not need a question. - # This cop checks an unnecessary question in predicate matcher. - # - # @example - # - # # bad - # expect(foo).to be_something? - # - # # good - # expect(foo).to be_something - class InvalidPredicateMatcher < Cop - MSG = 'Omit `?` from `%s`.' - - def_node_matcher :invalid_predicate_matcher?, <<-PATTERN - (send (send nil? :expect ...) #{Runners::ALL.node_pattern_union} $(send nil? #predicate?)) - PATTERN - - def on_send(node) - invalid_predicate_matcher?(node) do |predicate| - add_offense(predicate) - end - end - - private - - def predicate?(name) - name = name.to_s - name.start_with?('be_', 'have_') && name.end_with?('?') - end - - def message(predicate) - format(MSG, matcher: predicate.method_name) - end - end - end - end -end diff --git a/lib/rubocop/cop/rspec/is_expected_specify.rb b/lib/rubocop/cop/rspec/is_expected_specify.rb new file mode 100644 index 000000000..dd03383f9 --- /dev/null +++ b/lib/rubocop/cop/rspec/is_expected_specify.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Check for `specify` with `is_expected` and one-liner expectations. + # + # @example + # # bad + # specify { is_expected.to be_truthy } + # + # # good + # it { is_expected.to be_truthy } + # + # # good + # specify do + # # ... + # end + # specify { expect(sqrt(4)).to eq(2) } + # + class IsExpectedSpecify < Base + extend AutoCorrector + + RESTRICT_ON_SEND = %i[specify].freeze + IS_EXPECTED_METHODS = ::Set[:is_expected, :are_expected].freeze + MSG = 'Use `it` instead of `specify`.' + + # @!method offense?(node) + def_node_matcher :offense?, <<~PATTERN + (block (send _ :specify) _ (send (send _ IS_EXPECTED_METHODS) ...)) + PATTERN + + def on_send(node) + block_node = node.parent + return unless block_node&.single_line? && offense?(block_node) + + selector = node.loc.selector + add_offense(selector) do |corrector| + corrector.replace(selector, 'it') + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/it_behaves_like.rb b/lib/rubocop/cop/rspec/it_behaves_like.rb index 6da6f241d..a8f76cbac 100644 --- a/lib/rubocop/cop/rspec/it_behaves_like.rb +++ b/lib/rubocop/cop/rspec/it_behaves_like.rb @@ -5,37 +5,39 @@ module Cop module RSpec # Checks that only one `it_behaves_like` style is used. # - # @example when configuration is `EnforcedStyle: it_behaves_like` + # @example `EnforcedStyle: it_behaves_like` (default) # # bad # it_should_behave_like 'a foo' # # # good # it_behaves_like 'a foo' # - # @example when configuration is `EnforcedStyle: it_should_behave_like` + # @example `EnforcedStyle: it_should_behave_like` # # bad # it_behaves_like 'a foo' # # # good # it_should_behave_like 'a foo' - class ItBehavesLike < Cop + # + class ItBehavesLike < Base + extend AutoCorrector include ConfigurableEnforcedStyle - MSG = 'Prefer `%s` over `%s` when including '\ + MSG = 'Prefer `%s` over `%s` when including ' \ 'examples in a nested context.' + RESTRICT_ON_SEND = %i[it_behaves_like it_should_behave_like].freeze + # @!method example_inclusion_offense(node) def_node_matcher :example_inclusion_offense, '(send _ % ...)' def on_send(node) example_inclusion_offense(node, alternative_style) do - add_offense(node) + add_offense(node) do |corrector| + corrector.replace(node.loc.selector, style.to_s) + end end end - def autocorrect(node) - ->(corrector) { corrector.replace(node.loc.selector, style.to_s) } - end - private def message(_node) diff --git a/lib/rubocop/cop/rspec/iterated_expectation.rb b/lib/rubocop/cop/rspec/iterated_expectation.rb index 0da8ec134..2eb7ee6aa 100644 --- a/lib/rubocop/cop/rspec/iterated_expectation.rb +++ b/lib/rubocop/cop/rspec/iterated_expectation.rb @@ -15,11 +15,13 @@ module RSpec # it 'validates users' do # expect([user1, user2, user3]).to all(be_valid) # end - class IteratedExpectation < Cop + # + class IteratedExpectation < Base MSG = 'Prefer using the `all` matcher instead ' \ - 'of iterating over an array.' + 'of iterating over an array.' - def_node_matcher :each?, <<-PATTERN + # @!method each?(node) + def_node_matcher :each?, <<~PATTERN (block (send ... :each) (args (arg $_)) @@ -27,7 +29,15 @@ class IteratedExpectation < Cop ) PATTERN - def_node_matcher :expectation?, <<-PATTERN + # @!method each_numblock?(node) + def_node_matcher :each_numblock?, <<~PATTERN + (numblock + (send ... :each) _ $(...) + ) + PATTERN + + # @!method expectation?(node) + def_node_matcher :expectation?, <<~PATTERN (send (send nil? :expect (lvar %)) :to ...) PATTERN @@ -39,6 +49,14 @@ def on_block(node) end end + def on_numblock(node) + each_numblock?(node) do |body| + if single_expectation?(body, :_1) || only_expectations?(body, :_1) + add_offense(node.send_node) + end + end + end + private def single_expectation?(body, arg) @@ -46,6 +64,8 @@ def single_expectation?(body, arg) end def only_expectations?(body, arg) + return false unless body.each_child_node.any? + body.each_child_node.all? { |child| expectation?(child, arg) } end end diff --git a/lib/rubocop/cop/rspec/leading_subject.rb b/lib/rubocop/cop/rspec/leading_subject.rb index 513163685..a266ef49d 100644 --- a/lib/rubocop/cop/rspec/leading_subject.rb +++ b/lib/rubocop/cop/rspec/leading_subject.rb @@ -7,82 +7,78 @@ module RSpec # # @example # # bad - # let(:params) { blah } - # subject { described_class.new(params) } + # let(:params) { blah } + # subject { described_class.new(params) } # - # before { do_something } - # subject { described_class.new(params) } + # before { do_something } + # subject { described_class.new(params) } # - # it { expect_something } - # subject { described_class.new(params) } - # it { expect_something_else } + # it { expect_something } + # subject { described_class.new(params) } + # it { expect_something_else } # # # # good - # subject { described_class.new(params) } - # let(:params) { blah } + # subject { described_class.new(params) } + # let(:params) { blah } # # # good - # subject { described_class.new(params) } - # before { do_something } + # subject { described_class.new(params) } + # before { do_something } # # # good - # subject { described_class.new(params) } - # it { expect_something } - # it { expect_something_else } + # subject { described_class.new(params) } + # it { expect_something } + # it { expect_something_else } # - class LeadingSubject < Cop - include RangeHelp + class LeadingSubject < Base + extend AutoCorrector + include InsideExampleGroup MSG = 'Declare `subject` above any other `%s` declarations.' - def on_block(node) - return unless subject?(node) && !in_spec_block?(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless subject?(node) + return unless inside_example_group?(node) check_previous_nodes(node) end + private + def check_previous_nodes(node) - node.parent.each_child_node do |sibling| - if offending?(sibling) - add_offense( - node, - message: format(MSG, offending: sibling.method_name) - ) + offending_node(node) do |offender| + msg = format(MSG, offending: offender.method_name) + add_offense(node, message: msg) do |corrector| + autocorrect(corrector, node, offender) end - - break if offending?(sibling) || sibling.equal?(node) end end - def autocorrect(node) - lambda do |corrector| - first_node = find_first_offending_node(node) - first_node_position = first_node.loc.expression - indent = "\n" + ' ' * first_node.loc.column - corrector.insert_before(first_node_position, node.source + indent) - corrector.remove(node_range(node)) - end - end + def offending_node(node) + parent(node).each_child_node.find do |sibling| + break if sibling.equal?(node) - private - - def offending?(node) - let?(node) || hook?(node) || example?(node) + yield sibling if offending?(sibling) + end end - def find_first_offending_node(node) - node.parent.children.find { |sibling| offending?(sibling) } + def parent(node) + node.each_ancestor(:block).first.body end - def node_range(node) - range_by_whole_lines(node.source_range, include_final_newline: true) + def autocorrect(corrector, node, sibling) + RuboCop::RSpec::Corrector::MoveNode.new( + node, corrector, processed_source + ).move_before(sibling) end - def in_spec_block?(node) - node.each_ancestor(:block).any? do |ancestor| - example?(ancestor) - end + def offending?(node) + let?(node) || + hook?(node) || + example?(node) || + spec_group?(node) || + include?(node) end end end diff --git a/lib/rubocop/cop/rspec/leaky_constant_declaration.rb b/lib/rubocop/cop/rspec/leaky_constant_declaration.rb index 155aef735..bb651d325 100644 --- a/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +++ b/lib/rubocop/cop/rspec/leaky_constant_declaration.rb @@ -10,14 +10,14 @@ module RSpec # # If several examples may define a `DummyClass`, instead of being a # blank slate class as it will be in the first example, subsequent - # examples will be reopening it and modifying its behaviour in + # examples will be reopening it and modifying its behavior in # unpredictable ways. # Even worse when a class that exists in the codebase is reopened. # # Anonymous classes are fine, since they don't result in global # namespace name clashes. # - # @see https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants + # @see https://rspec.info/features/3-12/rspec-mocks/mutating-constants # # @example Constants leak between examples # # bad @@ -93,25 +93,28 @@ module RSpec # stub_const('SomeModule::SomeClass', foo_class) # end # end - class LeakyConstantDeclaration < Cop + class LeakyConstantDeclaration < Base MSG_CONST = 'Stub constant instead of declaring explicitly.' MSG_CLASS = 'Stub class constant instead of declaring explicitly.' MSG_MODULE = 'Stub module constant instead of declaring explicitly.' def on_casgn(node) return unless inside_describe_block?(node) + return if explicit_namespace?(node.namespace) add_offense(node, message: MSG_CONST) end def on_class(node) return unless inside_describe_block?(node) + return if explicit_namespace?(node.identifier.namespace) add_offense(node, message: MSG_CLASS) end def on_module(node) return unless inside_describe_block?(node) + return if explicit_namespace?(node.identifier.namespace) add_offense(node, message: MSG_MODULE) end @@ -119,11 +122,12 @@ def on_module(node) private def inside_describe_block?(node) - node.each_ancestor(:block).any?(&method(:in_example_or_shared_group?)) + node.each_ancestor(:block).any? { |ancestor| spec_group?(ancestor) } end - def_node_matcher :in_example_or_shared_group?, - (ExampleGroups::ALL + SharedGroups::ALL).block_pattern + def explicit_namespace?(namespace) + !namespace.nil? + end end end end diff --git a/lib/rubocop/cop/rspec/let_before_examples.rb b/lib/rubocop/cop/rspec/let_before_examples.rb index 5efb30d32..9a220bee5 100644 --- a/lib/rubocop/cop/rspec/let_before_examples.rb +++ b/lib/rubocop/cop/rspec/let_before_examples.rb @@ -6,7 +6,7 @@ module RSpec # Checks for `let` definitions that come after an example. # # @example - # # Bad + # # bad # let(:foo) { bar } # # it 'checks what foo does' do @@ -19,7 +19,7 @@ module RSpec # expect(some).to be # end # - # # Good + # # good # let(:foo) { bar } # let(:some) { other } # @@ -30,38 +30,43 @@ module RSpec # it 'checks what some does' do # expect(some).to be # end - class LetBeforeExamples < Cop - include RangeHelp - include RuboCop::RSpec::FinalEndLocation + class LetBeforeExamples < Base + extend AutoCorrector MSG = 'Move `let` before the examples in the group.' - def_node_matcher :example_or_group?, <<-PATTERN + # @!method example_or_group?(node) + def_node_matcher :example_or_group?, <<~PATTERN { - #{(Examples::ALL + ExampleGroups::ALL).block_pattern} - #{Includes::EXAMPLES.send_pattern} + (block (send nil? {#ExampleGroups.all #Examples.all} ...) ...) + (send nil? #Includes.examples ...) } PATTERN - def on_block(node) - return unless example_group_with_body?(node) + # @!method include_examples?(node) + def_node_matcher :include_examples?, <<~PATTERN + { + (block (send nil? :include_examples ...) ...) + (send nil? :include_examples ...) + } + PATTERN - check_let_declarations(node.body) if multiline_block?(node.body) + def self.autocorrect_incompatible_with + [RSpec::ScatteredLet] end - def autocorrect(node) - lambda do |corrector| - first_example = find_first_example(node.parent) - first_example_pos = first_example.loc.expression - indent = "\n" + ' ' * first_example.loc.column + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless example_group_with_body?(node) - corrector.insert_before(first_example_pos, source(node) + indent) - corrector.remove(node_range_with_surrounding_space(node)) - end + check_let_declarations(node.body) if multiline_block?(node.body) end private + def example_group_with_include_examples?(body) + body.children.any? { |sibling| include_examples?(sibling) } + end + def multiline_block?(block) block.begin_type? end @@ -70,10 +75,14 @@ def check_let_declarations(node) first_example = find_first_example(node) return unless first_example - node.each_child_node do |child| - next if child.sibling_index < first_example.sibling_index + correct = !example_group_with_include_examples?(node) - add_offense(child) if let?(child) + first_example.right_siblings.each do |sibling| + next unless let?(sibling) + + add_offense(sibling) do |corrector| + autocorrect(corrector, sibling, first_example) if correct + end end end @@ -81,17 +90,10 @@ def find_first_example(node) node.children.find { |sibling| example_or_group?(sibling) } end - def node_range_with_surrounding_space(node) - range = node_range(node) - range_by_whole_lines(range, include_final_newline: true) - end - - def source(node) - node_range(node).source - end - - def node_range(node) - node.loc.expression.with(end_pos: final_end_location(node).end_pos) + def autocorrect(corrector, node, first_example) + RuboCop::RSpec::Corrector::MoveNode.new( + node, corrector, processed_source + ).move_before(first_example) end end end diff --git a/lib/rubocop/cop/rspec/let_setup.rb b/lib/rubocop/cop/rspec/let_setup.rb index b98a67551..ba3f4ee21 100644 --- a/lib/rubocop/cop/rspec/let_setup.rb +++ b/lib/rubocop/cop/rspec/let_setup.rb @@ -6,36 +6,49 @@ module RSpec # Checks unreferenced `let!` calls being used for test setup. # # @example - # # Bad + # # bad # let!(:my_widget) { create(:widget) } # # it 'counts widgets' do # expect(Widget.count).to eq(1) # end # - # # Good + # # good # it 'counts widgets' do # create(:widget) # expect(Widget.count).to eq(1) # end # - # # Good + # # good # before { create(:widget) } # # it 'counts widgets' do # expect(Widget.count).to eq(1) # end - class LetSetup < Cop + class LetSetup < Base MSG = 'Do not use `let!` to setup objects not referenced in tests.' - def_node_search :let_bang, <<-PATTERN - (block $(send nil? :let! (sym $_)) args ...) + # @!method example_or_shared_group_or_including?(node) + def_node_matcher :example_or_shared_group_or_including?, <<~PATTERN + (block { + (send #rspec? {#SharedGroups.all #ExampleGroups.all} ...) + (send nil? #Includes.all ...) + } ...) PATTERN + # @!method let_bang(node) + def_node_matcher :let_bang, <<~PATTERN + { + (block $(send nil? :let! {(sym $_) (str $_)}) ...) + $(send nil? :let! {(sym $_) (str $_)} block_pass) + } + PATTERN + + # @!method method_called?(node) def_node_search :method_called?, '(send nil? %)' - def on_block(node) - return unless example_group?(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless example_or_shared_group_or_including?(node) unused_let_bang(node) do |let| add_offense(let) @@ -45,8 +58,14 @@ def on_block(node) private def unused_let_bang(node) - let_bang(node) do |method_send, method_name| - yield(method_send) unless method_called?(node, method_name) + child_let_bang(node) do |method_send, method_name| + yield(method_send) unless method_called?(node, method_name.to_sym) + end + end + + def child_let_bang(node, &block) + RuboCop::RSpec::ExampleGroup.new(node).lets.each do |let| + let_bang(let, &block) end end end diff --git a/lib/rubocop/cop/rspec/match_array.rb b/lib/rubocop/cop/rspec/match_array.rb new file mode 100644 index 000000000..17e32a907 --- /dev/null +++ b/lib/rubocop/cop/rspec/match_array.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Checks where `match_array` is used. + # + # This cop checks for the following: + # + # - Prefer `contain_exactly` when matching an array with values. + # - Prefer `eq` when using `match_array` with an empty array literal. + # + # @example + # # bad + # it { is_expected.to match_array([content1, content2]) } + # + # # good + # it { is_expected.to contain_exactly(content1, content2) } + # + # # good + # it { is_expected.to match_array([content] + array) } + # + # # good + # it { is_expected.to match_array(%w(tremble in fear foolish mortals)) } + # + class MatchArray < Base + extend AutoCorrector + + MSG = 'Prefer `contain_exactly` when matching an array literal.' + RESTRICT_ON_SEND = %i[match_array].freeze + + # @!method match_array_with_empty_array?(node) + def_node_matcher :match_array_with_empty_array?, <<~PATTERN + (send nil? :match_array (array)) + PATTERN + + def on_send(node) + return unless node.first_argument&.array_type? + return if match_array_with_empty_array?(node) + + check_populated_array(node) + end + + private + + def check_populated_array(node) + return if node.first_argument.percent_literal? + + add_offense(node) do |corrector| + array_contents = node.arguments.flat_map(&:to_a) + corrector.replace( + node, + "contain_exactly(#{array_contents.map(&:source).join(', ')})" + ) + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec/message_chain.rb b/lib/rubocop/cop/rspec/message_chain.rb index 0e9c020a2..14a09bb3a 100644 --- a/lib/rubocop/cop/rspec/message_chain.rb +++ b/lib/rubocop/cop/rspec/message_chain.rb @@ -9,23 +9,18 @@ module RSpec # # bad # allow(foo).to receive_message_chain(:bar, :baz).and_return(42) # - # # better + # # good # thing = Thing.new(baz: 42) # allow(foo).to receive(:bar).and_return(thing) # - class MessageChain < Cop + class MessageChain < Base MSG = 'Avoid stubbing using `%s`.' - - def_node_matcher :message_chain, <<-PATTERN - (send _ {:receive_message_chain :stub_chain} ...) - PATTERN + RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze def on_send(node) - message_chain(node) { add_offense(node, location: :selector) } - end - - def message(node) - format(MSG, method: node.method_name) + add_offense( + node.loc.selector, message: format(MSG, method: node.method_name) + ) end end end diff --git a/lib/rubocop/cop/rspec/message_expectation.rb b/lib/rubocop/cop/rspec/message_expectation.rb index 14b80996e..d3cb45ce1 100644 --- a/lib/rubocop/cop/rspec/message_expectation.rb +++ b/lib/rubocop/cop/rspec/message_expectation.rb @@ -8,7 +8,7 @@ module RSpec # This cop can be configured in your configuration using the # `EnforcedStyle` option and supports `--auto-gen-config`. # - # @example `EnforcedStyle: allow` + # @example `EnforcedStyle: allow` (default) # # # bad # expect(foo).to receive(:bar) @@ -24,17 +24,19 @@ module RSpec # # good # expect(foo).to receive(:bar) # - class MessageExpectation < Cop + class MessageExpectation < Base include ConfigurableEnforcedStyle MSG = 'Prefer `%