├── .codespellignore ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── codespell.yml │ ├── linting.yml │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── .yamllint ├── .yardopts ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── MIT-LICENSE.md ├── README.md ├── Rakefile ├── config └── default.yml ├── docs ├── antora.yml └── modules │ └── ROOT │ ├── nav.adoc │ └── pages │ ├── cops.adoc │ ├── cops_rspecrails.adoc │ ├── development.adoc │ ├── index.adoc │ ├── installation.adoc │ └── usage.adoc ├── lib ├── rubocop-rspec_rails.rb └── rubocop │ ├── cop │ ├── rspec_rails │ │ ├── avoid_setup_hook.rb │ │ ├── have_http_status.rb │ │ ├── http_status.rb │ │ ├── inferred_spec_type.rb │ │ ├── minitest_assertions.rb │ │ ├── negation_be_valid.rb │ │ └── travel_around.rb │ └── rspec_rails_cops.rb │ └── rspec_rails │ ├── config_formatter.rb │ ├── cop │ └── generator.rb │ ├── description_extractor.rb │ ├── plugin.rb │ └── version.rb ├── rubocop-rspec_rails.gemspec ├── spec ├── project │ ├── changelog_spec.rb │ └── default_config_spec.rb ├── rubocop │ └── cop │ │ └── rspec_rails │ │ ├── avoid_setup_hook_spec.rb │ │ ├── have_http_status_spec.rb │ │ ├── http_status_spec.rb │ │ ├── inferred_spec_type_spec.rb │ │ ├── minitest_assertions_spec.rb │ │ ├── negation_be_valid_spec.rb │ │ └── travel_around_spec.rb ├── shared │ ├── detects_style_behavior.rb │ └── smoke_test_examples.rb ├── smoke_tests │ ├── empty_spec.rb │ ├── factory_bot_spec.rb │ ├── no_tests_spec.rb │ └── weird_rspec_spec.rb ├── spec_helper.rb └── support │ ├── cli_spec_behavior.rb │ ├── expect_offense.rb │ └── file_helper.rb └── tasks ├── cops_documentation.rake ├── create_release_notes.rake └── cut_release.rake /.codespellignore: -------------------------------------------------------------------------------- 1 | xdescribe 2 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # .git-blame-ignore-revs 2 | # Add mdformat to the workflow in GitHub Actions 3 | 4874a5a4a2a58e76d343aaa02279cd93b16f5a30 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | CHANGELOG.md merge=union 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rubocop/rubocop-rspec 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 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. 4 | 5 | ## Issue reporting 6 | 7 | - Check that the issue has not already been reported. 8 | - Check that the issue has not already been fixed in the latest code (a.k.a. `master`). 9 | - Check if the issue is a non-goal of RuboCop RSpec. 10 | - Be clear, concise, and precise in your description of the problem. 11 | - Open an issue with a descriptive title and a summary in grammatically correct, complete sentences. 12 | - Report the versions of `rubocop-rspec`, as well as the output of `rubocop -V`. 13 | - Include any relevant code to the issue summary. 14 | 15 | ## Pull requests 16 | 17 | 1. Fork the project. 18 | 2. Create a feature branch. 19 | 3. Make sure to add tests. 20 | 4. Make sure the test suite passes (run `rake`). 21 | 5. Add a [changelog](https://github.com/rubocop/rubocop-rspec/blob/master/CHANGELOG.md) entry. 22 | 6. Commit your changes. 23 | 7. Push to the branch. 24 | 8. Create new Pull Request. 25 | 26 | ### Spell Checking 27 | 28 | 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 29 | [codespell](https://pypi.org/project/codespell/). 30 | `codespell` is written in [Python](https://www.python.org/) and you can run it with: 31 | 32 | ```console 33 | $ codespell --ignore-words=.codespellignore 34 | ``` 35 | 36 | ### Linting YAML files 37 | 38 | 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). 39 | `yamllint` is written in [Python](https://www.python.org/) and you can run it with: 40 | 41 | ```console 42 | $ yamllint . 43 | ``` 44 | 45 | ### Formatting Markdown files 46 | 47 | 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). 48 | `mdformat` is written in [Python](https://www.python.org/) and you can run it with: 49 | 50 | ```console 51 | $ mdformat . --number 52 | ``` 53 | 54 | ## Creating new cops 55 | 56 | - Document examples of good and bad code in your cop. 57 | - Add an entry to `config/default.yml`. It's an ordered list, so be sure to insert at the appropriate place. 58 | - 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). 59 | - 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. 60 | - Common pitfalls: 61 | - If your cop inspects code outside of an example, check for false positives when similarly named variables are used inside of the example. 62 | - If your cop inspects code inside of an example, check that it works when the example is empty (empty `describe`, `it`, etc.). 63 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Replace this text with a summary of the changes in your PR. The more detailed you are, the better.** 2 | 3 | ______________________________________________________________________ 4 | 5 | Before submitting the PR make sure the following are checked: 6 | 7 | - [ ] Feature branch is up-to-date with `master` (if not - rebase it). 8 | - [ ] Squashed related commits together. 9 | - [ ] Added tests. 10 | - [ ] Updated documentation. 11 | - [ ] Added an entry to the `CHANGELOG.md` if the new code introduces user-observable changes. 12 | - [ ] The build (`bundle exec rake`) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit). 13 | 14 | If you have created a new cop: 15 | 16 | - [ ] Added the new cop to `config/default.yml`. 17 | - [ ] The cop is configured as `Enabled: pending` in `config/default.yml`. 18 | - [ ] The cop documents examples of good and bad code. 19 | - [ ] The tests assert both that bad code is reported and that good code is not reported. 20 | - [ ] Set `VersionAdded: "<>"` in `default/config.yml`. 21 | 22 | If you have modified an existing cop's configuration options: 23 | 24 | - [ ] Set `VersionChanged: "<>"` in `config/default.yml`. 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | name: CodeSpell 2 | on: 3 | - pull_request 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 6 | cancel-in-progress: true 7 | jobs: 8 | codespell: 9 | name: CodeSpell 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: CodeSpell 14 | uses: codespell-project/actions-codespell@master 15 | with: 16 | check_filenames: true 17 | check_hidden: true 18 | ignore_words_file: .codespellignore 19 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | on: 3 | - pull_request 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 6 | cancel-in-progress: true 7 | jobs: 8 | yamllint: 9 | name: Yamllint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Yamllint 14 | uses: karancode/yamllint-github-action@master 15 | with: 16 | yamllint_strict: true 17 | yamllint_format: parsable 18 | yamllint_comment: true 19 | env: 20 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | mdformat: 22 | name: Mdformat 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Mdformat 27 | uses: ydah/mdformat-action@main 28 | with: 29 | number: true 30 | env: 31 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | confirm_config_and_documentation: 15 | runs-on: ubuntu-latest 16 | name: Confirm config and documentation 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: "3.4" 22 | bundler-cache: true 23 | - run: bundle exec rake confirm_config documentation_syntax_check confirm_documentation 24 | 25 | main: 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | ruby: 30 | - "2.7" 31 | - "3.0" 32 | - "3.1" 33 | - "3.2" 34 | - "3.3" 35 | - "3.4" 36 | - ruby-head 37 | - jruby-9.4 38 | task: 39 | - internal_investigation 40 | - spec 41 | name: "Ruby ${{ matrix.ruby }}: ${{ matrix.task }}" 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: ruby/setup-ruby@v1 45 | with: 46 | ruby-version: "${{ matrix.ruby }}" 47 | bundler-cache: true 48 | - run: NO_COVERAGE=true bundle exec rake ${{ matrix.task }} 49 | 50 | coverage: 51 | runs-on: ubuntu-latest 52 | name: "Test coverage" 53 | steps: 54 | - uses: actions/checkout@v4 55 | - uses: ruby/setup-ruby@v1 56 | with: 57 | ruby-version: "3.4" 58 | bundler-cache: true 59 | - run: bundle exec rake spec 60 | 61 | edge-rubocop: 62 | runs-on: ubuntu-latest 63 | strategy: 64 | matrix: 65 | task: 66 | - internal_investigation 67 | - spec 68 | name: "Edge RuboCop: ${{ matrix.task }}" 69 | steps: 70 | - uses: actions/checkout@v4 71 | - name: Use latest RuboCop from `master` 72 | run: | 73 | echo "gem 'rubocop', github: 'rubocop/rubocop'" > Gemfile.local 74 | - uses: ruby/setup-ruby@v1 75 | with: 76 | ruby-version: "3.4" 77 | bundler-cache: true 78 | - run: NO_COVERAGE=true bundle exec rake ${{ matrix.task }} 79 | 80 | edge-rubocop-rspec: 81 | runs-on: ubuntu-latest 82 | strategy: 83 | matrix: 84 | task: 85 | - internal_investigation 86 | - spec 87 | name: "Edge RuboCop RSpec: ${{ matrix.task }}" 88 | steps: 89 | - uses: actions/checkout@v4 90 | - name: Use latest RuboCop RSpec from `master` 91 | run: | 92 | sed -e '/rubocop-rspec/d' -i Gemfile 93 | echo "gem 'rubocop-rspec', github: 'rubocop/rubocop-rspec'" > Gemfile.local 94 | - uses: ruby/setup-ruby@v1 95 | with: 96 | ruby-version: "3.4" 97 | bundler-cache: true 98 | - run: NO_COVERAGE=true bundle exec rake ${{ matrix.task }} 99 | 100 | rspec4: 101 | runs-on: ubuntu-latest 102 | name: RSpec 4 103 | steps: 104 | - uses: actions/checkout@v4 105 | - name: Use latest RSpec 4 from `4-0-dev` branch 106 | run: | 107 | sed -e "/^gem 'rspec/d" -i Gemfile 108 | cat << EOF > Gemfile.local 109 | gem 'rspec', github: 'rspec/rspec', branch: '4-0-dev' 110 | gem 'rspec-core', github: 'rspec/rspec', branch: '4-0-dev' 111 | gem 'rspec-expectations', github: 'rspec/rspec', branch: '4-0-dev' 112 | gem 'rspec-mocks', github: 'rspec/rspec', branch: '4-0-dev' 113 | gem 'rspec-support', github: 'rspec/rspec', branch: '4-0-dev' 114 | EOF 115 | - uses: ruby/setup-ruby@v1 116 | with: 117 | ruby-version: "3.4" 118 | bundler-cache: true 119 | - run: NO_COVERAGE=true bundle exec rake spec 120 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: master 5 | paths: lib/rubocop/rspec_rails/version.rb 6 | jobs: 7 | publish: 8 | name: Publish to RubyGems 9 | runs-on: ubuntu-latest 10 | if: github.repository_owner == 'rubocop' 11 | permissions: 12 | contents: write 13 | id-token: write 14 | pull-requests: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Ruby 18 | uses: ruby/setup-ruby@v1 19 | with: 20 | bundler-cache: true 21 | ruby-version: ruby 22 | - uses: rubygems/release-gem@v1 23 | - name: Create a GitHub release 24 | env: 25 | GH_TOKEN: ${{ github.token }} 26 | run: | 27 | bundle exec rake create_release_notes 28 | gh release create $(git tag --points-at @) \ 29 | --title "RuboCop RSpec Rails $(git tag --points-at @)" \ 30 | --notes-file relnotes.md 31 | - name: Replace version in Antora config 32 | env: 33 | GH_TOKEN: ${{ github.token }} 34 | run: | 35 | sed -i 's/version:.*$/version: ~/' docs/antora.yml 36 | if ! git diff --exit-code docs/antora.yml; then 37 | git config user.name "${GITHUB_ACTOR}" 38 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 39 | git checkout -b switch-docs-version 40 | git add docs/antora.yml 41 | git commit -m "Switch docs version back" 42 | git push -u origin switch-docs-version 43 | gh pr create --fill --head switch-docs-version 44 | fi 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rdoc generated 2 | rdoc 3 | 4 | # yard generated 5 | doc 6 | .yardoc 7 | 8 | # bundler 9 | .bundle 10 | Gemfile.lock 11 | Gemfile.local 12 | 13 | # jeweler generated 14 | pkg 15 | 16 | /vendor 17 | 18 | .ruby-gemset 19 | .ruby-version 20 | 21 | # simplecov generated 22 | coverage 23 | 24 | # vscode generated 25 | .vscode 26 | 27 | /relnotes.md 28 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | plugins: 4 | - rubocop-performance 5 | - rubocop-rake 6 | - rubocop-rspec 7 | - rubocop-internal_affairs 8 | 9 | AllCops: 10 | DisplayCopNames: true 11 | TargetRubyVersion: 2.7 12 | NewCops: disable 13 | Exclude: 14 | - 'vendor/**/*' 15 | - 'spec/fixtures/**/*' 16 | - 'tmp/**/*' 17 | - 'spec/smoke_tests/**/*.rb' 18 | 19 | InternalAffairs/OnSendWithoutOnCSend: 20 | Enabled: false 21 | 22 | Layout/HashAlignment: 23 | EnforcedHashRocketStyle: 24 | - key 25 | - table 26 | EnforcedColonStyle: 27 | - key 28 | - table 29 | 30 | Layout/LineLength: 31 | Max: 80 # default: 120 32 | AllowedPatterns: 33 | - '^\s*# .*https?:\/\/.+\[.+\]\.?$' # Allow long asciidoc links 34 | 35 | Layout/MultilineMethodCallIndentation: 36 | EnforcedStyle: indented 37 | 38 | Layout/MultilineOperationIndentation: 39 | EnforcedStyle: indented 40 | 41 | Lint/InterpolationCheck: 42 | Exclude: 43 | - spec/**/*.rb 44 | 45 | # When the `edge-rubocop` build is red, and we decide to disable the cop, 46 | # the rest of the builds become red if the cop has not yet been released. 47 | # Instead of waiting for RuboCop releases to make `edge-rubocop` green, 48 | # we prefer keeping disable directives here and there and check if they 49 | # are still needed once in a while. 50 | Lint/RedundantCopDisableDirective: 51 | Enabled: false 52 | 53 | Metrics/BlockLength: 54 | Exclude: 55 | - rubocop-rspec_rails.gemspec 56 | - Rakefile 57 | - '**/*.rake' 58 | 59 | Naming/FileName: 60 | Exclude: 61 | - lib/rubocop-rspec_rails.rb 62 | 63 | Naming/InclusiveLanguage: 64 | Enabled: true 65 | CheckStrings: true 66 | FlaggedTerms: 67 | ' a offense': 68 | Suggestions: 69 | - an offense 70 | auto-correct: 71 | Suggestions: 72 | - autocorrect 73 | auto_correct: 74 | Suggestions: 75 | - autocorrect 76 | ' a violation': 77 | Suggestions: 78 | - an offense 79 | behaviour: 80 | Suggestions: 81 | - behavior 82 | offence: 83 | Suggestions: 84 | - offense 85 | 'does not registers': 86 | Suggestions: 87 | - does not register 88 | violation: 89 | Suggestions: 90 | - offense 91 | 92 | RSpec: 93 | Language: 94 | Expectations: 95 | - expect_correction 96 | - expect_no_offenses 97 | - expect_offense 98 | 99 | RSpec/ExampleLength: 100 | CountAsOne: 101 | - heredoc 102 | Max: 11 103 | 104 | RSpec/DescribeClass: 105 | Exclude: 106 | - spec/project/**/*.rb 107 | 108 | RSpec/MultipleExpectations: 109 | Max: 2 110 | 111 | RSpec/SpecFilePathFormat: 112 | CustomTransform: 113 | RSpecRails: rspec_rails 114 | 115 | Style/FormatStringToken: 116 | Exclude: 117 | - spec/rubocop/**/*.rb 118 | 119 | Style/RequireOrder: 120 | Enabled: true 121 | 122 | # Enable some of RuboCop's pending cops. 123 | 124 | Layout/LineContinuationSpacing: 125 | Enabled: true 126 | Layout/LineEndStringConcatenationIndentation: 127 | Enabled: true 128 | Lint/AmbiguousOperatorPrecedence: 129 | Enabled: true 130 | Lint/NonAtomicFileOperation: 131 | Enabled: true 132 | Style/EmptyHeredoc: 133 | Enabled: true 134 | Style/RedundantHeredocDelimiterQuotes: 135 | Enabled: true 136 | Style/RedundantStringEscape: 137 | Enabled: true 138 | Style/ReturnNilInPredicateMethodDefinition: 139 | Enabled: true 140 | 141 | # Enable pending rubocop-rspec cops. 142 | # 143 | # No pending cops yet. 144 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config --no-offense-counts --no-auto-gen-timestamp` 3 | # using RuboCop version 1.36.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | Rake/MethodDefinitionInTask: 10 | Exclude: 11 | - 'tasks/cut_release.rake' 12 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | SimpleCov.start do 4 | enable_coverage :branch 5 | minimum_coverage line: 100, branch: 100 6 | add_filter '/spec/' 7 | add_filter '/vendor/bundle/' 8 | end 9 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | rules: 4 | comments: 5 | min-spaces-from-content: 1 6 | document-start: disable 7 | line-length: 8 | allow-non-breakable-inline-mappings: true 9 | max: 100 10 | truthy: 11 | check-keys: false 12 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | --hide-void-return 3 | --tag safety:"Cop Safety Information" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Master (Unreleased) 4 | 5 | ## 2.31.0 (2025-03-10) 6 | 7 | - Handle unknown HTTP status codes for `RSpecRails/HttpStatus` cop. ([@viralpraxis]) 8 | - Fix a false negative for `RSpecRails/TravelAround` cop when passed as a proc to a travel method. ([@ydah]) 9 | - Make RuboCop RSpecRails work as a RuboCop plugin. ([@bquorning]) 10 | 11 | ## 2.30.0 (2024-06-12) 12 | 13 | - Fix an runtime error for rubocop-rspec +3.0. ([@bquorning]) 14 | 15 | ## 2.29.1 (2024-06-12) 16 | 17 | - Bump RuboCop requirement to +1.61. ([@ydah]) 18 | 19 | ## 2.29.0 (2024-06-08) 20 | 21 | - Support `AutoCorrect: contextual` option for LSP. ([@ydah]) 22 | 23 | ## 2.28.3 (2024-04-11) 24 | 25 | - Fix an error for Ambiguous cop name `RSpec/Rails/HttpStatus`. ([@ydah]) 26 | 27 | ## 2.28.2 (2024-03-31) 28 | 29 | - Fix a `NameError` by Cross-Referencing. ([@ydah]) 30 | - Fix an error for `RSpecRails/HttpStatus` when no rack gem is loaded with rubocop-rspec. ([@ydah]) 31 | - Fix an error for unrecognized cop or department `RSpecRails/HttpStatus` when also using rubocop-rails. ([@ydah]) 32 | 33 | ## 2.28.1 (2024-03-29) 34 | 35 | - Implicit dependency on RuboCop RSpec. Note that if you use rubocop-rspec_rails, you must also explicitly add rubocop-rspec to the Gemfile, because you are changing to an implicit dependency on RuboCop RSpec. ([@ydah]) 36 | 37 | ## 2.28.0 (2024-03-28) 38 | 39 | - Extracted from `rubocop-rspec` into a separate repository. ([@ydah]) 40 | 41 | ## Previously (see [rubocop-rspec's changelist](https://github.com/rubocop/rubocop-rspec/blob/v2.27.1/CHANGELOG.md) for details) 42 | 43 | - 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]) 44 | - Add configuration option `ResponseMethods` to `RSpec/Rails/HaveHttpStatus`. ([@ydah]) 45 | - Add support single quoted string and percent string and heredoc for `RSpec/Rails/HttpStatus`. ([@ydah]) 46 | - Add support `RSpec/Rails/HttpStatus` when `have_http_status` with string argument. ([@ydah]) 47 | - Mark to `Safe: false` for `RSpec/Rails/NegationBeValid` cop. ([@ydah]) 48 | - Add new `RSpec/Rails/NegationBeValid` cop. ([@ydah]) 49 | - Fix a false negative for `RSpec/ExcessiveDocstringSpacing` when finds description with em space. ([@ydah]) 50 | - Fix a false positive for `RSpec/EmptyExampleGroup` when example group with examples defined in `if` branch inside iterator. ([@ydah]) 51 | - Update the message output of `RSpec/ExpectActual` to include the word 'value'. ([@corydiamand]) 52 | - Fix a false negative for `RSpec/Pending` when `it` without body. ([@ydah]) 53 | - Add new `RSpec/ReceiveMessages` cop. ([@ydah]) 54 | - Change default.yml path to use `**/spec/*` instead of `spec/*`. ([@ydah]) 55 | - Add `AllowedIdentifiers` and `AllowedPatterns` configuration option to `RSpec/IndexedLet`. ([@ydah]) 56 | - Fix `RSpec/NamedSubject` when block has no body. ([@splattael]) 57 | - Fix `RSpec/LetBeforeExamples` autocorrect incompatible with `RSpec/ScatteredLet` autocorrect. ([@ydah]) 58 | - Update `RSpec/Focus` to support `shared_context` and `shared_examples`. ([@tmaier]) 59 | - Fix an error for `RSpec/Rails/HaveHttpStatus` with comparison with strings containing non-numeric characters. ([@ydah]) 60 | - Add support `be_status` style for `RSpec/Rails/HttpStatus`. ([@ydah]) 61 | - Fix order of expected and actual in correction for `RSpec/Rails/MinitestAssertions`. ([@mvz]) 62 | - Add `RSpec/Rails/TravelAround` cop. ([@r7kamura]) 63 | - Add new `RSpec/Rails/MinitestAssertions` cop. ([@ydah]) 64 | - Improved processing speed for `RSpec/Be`, `RSpec/ExpectActual`, `RSpec/ImplicitExpect`, `RSpec/MessageSpies`, `RSpec/PredicateMatcher` and `RSpec/Rails/HaveHttpStatus`. ([@ydah]) 65 | - Fix an error for `RSpec/Rails/InferredSpecType` with redundant type before other Hash metadata. ([@ydah]) 66 | - Add `RSpec/Rails/InferredSpecType` cop. ([@r7kamura]) 67 | - Add new `RSpec/Rails/HaveHttpStatus` cop. ([@akiomik]) 68 | - Exclude unrelated Rails directories from `RSpec/DescribeClass`. ([@MothOnMars]) 69 | - Add `RSpec/Rails/AvoidSetupHook` cop. ([@paydaylight]) 70 | - Change namespace of several cops (`Capybara/*` -> `RSpec/Capybara/*`, `FactoryBot/*` -> `RSpec/FactoryBot/*`, `Rails/*` -> `RSpec/Rails/*`). ([@pirj], [@bquorning]) 71 | - The `Rails/HttpStatus` cop is unavailable if the `rack` gem cannot be loaded. ([@bquorning]) 72 | - Fix `Rails/HttpStatus` not working with custom HTTP status codes. ([@bquorning]) 73 | - Add `RSpec/Rails/HttpStatus` cop to enforce consistent usage of the status format (numeric or symbolic). ([@anthony-robin], [@jojos003]) 74 | 75 | 76 | 77 | [@akiomik]: https://github.com/akiomik 78 | [@anthony-robin]: https://github.com/anthony-robin 79 | [@bquorning]: https://github.com/bquorning 80 | [@corydiamand]: https://github.com/corydiamand 81 | [@g-rath]: https://github.com/G-Rath 82 | [@jojos003]: https://github.com/jojos003 83 | [@mothonmars]: https://github.com/MothOnMars 84 | [@mvz]: https://github.com/mvz 85 | [@paydaylight]: https://github.com/paydaylight 86 | [@pirj]: https://github.com/pirj 87 | [@r7kamura]: https://github.com/r7kamura 88 | [@splattael]: https://github.com/splattael 89 | [@tmaier]: https://github.com/tmaier 90 | [@viralpraxis]: https://github.com/viralpraxis 91 | [@ydah]: https://github.com/ydah 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The RuboCop Community Code of Conduct 2 | 3 | **Note:** We have picked the following code of conduct based on [Ruby's own 4 | code of conduct](https://www.ruby-lang.org/en/conduct/). 5 | 6 | This document provides a few simple community guidelines for a safe, respectful, 7 | productive, and collaborative place for any person who is willing to contribute 8 | to the RuboCop community. It applies to all "collaborative spaces", which are 9 | defined as community communications channels (such as mailing lists, submitted 10 | patches, commit comments, etc.). 11 | 12 | - Participants will be tolerant of opposing views. 13 | - Participants must ensure that their language and actions are free of personal 14 | attacks and disparaging personal remarks. 15 | - When interpreting the words and actions of others, participants should always 16 | assume good intentions. 17 | - Behaviour which can be reasonably considered harassment will not be tolerated. 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'bump' 8 | gem 'rack' 9 | gem 'rake' 10 | gem 'rspec', '~> 3.11' 11 | gem 'rubocop-performance', '~> 1.24' 12 | gem 'rubocop-rake', '~> 0.7' 13 | gem 'simplecov', '>= 0.19' 14 | gem 'yard' 15 | 16 | local_gemfile = 'Gemfile.local' 17 | eval_gemfile(local_gemfile) if File.exist?(local_gemfile) 18 | -------------------------------------------------------------------------------- /MIT-LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ian MacLeod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RuboCop RSpec Rails 2 | 3 | [![Join the chat at https://gitter.im/rubocop-rspec/Lobby](https://badges.gitter.im/rubocop-rspec/Lobby.svg)](https://gitter.im/rubocop-rspec/Lobby) 4 | [![Gem Version](https://badge.fury.io/rb/rubocop-rspec_rails.svg)](https://rubygems.org/gems/rubocop-rspec_rails) 5 | ![CI](https://github.com/rubocop/rubocop-rspec_rails/workflows/CI/badge.svg) 6 | 7 | [RSpec Rails](https://rspec.info/)-specific analysis for your projects, as an extension to 8 | [RuboCop](https://github.com/rubocop/rubocop). 9 | 10 | ## Installation 11 | 12 | **This gem implicitly depends on the `rubocop-rspec` gem, so you should install it first.** 13 | Just install the `rubocop-rspec` and `rubocop-rspec_rails` gem 14 | 15 | ```bash 16 | gem install rubocop-rspec rubocop-rspec_rails 17 | ``` 18 | 19 | or if you use bundler put this in your `Gemfile` 20 | 21 | ```ruby 22 | gem 'rubocop-rspec', require: false 23 | gem 'rubocop-rspec_rails', require: false 24 | ``` 25 | 26 | ## Usage 27 | 28 | You need to tell RuboCop to load the RSpec Rails extension. There are three 29 | ways to do this: 30 | 31 | ### RuboCop configuration file 32 | 33 | Put this into your `.rubocop.yml`. 34 | 35 | ```yaml 36 | plugins: rubocop-rspec_rails 37 | ``` 38 | 39 | Alternatively, use the following array notation when specifying multiple extensions. 40 | 41 | ```yaml 42 | plugins: 43 | - rubocop-rspec 44 | - rubocop-rspec_rails 45 | ``` 46 | 47 | Now you can run `rubocop` and it will automatically load the RuboCop RSpec Rails 48 | cops together with the standard cops. 49 | 50 | > [!NOTE] 51 | > The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. 52 | 53 | ### Command line 54 | 55 | ```bash 56 | rubocop --plugin rubocop-rspec_rails 57 | ``` 58 | 59 | ### Rake task 60 | 61 | ```ruby 62 | RuboCop::RakeTask.new do |task| 63 | task.plugins << 'rubocop-rspec_rails' 64 | end 65 | ``` 66 | 67 | ## Documentation 68 | 69 | You can read more about RuboCop RSpec Rails in its [official manual](https://docs.rubocop.org/rubocop-rspec_rails). 70 | 71 | ## The Cops 72 | 73 | All cops are located under 74 | [`lib/rubocop/cop/rspec_rails`](lib/rubocop/cop/rspec_rails), and contain 75 | examples/documentation. 76 | 77 | In your `.rubocop.yml`, you may treat the RSpec Rails cops just like any other 78 | cop. For example: 79 | 80 | ```yaml 81 | RSpecRails/AvoidSetupHook: 82 | Exclude: 83 | - spec/my_poorly_named_spec_file.rb 84 | ``` 85 | 86 | ## Contributing 87 | 88 | Checkout the [contribution guidelines](.github/CONTRIBUTING.md). 89 | 90 | ## License 91 | 92 | `rubocop-rspec_rails` is MIT licensed. [See the accompanying file](MIT-LICENSE.md) for 93 | the full text. 94 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'open3' 4 | 5 | require 'bundler' 6 | require 'bundler/gem_tasks' 7 | 8 | begin 9 | Bundler.setup(:default, :development) 10 | rescue Bundler::BundlerError => e 11 | warn e.message 12 | warn 'Run `bundle install` to install missing gems' 13 | exit e.status_code 14 | end 15 | 16 | require 'rspec/core/rake_task' 17 | require 'rubocop/rake_task' 18 | 19 | Dir['tasks/**/*.rake'].each { |t| load t } 20 | 21 | RSpec::Core::RakeTask.new(:spec) do |spec| 22 | spec.pattern = FileList['spec/**/*_spec.rb'] 23 | end 24 | 25 | desc 'Run RuboCop over this gem' 26 | RuboCop::RakeTask.new(:internal_investigation) 27 | 28 | desc 'Build config/default.yml' 29 | task :build_config do 30 | require 'yard' 31 | 32 | require 'rubocop-rspec_rails' 33 | require 'rubocop/rspec_rails/config_formatter' 34 | require 'rubocop/rspec_rails/description_extractor' 35 | 36 | glob = File.join('lib', 'rubocop', 'cop', 'rspec_rails', '*.rb') 37 | YARD::Tags::Library.define_tag('Cop Safety Information', :safety) 38 | YARD.parse(Dir[glob], []) 39 | 40 | descriptions = 41 | RuboCop::RSpecRails::DescriptionExtractor.new( 42 | YARD::Registry.all(:class) 43 | ).to_h 44 | current_config = if Psych::VERSION >= '4.0.0' # RUBY_VERSION >= '3.1.0' 45 | YAML.unsafe_load_file('config/default.yml') 46 | else 47 | YAML.load_file('config/default.yml') 48 | end 49 | 50 | File.write( 51 | 'config/default.yml', 52 | RuboCop::RSpecRails::ConfigFormatter.new( 53 | current_config, descriptions 54 | ).dump 55 | ) 56 | end 57 | 58 | desc 'Confirm config/default.yml is up to date' 59 | task confirm_config: :build_config do 60 | _, stdout, _, process = 61 | Open3.popen3('git diff --exit-code config/default.yml') 62 | 63 | raise <<~ERROR unless process.value.success? 64 | default.yml is out of sync: 65 | 66 | #{stdout.read} 67 | Please run `rake build_config` 68 | ERROR 69 | end 70 | 71 | desc 'Confirm documentation is up to date' 72 | task confirm_documentation: :generate_cops_documentation do 73 | _, _, _, process = 74 | Open3.popen3('git diff --exit-code docs/') 75 | 76 | unless process.value.success? 77 | raise 'Please run `rake generate_cops_documentation` ' \ 78 | 'and add docs/ to the commit.' 79 | end 80 | end 81 | 82 | task default: %i[build_config spec 83 | internal_investigation 84 | confirm_config 85 | documentation_syntax_check 86 | confirm_documentation] 87 | 88 | desc 'Generate a new cop template' 89 | task :new_cop, [:cop] do |_task, args| 90 | require 'rubocop' 91 | require_relative 'lib/rubocop/rspec_rails/cop/generator' 92 | 93 | cop_name = args.fetch(:cop) do 94 | warn "usage: bundle exec rake 'new_cop[Department/Name]'" 95 | exit! 96 | end 97 | 98 | generator = RuboCop::RSpecRails::Cop::Generator.new(cop_name) 99 | generator.write_source 100 | generator.write_spec 101 | generator.inject_require( 102 | root_file_path: 'lib/rubocop/cop/rspec_rails_cops.rb' 103 | ) 104 | generator.inject_config 105 | 106 | puts generator.todo 107 | end 108 | -------------------------------------------------------------------------------- /config/default.yml: -------------------------------------------------------------------------------- 1 | --- 2 | RSpecRails: 3 | Enabled: true 4 | DocumentationBaseURL: https://docs.rubocop.org/rubocop-rspec_rails 5 | Include: 6 | - "**/*_spec.rb" 7 | - "**/spec/**/*" 8 | 9 | RSpecRails/AvoidSetupHook: 10 | Description: Checks that tests use RSpec `before` hook over Rails `setup` method. 11 | Enabled: pending 12 | VersionAdded: '2.4' 13 | Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/AvoidSetupHook 14 | 15 | RSpecRails/HaveHttpStatus: 16 | Description: Checks that tests use `have_http_status` instead of equality matchers. 17 | Enabled: pending 18 | ResponseMethods: 19 | - response 20 | - last_response 21 | SafeAutoCorrect: false 22 | VersionAdded: '2.12' 23 | VersionChanged: '2.27' 24 | Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HaveHttpStatus 25 | 26 | RSpecRails/HttpStatus: 27 | Description: Enforces use of symbolic or numeric value to describe HTTP status. 28 | Enabled: true 29 | EnforcedStyle: symbolic 30 | SupportedStyles: 31 | - numeric 32 | - symbolic 33 | - be_status 34 | VersionAdded: '1.23' 35 | VersionChanged: '2.20' 36 | Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatus 37 | 38 | RSpecRails/InferredSpecType: 39 | Description: Identifies redundant spec type. 40 | Enabled: pending 41 | Safe: false 42 | VersionAdded: '2.14' 43 | Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/InferredSpecType 44 | Inferences: 45 | channels: channel 46 | controllers: controller 47 | features: feature 48 | generator: generator 49 | helpers: helper 50 | jobs: job 51 | mailboxes: mailbox 52 | mailers: mailer 53 | models: model 54 | requests: request 55 | integration: request 56 | api: request 57 | routing: routing 58 | system: system 59 | views: view 60 | 61 | RSpecRails/MinitestAssertions: 62 | Description: Check if using Minitest-like matchers. 63 | Enabled: pending 64 | VersionAdded: '2.17' 65 | Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/MinitestAssertions 66 | 67 | RSpecRails/NegationBeValid: 68 | Description: Enforces use of `be_invalid` or `not_to` for negated be_valid. 69 | AutoCorrect: contextual 70 | Safe: false 71 | EnforcedStyle: not_to 72 | SupportedStyles: 73 | - not_to 74 | - be_invalid 75 | Enabled: pending 76 | VersionAdded: '2.23' 77 | VersionChanged: '2.29' 78 | Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/NegationBeValid 79 | 80 | RSpecRails/TravelAround: 81 | Description: Prefer to travel in `before` rather than `around`. 82 | Enabled: pending 83 | Safe: false 84 | VersionAdded: '2.19' 85 | Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/TravelAround 86 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: rubocop-rspec_rails 2 | title: RuboCop RSpec Rails 3 | version: ~ 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Home] 2 | * xref:installation.adoc[Installation] 3 | * xref:usage.adoc[Usage] 4 | * xref:cops.adoc[Cops] 5 | * xref:development.adoc[Development] 6 | * Cops Documentation 7 | ** xref:cops_rspecrails.adoc[RSpecRails] 8 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/cops.adoc: -------------------------------------------------------------------------------- 1 | // START_COP_LIST 2 | 3 | === Department xref:cops_rspecrails.adoc[RSpecRails] 4 | 5 | * xref:cops_rspecrails.adoc#rspecrailsavoidsetuphook[RSpecRails/AvoidSetupHook] 6 | * xref:cops_rspecrails.adoc#rspecrailshavehttpstatus[RSpecRails/HaveHttpStatus] 7 | * xref:cops_rspecrails.adoc#rspecrailshttpstatus[RSpecRails/HttpStatus] 8 | * xref:cops_rspecrails.adoc#rspecrailsinferredspectype[RSpecRails/InferredSpecType] 9 | * xref:cops_rspecrails.adoc#rspecrailsminitestassertions[RSpecRails/MinitestAssertions] 10 | * xref:cops_rspecrails.adoc#rspecrailsnegationbevalid[RSpecRails/NegationBeValid] 11 | * xref:cops_rspecrails.adoc#rspecrailstravelaround[RSpecRails/TravelAround] 12 | 13 | // END_COP_LIST 14 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/cops_rspecrails.adoc: -------------------------------------------------------------------------------- 1 | //// 2 | Do NOT edit this file by hand directly, as it is automatically generated. 3 | 4 | Please make any necessary changes to the cop documentation within the source files themselves. 5 | //// 6 | 7 | = RSpecRails 8 | 9 | [#rspecrailsavoidsetuphook] 10 | == RSpecRails/AvoidSetupHook 11 | 12 | |=== 13 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 14 | 15 | | Pending 16 | | Yes 17 | | Always 18 | | 2.4 19 | | - 20 | |=== 21 | 22 | Checks that tests use RSpec `before` hook over Rails `setup` method. 23 | 24 | [#examples-rspecrailsavoidsetuphook] 25 | === Examples 26 | 27 | [source,ruby] 28 | ---- 29 | # bad 30 | setup do 31 | allow(foo).to receive(:bar) 32 | end 33 | 34 | # good 35 | before do 36 | allow(foo).to receive(:bar) 37 | end 38 | ---- 39 | 40 | [#references-rspecrailsavoidsetuphook] 41 | === References 42 | 43 | * https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/AvoidSetupHook 44 | 45 | [#rspecrailshavehttpstatus] 46 | == RSpecRails/HaveHttpStatus 47 | 48 | |=== 49 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 50 | 51 | | Pending 52 | | Yes 53 | | Always (Unsafe) 54 | | 2.12 55 | | 2.27 56 | |=== 57 | 58 | Checks that tests use `have_http_status` instead of equality matchers. 59 | 60 | [#examples-rspecrailshavehttpstatus] 61 | === Examples 62 | 63 | [#responsemethods_-__response__-_last_response__-_default_-rspecrailshavehttpstatus] 64 | ==== ResponseMethods: ['response', 'last_response'] (default) 65 | 66 | [source,ruby] 67 | ---- 68 | # bad 69 | expect(response.status).to be(200) 70 | expect(last_response.code).to eq("200") 71 | 72 | # good 73 | expect(response).to have_http_status(200) 74 | expect(last_response).to have_http_status(200) 75 | ---- 76 | 77 | [#responsemethods_-__foo_response__-rspecrailshavehttpstatus] 78 | ==== ResponseMethods: ['foo_response'] 79 | 80 | [source,ruby] 81 | ---- 82 | # bad 83 | expect(foo_response.status).to be(200) 84 | 85 | # good 86 | expect(foo_response).to have_http_status(200) 87 | 88 | # also good 89 | expect(response).to have_http_status(200) 90 | expect(last_response).to have_http_status(200) 91 | ---- 92 | 93 | [#configurable-attributes-rspecrailshavehttpstatus] 94 | === Configurable attributes 95 | 96 | |=== 97 | | Name | Default value | Configurable values 98 | 99 | | ResponseMethods 100 | | `response`, `last_response` 101 | | Array 102 | |=== 103 | 104 | [#references-rspecrailshavehttpstatus] 105 | === References 106 | 107 | * https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HaveHttpStatus 108 | 109 | [#rspecrailshttpstatus] 110 | == RSpecRails/HttpStatus 111 | 112 | |=== 113 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 114 | 115 | | Enabled 116 | | Yes 117 | | Always 118 | | 1.23 119 | | 2.20 120 | |=== 121 | 122 | Enforces use of symbolic or numeric value to describe HTTP status. 123 | 124 | This cop inspects only `have_http_status` calls. 125 | So, this cop does not check if a method starting with `be_*` is used 126 | when setting for `EnforcedStyle: symbolic` or 127 | `EnforcedStyle: numeric`. 128 | This cop is also capable of detecting unknown HTTP status codes. 129 | 130 | [#examples-rspecrailshttpstatus] 131 | === Examples 132 | 133 | [#_enforcedstyle_-symbolic_-_default_-rspecrailshttpstatus] 134 | ==== `EnforcedStyle: symbolic` (default) 135 | 136 | [source,ruby] 137 | ---- 138 | # bad 139 | it { is_expected.to have_http_status 200 } 140 | it { is_expected.to have_http_status 404 } 141 | it { is_expected.to have_http_status "403" } 142 | 143 | # good 144 | it { is_expected.to have_http_status :ok } 145 | it { is_expected.to have_http_status :not_found } 146 | it { is_expected.to have_http_status :forbidden } 147 | it { is_expected.to have_http_status :success } 148 | it { is_expected.to have_http_status :error } 149 | ---- 150 | 151 | [#_enforcedstyle_-numeric_-rspecrailshttpstatus] 152 | ==== `EnforcedStyle: numeric` 153 | 154 | [source,ruby] 155 | ---- 156 | # bad 157 | it { is_expected.to have_http_status :ok } 158 | it { is_expected.to have_http_status :not_found } 159 | it { is_expected.to have_http_status "forbidden" } 160 | 161 | # good 162 | it { is_expected.to have_http_status 200 } 163 | it { is_expected.to have_http_status 404 } 164 | it { is_expected.to have_http_status 403 } 165 | it { is_expected.to have_http_status :success } 166 | it { is_expected.to have_http_status :error } 167 | ---- 168 | 169 | [#_enforcedstyle_-be_status_-rspecrailshttpstatus] 170 | ==== `EnforcedStyle: be_status` 171 | 172 | [source,ruby] 173 | ---- 174 | # bad 175 | it { is_expected.to have_http_status :ok } 176 | it { is_expected.to have_http_status :not_found } 177 | it { is_expected.to have_http_status "forbidden" } 178 | it { is_expected.to have_http_status 200 } 179 | it { is_expected.to have_http_status 404 } 180 | it { is_expected.to have_http_status "403" } 181 | 182 | # good 183 | it { is_expected.to be_ok } 184 | it { is_expected.to be_not_found } 185 | it { is_expected.to have_http_status :success } 186 | it { is_expected.to have_http_status :error } 187 | ---- 188 | 189 | [source,ruby] 190 | ---- 191 | # bad 192 | it { is_expected.to have_http_status :oki_doki } 193 | 194 | # good 195 | it { is_expected.to have_http_status :ok } 196 | ---- 197 | 198 | [#configurable-attributes-rspecrailshttpstatus] 199 | === Configurable attributes 200 | 201 | |=== 202 | | Name | Default value | Configurable values 203 | 204 | | EnforcedStyle 205 | | `symbolic` 206 | | `numeric`, `symbolic`, `be_status` 207 | |=== 208 | 209 | [#references-rspecrailshttpstatus] 210 | === References 211 | 212 | * https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatus 213 | 214 | [#rspecrailsinferredspectype] 215 | == RSpecRails/InferredSpecType 216 | 217 | |=== 218 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 219 | 220 | | Pending 221 | | No 222 | | Always (Unsafe) 223 | | 2.14 224 | | - 225 | |=== 226 | 227 | Identifies redundant spec type. 228 | 229 | After setting up rspec-rails, you will have enabled 230 | `config.infer_spec_type_from_file_location!` by default in 231 | spec/rails_helper.rb. This cop works in conjunction with this config. 232 | If you disable this config, disable this cop as well. 233 | 234 | [#safety-rspecrailsinferredspectype] 235 | === Safety 236 | 237 | This cop is marked as unsafe because 238 | `config.infer_spec_type_from_file_location!` may not be enabled. 239 | 240 | [#examples-rspecrailsinferredspectype] 241 | === Examples 242 | 243 | [source,ruby] 244 | ---- 245 | # bad 246 | # spec/models/user_spec.rb 247 | RSpec.describe User, type: :model do 248 | end 249 | 250 | # good 251 | # spec/models/user_spec.rb 252 | RSpec.describe User do 253 | end 254 | 255 | # good 256 | # spec/models/user_spec.rb 257 | RSpec.describe User, type: :common do 258 | end 259 | ---- 260 | 261 | [#_inferences_-configuration-rspecrailsinferredspectype] 262 | ==== `Inferences` configuration 263 | 264 | [source,ruby] 265 | ---- 266 | # .rubocop.yml 267 | # RSpecRails/InferredSpecType: 268 | # Inferences: 269 | # services: service 270 | 271 | # bad 272 | # spec/services/user_spec.rb 273 | RSpec.describe User, type: :service do 274 | end 275 | 276 | # good 277 | # spec/services/user_spec.rb 278 | RSpec.describe User do 279 | end 280 | 281 | # good 282 | # spec/services/user_spec.rb 283 | RSpec.describe User, type: :common do 284 | end 285 | ---- 286 | 287 | [#configurable-attributes-rspecrailsinferredspectype] 288 | === Configurable attributes 289 | 290 | |=== 291 | | Name | Default value | Configurable values 292 | 293 | | Inferences 294 | | `{"channels" => "channel", "controllers" => "controller", "features" => "feature", "generator" => "generator", "helpers" => "helper", "jobs" => "job", "mailboxes" => "mailbox", "mailers" => "mailer", "models" => "model", "requests" => "request", "integration" => "request", "api" => "request", "routing" => "routing", "system" => "system", "views" => "view"}` 295 | | 296 | |=== 297 | 298 | [#references-rspecrailsinferredspectype] 299 | === References 300 | 301 | * https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/InferredSpecType 302 | 303 | [#rspecrailsminitestassertions] 304 | == RSpecRails/MinitestAssertions 305 | 306 | |=== 307 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 308 | 309 | | Pending 310 | | Yes 311 | | Always 312 | | 2.17 313 | | - 314 | |=== 315 | 316 | Check if using Minitest-like matchers. 317 | 318 | Check the use of minitest-like matchers 319 | starting with `assert_` or `refute_`. 320 | 321 | [#examples-rspecrailsminitestassertions] 322 | === Examples 323 | 324 | [source,ruby] 325 | ---- 326 | # bad 327 | assert_equal(a, b) 328 | assert_equal a, b, "must be equal" 329 | assert_not_includes a, b 330 | refute_equal(a, b) 331 | assert_nil a 332 | refute_empty(b) 333 | assert_true(a) 334 | assert_false(a) 335 | 336 | # good 337 | expect(b).to eq(a) 338 | expect(b).to(eq(a), "must be equal") 339 | expect(a).not_to include(b) 340 | expect(b).not_to eq(a) 341 | expect(a).to eq(nil) 342 | expect(a).not_to be_empty 343 | expect(a).to be(true) 344 | expect(a).to be(false) 345 | ---- 346 | 347 | [#references-rspecrailsminitestassertions] 348 | === References 349 | 350 | * https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/MinitestAssertions 351 | 352 | [#rspecrailsnegationbevalid] 353 | == RSpecRails/NegationBeValid 354 | 355 | |=== 356 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 357 | 358 | | Pending 359 | | No 360 | | Command-line only (Unsafe) 361 | | 2.23 362 | | 2.29 363 | |=== 364 | 365 | Enforces use of `be_invalid` or `not_to` for negated be_valid. 366 | 367 | [#safety-rspecrailsnegationbevalid] 368 | === Safety 369 | 370 | This cop is unsafe because it cannot guarantee that 371 | the test target is an instance of `ActiveModel::Validations``. 372 | 373 | [#examples-rspecrailsnegationbevalid] 374 | === Examples 375 | 376 | [#enforcedstyle_-not_to-_default_-rspecrailsnegationbevalid] 377 | ==== EnforcedStyle: not_to (default) 378 | 379 | [source,ruby] 380 | ---- 381 | # bad 382 | expect(foo).to be_invalid 383 | 384 | # good 385 | expect(foo).not_to be_valid 386 | 387 | # good (with method chain) 388 | expect(foo).to be_invalid.and be_odd 389 | ---- 390 | 391 | [#enforcedstyle_-be_invalid-rspecrailsnegationbevalid] 392 | ==== EnforcedStyle: be_invalid 393 | 394 | [source,ruby] 395 | ---- 396 | # bad 397 | expect(foo).not_to be_valid 398 | 399 | # good 400 | expect(foo).to be_invalid 401 | 402 | # good (with method chain) 403 | expect(foo).to be_invalid.or be_even 404 | ---- 405 | 406 | [#configurable-attributes-rspecrailsnegationbevalid] 407 | === Configurable attributes 408 | 409 | |=== 410 | | Name | Default value | Configurable values 411 | 412 | | EnforcedStyle 413 | | `not_to` 414 | | `not_to`, `be_invalid` 415 | |=== 416 | 417 | [#references-rspecrailsnegationbevalid] 418 | === References 419 | 420 | * https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/NegationBeValid 421 | 422 | [#rspecrailstravelaround] 423 | == RSpecRails/TravelAround 424 | 425 | |=== 426 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 427 | 428 | | Pending 429 | | No 430 | | Always (Unsafe) 431 | | 2.19 432 | | - 433 | |=== 434 | 435 | Prefer to travel in `before` rather than `around`. 436 | 437 | [#safety-rspecrailstravelaround] 438 | === Safety 439 | 440 | This cop is unsafe because the automatic `travel_back` is only run 441 | on test cases that are considered as Rails related. 442 | 443 | And also, this cop's autocorrection is unsafe because the order of 444 | execution will change if other steps exist before traveling in 445 | `around`. 446 | 447 | [#examples-rspecrailstravelaround] 448 | === Examples 449 | 450 | [source,ruby] 451 | ---- 452 | # bad 453 | around do |example| 454 | freeze_time do 455 | example.run 456 | end 457 | end 458 | 459 | # bad 460 | around do |example| 461 | freeze_time(&example) 462 | end 463 | 464 | # good 465 | before { freeze_time } 466 | ---- 467 | 468 | [#references-rspecrailstravelaround] 469 | === References 470 | 471 | * https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/TravelAround 472 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/development.adoc: -------------------------------------------------------------------------------- 1 | = Development 2 | 3 | This page describes considerations when developing RSpec Rails-specific cops. It is intended to be a complement to the general https://docs.rubocop.org/rubocop/development.html[RuboCop development documentation]. 4 | 5 | == Create a new cop 6 | 7 | NOTE: Clone the repository and run `bundle install` if not done yet. 8 | The following rake task can only be run inside the rubocop project directory itself. 9 | 10 | Use the bundled rake task `new_cop` to generate a cop template: 11 | 12 | [source,sh] 13 | ---- 14 | $ bundle exec rake 'new_cop[RSpecRails/CopName]' 15 | [create] lib/rubocop/cop/rspec_rails/cop_name.rb 16 | [create] spec/rubocop/cop/rspec_rails/cop_name_spec.rb 17 | [modify] lib/rubocop/cop/rspec_rails_cops.rb - `require_relative 'rspec_rails/cop_name'` was injected. 18 | [modify] A configuration for the cop is added into config/default.yml. 19 | Do 4 steps: 20 | 1. Modify the description of RSpecRails/CopName in config/default.yml 21 | 2. Implement your new cop in the generated file! 22 | 3. Add an entry about new cop to CHANGELOG.md 23 | 4. Commit your new cop with a message such as 24 | e.g. "Add new `#{badge}` cop" 25 | ---- 26 | 27 | === Choose a Name 28 | 29 | Use the following rules to give the new cop a name: 30 | 31 | * Pick a department. See the xref:cops.adoc[list of existing departments] 32 | * The name is self-explanatory 33 | * The name explains the offense the cop detects, e.g. `ExtraSpacing` 34 | * The name starts with a noun instead of a verb, e.g. `ArrayAlignment` instead of `AlignArray` 35 | * The name is easy to understand, e.g. `IndentationStyle` instead of just `Tab` 36 | * The name is specific, e.g. `DuplicateHashKey` instead of just `DuplicateKey` 37 | * The name is neutral when possible and accommodates multiple styles when feasible, e.g. `EmptyLineBeforeBegin`. 38 | * The name uses commonly-used terms, e.g. `RedundantPercentI` instead of `RedundantPercentSymbolArray` 39 | * The name uses correct terms, e.g. arguments in a method call, and parameters in a method signature 40 | * Lines with no symbols are called "empty", not "blank", e.g. `LeadingEmptyLines` instead of `LeadingBlankLines` 41 | * Prefer "redundant" to "unneeded", e.g. `RedundantSelf` instead of `UnneededSelf` 42 | 43 | See the https://github.com/rubocop/rubocop/blob/12fd014e255617a08b7b42aa5df0745e7382af88/config/obsoletion.yml#L4["renamed" section of `config/obsoletion.yml`] 44 | for good and bad examples (old name is on the left, new name on the right). 45 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = RuboCop RSpec Rails 2 | 3 | https://rspec.info/features/6-1/rspec-rails/[RSpec Rails]-specific analysis for your projects, as an extension to 4 | https://github.com/rubocop/rubocop[RuboCop]. 5 | 6 | RuboCop RSpec Rails follows the https://docs.rubocop.org/rubocop/versioning.html[RuboCop versioning guide]. 7 | In a nutshell, between major versions new cops are introduced in a special `pending` status. 8 | That means that they won't be run unless explicitly told otherwise. 9 | RuboCop will warn on start that certain cops are neither explicitly enabled and disabled. 10 | On a major version release, all `pending` cops are enabled. 11 | 12 | == Project Goals 13 | 14 | * Simplify the process of adopting new RSpec Rails functionality 15 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/installation.adoc: -------------------------------------------------------------------------------- 1 | = Installation 2 | 3 | *This gem implicitly depends on the `rubocop-rspec` gem, so you should install it first.* 4 | Just install the `rubocop-rspec` and `rubocop-rspec_rails` gem 5 | 6 | [source,bash] 7 | ---- 8 | gem install rubocop-rspec rubocop-rspec_rails 9 | ---- 10 | 11 | or if you use bundler put this in your `Gemfile` 12 | 13 | [source,ruby] 14 | ---- 15 | gem 'rubocop-rspec', require: false 16 | gem 'rubocop-rspec_rails', require: false 17 | ---- 18 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/usage.adoc: -------------------------------------------------------------------------------- 1 | = Usage 2 | 3 | You need to tell RuboCop to load the RSpec Rails extension. 4 | There are three ways to do this: 5 | 6 | == RuboCop configuration file 7 | 8 | Put this into your `.rubocop.yml`: 9 | 10 | ---- 11 | plugins: rubocop-rspec_rails 12 | ---- 13 | 14 | or, if you are using several extensions: 15 | 16 | ---- 17 | plugins: 18 | - rubocop-rspec 19 | - rubocop-rspec_rails 20 | ---- 21 | 22 | Now you can run `rubocop` and it will automatically load the RuboCop RSpec Rails 23 | cops together with the standard cops. 24 | 25 | NOTE: The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. 26 | 27 | == Command line 28 | 29 | [source,bash] 30 | ---- 31 | $ rubocop --plugin rubocop-rspec_rails 32 | ---- 33 | 34 | == Rake task 35 | 36 | [source,ruby] 37 | ---- 38 | RuboCop::RakeTask.new do |task| 39 | task.plugins << 'rubocop-rspec_rails' 40 | end 41 | ---- 42 | 43 | == Inspecting files that don't end with `_spec.rb` 44 | 45 | By default, `rubocop-rspec_rails` only inspects code within paths ending in `_spec.rb` or including `spec/`. You can override this setting in your config file by setting `Include`: 46 | 47 | [source,yaml] 48 | ---- 49 | # Inspect files in `test/` directory 50 | RSpecRails: 51 | Include: 52 | - '**/test/**/*' 53 | ---- 54 | 55 | [source,yaml] 56 | ---- 57 | # Inspect only files ending with `_test.rb` 58 | RSpecRails: 59 | Include: 60 | - '**/*_test.rb' 61 | ---- 62 | 63 | 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. 64 | -------------------------------------------------------------------------------- /lib/rubocop-rspec_rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'yaml' 5 | 6 | require 'rubocop' 7 | require 'rubocop/rspec/language' 8 | 9 | require_relative 'rubocop/rspec_rails/plugin' 10 | require_relative 'rubocop/rspec_rails/version' 11 | 12 | require 'rubocop/cop/rspec/base' 13 | require_relative 'rubocop/cop/rspec_rails_cops' 14 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails/avoid_setup_hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpecRails 6 | # Checks that tests use RSpec `before` hook over Rails `setup` method. 7 | # 8 | # @example 9 | # # bad 10 | # setup do 11 | # allow(foo).to receive(:bar) 12 | # end 13 | # 14 | # # good 15 | # before do 16 | # allow(foo).to receive(:bar) 17 | # end 18 | # 19 | class AvoidSetupHook < ::RuboCop::Cop::Base 20 | extend AutoCorrector 21 | 22 | MSG = 'Use `before` instead of `setup`.' 23 | 24 | # @!method setup_call(node) 25 | def_node_matcher :setup_call, <<~PATTERN 26 | (block 27 | $(send nil? :setup) 28 | (args) _) 29 | PATTERN 30 | 31 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 32 | setup_call(node) do |setup| 33 | add_offense(node) do |corrector| 34 | corrector.replace setup, 'before' 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails/have_http_status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpecRails 6 | # Checks that tests use `have_http_status` instead of equality matchers. 7 | # 8 | # @example ResponseMethods: ['response', 'last_response'] (default) 9 | # # bad 10 | # expect(response.status).to be(200) 11 | # expect(last_response.code).to eq("200") 12 | # 13 | # # good 14 | # expect(response).to have_http_status(200) 15 | # expect(last_response).to have_http_status(200) 16 | # 17 | # @example ResponseMethods: ['foo_response'] 18 | # # bad 19 | # expect(foo_response.status).to be(200) 20 | # 21 | # # good 22 | # expect(foo_response).to have_http_status(200) 23 | # 24 | # # also good 25 | # expect(response).to have_http_status(200) 26 | # expect(last_response).to have_http_status(200) 27 | # 28 | class HaveHttpStatus < ::RuboCop::Cop::Base 29 | extend AutoCorrector 30 | 31 | MSG = 32 | 'Prefer `expect(%s).%s ' \ 33 | 'have_http_status(%s)` over `%s`.' 34 | 35 | RUNNERS = %i[to to_not not_to].to_set 36 | RESTRICT_ON_SEND = RUNNERS 37 | 38 | # @!method match_status(node) 39 | def_node_matcher :match_status, <<~PATTERN 40 | (send 41 | (send nil? :expect 42 | $(send $(send nil? #response_methods?) {:status :code}) 43 | ) 44 | $RUNNERS 45 | $(send nil? {:be :eq :eql :equal} ({int str} $_)) 46 | ) 47 | PATTERN 48 | 49 | def on_send(node) # rubocop:disable Metrics/MethodLength 50 | match_status(node) do 51 | |response_status, response_method, to, match, status| 52 | return unless status.to_s.match?(/\A\d+\z/) 53 | 54 | message = format(MSG, response: response_method.method_name, 55 | to: to, status: status, 56 | bad_code: node.source) 57 | add_offense(node, message: message) do |corrector| 58 | corrector.replace(response_status, response_method.method_name) 59 | corrector.replace(match.loc.selector, 'have_http_status') 60 | corrector.replace(match.first_argument, status.to_s) 61 | end 62 | end 63 | end 64 | 65 | private 66 | 67 | def response_methods?(name) 68 | response_methods.include?(name.to_s) 69 | end 70 | 71 | def response_methods 72 | cop_config.fetch('ResponseMethods', []) 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails/http_status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'rack/utils' 5 | rescue LoadError 6 | # RSpecRails/HttpStatus cannot be loaded if rack/utils is unavailable. 7 | end 8 | 9 | module RuboCop 10 | module Cop 11 | module RSpecRails 12 | # Enforces use of symbolic or numeric value to describe HTTP status. 13 | # 14 | # This cop inspects only `have_http_status` calls. 15 | # So, this cop does not check if a method starting with `be_*` is used 16 | # when setting for `EnforcedStyle: symbolic` or 17 | # `EnforcedStyle: numeric`. 18 | # This cop is also capable of detecting unknown HTTP status codes. 19 | # 20 | # @example `EnforcedStyle: symbolic` (default) 21 | # # bad 22 | # it { is_expected.to have_http_status 200 } 23 | # it { is_expected.to have_http_status 404 } 24 | # it { is_expected.to have_http_status "403" } 25 | # 26 | # # good 27 | # it { is_expected.to have_http_status :ok } 28 | # it { is_expected.to have_http_status :not_found } 29 | # it { is_expected.to have_http_status :forbidden } 30 | # it { is_expected.to have_http_status :success } 31 | # it { is_expected.to have_http_status :error } 32 | # 33 | # @example `EnforcedStyle: numeric` 34 | # # bad 35 | # it { is_expected.to have_http_status :ok } 36 | # it { is_expected.to have_http_status :not_found } 37 | # it { is_expected.to have_http_status "forbidden" } 38 | # 39 | # # good 40 | # it { is_expected.to have_http_status 200 } 41 | # it { is_expected.to have_http_status 404 } 42 | # it { is_expected.to have_http_status 403 } 43 | # it { is_expected.to have_http_status :success } 44 | # it { is_expected.to have_http_status :error } 45 | # 46 | # @example `EnforcedStyle: be_status` 47 | # # bad 48 | # it { is_expected.to have_http_status :ok } 49 | # it { is_expected.to have_http_status :not_found } 50 | # it { is_expected.to have_http_status "forbidden" } 51 | # it { is_expected.to have_http_status 200 } 52 | # it { is_expected.to have_http_status 404 } 53 | # it { is_expected.to have_http_status "403" } 54 | # 55 | # # good 56 | # it { is_expected.to be_ok } 57 | # it { is_expected.to be_not_found } 58 | # it { is_expected.to have_http_status :success } 59 | # it { is_expected.to have_http_status :error } 60 | # 61 | # @example 62 | # # bad 63 | # it { is_expected.to have_http_status :oki_doki } 64 | # 65 | # # good 66 | # it { is_expected.to have_http_status :ok } 67 | class HttpStatus < ::RuboCop::Cop::Base 68 | extend AutoCorrector 69 | include ConfigurableEnforcedStyle 70 | RESTRICT_ON_SEND = %i[have_http_status].freeze 71 | 72 | # @!method http_status(node) 73 | def_node_matcher :http_status, <<~PATTERN 74 | (send nil? :have_http_status ${int sym str}) 75 | PATTERN 76 | 77 | def on_send(node) # rubocop:disable Metrics/MethodLength 78 | return unless defined?(::Rack::Utils::SYMBOL_TO_STATUS_CODE) 79 | 80 | http_status(node) do |arg| 81 | return if arg.str_type? && arg.heredoc? 82 | 83 | checker = checker_class.new(arg) 84 | return unless checker.offensive? 85 | 86 | add_offense(checker.offense_range, 87 | message: checker.message) do |corrector| 88 | next unless checker.autocorrectable? 89 | 90 | corrector.replace(checker.offense_range, checker.prefer) 91 | end 92 | end 93 | end 94 | 95 | private 96 | 97 | def checker_class 98 | case style 99 | when :symbolic 100 | SymbolicStyleChecker 101 | when :numeric 102 | NumericStyleChecker 103 | when :be_status 104 | BeStatusStyleChecker 105 | else 106 | # :nocov: 107 | :noop 108 | # :nocov: 109 | end 110 | end 111 | 112 | # :nodoc: 113 | class StyleCheckerBase 114 | MSG = 'Prefer `%s` over `%s` ' \ 115 | 'to describe HTTP status code.' 116 | MSG_UNKNOWN_STATUS_CODE = 'Unknown status code.' 117 | ALLOWED_STATUSES = %i[error success missing redirect].freeze 118 | 119 | attr_reader :node 120 | 121 | def initialize(node) 122 | @node = node 123 | end 124 | 125 | def message 126 | if autocorrectable? 127 | format(MSG, prefer: prefer, current: current) 128 | else 129 | MSG_UNKNOWN_STATUS_CODE 130 | end 131 | end 132 | 133 | def current 134 | offense_range.source 135 | end 136 | 137 | def offense_range 138 | node 139 | end 140 | 141 | def allowed_symbol? 142 | node.sym_type? && ALLOWED_STATUSES.include?(node.value) 143 | end 144 | 145 | def custom_http_status_code? 146 | node.int_type? && 147 | !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(node.source.to_i) 148 | end 149 | end 150 | 151 | # :nodoc: 152 | class SymbolicStyleChecker < StyleCheckerBase 153 | def offensive? 154 | !node.sym_type? && !custom_http_status_code? 155 | end 156 | 157 | def autocorrectable? 158 | !!symbol 159 | end 160 | 161 | def prefer 162 | symbol.inspect 163 | end 164 | 165 | private 166 | 167 | def symbol 168 | ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number) 169 | end 170 | 171 | def number 172 | node.value.to_i 173 | end 174 | end 175 | 176 | # :nodoc: 177 | class NumericStyleChecker < StyleCheckerBase 178 | def offensive? 179 | !node.int_type? && !allowed_symbol? 180 | end 181 | 182 | def autocorrectable? 183 | !!number 184 | end 185 | 186 | def prefer 187 | number.to_s 188 | end 189 | 190 | private 191 | 192 | def symbol 193 | node.value 194 | end 195 | 196 | def number 197 | ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol.to_sym] 198 | end 199 | end 200 | 201 | # :nodoc: 202 | class BeStatusStyleChecker < StyleCheckerBase 203 | def offensive? 204 | (!node.sym_type? && !custom_http_status_code?) || 205 | (!node.int_type? && !allowed_symbol?) 206 | end 207 | 208 | def autocorrectable? 209 | !!status_code 210 | end 211 | 212 | def offense_range 213 | node.parent 214 | end 215 | 216 | def prefer 217 | "be_#{status_code}" 218 | end 219 | 220 | private 221 | 222 | def status_code 223 | if node.sym_type? 224 | node.value 225 | elsif node.int_type? 226 | symbol 227 | else 228 | normalize_str 229 | end 230 | end 231 | 232 | def symbol 233 | ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number) 234 | end 235 | 236 | def number 237 | node.value.to_i 238 | end 239 | 240 | def normalize_str 241 | str = node.value.to_s 242 | if str.match?(/\A\d+\z/) 243 | ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(str.to_i) 244 | elsif ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(str.to_sym) 245 | str 246 | end 247 | end 248 | end 249 | end 250 | end 251 | end 252 | end 253 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails/inferred_spec_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpecRails 6 | # Identifies redundant spec type. 7 | # 8 | # After setting up rspec-rails, you will have enabled 9 | # `config.infer_spec_type_from_file_location!` by default in 10 | # spec/rails_helper.rb. This cop works in conjunction with this config. 11 | # If you disable this config, disable this cop as well. 12 | # 13 | # @safety 14 | # This cop is marked as unsafe because 15 | # `config.infer_spec_type_from_file_location!` may not be enabled. 16 | # 17 | # @example 18 | # # bad 19 | # # spec/models/user_spec.rb 20 | # RSpec.describe User, type: :model do 21 | # end 22 | # 23 | # # good 24 | # # spec/models/user_spec.rb 25 | # RSpec.describe User do 26 | # end 27 | # 28 | # # good 29 | # # spec/models/user_spec.rb 30 | # RSpec.describe User, type: :common do 31 | # end 32 | # 33 | # @example `Inferences` configuration 34 | # # .rubocop.yml 35 | # # RSpecRails/InferredSpecType: 36 | # # Inferences: 37 | # # services: service 38 | # 39 | # # bad 40 | # # spec/services/user_spec.rb 41 | # RSpec.describe User, type: :service do 42 | # end 43 | # 44 | # # good 45 | # # spec/services/user_spec.rb 46 | # RSpec.describe User do 47 | # end 48 | # 49 | # # good 50 | # # spec/services/user_spec.rb 51 | # RSpec.describe User, type: :common do 52 | # end 53 | class InferredSpecType < ::RuboCop::Cop::RSpec::Base 54 | extend AutoCorrector 55 | 56 | MSG = 'Remove redundant spec type.' 57 | 58 | # @param [RuboCop::AST::BlockNode] node 59 | def on_block(node) 60 | return unless example_group?(node) 61 | 62 | pair_node = describe_with_type(node) 63 | return unless pair_node 64 | return unless inferred_type?(pair_node) 65 | 66 | removable_node = detect_removable_node(pair_node) 67 | add_offense(removable_node) do |corrector| 68 | autocorrect(corrector, removable_node) 69 | end 70 | end 71 | alias on_numblock on_block 72 | 73 | private 74 | 75 | # @!method describe_with_type(node) 76 | # @param [RuboCop::AST::BlockNode] node 77 | # @return [RuboCop::AST::PairNode, nil] 78 | def_node_matcher :describe_with_type, <<~PATTERN 79 | (block 80 | (send #rspec? #ExampleGroups.all 81 | ... 82 | (hash <$(pair (sym :type) sym) ...>) 83 | ) 84 | ... 85 | ) 86 | PATTERN 87 | 88 | # @param [RuboCop::AST::Corrector] corrector 89 | # @param [RuboCop::AST::Node] node 90 | def autocorrect(corrector, node) 91 | corrector.remove(remove_range(node)) 92 | end 93 | 94 | # @param [RuboCop::AST::Node] node 95 | # @return [Parser::Source::Range] 96 | # rubocop:disable Metrics/MethodLength 97 | def remove_range(node) 98 | if node.left_sibling 99 | node.source_range.with( 100 | begin_pos: node.left_sibling.source_range.end_pos 101 | ) 102 | elsif node.right_sibling 103 | node.source_range.with( 104 | end_pos: node.right_sibling.source_range.begin_pos 105 | ) 106 | else 107 | # :nocov: 108 | :noop 109 | # :nocov: 110 | end 111 | end 112 | # rubocop:enable Metrics/MethodLength 113 | 114 | # @param [RuboCop::AST::PairNode] node 115 | # @return [RuboCop::AST::Node] 116 | def detect_removable_node(node) 117 | if node.parent.pairs.size == 1 118 | node.parent 119 | else 120 | node 121 | end 122 | end 123 | 124 | # @return [String] 125 | def file_path 126 | processed_source.file_path 127 | end 128 | 129 | # @param [RuboCop::AST::PairNode] node 130 | # @return [Boolean] 131 | def inferred_type?(node) 132 | inferred_type_from_file_path.inspect == node.value.source 133 | end 134 | 135 | # @return [Symbol, nil] 136 | def inferred_type_from_file_path 137 | inferences.find do |prefix, type| 138 | break type.to_sym if file_path.include?("spec/#{prefix}/") 139 | end 140 | end 141 | 142 | # @return [Hash] 143 | def inferences 144 | cop_config['Inferences'] || {} 145 | end 146 | end 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails/minitest_assertions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpecRails 6 | # Check if using Minitest-like matchers. 7 | # 8 | # Check the use of minitest-like matchers 9 | # starting with `assert_` or `refute_`. 10 | # 11 | # @example 12 | # # bad 13 | # assert_equal(a, b) 14 | # assert_equal a, b, "must be equal" 15 | # assert_not_includes a, b 16 | # refute_equal(a, b) 17 | # assert_nil a 18 | # refute_empty(b) 19 | # assert_true(a) 20 | # assert_false(a) 21 | # 22 | # # good 23 | # expect(b).to eq(a) 24 | # expect(b).to(eq(a), "must be equal") 25 | # expect(a).not_to include(b) 26 | # expect(b).not_to eq(a) 27 | # expect(a).to eq(nil) 28 | # expect(a).not_to be_empty 29 | # expect(a).to be(true) 30 | # expect(a).to be(false) 31 | # 32 | class MinitestAssertions < ::RuboCop::Cop::Base 33 | extend AutoCorrector 34 | 35 | # :nodoc: 36 | class BasicAssertion 37 | extend NodePattern::Macros 38 | 39 | attr_reader :expected, :actual, :failure_message 40 | 41 | def self.minitest_assertion 42 | # :nocov: 43 | raise NotImplementedError 44 | # :nocov: 45 | end 46 | 47 | def initialize(expected, actual, failure_message) 48 | @expected = expected&.source 49 | @actual = actual.source 50 | @failure_message = failure_message&.source 51 | end 52 | 53 | def replaced(node) 54 | runner = negated?(node) ? 'not_to' : 'to' 55 | if failure_message.nil? 56 | "expect(#{actual}).#{runner} #{assertion}" 57 | else 58 | "expect(#{actual}).#{runner}(#{assertion}, #{failure_message})" 59 | end 60 | end 61 | 62 | def negated?(node) 63 | node.method_name.start_with?('assert_not_', 'refute_') 64 | end 65 | 66 | def assertion 67 | # :nocov: 68 | raise NotImplementedError 69 | # :nocov: 70 | end 71 | end 72 | 73 | # :nodoc: 74 | class EqualAssertion < BasicAssertion 75 | MATCHERS = %i[ 76 | assert_equal 77 | assert_not_equal 78 | refute_equal 79 | ].freeze 80 | 81 | # @!method self.minitest_assertion(node) 82 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 83 | (send nil? {:assert_equal :assert_not_equal :refute_equal} $_ $_ $_?) 84 | PATTERN 85 | 86 | def self.match(expected, actual, failure_message) 87 | new(expected, actual, failure_message.first) 88 | end 89 | 90 | def assertion 91 | "eq(#{expected})" 92 | end 93 | end 94 | 95 | # :nodoc: 96 | class KindOfAssertion < BasicAssertion 97 | MATCHERS = %i[ 98 | assert_kind_of 99 | assert_not_kind_of 100 | refute_kind_of 101 | ].freeze 102 | 103 | # @!method self.minitest_assertion(node) 104 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 105 | (send nil? {:assert_kind_of :assert_not_kind_of :refute_kind_of} $_ $_ $_?) 106 | PATTERN 107 | 108 | def self.match(expected, actual, failure_message) 109 | new(expected, actual, failure_message.first) 110 | end 111 | 112 | def assertion 113 | "be_a_kind_of(#{expected})" 114 | end 115 | end 116 | 117 | # :nodoc: 118 | class InstanceOfAssertion < BasicAssertion 119 | MATCHERS = %i[ 120 | assert_instance_of 121 | assert_not_instance_of 122 | refute_instance_of 123 | ].freeze 124 | 125 | # @!method self.minitest_assertion(node) 126 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 127 | (send nil? {:assert_instance_of :assert_not_instance_of :refute_instance_of} $_ $_ $_?) 128 | PATTERN 129 | 130 | def self.match(expected, actual, failure_message) 131 | new(expected, actual, failure_message.first) 132 | end 133 | 134 | def assertion 135 | "be_an_instance_of(#{expected})" 136 | end 137 | end 138 | 139 | # :nodoc: 140 | class IncludesAssertion < BasicAssertion 141 | MATCHERS = %i[ 142 | assert_includes 143 | assert_not_includes 144 | refute_includes 145 | ].freeze 146 | 147 | # @!method self.minitest_assertion(node) 148 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 149 | (send nil? {:assert_includes :assert_not_includes :refute_includes} $_ $_ $_?) 150 | PATTERN 151 | 152 | def self.match(collection, expected, failure_message) 153 | new(expected, collection, failure_message.first) 154 | end 155 | 156 | def assertion 157 | "include(#{expected})" 158 | end 159 | end 160 | 161 | # :nodoc: 162 | class InDeltaAssertion < BasicAssertion 163 | MATCHERS = %i[ 164 | assert_in_delta 165 | assert_not_in_delta 166 | refute_in_delta 167 | ].freeze 168 | 169 | # @!method self.minitest_assertion(node) 170 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 171 | (send nil? {:assert_in_delta :assert_not_in_delta :refute_in_delta} $_ $_ $_? $_?) 172 | PATTERN 173 | 174 | def self.match(expected, actual, delta, failure_message) 175 | new(expected, actual, delta.first, failure_message.first) 176 | end 177 | 178 | def initialize(expected, actual, delta, fail_message) 179 | super(expected, actual, fail_message) 180 | 181 | @delta = delta&.source || '0.001' 182 | end 183 | 184 | def assertion 185 | "be_within(#{@delta}).of(#{expected})" 186 | end 187 | end 188 | 189 | # :nodoc: 190 | class PredicateAssertion < BasicAssertion 191 | MATCHERS = %i[ 192 | assert_predicate 193 | assert_not_predicate 194 | refute_predicate 195 | ].freeze 196 | 197 | # @!method self.minitest_assertion(node) 198 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 199 | (send nil? {:assert_predicate :assert_not_predicate :refute_predicate} $_ ${sym} $_?) 200 | PATTERN 201 | 202 | def self.match(subject, predicate, failure_message) 203 | return nil unless predicate.value.end_with?('?') 204 | 205 | new(predicate, subject, failure_message.first) 206 | end 207 | 208 | def assertion 209 | "be_#{expected.delete_prefix(':').delete_suffix('?')}" 210 | end 211 | end 212 | 213 | # :nodoc: 214 | class MatchAssertion < BasicAssertion 215 | MATCHERS = %i[ 216 | assert_match 217 | refute_match 218 | ].freeze 219 | 220 | # @!method self.minitest_assertion(node) 221 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 222 | (send nil? {:assert_match :refute_match} $_ $_ $_?) 223 | PATTERN 224 | 225 | def self.match(matcher, actual, failure_message) 226 | new(matcher, actual, failure_message.first) 227 | end 228 | 229 | def assertion 230 | "match(#{expected})" 231 | end 232 | end 233 | 234 | # :nodoc: 235 | class NilAssertion < BasicAssertion 236 | MATCHERS = %i[ 237 | assert_nil 238 | assert_not_nil 239 | refute_nil 240 | ].freeze 241 | 242 | # @!method self.minitest_assertion(node) 243 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 244 | (send nil? {:assert_nil :assert_not_nil :refute_nil} $_ $_?) 245 | PATTERN 246 | 247 | def self.match(actual, failure_message) 248 | new(nil, actual, failure_message.first) 249 | end 250 | 251 | def assertion 252 | 'eq(nil)' 253 | end 254 | end 255 | 256 | # :nodoc: 257 | class EmptyAssertion < BasicAssertion 258 | MATCHERS = %i[ 259 | assert_empty 260 | assert_not_empty 261 | refute_empty 262 | ].freeze 263 | 264 | # @!method self.minitest_assertion(node) 265 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 266 | (send nil? {:assert_empty :assert_not_empty :refute_empty} $_ $_?) 267 | PATTERN 268 | 269 | def self.match(actual, failure_message) 270 | new(nil, actual, failure_message.first) 271 | end 272 | 273 | def assertion 274 | 'be_empty' 275 | end 276 | end 277 | 278 | # :nodoc: 279 | class TrueAssertion < BasicAssertion 280 | MATCHERS = %i[ 281 | assert_true 282 | ].freeze 283 | 284 | # @!method self.minitest_assertion(node) 285 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 286 | (send nil? {:assert_true} $_ $_?) 287 | PATTERN 288 | 289 | def self.match(actual, failure_message) 290 | new(nil, actual, failure_message.first) 291 | end 292 | 293 | def assertion 294 | 'be(true)' 295 | end 296 | end 297 | 298 | # :nodoc: 299 | class FalseAssertion < BasicAssertion 300 | MATCHERS = %i[ 301 | assert_false 302 | ].freeze 303 | 304 | # @!method self.minitest_assertion(node) 305 | def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective 306 | (send nil? {:assert_false} $_ $_?) 307 | PATTERN 308 | 309 | def self.match(actual, failure_message) 310 | new(nil, actual, failure_message.first) 311 | end 312 | 313 | def assertion 314 | 'be(false)' 315 | end 316 | end 317 | 318 | MSG = 'Use `%s`.' 319 | 320 | # TODO: replace with `BasicAssertion.subclasses` in Ruby 3.1+ 321 | ASSERTION_MATCHERS = constants(false).filter_map do |c| 322 | const = const_get(c) 323 | 324 | const if const.is_a?(Class) && const.superclass == BasicAssertion 325 | end 326 | 327 | RESTRICT_ON_SEND = ASSERTION_MATCHERS.flat_map { |m| m::MATCHERS } 328 | 329 | def on_send(node) 330 | ASSERTION_MATCHERS.each do |m| 331 | m.minitest_assertion(node) do |*args| 332 | assertion = m.match(*args) 333 | 334 | next if assertion.nil? 335 | 336 | on_assertion(node, assertion) 337 | end 338 | end 339 | end 340 | 341 | def on_assertion(node, assertion) 342 | preferred = assertion.replaced(node) 343 | add_offense(node, message: message(preferred)) do |corrector| 344 | corrector.replace(node, preferred) 345 | end 346 | end 347 | 348 | def message(preferred) 349 | format(MSG, prefer: preferred) 350 | end 351 | end 352 | end 353 | end 354 | end 355 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails/negation_be_valid.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpecRails 6 | # Enforces use of `be_invalid` or `not_to` for negated be_valid. 7 | # 8 | # @safety 9 | # This cop is unsafe because it cannot guarantee that 10 | # the test target is an instance of `ActiveModel::Validations``. 11 | # 12 | # @example EnforcedStyle: not_to (default) 13 | # # bad 14 | # expect(foo).to be_invalid 15 | # 16 | # # good 17 | # expect(foo).not_to be_valid 18 | # 19 | # # good (with method chain) 20 | # expect(foo).to be_invalid.and be_odd 21 | # 22 | # @example EnforcedStyle: be_invalid 23 | # # bad 24 | # expect(foo).not_to be_valid 25 | # 26 | # # good 27 | # expect(foo).to be_invalid 28 | # 29 | # # good (with method chain) 30 | # expect(foo).to be_invalid.or be_even 31 | # 32 | class NegationBeValid < ::RuboCop::Cop::Base 33 | extend AutoCorrector 34 | include ConfigurableEnforcedStyle 35 | 36 | MSG = 'Use `expect(...).%s %s`.' 37 | RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze 38 | 39 | # @!method not_to?(node) 40 | def_node_matcher :not_to?, <<~PATTERN 41 | (send ... :not_to (send nil? :be_valid ...)) 42 | PATTERN 43 | 44 | # @!method be_invalid?(node) 45 | def_node_matcher :be_invalid?, <<~PATTERN 46 | (send ... :to (send nil? :be_invalid ...)) 47 | PATTERN 48 | 49 | def on_send(node) 50 | return unless offense?(node.parent) 51 | 52 | add_offense(offense_range(node), 53 | message: message(node.method_name)) do |corrector| 54 | corrector.replace(node.parent.loc.selector, replaced_runner) 55 | corrector.replace(node.loc.selector, replaced_matcher) 56 | end 57 | end 58 | 59 | private 60 | 61 | def offense?(node) 62 | case style 63 | when :not_to 64 | be_invalid?(node) 65 | when :be_invalid 66 | not_to?(node) 67 | else 68 | # :nocov: 69 | :noop 70 | # :nocov: 71 | end 72 | end 73 | 74 | def offense_range(node) 75 | node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos) 76 | end 77 | 78 | def message(_matcher) 79 | format(MSG, runner: replaced_runner, matcher: replaced_matcher) 80 | end 81 | 82 | def replaced_runner 83 | case style 84 | when :not_to 85 | 'not_to' 86 | when :be_invalid 87 | 'to' 88 | else 89 | # :nocov: 90 | :noop 91 | # :nocov: 92 | end 93 | end 94 | 95 | def replaced_matcher 96 | case style 97 | when :not_to 98 | 'be_valid' 99 | when :be_invalid 100 | 'be_invalid' 101 | else 102 | # :nocov: 103 | :noop 104 | # :nocov: 105 | end 106 | end 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails/travel_around.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module RSpecRails 6 | # Prefer to travel in `before` rather than `around`. 7 | # 8 | # @safety 9 | # This cop is unsafe because the automatic `travel_back` is only run 10 | # on test cases that are considered as Rails related. 11 | # 12 | # And also, this cop's autocorrection is unsafe because the order of 13 | # execution will change if other steps exist before traveling in 14 | # `around`. 15 | # 16 | # @example 17 | # # bad 18 | # around do |example| 19 | # freeze_time do 20 | # example.run 21 | # end 22 | # end 23 | # 24 | # # bad 25 | # around do |example| 26 | # freeze_time(&example) 27 | # end 28 | # 29 | # # good 30 | # before { freeze_time } 31 | # 32 | class TravelAround < ::RuboCop::Cop::Base 33 | extend AutoCorrector 34 | 35 | MSG = 'Prefer to travel in `before` rather than `around`.' 36 | 37 | TRAVEL_METHOD_NAMES = %i[ 38 | freeze_time 39 | travel 40 | travel_to 41 | ].to_set.freeze 42 | 43 | # @!method extract_run_in_travel(node) 44 | def_node_matcher :extract_run_in_travel, <<~PATTERN 45 | (block 46 | $(send nil? TRAVEL_METHOD_NAMES ...) 47 | (args ...) 48 | (send _ :run) 49 | ) 50 | PATTERN 51 | 52 | # @!method extract_travel_with_block_pass(node) 53 | def_node_search :extract_travel_with_block_pass, <<~PATTERN 54 | $(send _ TRAVEL_METHOD_NAMES 55 | (block_pass $lvar) 56 | ) 57 | PATTERN 58 | 59 | # @!method match_around_each?(node) 60 | def_node_matcher :match_around_each?, <<~PATTERN 61 | (block 62 | (send _ :around (sym :each)?) 63 | ... 64 | ) 65 | PATTERN 66 | 67 | def on_block(node) 68 | extract_run_in_travel(node) do |run_node| 69 | run_in_travel(node, run_node) 70 | end 71 | extract_travel_with_block_pass(node) do |travel_node, lvar| 72 | travel_with_block_pass(travel_node, lvar) 73 | end 74 | end 75 | alias on_numblock on_block 76 | 77 | private 78 | 79 | def run_in_travel(node, run_node) 80 | around_node = extract_surrounding_around_block(run_node) 81 | return unless around_node 82 | 83 | add_offense(node) do |corrector| 84 | corrector.replace(node, node.body.source) 85 | corrector.insert_before(around_node, 86 | "before { #{run_node.source} }\n\n") 87 | end 88 | end 89 | 90 | def travel_with_block_pass(node, lvar) 91 | around_node = extract_surrounding_around_block(node) 92 | return unless around_node 93 | 94 | add_offense(node) do |corrector| 95 | corrector.replace(node, "#{lvar.name}.run") 96 | corrector.insert_before( 97 | around_node, 98 | "before { #{node.method_name} }\n\n" 99 | ) 100 | end 101 | end 102 | 103 | # @param node [RuboCop::AST::BlockNode] 104 | # @return [RuboCop::AST::BlockNode, nil] 105 | def extract_surrounding_around_block(node) 106 | node.each_ancestor(:block).find do |ancestor| 107 | match_around_each?(ancestor) 108 | end 109 | end 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/rubocop/cop/rspec_rails_cops.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'rspec_rails/avoid_setup_hook' 4 | require_relative 'rspec_rails/have_http_status' 5 | require_relative 'rspec_rails/http_status' 6 | require_relative 'rspec_rails/inferred_spec_type' 7 | require_relative 'rspec_rails/minitest_assertions' 8 | require_relative 'rspec_rails/negation_be_valid' 9 | require_relative 'rspec_rails/travel_around' 10 | -------------------------------------------------------------------------------- /lib/rubocop/rspec_rails/config_formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | 5 | module RuboCop 6 | module RSpecRails 7 | # Builds a YAML config file from two config hashes 8 | class ConfigFormatter 9 | EXTENSION_ROOT_DEPARTMENT = %r{^(RSpecRails/)}.freeze 10 | SUBDEPARTMENTS = [].freeze 11 | AMENDMENTS = [].freeze 12 | COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/' 13 | 14 | def initialize(config, descriptions) 15 | @config = config 16 | @descriptions = descriptions 17 | end 18 | 19 | def dump 20 | YAML.dump(unified_config) 21 | .gsub(EXTENSION_ROOT_DEPARTMENT, "\n\\1") 22 | .gsub(/^(\s+)- /, '\1 - ') 23 | .gsub('"~"', '~') 24 | end 25 | 26 | private 27 | 28 | def unified_config 29 | cops.each_with_object(config.dup) do |cop, unified| 30 | next if SUBDEPARTMENTS.include?(cop) || AMENDMENTS.include?(cop) 31 | 32 | replace_nil(unified[cop]) 33 | unified[cop].merge!(descriptions.fetch(cop)) 34 | unified[cop]['Reference'] = reference(cop) 35 | end 36 | end 37 | 38 | def cops 39 | (descriptions.keys | config.keys).grep(EXTENSION_ROOT_DEPARTMENT) 40 | end 41 | 42 | def replace_nil(config) 43 | config.each do |key, value| 44 | config[key] = '~' if value.nil? 45 | end 46 | end 47 | 48 | def reference(cop) 49 | COP_DOC_BASE_URL + cop 50 | end 51 | 52 | attr_reader :config, :descriptions 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/rubocop/rspec_rails/cop/generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module RSpecRails 5 | module Cop 6 | # Source and spec generator for new cops 7 | # 8 | # This generator will take a cop name and generate a source file 9 | # and spec file when given a valid qualified cop name. 10 | # @api private 11 | class Generator < RuboCop::Cop::Generator 12 | def todo 13 | <<~TODO 14 | Do 4 steps: 15 | 1. Modify the description of #{badge} in config/default.yml 16 | 2. Implement your new cop in the generated file! 17 | 3. Add an entry about new cop to CHANGELOG.md 18 | 4. Commit your new cop with a message such as 19 | e.g. "Add new `#{badge}` cop" 20 | TODO 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/rubocop/rspec_rails/description_extractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module RSpecRails 5 | # Extracts cop descriptions from YARD docstrings 6 | class DescriptionExtractor 7 | def initialize(yardocs) 8 | @code_objects = yardocs.map(&CodeObject.public_method(:new)) 9 | end 10 | 11 | def to_h 12 | code_objects 13 | .select(&:rspec_rails_cop?) 14 | .map(&:configuration) 15 | .reduce(:merge) 16 | end 17 | 18 | private 19 | 20 | attr_reader :code_objects 21 | 22 | # Decorator of a YARD code object for working with documented rspec cops 23 | class CodeObject 24 | RSPEC_RAILS_COP_CLASS_NAME = 'RuboCop::Cop::RSpec::Base' 25 | RUBOCOP_COP_CLASS_NAME = 'RuboCop::Cop::Base' 26 | 27 | def initialize(yardoc) 28 | @yardoc = yardoc 29 | end 30 | 31 | # Test if the YARD code object documents a concrete cop class 32 | # 33 | # @return [Boolean] 34 | def rspec_rails_cop? 35 | cop_subclass? && !abstract? 36 | end 37 | 38 | # Configuration for the documented cop that would live in default.yml 39 | # 40 | # @return [Hash] 41 | def configuration 42 | { cop_name => { 'Description' => description } } 43 | end 44 | 45 | private 46 | 47 | def cop_name 48 | Object.const_get(documented_constant).cop_name 49 | end 50 | 51 | def description 52 | yardoc.docstring.split("\n\n").first.to_s 53 | end 54 | 55 | def documented_constant 56 | yardoc.to_s 57 | end 58 | 59 | def cop_subclass? 60 | [RSPEC_RAILS_COP_CLASS_NAME, 61 | RUBOCOP_COP_CLASS_NAME].include?(yardoc.superclass.path) 62 | end 63 | 64 | def abstract? 65 | yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') } 66 | end 67 | 68 | attr_reader :yardoc 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/rubocop/rspec_rails/plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'lint_roller' 4 | 5 | module RuboCop 6 | module RSpecRails 7 | # A plugin that integrates RuboCop RSpecRails with RuboCop's plugin system. 8 | class Plugin < LintRoller::Plugin 9 | # :nocov: 10 | def about 11 | LintRoller::About.new( 12 | name: 'rubocop-rspec_rails', 13 | version: Version::STRING, 14 | homepage: 'https://github.com/rubocop/rubocop-rspec_rails', 15 | description: 'Code style checking for RSpec Rails files.' 16 | ) 17 | end 18 | # :nocov: 19 | 20 | def supported?(context) 21 | context.engine == :rubocop 22 | end 23 | 24 | def rules(_context) 25 | project_root = Pathname.new(__dir__).join('../../..') 26 | 27 | LintRoller::Rules.new( 28 | type: :path, 29 | config_format: :rubocop, 30 | value: project_root.join('config/default.yml') 31 | ) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/rubocop/rspec_rails/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module RSpecRails 5 | # Version information for the RSpec Rails RuboCop plugin. 6 | module Version 7 | STRING = '2.31.0' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /rubocop-rspec_rails.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path('lib', __dir__) 4 | require 'rubocop/rspec_rails/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'rubocop-rspec_rails' 8 | spec.summary = 'Code style checking for RSpec Rails files' 9 | spec.description = <<~DESCRIPTION 10 | Code style checking for RSpec Rails files. 11 | A plugin for the RuboCop code style enforcing & linting tool. 12 | DESCRIPTION 13 | spec.homepage = 'https://github.com/rubocop/rubocop-rspec_rails' 14 | spec.authors = [ 15 | 'Benjamin Quorning', 'Phil Pirozhkov', 'Maxim Krizhanovsky', 'Yudai Takada' 16 | ] 17 | spec.licenses = ['MIT'] 18 | 19 | spec.version = RuboCop::RSpecRails::Version::STRING 20 | spec.platform = Gem::Platform::RUBY 21 | spec.required_ruby_version = '>= 2.7.0' 22 | 23 | spec.require_paths = ['lib'] 24 | spec.files = Dir[ 25 | 'lib/**/*', 26 | 'config/*', 27 | '*.md' 28 | ] 29 | spec.extra_rdoc_files = ['MIT-LICENSE.md', 'README.md'] 30 | 31 | spec.metadata = { 32 | 'changelog_uri' => 'https://github.com/rubocop/rubocop-rspec_rails/blob/master/CHANGELOG.md', 33 | 'documentation_uri' => 'https://docs.rubocop.org/rubocop-rspec_rails/', 34 | 'rubygems_mfa_required' => 'true', 35 | 'default_lint_roller_plugin' => 'RuboCop::RSpecRails::Plugin' 36 | } 37 | 38 | spec.add_dependency 'lint_roller', '~> 1.1' 39 | spec.add_dependency 'rubocop', '~> 1.72', '>= 1.72.1' 40 | spec.add_runtime_dependency 'rubocop-rspec', '~> 3.5' 41 | end 42 | -------------------------------------------------------------------------------- /spec/project/changelog_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'CHANGELOG.md' do 4 | subject(:changelog) { SpecHelper::ROOT.join('CHANGELOG.md').read } 5 | 6 | it 'has link definitions for all implicit links' do 7 | entries, contributors = 8 | changelog.split("\n\n") 9 | 10 | implicit_link_names = entries.scan(/\[@(.+?)\]/).flatten.uniq 11 | expected_links = implicit_link_names.sort_by(&:downcase).map do |name| 12 | "[@#{name.downcase}]: https://github.com/#{name}\n" 13 | end.join 14 | 15 | expect(contributors).to eq(expected_links) 16 | end 17 | 18 | describe 'contributors list' do 19 | let(:contributors) do 20 | changelog.split("\n\n").last 21 | .lines 22 | end 23 | 24 | it 'does not contain duplicates' do 25 | expect(contributors.uniq).to eq(contributors) 26 | end 27 | 28 | it 'is alphabetically sorted (case insensitive)' do 29 | expect(contributors.sort_by(&:downcase)).to eq(contributors) 30 | end 31 | end 32 | 33 | describe 'entry' do 34 | subject(:entries) { lines.grep(/^-/).map(&:chomp) } 35 | 36 | let(:lines) { changelog.each_line } 37 | 38 | it 'has some entries' do 39 | expect(entries).not_to be_empty 40 | end 41 | 42 | it 'has a link to the contributors at the end' do 43 | expect(entries).to all(match(/\(\[@\S+\](?:, \[@\S+\])*\)$/)) 44 | end 45 | 46 | describe 'body' do 47 | let(:bodies) do 48 | entries.map do |entry| 49 | entry 50 | .sub(/^-\s*(?:\[.+?\):\s*)?/, '') 51 | .sub(/\s*\([^)]+\)$/, '') 52 | end 53 | end 54 | 55 | it 'does not start with a lower case' do 56 | bodies.each do |body| 57 | expect(body).not_to match(/^[a-z]/) 58 | end 59 | end 60 | 61 | it 'ends with a punctuation' do 62 | expect(bodies).to all(match(/[.!]$/)) 63 | end 64 | 65 | it 'does not use consecutive whitespaces' do 66 | entries.each do |entry| 67 | expect(entry).not_to match(/\s{2,}/) 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/project/default_config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe 'config/default.yml' do 4 | subject(:default_config) do 5 | RuboCop::ConfigLoader.load_yaml_configuration('config/default.yml') 6 | end 7 | 8 | let(:namespaces) do 9 | { 10 | 'rspec_rails' => 'RSpecRails' 11 | } 12 | end 13 | 14 | let(:cop_names) do 15 | glob = SpecHelper::ROOT.join('lib', 'rubocop', 'cop', 'rspec_rails', '*.rb') 16 | cop_names = Pathname.glob(glob).map do |file| 17 | file_name = file.basename('.rb').to_s 18 | cop_name = file_name.gsub(/(^|_)(.)/) { Regexp.last_match(2).upcase } 19 | namespace = namespaces[file.dirname.basename.to_s] 20 | "#{namespace}/#{cop_name}" 21 | end 22 | 23 | cop_names - %w[RSpecRails/Base] 24 | end 25 | 26 | let(:config_keys) do 27 | cop_names + namespaces.values 28 | end 29 | 30 | let(:unsafe_cops) do 31 | require 'yard' 32 | YARD::Registry.load! 33 | YARD::Registry.all(:class).select do |example| 34 | example.tags.any? { |tag| tag.tag_name == 'safety' } 35 | end 36 | end 37 | 38 | let(:unsafe_cop_names) do 39 | unsafe_cops.map do |cop| 40 | dept_and_cop_names = 41 | cop.path.split('::')[2..] # Drop `RuboCop::Cop` from class name. 42 | dept_and_cop_names.join('/') 43 | end 44 | end 45 | 46 | def cop_configuration(config_key) 47 | cop_names.map do |cop_name| 48 | cop_config = default_config[cop_name] 49 | 50 | cop_config.fetch(config_key) do 51 | raise "Expected #{cop_name} to have #{config_key} configuration key" 52 | end 53 | end 54 | end 55 | 56 | it 'has configuration for all cops' do 57 | expect(default_config.keys) 58 | .to match_array(config_keys) 59 | end 60 | 61 | it 'sorts configuration keys alphabetically with nested namespaces last' do 62 | keys = default_config.keys.select { |key| key.start_with?('RSpecRails') } 63 | namespaced_keys = keys.select do |key| 64 | key.start_with?(*(namespaces.values - ['RSpecRails'])) 65 | end 66 | 67 | expected = keys.sort_by do |key| 68 | namespaced = namespaced_keys.include?(key) ? 1 : 0 69 | "#{namespaced} #{key}" 70 | end 71 | 72 | keys.each_with_index do |key, idx| 73 | expect(key).to eq expected[idx] 74 | end 75 | end 76 | 77 | it 'has descriptions for all cops' do 78 | expect(cop_configuration('Description')).to all(be_a(String)) 79 | end 80 | 81 | it 'does not have newlines in cop descriptions' do 82 | cop_configuration('Description').each do |value| 83 | expect(value).not_to include("\n") 84 | end 85 | end 86 | 87 | it 'ends every description with a period' do 88 | expect(cop_configuration('Description')).to all(end_with('.')) 89 | end 90 | 91 | it 'includes a valid Enabled for every cop' do 92 | expect(cop_configuration('Enabled')) 93 | .to all be(true).or(be(false)).or(eq('pending')) 94 | end 95 | 96 | it 'does not include unnecessary `SafeAutoCorrect: false`' do 97 | cop_names.each do |cop_name| 98 | next unless default_config.dig(cop_name, 'Safe') == false 99 | 100 | safe_autocorrect = default_config.dig(cop_name, 'SafeAutoCorrect') 101 | 102 | expect(safe_autocorrect).not_to( 103 | be(false), 104 | "`#{cop_name}` has unnecessary `SafeAutoCorrect: false` config." 105 | ) 106 | end 107 | end 108 | 109 | it 'is expected that all cops documented with `@safety` are `Safe: false`' \ 110 | ' or `SafeAutoCorrect: false`' do 111 | unsafe_cop_names.each do |cop_name| 112 | unsafe = default_config[cop_name]['Safe'] == false || 113 | default_config[cop_name]['SafeAutoCorrect'] == false 114 | expect(unsafe).to( 115 | be(true), 116 | "`#{cop_name}` cop should be set `Safe: false` or " \ 117 | '`SafeAutoCorrect: false` because `@safety` YARD tag exists.' 118 | ) 119 | YARD::Registry.clear 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /spec/rubocop/cop/rspec_rails/avoid_setup_hook_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::RSpecRails::AvoidSetupHook do 4 | it 'registers an offense for `setup`' do 5 | expect_offense(<<~RUBY) 6 | setup do 7 | ^^^^^^^^ Use `before` instead of `setup`. 8 | allow(foo).to receive(:bar) 9 | end 10 | RUBY 11 | 12 | expect_correction(<<~RUBY) 13 | before do 14 | allow(foo).to receive(:bar) 15 | end 16 | RUBY 17 | end 18 | 19 | it 'does not register an offense for `before`' do 20 | expect_no_offenses(<<~RUBY) 21 | before do 22 | allow(foo).to receive(:bar) 23 | end 24 | RUBY 25 | end 26 | 27 | it 'does not register an offense for an unrelated `setup` call' do 28 | expect_no_offenses(<<~RUBY) 29 | navigation.setup do 30 | direction 'to infinity!' 31 | end 32 | RUBY 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/rubocop/cop/rspec_rails/have_http_status_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::RSpecRails::HaveHttpStatus do 4 | it 'registers an offense for `expect(response.status).to be(200)`' do 5 | expect_offense(<<~RUBY) 6 | it { expect(response.status).to be(200) } 7 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `expect(response).to have_http_status(200)` over `expect(response.status).to be(200)`. 8 | RUBY 9 | 10 | expect_correction(<<~RUBY) 11 | it { expect(response).to have_http_status(200) } 12 | RUBY 13 | end 14 | 15 | it 'registers an offense for `expect(response.status).not_to eq(404)`' do 16 | expect_offense(<<~RUBY) 17 | it { expect(response.status).not_to eq(404) } 18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `expect(response).not_to have_http_status(404)` over `expect(response.status).not_to eq(404)`. 19 | RUBY 20 | 21 | expect_correction(<<~RUBY) 22 | it { expect(response).not_to have_http_status(404) } 23 | RUBY 24 | end 25 | 26 | it 'registers an offense for `expect(response.code).to eq("200")`' do 27 | expect_offense(<<~RUBY) 28 | it { expect(response.code).to eq("200") } 29 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `expect(response).to have_http_status(200)` over `expect(response.code).to eq("200")`. 30 | RUBY 31 | 32 | expect_correction(<<~RUBY) 33 | it { expect(response).to have_http_status(200) } 34 | RUBY 35 | end 36 | 37 | it 'registers an offense for `expect(last_response.status).to eql("200")`' do 38 | expect_offense(<<~RUBY) 39 | it { expect(last_response.status).to eql("200") } 40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `expect(last_response).to have_http_status(200)` over `expect(last_response.status).to eql("200")`. 41 | RUBY 42 | 43 | expect_correction(<<~RUBY) 44 | it { expect(last_response).to have_http_status(200) } 45 | RUBY 46 | end 47 | 48 | it 'does not register an offense for `is_expected.to be(200)`' do 49 | expect_no_offenses(<<~RUBY) 50 | it { is_expected.to be(200) } 51 | RUBY 52 | end 53 | 54 | it 'does not register an offense for `expect(res.status).to be(200)`' do 55 | expect_no_offenses(<<~RUBY) 56 | it { expect(res.status).to be(200) } 57 | RUBY 58 | end 59 | 60 | it 'ignores statuses that would not coerce to an integer' do 61 | expect_no_offenses(<<~RUBY) 62 | it { expect(response.status).to eq("404 Not Found") } 63 | RUBY 64 | end 65 | 66 | context 'when configured with ResponseMethods: [foo_response]' do 67 | let(:cop_config) { { 'ResponseMethods' => %w[foo_response] } } 68 | 69 | it 'registers an offense for `expect(foo_response.status).to be(200)`' do 70 | expect_offense(<<~RUBY) 71 | it { expect(foo_response.status).to be(200) } 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `expect(foo_response).to have_http_status(200)` over `expect(foo_response.status).to be(200)`. 73 | RUBY 74 | 75 | expect_correction(<<~RUBY) 76 | it { expect(foo_response).to have_http_status(200) } 77 | RUBY 78 | end 79 | 80 | it 'does not register an offense for ' \ 81 | '`expect(response.status).to be(200)`' do 82 | expect_no_offenses(<<~RUBY) 83 | it { expect(response.status).to be(200) } 84 | RUBY 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/rubocop/cop/rspec_rails/http_status_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::RSpecRails::HttpStatus do 4 | context 'when EnforcedStyle is `symbolic`' do 5 | let(:cop_config) { { 'EnforcedStyle' => 'symbolic' } } 6 | 7 | it 'registers an offense when using numeric value' do 8 | expect_offense(<<~RUBY) 9 | it { is_expected.to have_http_status 200 } 10 | ^^^ Prefer `:ok` over `200` to describe HTTP status code. 11 | RUBY 12 | 13 | expect_correction(<<~RUBY) 14 | it { is_expected.to have_http_status :ok } 15 | RUBY 16 | end 17 | 18 | it 'registers an offense when using double quoted string value' do 19 | expect_offense(<<~RUBY) 20 | it { is_expected.to have_http_status "200" } 21 | ^^^^^ Prefer `:ok` over `"200"` to describe HTTP status code. 22 | RUBY 23 | 24 | expect_correction(<<~RUBY) 25 | it { is_expected.to have_http_status :ok } 26 | RUBY 27 | end 28 | 29 | it 'registers an offense when using single quoted string value' do 30 | expect_offense(<<~RUBY) 31 | it { is_expected.to have_http_status '200' } 32 | ^^^^^ Prefer `:ok` over `'200'` to describe HTTP status code. 33 | RUBY 34 | 35 | expect_correction(<<~RUBY) 36 | it { is_expected.to have_http_status :ok } 37 | RUBY 38 | end 39 | 40 | it 'registers an offense when using percent string value' do 41 | expect_offense(<<~RUBY) 42 | it { is_expected.to have_http_status %[200] } 43 | ^^^^^^ Prefer `:ok` over `%[200]` to describe HTTP status code. 44 | RUBY 45 | 46 | expect_correction(<<~RUBY) 47 | it { is_expected.to have_http_status :ok } 48 | RUBY 49 | end 50 | 51 | it 'does not register an offense when using here document' do 52 | expect_no_offenses(<<~RUBY) 53 | it { is_expected.to have_http_status <<~HTTP } 54 | 200 55 | HTTP 56 | RUBY 57 | end 58 | 59 | it 'does not register an offense when using symbolic value' do 60 | expect_no_offenses(<<~RUBY) 61 | it { is_expected.to have_http_status :ok } 62 | RUBY 63 | end 64 | 65 | it 'does not register an offense when using custom HTTP code' do 66 | expect_no_offenses(<<~RUBY) 67 | it { is_expected.to have_http_status 550 } 68 | RUBY 69 | end 70 | 71 | context 'with parenthesis' do 72 | it 'registers an offense when using numeric value' do 73 | expect_offense(<<~RUBY) 74 | it { is_expected.to have_http_status(404) } 75 | ^^^ Prefer `:not_found` over `404` to describe HTTP status code. 76 | RUBY 77 | 78 | expect_correction(<<~RUBY) 79 | it { is_expected.to have_http_status(:not_found) } 80 | RUBY 81 | end 82 | end 83 | 84 | it 'registers an offense for unknown status code' do 85 | expect_offense(<<~RUBY) 86 | it { is_expected.to have_http_status("some-custom-string") } 87 | ^^^^^^^^^^^^^^^^^^^^ Unknown status code. 88 | RUBY 89 | 90 | expect_no_corrections 91 | end 92 | 93 | it 'registers no offense when Rack is not loaded' do 94 | hide_const('Rack') 95 | 96 | expect_no_offenses(<<~RUBY) 97 | it { is_expected.to have_http_status(404) } 98 | RUBY 99 | end 100 | end 101 | 102 | context 'when EnforcedStyle is `numeric`' do 103 | let(:cop_config) { { 'EnforcedStyle' => 'numeric' } } 104 | 105 | it 'registers an offense when using symbolic value' do 106 | expect_offense(<<~RUBY) 107 | it { is_expected.to have_http_status :ok } 108 | ^^^ Prefer `200` over `:ok` to describe HTTP status code. 109 | RUBY 110 | 111 | expect_correction(<<~RUBY) 112 | it { is_expected.to have_http_status 200 } 113 | RUBY 114 | end 115 | 116 | it 'registers an offense when using double quoted string value' do 117 | expect_offense(<<~RUBY) 118 | it { is_expected.to have_http_status "ok" } 119 | ^^^^ Prefer `200` over `"ok"` to describe HTTP status code. 120 | RUBY 121 | 122 | expect_correction(<<~RUBY) 123 | it { is_expected.to have_http_status 200 } 124 | RUBY 125 | end 126 | 127 | it 'registers an offense when using single quoted string value' do 128 | expect_offense(<<~RUBY) 129 | it { is_expected.to have_http_status 'ok' } 130 | ^^^^ Prefer `200` over `'ok'` to describe HTTP status code. 131 | RUBY 132 | 133 | expect_correction(<<~RUBY) 134 | it { is_expected.to have_http_status 200 } 135 | RUBY 136 | end 137 | 138 | it 'does not register an offense when using numeric value' do 139 | expect_no_offenses(<<~RUBY) 140 | it { is_expected.to have_http_status 200 } 141 | RUBY 142 | end 143 | 144 | it 'does not register an offense when using allowed symbols' do 145 | expect_no_offenses(<<~RUBY) 146 | it { is_expected.to have_http_status :error } 147 | it { is_expected.to have_http_status :success } 148 | it { is_expected.to have_http_status :missing } 149 | it { is_expected.to have_http_status :redirect } 150 | RUBY 151 | end 152 | 153 | it 'registers an offense for unknown status code' do 154 | expect_offense(<<~RUBY) 155 | it { is_expected.to have_http_status("some-custom-string") } 156 | ^^^^^^^^^^^^^^^^^^^^ Unknown status code. 157 | RUBY 158 | 159 | expect_no_corrections 160 | end 161 | 162 | context 'with parenthesis' do 163 | it 'registers an offense when using symbolic value' do 164 | expect_offense(<<~RUBY) 165 | it { is_expected.to have_http_status(:not_found) } 166 | ^^^^^^^^^^ Prefer `404` over `:not_found` to describe HTTP status code. 167 | RUBY 168 | 169 | expect_correction(<<~RUBY) 170 | it { is_expected.to have_http_status(404) } 171 | RUBY 172 | end 173 | end 174 | 175 | it 'registers no offense when Rack is not loaded' do 176 | hide_const('Rack') 177 | 178 | expect_no_offenses(<<~RUBY) 179 | it { is_expected.to have_http_status(:not_found) } 180 | RUBY 181 | end 182 | end 183 | 184 | context 'when EnforcedStyle is `be_status`' do 185 | let(:cop_config) { { 'EnforcedStyle' => 'be_status' } } 186 | 187 | it 'registers an offense when using numeric value' do 188 | expect_offense(<<~RUBY) 189 | it { is_expected.to have_http_status 200 } 190 | ^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status 200` to describe HTTP status code. 191 | RUBY 192 | 193 | expect_correction(<<~RUBY) 194 | it { is_expected.to be_ok } 195 | RUBY 196 | end 197 | 198 | it 'registers an offense when using symbolic value' do 199 | expect_offense(<<~RUBY) 200 | it { is_expected.to have_http_status :ok } 201 | ^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status :ok` to describe HTTP status code. 202 | RUBY 203 | 204 | expect_correction(<<~RUBY) 205 | it { is_expected.to be_ok } 206 | RUBY 207 | end 208 | 209 | it 'registers an offense when using double quoted string value' do 210 | expect_offense(<<~RUBY) 211 | it { is_expected.to have_http_status "200" } 212 | ^^^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status "200"` to describe HTTP status code. 213 | it { is_expected.to have_http_status "ok" } 214 | ^^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status "ok"` to describe HTTP status code. 215 | RUBY 216 | 217 | expect_correction(<<~RUBY) 218 | it { is_expected.to be_ok } 219 | it { is_expected.to be_ok } 220 | RUBY 221 | end 222 | 223 | it 'registers an offense when using single quoted string value' do 224 | expect_offense(<<~RUBY) 225 | it { is_expected.to have_http_status '200' } 226 | ^^^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status '200'` to describe HTTP status code. 227 | it { is_expected.to have_http_status 'ok' } 228 | ^^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status 'ok'` to describe HTTP status code. 229 | RUBY 230 | 231 | expect_correction(<<~RUBY) 232 | it { is_expected.to be_ok } 233 | it { is_expected.to be_ok } 234 | RUBY 235 | end 236 | 237 | it 'registers an offense when using percent string value' do 238 | expect_offense(<<~RUBY) 239 | it { is_expected.to have_http_status %[200] } 240 | ^^^^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status %[200]` to describe HTTP status code. 241 | it { is_expected.to have_http_status %[ok] } 242 | ^^^^^^^^^^^^^^^^^^^^^^ Prefer `be_ok` over `have_http_status %[ok]` to describe HTTP status code. 243 | RUBY 244 | 245 | expect_correction(<<~RUBY) 246 | it { is_expected.to be_ok } 247 | it { is_expected.to be_ok } 248 | RUBY 249 | end 250 | 251 | it 'does not register an offense when using here document' do 252 | expect_no_offenses(<<~RUBY) 253 | it { is_expected.to have_http_status <<~HTTP } 254 | 200 255 | HTTP 256 | RUBY 257 | end 258 | 259 | it 'does not register an offense when using numeric value' do 260 | expect_no_offenses(<<~RUBY) 261 | it { is_expected.to be_ok } 262 | RUBY 263 | end 264 | 265 | it 'does not register an offense when using allowed symbols' do 266 | expect_no_offenses(<<~RUBY) 267 | it { is_expected.to have_http_status :error } 268 | it { is_expected.to have_http_status :success } 269 | it { is_expected.to have_http_status :missing } 270 | it { is_expected.to have_http_status :redirect } 271 | RUBY 272 | end 273 | 274 | it 'registers an offense for unknown status code' do 275 | expect_offense(<<~RUBY) 276 | it { is_expected.to have_http_status("some-custom-string") } 277 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unknown status code. 278 | RUBY 279 | 280 | expect_no_corrections 281 | end 282 | 283 | context 'with parenthesis' do 284 | it 'registers an offense when using numeric value' do 285 | expect_offense(<<~RUBY) 286 | it { is_expected.to have_http_status(404) } 287 | ^^^^^^^^^^^^^^^^^^^^^ Prefer `be_not_found` over `have_http_status(404)` to describe HTTP status code. 288 | RUBY 289 | 290 | expect_correction(<<~RUBY) 291 | it { is_expected.to be_not_found } 292 | RUBY 293 | end 294 | 295 | it 'registers an offense when using symbolic value' do 296 | expect_offense(<<~RUBY) 297 | it { is_expected.to have_http_status(:not_found) } 298 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `be_not_found` over `have_http_status(:not_found)` to describe HTTP status code. 299 | RUBY 300 | 301 | expect_correction(<<~RUBY) 302 | it { is_expected.to be_not_found } 303 | RUBY 304 | end 305 | end 306 | 307 | it 'registers no offense when Rack is not loaded' do 308 | hide_const('Rack') 309 | 310 | expect_no_offenses(<<~RUBY) 311 | it { is_expected.to have_http_status(:not_found) } 312 | RUBY 313 | end 314 | end 315 | end 316 | -------------------------------------------------------------------------------- /spec/rubocop/cop/rspec_rails/inferred_spec_type_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::RSpecRails::InferredSpecType do 4 | describe 'with necessary type in keyword arguments' do 5 | it 'does not register any offense' do 6 | expect_no_offenses(<<~RUBY) 7 | RSpec.describe User, type: :model do 8 | end 9 | RUBY 10 | end 11 | end 12 | 13 | describe 'with redundant type in keyword arguments' do 14 | it 'registers and corrects an offense' do 15 | expect_offense(<<~RUBY, '/path/to/project/spec/models/user_spec.rb') 16 | RSpec.describe User, type: :model do 17 | ^^^^^^^^^^^^ Remove redundant spec type. 18 | end 19 | RUBY 20 | 21 | expect_correction(<<~RUBY) 22 | RSpec.describe User do 23 | end 24 | RUBY 25 | end 26 | end 27 | 28 | describe 'with redundant type in Hash arguments' do 29 | it 'registers and corrects an offense' do 30 | expect_offense(<<~RUBY, '/path/to/project/spec/models/user_spec.rb') 31 | RSpec.describe User, { type: :model } do 32 | ^^^^^^^^^^^^^^^^ Remove redundant spec type. 33 | end 34 | RUBY 35 | 36 | expect_correction(<<~RUBY) 37 | RSpec.describe User do 38 | end 39 | RUBY 40 | end 41 | end 42 | 43 | describe 'with redundant type before other Hash metadata' do 44 | it 'registers and corrects an offense' do 45 | expect_offense(<<~RUBY, '/path/to/project/spec/models/user_spec.rb') 46 | RSpec.describe User, type: :model, other: true do 47 | ^^^^^^^^^^^^ Remove redundant spec type. 48 | end 49 | RUBY 50 | 51 | expect_correction(<<~RUBY) 52 | RSpec.describe User, other: true do 53 | end 54 | RUBY 55 | end 56 | end 57 | 58 | describe 'with redundant type after other Hash metadata' do 59 | it 'registers and corrects an offense' do 60 | expect_offense(<<~RUBY, '/path/to/project/spec/models/user_spec.rb') 61 | RSpec.describe User, other: true, type: :model do 62 | ^^^^^^^^^^^^ Remove redundant spec type. 63 | end 64 | RUBY 65 | 66 | expect_correction(<<~RUBY) 67 | RSpec.describe User, other: true do 68 | end 69 | RUBY 70 | end 71 | end 72 | 73 | describe 'with redundant type and other Symbol metadata' do 74 | it 'registers and corrects an offense' do 75 | expect_offense(<<~RUBY, '/path/to/project/spec/models/user_spec.rb') 76 | RSpec.describe User, :other, type: :model do 77 | ^^^^^^^^^^^^ Remove redundant spec type. 78 | end 79 | RUBY 80 | 81 | expect_correction(<<~RUBY) 82 | RSpec.describe User, :other do 83 | end 84 | RUBY 85 | end 86 | end 87 | 88 | describe 'with redundant type and receiver-less describe' do 89 | it 'registers and corrects an offense' do 90 | expect_offense(<<~RUBY, '/path/to/project/spec/models/user_spec.rb') 91 | describe User, type: :model do 92 | ^^^^^^^^^^^^ Remove redundant spec type. 93 | end 94 | RUBY 95 | 96 | expect_correction(<<~RUBY) 97 | describe User do 98 | end 99 | RUBY 100 | end 101 | end 102 | 103 | describe 'with redundant type in inner example group' do 104 | it 'registers and corrects an offense' do 105 | expect_offense(<<~RUBY, '/path/to/project/spec/models/user_spec.rb') 106 | RSpec.describe User do 107 | describe 'inner', type: :model do 108 | ^^^^^^^^^^^^ Remove redundant spec type. 109 | end 110 | end 111 | RUBY 112 | 113 | expect_correction(<<~RUBY) 114 | RSpec.describe User do 115 | describe 'inner' do 116 | end 117 | end 118 | RUBY 119 | end 120 | end 121 | 122 | describe 'with Inferences configuration' do 123 | let(:cop_config) do 124 | { 125 | 'Inferences' => { 126 | 'services' => 'service' 127 | } 128 | } 129 | end 130 | 131 | it 'registers and corrects an offense' do 132 | expect_offense(<<~RUBY, '/path/to/project/spec/services/user_spec.rb') 133 | RSpec.describe User, type: :service do 134 | ^^^^^^^^^^^^^^ Remove redundant spec type. 135 | end 136 | RUBY 137 | 138 | expect_correction(<<~RUBY) 139 | RSpec.describe User do 140 | end 141 | RUBY 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /spec/rubocop/cop/rspec_rails/minitest_assertions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::RSpecRails::MinitestAssertions do 4 | context 'with equal assertions' do 5 | it 'registers an offense when using `assert_equal`' do 6 | expect_offense(<<~RUBY) 7 | assert_equal(a, b) 8 | ^^^^^^^^^^^^^^^^^^ Use `expect(b).to eq(a)`. 9 | RUBY 10 | 11 | expect_correction(<<~RUBY) 12 | expect(b).to eq(a) 13 | RUBY 14 | end 15 | 16 | it 'registers an offense when using `assert_equal` with no parentheses' do 17 | expect_offense(<<~RUBY) 18 | assert_equal a, b 19 | ^^^^^^^^^^^^^^^^^ Use `expect(b).to eq(a)`. 20 | RUBY 21 | 22 | expect_correction(<<~RUBY) 23 | expect(b).to eq(a) 24 | RUBY 25 | end 26 | 27 | it 'registers an offense when using `assert_equal` with failure message' do 28 | expect_offense(<<~RUBY) 29 | assert_equal a, b, "must be equal" 30 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to(eq(a), "must be equal")`. 31 | RUBY 32 | 33 | expect_correction(<<~RUBY) 34 | expect(b).to(eq(a), "must be equal") 35 | RUBY 36 | end 37 | 38 | it 'registers an offense when using `assert_equal` with ' \ 39 | 'multi-line arguments' do 40 | expect_offense(<<~RUBY) 41 | assert_equal(a, 42 | ^^^^^^^^^^^^^^^ Use `expect(b).to(eq(a), "must be equal")`. 43 | b, 44 | "must be equal") 45 | RUBY 46 | 47 | expect_correction(<<~RUBY) 48 | expect(b).to(eq(a), "must be equal") 49 | RUBY 50 | end 51 | 52 | it 'registers an offense when using `assert_not_equal`' do 53 | expect_offense(<<~RUBY) 54 | assert_not_equal a, b 55 | ^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to eq(a)`. 56 | RUBY 57 | 58 | expect_correction(<<~RUBY) 59 | expect(b).not_to eq(a) 60 | RUBY 61 | end 62 | 63 | it 'registers an offense when using `refute_equal`' do 64 | expect_offense(<<~RUBY) 65 | refute_equal a, b 66 | ^^^^^^^^^^^^^^^^^ Use `expect(b).not_to eq(a)`. 67 | RUBY 68 | 69 | expect_correction(<<~RUBY) 70 | expect(b).not_to eq(a) 71 | RUBY 72 | end 73 | 74 | it 'does not register an offense when using `expect(b).to eq(a)`' do 75 | expect_no_offenses(<<~RUBY) 76 | expect(b).to eq(a) 77 | RUBY 78 | end 79 | 80 | it 'does not register an offense when using `expect(b).not_to eq(a)`' do 81 | expect_no_offenses(<<~RUBY) 82 | expect(b).not_to eq(a) 83 | RUBY 84 | end 85 | end 86 | 87 | context 'with kind_of assertions' do 88 | it 'registers an offense when using `assert_kind_of`' do 89 | expect_offense(<<~RUBY) 90 | assert_kind_of(a, b) 91 | ^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_a_kind_of(a)`. 92 | RUBY 93 | 94 | expect_correction(<<~RUBY) 95 | expect(b).to be_a_kind_of(a) 96 | RUBY 97 | end 98 | 99 | it 'registers an offense when using `assert_kind_of` with ' \ 100 | 'no parentheses' do 101 | expect_offense(<<~RUBY) 102 | assert_kind_of a, b 103 | ^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_a_kind_of(a)`. 104 | RUBY 105 | 106 | expect_correction(<<~RUBY) 107 | expect(b).to be_a_kind_of(a) 108 | RUBY 109 | end 110 | 111 | it 'registers an offense when using `assert_kind_of` with ' \ 112 | 'failure message' do 113 | expect_offense(<<~RUBY) 114 | assert_kind_of a, b, "must be kind of" 115 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to(be_a_kind_of(a), "must be kind of")`. 116 | RUBY 117 | 118 | expect_correction(<<~RUBY) 119 | expect(b).to(be_a_kind_of(a), "must be kind of") 120 | RUBY 121 | end 122 | 123 | it 'registers an offense when using `assert_kind_of` with ' \ 124 | 'multi-line arguments' do 125 | expect_offense(<<~RUBY) 126 | assert_kind_of(a, 127 | ^^^^^^^^^^^^^^^^^ Use `expect(b).to(be_a_kind_of(a), "must be kind of")`. 128 | b, 129 | "must be kind of") 130 | RUBY 131 | 132 | expect_correction(<<~RUBY) 133 | expect(b).to(be_a_kind_of(a), "must be kind of") 134 | RUBY 135 | end 136 | 137 | it 'registers an offense when using `assert_not_kind_of`' do 138 | expect_offense(<<~RUBY) 139 | assert_not_kind_of a, b 140 | ^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to be_a_kind_of(a)`. 141 | RUBY 142 | 143 | expect_correction(<<~RUBY) 144 | expect(b).not_to be_a_kind_of(a) 145 | RUBY 146 | end 147 | 148 | it 'registers an offense when using `refute_kind_of`' do 149 | expect_offense(<<~RUBY) 150 | refute_kind_of a, b 151 | ^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to be_a_kind_of(a)`. 152 | RUBY 153 | 154 | expect_correction(<<~RUBY) 155 | expect(b).not_to be_a_kind_of(a) 156 | RUBY 157 | end 158 | 159 | it 'does not register an offense when ' \ 160 | 'using `expect(b).to be_a_kind_of(a)`' do 161 | expect_no_offenses(<<~RUBY) 162 | expect(b).to be_a_kind_of(a) 163 | RUBY 164 | end 165 | 166 | it 'does not register an offense when ' \ 167 | 'using `expect(b).not_to be_a_kind_of(a)`' do 168 | expect_no_offenses(<<~RUBY) 169 | expect(b).not_to be_a_kind_of(a) 170 | RUBY 171 | end 172 | 173 | it 'does not register an offense when ' \ 174 | 'using `expect(b).to be_kind_of(a)`' do 175 | expect_no_offenses(<<~RUBY) 176 | expect(b).to be_kind_of(a) 177 | RUBY 178 | end 179 | 180 | it 'does not register an offense when ' \ 181 | 'using `expect(b).not_to be_kind_of(a)`' do 182 | expect_no_offenses(<<~RUBY) 183 | expect(b).not_to be_kind_of(a) 184 | RUBY 185 | end 186 | end 187 | 188 | context 'with instance_of assertions' do 189 | it 'registers an offense when using `assert_instance_of`' do 190 | expect_offense(<<~RUBY) 191 | assert_instance_of(a, b) 192 | ^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_an_instance_of(a)`. 193 | RUBY 194 | 195 | expect_correction(<<~RUBY) 196 | expect(b).to be_an_instance_of(a) 197 | RUBY 198 | end 199 | 200 | it 'registers an offense when using `assert_instance_of` with ' \ 201 | 'no parentheses' do 202 | expect_offense(<<~RUBY) 203 | assert_instance_of a, b 204 | ^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_an_instance_of(a)`. 205 | RUBY 206 | 207 | expect_correction(<<~RUBY) 208 | expect(b).to be_an_instance_of(a) 209 | RUBY 210 | end 211 | 212 | it 'registers an offense when using `assert_instance_of` with' \ 213 | 'failure message' do 214 | expect_offense(<<~RUBY) 215 | assert_instance_of a, b, "must be instance of" 216 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to(be_an_instance_of(a), "must be instance of")`. 217 | RUBY 218 | 219 | expect_correction(<<~RUBY) 220 | expect(b).to(be_an_instance_of(a), "must be instance of") 221 | RUBY 222 | end 223 | 224 | it 'registers an offense when using `assert_instance_of` with ' \ 225 | 'multi-line arguments' do 226 | expect_offense(<<~RUBY) 227 | assert_instance_of(a, 228 | ^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to(be_an_instance_of(a), "must be instance of")`. 229 | b, 230 | "must be instance of") 231 | RUBY 232 | 233 | expect_correction(<<~RUBY) 234 | expect(b).to(be_an_instance_of(a), "must be instance of") 235 | RUBY 236 | end 237 | 238 | it 'registers an offense when using `assert_not_instance_of`' do 239 | expect_offense(<<~RUBY) 240 | assert_not_instance_of a, b 241 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to be_an_instance_of(a)`. 242 | RUBY 243 | 244 | expect_correction(<<~RUBY) 245 | expect(b).not_to be_an_instance_of(a) 246 | RUBY 247 | end 248 | 249 | it 'registers an offense when using `refute_instance_of`' do 250 | expect_offense(<<~RUBY) 251 | refute_instance_of a, b 252 | ^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to be_an_instance_of(a)`. 253 | RUBY 254 | 255 | expect_correction(<<~RUBY) 256 | expect(b).not_to be_an_instance_of(a) 257 | RUBY 258 | end 259 | 260 | it 'does not register an offense when ' \ 261 | 'using `expect(b).to be_an_instance_of(a)`' do 262 | expect_no_offenses(<<~RUBY) 263 | expect(b).to be_an_instance_of(a) 264 | RUBY 265 | end 266 | 267 | it 'does not register an offense when ' \ 268 | 'using `expect(b).not_to be_an_instance_of(a)`' do 269 | expect_no_offenses(<<~RUBY) 270 | expect(b).not_to be_an_instance_of(a) 271 | RUBY 272 | end 273 | 274 | it 'does not register an offense when ' \ 275 | 'using `expect(b).to be_instance_of(a)`' do 276 | expect_no_offenses(<<~RUBY) 277 | expect(b).to be_instance_of(a) 278 | RUBY 279 | end 280 | 281 | it 'does not register an offense when ' \ 282 | 'using `expect(b).not_to be_instance_of(a)`' do 283 | expect_no_offenses(<<~RUBY) 284 | expect(b).not_to be_instance_of(a) 285 | RUBY 286 | end 287 | end 288 | 289 | context 'with includes assertions' do 290 | it 'registers an offense when using `assert_includes`' do 291 | expect_offense(<<~RUBY) 292 | assert_includes(a, b) 293 | ^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to include(b)`. 294 | RUBY 295 | 296 | expect_correction(<<~RUBY) 297 | expect(a).to include(b) 298 | RUBY 299 | end 300 | 301 | it 'registers an offense when using `assert_includes` with ' \ 302 | 'no parentheses' do 303 | expect_offense(<<~RUBY) 304 | assert_includes a, b 305 | ^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to include(b)`. 306 | RUBY 307 | 308 | expect_correction(<<~RUBY) 309 | expect(a).to include(b) 310 | RUBY 311 | end 312 | 313 | it 'registers an offense when using `assert_includes` with ' \ 314 | 'failure message' do 315 | expect_offense(<<~RUBY) 316 | assert_includes a, b, "must be include" 317 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(include(b), "must be include")`. 318 | RUBY 319 | 320 | expect_correction(<<~RUBY) 321 | expect(a).to(include(b), "must be include") 322 | RUBY 323 | end 324 | 325 | it 'registers an offense when using `assert_includes` with ' \ 326 | 'multi-line arguments' do 327 | expect_offense(<<~RUBY) 328 | assert_includes(a, 329 | ^^^^^^^^^^^^^^^^^^ Use `expect(a).to(include(b), "must be include")`. 330 | b, 331 | "must be include") 332 | RUBY 333 | 334 | expect_correction(<<~RUBY) 335 | expect(a).to(include(b), "must be include") 336 | RUBY 337 | end 338 | 339 | it 'registers an offense when using `assert_not_includes`' do 340 | expect_offense(<<~RUBY) 341 | assert_not_includes a, b 342 | ^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).not_to include(b)`. 343 | RUBY 344 | 345 | expect_correction(<<~RUBY) 346 | expect(a).not_to include(b) 347 | RUBY 348 | end 349 | 350 | it 'registers an offense when using `refute_includes`' do 351 | expect_offense(<<~RUBY) 352 | refute_includes a, b 353 | ^^^^^^^^^^^^^^^^^^^^ Use `expect(a).not_to include(b)`. 354 | RUBY 355 | 356 | expect_correction(<<~RUBY) 357 | expect(a).not_to include(b) 358 | RUBY 359 | end 360 | 361 | it 'does not register an offense when using `expect(a).to include(b)`' do 362 | expect_no_offenses(<<~RUBY) 363 | expect(a).to include(b) 364 | RUBY 365 | end 366 | 367 | it 'does not register an offense when ' \ 368 | 'using `expect(a).not_to include(b)`' do 369 | expect_no_offenses(<<~RUBY) 370 | expect(a).not_to include(b) 371 | RUBY 372 | end 373 | end 374 | 375 | context 'with in_delta assertions' do 376 | it 'registers an offense when using `assert_in_delta`' do 377 | expect_offense(<<~RUBY) 378 | assert_in_delta(a, b) 379 | ^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_within(0.001).of(a)`. 380 | RUBY 381 | 382 | expect_correction(<<~RUBY) 383 | expect(b).to be_within(0.001).of(a) 384 | RUBY 385 | end 386 | 387 | it 'registers an offense when using `assert_in_delta` with ' \ 388 | 'no parentheses' do 389 | expect_offense(<<~RUBY) 390 | assert_in_delta a, b 391 | ^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_within(0.001).of(a)`. 392 | RUBY 393 | 394 | expect_correction(<<~RUBY) 395 | expect(b).to be_within(0.001).of(a) 396 | RUBY 397 | end 398 | 399 | it 'registers an offense when using `assert_in_delta` with ' \ 400 | 'a custom delta' do 401 | expect_offense(<<~RUBY) 402 | assert_in_delta a, b, 1 403 | ^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_within(1).of(a)`. 404 | RUBY 405 | 406 | expect_correction(<<~RUBY) 407 | expect(b).to be_within(1).of(a) 408 | RUBY 409 | end 410 | 411 | it 'registers an offense when using `assert_in_delta` with ' \ 412 | 'a custom delta from a variable' do 413 | expect_offense(<<~RUBY) 414 | delta = 1 415 | 416 | assert_in_delta a, b, delta 417 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_within(delta).of(a)`. 418 | RUBY 419 | 420 | expect_correction(<<~RUBY) 421 | delta = 1 422 | 423 | expect(b).to be_within(delta).of(a) 424 | RUBY 425 | end 426 | 427 | it 'registers an offense when using `assert_in_delta` with ' \ 428 | 'a custom delta and a failure message' do 429 | expect_offense(<<~RUBY) 430 | assert_in_delta a, b, 1, "must be within delta" 431 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to(be_within(1).of(a), "must be within delta")`. 432 | RUBY 433 | 434 | expect_correction(<<~RUBY) 435 | expect(b).to(be_within(1).of(a), "must be within delta") 436 | RUBY 437 | end 438 | 439 | it 'registers an offense when using `assert_in_delta` with ' \ 440 | 'multi-line arguments' do 441 | expect_offense(<<~RUBY) 442 | assert_in_delta(a, 443 | ^^^^^^^^^^^^^^^^^^ Use `expect(b).to be_within(1).of(a)`. 444 | b, 445 | 1) 446 | RUBY 447 | 448 | expect_correction(<<~RUBY) 449 | expect(b).to be_within(1).of(a) 450 | RUBY 451 | end 452 | 453 | it 'registers an offense when using `assert_not_in_delta`' do 454 | expect_offense(<<~RUBY) 455 | assert_not_in_delta a, b 456 | ^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to be_within(0.001).of(a)`. 457 | RUBY 458 | 459 | expect_correction(<<~RUBY) 460 | expect(b).not_to be_within(0.001).of(a) 461 | RUBY 462 | end 463 | 464 | it 'registers an offense when using `refute_in_delta`' do 465 | expect_offense(<<~RUBY) 466 | refute_in_delta a, b 467 | ^^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to be_within(0.001).of(a)`. 468 | RUBY 469 | 470 | expect_correction(<<~RUBY) 471 | expect(b).not_to be_within(0.001).of(a) 472 | RUBY 473 | end 474 | 475 | it 'does not register an offense when ' \ 476 | 'using `expect(b).to be_within(1).of(a)`' do 477 | expect_no_offenses(<<~RUBY) 478 | expect(b).to be_within(1).of(a) 479 | RUBY 480 | end 481 | 482 | it 'does not register an offense when ' \ 483 | 'using `expect(b).not_to be_within(1).of(a)`' do 484 | expect_no_offenses(<<~RUBY) 485 | expect(b).not_to be_within(1).of(a) 486 | RUBY 487 | end 488 | end 489 | 490 | context 'with match assertions' do 491 | it 'registers an offense when using `assert_match`' do 492 | expect_offense(<<~RUBY) 493 | assert_match(/xyz/, b) 494 | ^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to match(/xyz/)`. 495 | RUBY 496 | 497 | expect_correction(<<~RUBY) 498 | expect(b).to match(/xyz/) 499 | RUBY 500 | end 501 | 502 | it 'registers an offense when using `assert_match` with no parentheses' do 503 | expect_offense(<<~RUBY) 504 | assert_match /xyz/, b 505 | ^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to match(/xyz/)`. 506 | RUBY 507 | 508 | expect_correction(<<~RUBY) 509 | expect(b).to match(/xyz/) 510 | RUBY 511 | end 512 | 513 | it 'registers an offense when using `assert_match` with failure message' do 514 | expect_offense(<<~RUBY) 515 | assert_match /xyz/, b, "must match" 516 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).to(match(/xyz/), "must match")`. 517 | RUBY 518 | 519 | expect_correction(<<~RUBY) 520 | expect(b).to(match(/xyz/), "must match") 521 | RUBY 522 | end 523 | 524 | it 'registers an offense when using `assert_match` with ' \ 525 | 'multi-line arguments' do 526 | expect_offense(<<~RUBY) 527 | assert_match(/xyz/, 528 | ^^^^^^^^^^^^^^^^^^^ Use `expect(b).to(match(/xyz/), "must match")`. 529 | b, 530 | "must match") 531 | RUBY 532 | 533 | expect_correction(<<~RUBY) 534 | expect(b).to(match(/xyz/), "must match") 535 | RUBY 536 | end 537 | 538 | it 'registers an offense when using `refute_match`' do 539 | expect_offense(<<~RUBY) 540 | refute_match /xyz/, b 541 | ^^^^^^^^^^^^^^^^^^^^^ Use `expect(b).not_to match(/xyz/)`. 542 | RUBY 543 | 544 | expect_correction(<<~RUBY) 545 | expect(b).not_to match(/xyz/) 546 | RUBY 547 | end 548 | 549 | it 'does not register an offense when using `expect(b).to match(/xyz/)`' do 550 | expect_no_offenses(<<~RUBY) 551 | expect(b).to match(/xyz/) 552 | RUBY 553 | end 554 | 555 | it 'does not register an offense when ' \ 556 | 'using `expect(b).not_to match(/xyz/)`' do 557 | expect_no_offenses(<<~RUBY) 558 | expect(b).not_to match(/xyz/) 559 | RUBY 560 | end 561 | end 562 | 563 | context 'with nil assertions' do 564 | it 'registers an offense when using `assert_nil`' do 565 | expect_offense(<<~RUBY) 566 | assert_nil(a) 567 | ^^^^^^^^^^^^^ Use `expect(a).to eq(nil)`. 568 | RUBY 569 | 570 | expect_correction(<<~RUBY) 571 | expect(a).to eq(nil) 572 | RUBY 573 | end 574 | 575 | it 'registers an offense when using `assert_nil` with no parentheses' do 576 | expect_offense(<<~RUBY) 577 | assert_nil a 578 | ^^^^^^^^^^^^ Use `expect(a).to eq(nil)`. 579 | RUBY 580 | 581 | expect_correction(<<~RUBY) 582 | expect(a).to eq(nil) 583 | RUBY 584 | end 585 | 586 | it 'registers an offense when using `assert_nil` with failure message' do 587 | expect_offense(<<~RUBY) 588 | assert_nil a, "must be nil" 589 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(eq(nil), "must be nil")`. 590 | RUBY 591 | 592 | expect_correction(<<~RUBY) 593 | expect(a).to(eq(nil), "must be nil") 594 | RUBY 595 | end 596 | 597 | it 'registers an offense when using `assert_nil` with ' \ 598 | 'multi-line arguments' do 599 | expect_offense(<<~RUBY) 600 | assert_nil(a, 601 | ^^^^^^^^^^^^^ Use `expect(a).to(eq(nil), "must be nil")`. 602 | "must be nil") 603 | RUBY 604 | 605 | expect_correction(<<~RUBY) 606 | expect(a).to(eq(nil), "must be nil") 607 | RUBY 608 | end 609 | 610 | it 'registers an offense when using `assert_not_nil`' do 611 | expect_offense(<<~RUBY) 612 | assert_not_nil a 613 | ^^^^^^^^^^^^^^^^ Use `expect(a).not_to eq(nil)`. 614 | RUBY 615 | 616 | expect_correction(<<~RUBY) 617 | expect(a).not_to eq(nil) 618 | RUBY 619 | end 620 | 621 | it 'registers an offense when using `refute_nil`' do 622 | expect_offense(<<~RUBY) 623 | refute_nil a 624 | ^^^^^^^^^^^^ Use `expect(a).not_to eq(nil)`. 625 | RUBY 626 | 627 | expect_correction(<<~RUBY) 628 | expect(a).not_to eq(nil) 629 | RUBY 630 | end 631 | 632 | it 'does not register an offense when using `expect(a).to eq(nil)`' do 633 | expect_no_offenses(<<~RUBY) 634 | expect(a).to eq(nil) 635 | RUBY 636 | end 637 | 638 | it 'does not register an offense when using `expect(a).not_to eq(nil)`' do 639 | expect_no_offenses(<<~RUBY) 640 | expect(a).not_to eq(nil) 641 | RUBY 642 | end 643 | end 644 | 645 | context 'with empty assertions' do 646 | it 'registers an offense when using `assert_empty`' do 647 | expect_offense(<<~RUBY) 648 | assert_empty(a) 649 | ^^^^^^^^^^^^^^^ Use `expect(a).to be_empty`. 650 | RUBY 651 | 652 | expect_correction(<<~RUBY) 653 | expect(a).to be_empty 654 | RUBY 655 | end 656 | 657 | it 'registers an offense when using `assert_empty` with no parentheses' do 658 | expect_offense(<<~RUBY) 659 | assert_empty a 660 | ^^^^^^^^^^^^^^ Use `expect(a).to be_empty`. 661 | RUBY 662 | 663 | expect_correction(<<~RUBY) 664 | expect(a).to be_empty 665 | RUBY 666 | end 667 | 668 | it 'registers an offense when using `assert_empty` with failure message' do 669 | expect_offense(<<~RUBY) 670 | assert_empty a, "must be empty" 671 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(be_empty, "must be empty")`. 672 | RUBY 673 | 674 | expect_correction(<<~RUBY) 675 | expect(a).to(be_empty, "must be empty") 676 | RUBY 677 | end 678 | 679 | it 'registers an offense when using `assert_empty` with ' \ 680 | 'multi-line arguments' do 681 | expect_offense(<<~RUBY) 682 | assert_empty(a, 683 | ^^^^^^^^^^^^^^^ Use `expect(a).to(be_empty, "must be empty")`. 684 | "must be empty") 685 | RUBY 686 | 687 | expect_correction(<<~RUBY) 688 | expect(a).to(be_empty, "must be empty") 689 | RUBY 690 | end 691 | 692 | it 'registers an offense when using `assert_not_empty`' do 693 | expect_offense(<<~RUBY) 694 | assert_not_empty a 695 | ^^^^^^^^^^^^^^^^^^ Use `expect(a).not_to be_empty`. 696 | RUBY 697 | 698 | expect_correction(<<~RUBY) 699 | expect(a).not_to be_empty 700 | RUBY 701 | end 702 | 703 | it 'registers an offense when using `refute_empty`' do 704 | expect_offense(<<~RUBY) 705 | refute_empty a 706 | ^^^^^^^^^^^^^^ Use `expect(a).not_to be_empty`. 707 | RUBY 708 | 709 | expect_correction(<<~RUBY) 710 | expect(a).not_to be_empty 711 | RUBY 712 | end 713 | 714 | it 'does not register an offense when using `expect(a).to be_empty`' do 715 | expect_no_offenses(<<~RUBY) 716 | expect(a).to be_empty 717 | RUBY 718 | end 719 | 720 | it 'does not register an offense when using `expect(a).not_to be_empty`' do 721 | expect_no_offenses(<<~RUBY) 722 | expect(a).not_to be_empty 723 | RUBY 724 | end 725 | end 726 | 727 | context 'with boolean assertions' do 728 | it 'registers an offense when using `assert_true`' do 729 | expect_offense(<<~RUBY) 730 | assert_true(a) 731 | ^^^^^^^^^^^^^^ Use `expect(a).to be(true)`. 732 | RUBY 733 | 734 | expect_correction(<<~RUBY) 735 | expect(a).to be(true) 736 | RUBY 737 | end 738 | 739 | it 'registers an offense when using `assert_true` with no parentheses' do 740 | expect_offense(<<~RUBY) 741 | assert_true a 742 | ^^^^^^^^^^^^^ Use `expect(a).to be(true)`. 743 | RUBY 744 | 745 | expect_correction(<<~RUBY) 746 | expect(a).to be(true) 747 | RUBY 748 | end 749 | 750 | it 'registers an offense when using `assert_true` with failure message' do 751 | expect_offense(<<~RUBY) 752 | assert_true a, "must be true" 753 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(be(true), "must be true")`. 754 | RUBY 755 | 756 | expect_correction(<<~RUBY) 757 | expect(a).to(be(true), "must be true") 758 | RUBY 759 | end 760 | 761 | it 'registers an offense when using `assert_true` with ' \ 762 | 'multi-line arguments' do 763 | expect_offense(<<~RUBY) 764 | assert_true(a, 765 | ^^^^^^^^^^^^^^ Use `expect(a).to(be(true), "must be true")`. 766 | "must be true") 767 | RUBY 768 | 769 | expect_correction(<<~RUBY) 770 | expect(a).to(be(true), "must be true") 771 | RUBY 772 | end 773 | 774 | it 'registers an offense when using `assert_false`' do 775 | expect_offense(<<~RUBY) 776 | assert_false(a) 777 | ^^^^^^^^^^^^^^^ Use `expect(a).to be(false)`. 778 | RUBY 779 | 780 | expect_correction(<<~RUBY) 781 | expect(a).to be(false) 782 | RUBY 783 | end 784 | 785 | it 'registers an offense when using `assert_false` with no parentheses' do 786 | expect_offense(<<~RUBY) 787 | assert_false a 788 | ^^^^^^^^^^^^^^ Use `expect(a).to be(false)`. 789 | RUBY 790 | 791 | expect_correction(<<~RUBY) 792 | expect(a).to be(false) 793 | RUBY 794 | end 795 | 796 | it 'registers an offense when using `assert_false` with failure message' do 797 | expect_offense(<<~RUBY) 798 | assert_false a, "must be false" 799 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(be(false), "must be false")`. 800 | RUBY 801 | 802 | expect_correction(<<~RUBY) 803 | expect(a).to(be(false), "must be false") 804 | RUBY 805 | end 806 | 807 | it 'registers an offense when using `assert_false` with ' \ 808 | 'multi-line arguments' do 809 | expect_offense(<<~RUBY) 810 | assert_false(a, 811 | ^^^^^^^^^^^^^^^ Use `expect(a).to(be(false), "must be false")`. 812 | "must be false") 813 | RUBY 814 | 815 | expect_correction(<<~RUBY) 816 | expect(a).to(be(false), "must be false") 817 | RUBY 818 | end 819 | end 820 | 821 | context 'with predicate assertions' do 822 | it 'registers an offense when using `assert_predicate` with ' \ 823 | 'an actual predicate' do 824 | expect_offense(<<~RUBY) 825 | assert_predicate(a, :valid?) 826 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to be_valid`. 827 | RUBY 828 | 829 | expect_correction(<<~RUBY) 830 | expect(a).to be_valid 831 | RUBY 832 | end 833 | 834 | it 'registers an offense when using `assert_predicate` with ' \ 835 | 'an actual predicate and no parentheses' do 836 | expect_offense(<<~RUBY) 837 | assert_predicate a, :valid? 838 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to be_valid`. 839 | RUBY 840 | 841 | expect_correction(<<~RUBY) 842 | expect(a).to be_valid 843 | RUBY 844 | end 845 | 846 | it 'registers an offense when using `assert_predicate` with ' \ 847 | 'an actual predicate and a failure message' do 848 | expect_offense(<<~RUBY) 849 | assert_predicate a, :valid?, "must be valid" 850 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(be_valid, "must be valid")`. 851 | RUBY 852 | 853 | expect_correction(<<~RUBY) 854 | expect(a).to(be_valid, "must be valid") 855 | RUBY 856 | end 857 | 858 | it 'registers an offense when using `assert_predicate` with ' \ 859 | 'an actual predicate and multi-line arguments' do 860 | expect_offense(<<~RUBY) 861 | assert_predicate(a, 862 | ^^^^^^^^^^^^^^^^^^^ Use `expect(a).to(be_valid, "must be valid")`. 863 | :valid?, 864 | "must be valid") 865 | RUBY 866 | 867 | expect_correction(<<~RUBY) 868 | expect(a).to(be_valid, "must be valid") 869 | RUBY 870 | end 871 | 872 | it 'registers an offense when using `assert_not_predicate` with ' \ 873 | 'an actual predicate' do 874 | expect_offense(<<~RUBY) 875 | assert_not_predicate a, :valid? 876 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).not_to be_valid`. 877 | RUBY 878 | 879 | expect_correction(<<~RUBY) 880 | expect(a).not_to be_valid 881 | RUBY 882 | end 883 | 884 | it 'registers an offense when using `refute_predicate` with ' \ 885 | 'an actual predicate' do 886 | expect_offense(<<~RUBY) 887 | refute_predicate a, :valid? 888 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `expect(a).not_to be_valid`. 889 | RUBY 890 | 891 | expect_correction(<<~RUBY) 892 | expect(a).not_to be_valid 893 | RUBY 894 | end 895 | 896 | it 'does not register an offense when using `expect(a).to be_predicate`' do 897 | expect_no_offenses(<<~RUBY) 898 | expect(a).to be_predicate 899 | RUBY 900 | end 901 | 902 | it 'does not register an offense when using ' \ 903 | '`expect(a).not_to be_predicate`' do 904 | expect_no_offenses(<<~RUBY) 905 | expect(a).not_to be_predicate 906 | RUBY 907 | end 908 | 909 | it 'does not register an offense when using `assert_predicate` with ' \ 910 | 'not a predicate' do 911 | expect_no_offenses(<<~RUBY) 912 | assert_predicate foo, :do_something 913 | RUBY 914 | end 915 | 916 | it 'does not register an offense when using `assert_not_predicate` with ' \ 917 | 'not a predicate' do 918 | expect_no_offenses(<<~RUBY) 919 | assert_not_predicate foo, :do_something 920 | RUBY 921 | end 922 | 923 | it 'does not register an offense when using `refute_predicate` with ' \ 924 | 'not a predicate' do 925 | expect_no_offenses(<<~RUBY) 926 | refute_predicate foo, :do_something 927 | RUBY 928 | end 929 | 930 | it 'does not register an offense when the predicate is not a symbol' do 931 | expect_no_offenses(<<~RUBY) 932 | assert_predicate a, 1 933 | RUBY 934 | end 935 | 936 | it 'does not register an offense when the predicate is missing' do 937 | expect_no_offenses(<<~RUBY) 938 | assert_predicate a, "whoops, we forgot about the actual predicate!" 939 | RUBY 940 | end 941 | 942 | it 'does not register an offense when the predicate is a variable' do 943 | expect_no_offenses(<<~RUBY) 944 | foo = :foo? 945 | 946 | assert_predicate a, foo 947 | RUBY 948 | end 949 | end 950 | end 951 | -------------------------------------------------------------------------------- /spec/rubocop/cop/rspec_rails/negation_be_valid_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::RSpecRails::NegationBeValid do 4 | let(:cop_config) { { 'EnforcedStyle' => enforced_style } } 5 | 6 | context 'with EnforcedStyle `not_to`' do 7 | let(:enforced_style) { 'not_to' } 8 | 9 | it 'registers an offense when using ' \ 10 | '`expect(...).to be_invalid`' do 11 | expect_offense(<<~RUBY) 12 | expect(foo).to be_invalid 13 | ^^^^^^^^^^^^^ Use `expect(...).not_to be_valid`. 14 | RUBY 15 | end 16 | 17 | it 'does not register an offense when using ' \ 18 | '`expect(...).not_to be_valid`' do 19 | expect_no_offenses(<<~RUBY) 20 | expect(foo).not_to be_valid 21 | RUBY 22 | end 23 | 24 | it 'does not register an offense when using ' \ 25 | '`expect(...).to be_valid`' do 26 | expect_no_offenses(<<~RUBY) 27 | expect(foo).to be_valid 28 | RUBY 29 | end 30 | 31 | it 'does not register an offense when using ' \ 32 | '`expect(...).to be_invalid` and method chain' do 33 | expect_no_offenses(<<~RUBY) 34 | expect(foo).to be_invalid.and be_odd 35 | expect(foo).to be_invalid.or be_even 36 | RUBY 37 | end 38 | end 39 | 40 | context 'with EnforcedStyle `be_invalid`' do 41 | let(:enforced_style) { 'be_invalid' } 42 | 43 | it 'registers an offense when using ' \ 44 | '`expect(...).not_to be_valid`' do 45 | expect_offense(<<~RUBY) 46 | expect(foo).not_to be_valid 47 | ^^^^^^^^^^^^^^^ Use `expect(...).to be_invalid`. 48 | RUBY 49 | end 50 | 51 | it 'does not register an offense when using ' \ 52 | '`expect(...).to be_invalid`' do 53 | expect_no_offenses(<<~RUBY) 54 | expect(foo).to be_invalid 55 | RUBY 56 | end 57 | 58 | it 'does not register an offense when using ' \ 59 | '`expect(...).to be_valid`' do 60 | expect_no_offenses(<<~RUBY) 61 | expect(foo).to be_valid 62 | RUBY 63 | end 64 | 65 | it 'does not register an offense when using ' \ 66 | '`expect(...).not_to be_valid` and method chain' do 67 | expect_no_offenses(<<~RUBY) 68 | expect(foo).not_to be_valid.and be_odd 69 | expect(foo).not_to be_valid.or be_even 70 | RUBY 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/rubocop/cop/rspec_rails/travel_around_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::RSpecRails::TravelAround do 4 | context 'with `freeze_time` in `before`' do 5 | it 'registers no offense' do 6 | expect_no_offenses(<<~RUBY) 7 | before { freeze_time } 8 | RUBY 9 | end 10 | end 11 | 12 | context 'with `freeze_time` in `around(:all)`' do 13 | it 'registers no offense' do 14 | expect_no_offenses(<<~RUBY) 15 | around(:all) do |example| 16 | freeze_time do 17 | example.run 18 | end 19 | end 20 | RUBY 21 | end 22 | end 23 | 24 | context 'with `freeze_time` in `around(:suite)`' do 25 | it 'registers no offense' do 26 | expect_no_offenses(<<~RUBY) 27 | around(:suite) do |example| 28 | freeze_time do 29 | example.run 30 | end 31 | end 32 | RUBY 33 | end 34 | end 35 | 36 | context 'with another step in `freeze_time`' do 37 | it 'registers no offense' do 38 | expect_no_offenses(<<~RUBY) 39 | around do |example| 40 | freeze_time do 41 | do_some_preparation 42 | example.run 43 | end 44 | end 45 | RUBY 46 | end 47 | end 48 | 49 | context 'with `freeze_time` in `around`' do 50 | it 'registers offense' do 51 | expect_offense(<<~RUBY) 52 | around do |example| 53 | freeze_time do 54 | ^^^^^^^^^^^^^^ Prefer to travel in `before` rather than `around`. 55 | example.run 56 | end 57 | end 58 | RUBY 59 | 60 | expect_correction(<<~RUBY) 61 | before { freeze_time } 62 | 63 | around do |example| 64 | example.run 65 | end 66 | RUBY 67 | end 68 | end 69 | 70 | context 'with `freeze_time` with `&example` in `around`' do 71 | it 'registers offense' do 72 | expect_offense(<<~RUBY) 73 | around do |example| 74 | freeze_time(&example) 75 | ^^^^^^^^^^^^^^^^^^^^^ Prefer to travel in `before` rather than `around`. 76 | end 77 | RUBY 78 | 79 | expect_correction(<<~RUBY) 80 | before { freeze_time } 81 | 82 | around do |example| 83 | example.run 84 | end 85 | RUBY 86 | end 87 | end 88 | 89 | context 'with `freeze_time` with `&example` not in `around`' do 90 | it 'registers no offense' do 91 | expect_no_offenses(<<~RUBY) 92 | examples.each do |example| 93 | freeze_time(&example) 94 | end 95 | RUBY 96 | end 97 | end 98 | 99 | context 'with `freeze_time` in `around(:each)`' do 100 | it 'registers offense' do 101 | expect_offense(<<~RUBY) 102 | around(:each) do |example| 103 | freeze_time do 104 | ^^^^^^^^^^^^^^ Prefer to travel in `before` rather than `around`. 105 | example.run 106 | end 107 | end 108 | RUBY 109 | 110 | expect_correction(<<~RUBY) 111 | before { freeze_time } 112 | 113 | around(:each) do |example| 114 | example.run 115 | end 116 | RUBY 117 | end 118 | end 119 | 120 | context 'with `freeze_time` and another node in `around`' do 121 | it 'registers offense' do 122 | expect_offense(<<~RUBY) 123 | around do |example| 124 | foo 125 | 126 | freeze_time do 127 | ^^^^^^^^^^^^^^ Prefer to travel in `before` rather than `around`. 128 | example.run 129 | end 130 | end 131 | RUBY 132 | 133 | expect_correction(<<~RUBY) 134 | before { freeze_time } 135 | 136 | around do |example| 137 | foo 138 | 139 | example.run 140 | end 141 | RUBY 142 | end 143 | end 144 | 145 | context 'with `freeze_time` with `&example` and another node in `around`' do 146 | it 'registers offense' do 147 | expect_offense(<<~RUBY) 148 | around do |example| 149 | foo 150 | 151 | freeze_time(&example) 152 | ^^^^^^^^^^^^^^^^^^^^^ Prefer to travel in `before` rather than `around`. 153 | end 154 | RUBY 155 | 156 | expect_correction(<<~RUBY) 157 | before { freeze_time } 158 | 159 | around do |example| 160 | foo 161 | 162 | example.run 163 | end 164 | RUBY 165 | end 166 | end 167 | 168 | context 'with `travel` in `around`' do 169 | it 'registers offense' do 170 | expect_offense(<<~RUBY) 171 | around do |example| 172 | travel(duration) do 173 | ^^^^^^^^^^^^^^^^^^^ Prefer to travel in `before` rather than `around`. 174 | example.run 175 | end 176 | end 177 | RUBY 178 | 179 | expect_correction(<<~RUBY) 180 | before { travel(duration) } 181 | 182 | around do |example| 183 | example.run 184 | end 185 | RUBY 186 | end 187 | end 188 | 189 | context 'with `travel_to` in `around`' do 190 | it 'registers offense' do 191 | expect_offense(<<~RUBY) 192 | around do |example| 193 | travel_to(time) do 194 | ^^^^^^^^^^^^^^^^^^ Prefer to travel in `before` rather than `around`. 195 | example.run 196 | end 197 | end 198 | RUBY 199 | 200 | expect_correction(<<~RUBY) 201 | before { travel_to(time) } 202 | 203 | around do |example| 204 | example.run 205 | end 206 | RUBY 207 | end 208 | end 209 | end 210 | -------------------------------------------------------------------------------- /spec/shared/detects_style_behavior.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.shared_examples 'detects style' do |source, style, filename: 'x_spec.rb'| 4 | it 'generates a todo based on the detected style' do 5 | inspect_source(source, filename) 6 | 7 | expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => style) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/shared/smoke_test_examples.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.shared_examples 'smoke test', type: :cop_spec do 4 | context 'with default configuration' do 5 | # This is overridden to avoid a number of specs that define `cop_config` 6 | # (so it is referenced in the 'config' shared context) but do not define 7 | # all of the dependent configuration options until inside of a context 8 | # that is out of scope, causing a NameError. 9 | let(:cop_config) { {} } 10 | 11 | stress_tests = Pathname.glob('spec/smoke_tests/*.rb') 12 | 13 | raise 'No smoke tests could be found!' if stress_tests.empty? 14 | 15 | stress_tests.each do |path| 16 | it "does not crash on smoke test: #{path}" do 17 | source = path.read 18 | file_name = path.to_s 19 | 20 | aggregate_failures do 21 | expect { inspect_source(source, file_name) }.not_to raise_error 22 | expect { autocorrect_source(source, file_name) }.not_to raise_error 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/smoke_tests/empty_spec.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubocop/rubocop-rspec_rails/1aefde0290d1a00e13a82d2a7977be41c117655b/spec/smoke_tests/empty_spec.rb -------------------------------------------------------------------------------- /spec/smoke_tests/factory_bot_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Since FactoryBot is not a dependency, none of this should be executed. We just 4 | # need the AST to exist. 5 | if false 6 | FactoryBot.define do 7 | factory :foo do 8 | bar {} 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/smoke_tests/no_tests_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: 2 | 3 | # This is sort of a test, but there's no rspec to see here. 4 | raise 'Uh oh, the Party seems to have gotten into ruby.' if 2 + 2 == 5 5 | -------------------------------------------------------------------------------- /spec/smoke_tests/weird_rspec_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Weirdness' do 2 | subject! { nil } 3 | subject { nil } 4 | subject(:foo) { nil } 5 | subject!(:foo) { nil } 6 | 7 | subject! (:foo) { |something| nil } 8 | subject :foo do end 9 | 10 | let(:foo) { |something| something } 11 | let (:foo) { 1 } 12 | let! (:bar){} 13 | 14 | bar = -> {} 15 | let(:foo, &bar) 16 | 17 | let :a do end 18 | 19 | let(:bar) { <<-HEREDOC } 20 | What a pain. 21 | HEREDOC 22 | 23 | let(:bar) { <<-'HEREDOC' } 24 | Even odder. 25 | HEREDOC 26 | 27 | let(:baz) do 28 | <<-INSIDE 29 | Hi. I'm in your lets. 30 | INSIDE 31 | end 32 | 33 | let(:hi) {} 34 | let(:bye) do 35 | end 36 | 37 | let(:doop) { foo; 1 } 38 | 39 | let('foo') { 1 } 40 | let('foo' \ 41 | 'bar') { 1 } 42 | 43 | let!('foo') { 1 } 44 | let!('foo' \ 45 | 'bar') { 1 } 46 | 47 | let("foo#{1}") { 1 } 48 | let!("foo#{1}") { 1 } 49 | 50 | it {} 51 | specify {} 52 | 53 | it 'works', metadata: true do 54 | end 55 | 56 | describe {} 57 | context {} 58 | 59 | describe '#nothing' do 60 | end 61 | 62 | it 'is empty' do 63 | end 64 | 65 | it '' do end 66 | describe do end 67 | context do end 68 | shared_examples 'a' do end 69 | 70 | describe 'things' do 71 | context 'with context' do 72 | end 73 | end 74 | 75 | shared_examples 'weird rspec' do 76 | end 77 | 78 | shared_examples :something do 79 | end 80 | 81 | context 'test' do 82 | include_examples 'weird rspec' 83 | include_examples('weird rspec', serious: true) do 84 | it_behaves_like :something 85 | end 86 | end 87 | 88 | it_behaves_like :something 89 | it_should_behave_like :something if RSpec::Core::Version::STRING < '4.0' 90 | 91 | it_behaves_like :something do 92 | let(:foo) { 'bar' } 93 | end 94 | 95 | it_behaves_like(:something) do |arg, *args, &block| 96 | end 97 | 98 | before {} 99 | context 'never run' do 100 | around {} 101 | end 102 | after {} 103 | 104 | before { <<-DOC } 105 | Eh, what's up? 106 | DOC 107 | 108 | around { |test| test.run; <<-DOC } 109 | Eh, what's up? 110 | DOC 111 | 112 | after { <<-DOC } 113 | Eh, what's up? 114 | DOC 115 | 116 | around do |test| 117 | test.run 118 | end 119 | 120 | it 'is expecting you' do 121 | expect('you').to eql('you') 122 | end 123 | 124 | it 'is expecting you not to raise an error' do 125 | expect { 'you' }.not_to raise_error 126 | end 127 | 128 | it 'has chained expectations' do 129 | expect('you').to eql('you').and(match(/y/)) 130 | end 131 | 132 | %w[who likes dynamic examples].each do |word| 133 | let(word) { word } 134 | 135 | describe "#{word}" do 136 | context "#{word}" do 137 | it "lets the word '#{word}' be '#{word}'" do 138 | expect(send(word)).to eql(word) 139 | end 140 | end 141 | end 142 | end 143 | 144 | it { foo; 1 && 2} 145 | it('has a description too') { foo; 1 && 2} 146 | 147 | it %{quotes a string weird} do 148 | end 149 | 150 | it((' '.strip! ; 1 && 'isnt a simple string')) do 151 | expect(nil).to be(nil) 152 | end 153 | 154 | it((' '.strip! ; 1 && 'isnt a simple string')) do 155 | double = double(:foo) 156 | 157 | allow(double).to receive(:oogabooga).with(nil).and_return(nil) 158 | 159 | expect(double.oogabooga(nil)).to be(nil) 160 | 161 | expect(double).to have_received(:oogabooga).once 162 | end 163 | 164 | it 'uses a matcher' do 165 | expect([].empty?).to be(true) 166 | expect([]).to be_empty 167 | end 168 | 169 | let(:klass) do 170 | Class.new do 171 | def initialize(thing) 172 | @thing = thing 173 | end 174 | 175 | def announce 176 | 'wooo, so dynamic!' 177 | end 178 | end 179 | end 180 | 181 | it 'it does a thing' do 182 | end 183 | 184 | it 'It does a thing' do 185 | end 186 | 187 | it 'should not do the thing' do 188 | end 189 | 190 | specify do 191 | foo = double(:bar) 192 | allow(foo).to receive_message_chain(bar: 42, baz: 42) 193 | allow(foo).to receive(:bar) 194 | allow(foo).to receive_messages(bar: 42, baz: 42) 195 | end 196 | end 197 | 198 | RSpec.describe {} 199 | RSpec.shared_examples('pointless') {} 200 | RSpec.shared_context('even pointless-er') {} 201 | RSpec.describe do end 202 | RSpec.shared_examples('pointless2') do end 203 | RSpec.shared_context('even pointless-er2') do end 204 | RSpec.describe 205 | RSpec.describe 'empty' 206 | 207 | class Broken 208 | end 209 | 210 | RSpec.describe Broken do 211 | end 212 | 213 | RSpec.describe 'RuboCopBug' do 214 | subject { true } 215 | 216 | before do 217 | each_row = allow(double(:exporter)).to receive(:each_row) 218 | 219 | [1, 2].each do |sig| 220 | each_row = each_row.and_yield(sig) 221 | end 222 | end 223 | 224 | it 'has a single example' do 225 | expect(subject).to be_truthy 226 | end 227 | 228 | it 'has an expectation' do 229 | stats = double(event: nil) 230 | 231 | stats.event('tada!') 232 | 233 | expect(stats) 234 | .to have_received(:event) 235 | .with('tada!') 236 | end 237 | end 238 | 239 | RSpec.describe do 240 | let(:uh_oh) { <<-HERE.strip + ", #{<<-THERE.strip}" } 241 | Seriously 242 | HERE 243 | who designed these things? 244 | THERE 245 | 246 | it 'is insane' do 247 | expect(uh_oh).to eql('Seriously, who designed these things?') 248 | end 249 | end 250 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubocop' 4 | require 'rubocop/rspec/shared_contexts/default_rspec_language_config_context' 5 | require 'rubocop/rspec/support' # `expect_offense` etc 6 | 7 | require 'simplecov' unless ENV['NO_COVERAGE'] 8 | 9 | module SpecHelper 10 | ROOT = Pathname.new(__dir__).parent.freeze 11 | end 12 | 13 | spec_helper_glob = 14 | '{support,shared,../lib/rubocop/rspec/shared_contexts}/*.rb' 15 | Dir 16 | .glob(File.expand_path(spec_helper_glob, __dir__)) 17 | .sort 18 | .each(&method(:require)) 19 | 20 | RSpec.configure do |config| 21 | # Set metadata so smoke tests are run on all cop specs 22 | config.define_derived_metadata( 23 | file_path: %r{/spec/rubocop/cop/} 24 | ) do |meta| 25 | meta[:type] = :cop_spec 26 | end 27 | 28 | # Include config shared context for all cop specs 29 | config.define_derived_metadata(type: :cop_spec) do |meta| 30 | meta[:config] = true 31 | end 32 | 33 | config.order = :random 34 | 35 | # Run focused tests with `fdescribe`, `fit`, `:focus` etc. 36 | config.filter_run_when_matching :focus 37 | 38 | # We should address configuration warnings when we upgrade 39 | config.raise_errors_for_deprecations! 40 | 41 | # RSpec gives helpful warnings when you are doing something wrong. 42 | # We should take their advice! 43 | config.raise_on_warning = true 44 | 45 | config.include_context 'with default RSpec/Language config', :config 46 | config.include_context 'smoke test', type: :cop_spec 47 | end 48 | 49 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 50 | 51 | require 'rubocop-rspec' 52 | require 'rubocop-rspec_rails' 53 | -------------------------------------------------------------------------------- /spec/support/cli_spec_behavior.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.shared_context 'when cli spec behavior' do 4 | include_context 'mock console output' 5 | 6 | include FileHelper 7 | 8 | before do 9 | RuboCop::ConfigLoader.debug = false 10 | 11 | # OPTIMIZE: Makes these specs faster. Work directory (the parent of 12 | # .rubocop_cache) is removed afterwards anyway. 13 | RuboCop::ResultCache.inhibit_cleanup = true 14 | end 15 | 16 | # Wrap all cli specs in `aggregate_failures` so that the expected and 17 | # actual results of every expectation per example are shown. This is 18 | # helpful because it shows information like expected and actual 19 | # $stdout messages while not using `aggregate_failures` will only 20 | # show information about expected and actual exit code 21 | around { |example| aggregate_failures(&example) } 22 | 23 | after { RuboCop::ResultCache.inhibit_cleanup = false } 24 | end 25 | -------------------------------------------------------------------------------- /spec/support/expect_offense.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop-rspec gem extension of RuboCop's ExpectOffense module. 4 | # 5 | # This mixin is the same as rubocop's ExpectOffense except the default 6 | # filename ends with `_spec.rb` 7 | # 8 | # Cops assigned to departments may focus on different files, so it is 9 | # possible to override the inspected file name. 10 | module ExpectOffense 11 | include RuboCop::RSpec::ExpectOffense 12 | 13 | DEFAULT_FILENAME = 'example_spec.rb' 14 | 15 | def expect_offense(source, filename = inspected_source_filename, 16 | *args, **kwargs) 17 | super 18 | end 19 | 20 | def expect_no_offenses(source, filename = inspected_source_filename) 21 | super 22 | end 23 | 24 | def inspected_source_filename 25 | DEFAULT_FILENAME 26 | end 27 | 28 | def expect_global_offense(source, file = nil, message = '') 29 | processed_source = parse_source(source, file) 30 | offenses = _investigate(cop, processed_source) 31 | expect(offenses.size).to eq(1) 32 | expect(offenses.first.message).to eq(message) 33 | end 34 | 35 | def expect_no_global_offenses(source, file = nil) 36 | processed_source = parse_source(source, file) 37 | offenses = _investigate(cop, processed_source) 38 | expect(offenses.size).to eq(0) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/support/file_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'fileutils' 4 | 5 | module FileHelper 6 | def create_file(file_path, content) 7 | file_path = File.expand_path(file_path) 8 | create_dir file_path 9 | 10 | File.open(file_path, 'w') do |file| 11 | case content 12 | when String 13 | file.puts content 14 | when Array 15 | file.puts content.join("\n") 16 | end 17 | end 18 | end 19 | 20 | def create_dir(file_path) 21 | dir_path = File.dirname(file_path) 22 | FileUtils.makedirs dir_path 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /tasks/cops_documentation.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubocop' 4 | require 'rubocop-rspec_rails' 5 | require 'rubocop/cops_documentation_generator' 6 | require 'yard' 7 | 8 | YARD::Rake::YardocTask.new(:yard_for_generate_documentation) do |task| 9 | task.files = ['lib/rubocop/cop/**/*.rb'] 10 | task.options = ['--no-output'] 11 | end 12 | 13 | desc 'Generate docs of all cops departments' 14 | task generate_cops_documentation: :yard_for_generate_documentation do 15 | generator = CopsDocumentationGenerator.new( 16 | departments: %w[RSpecRails], plugin_name: 'rubocop-rspec_rails' 17 | ) 18 | generator.call 19 | end 20 | 21 | desc 'Syntax check for the documentation comments' 22 | task documentation_syntax_check: :yard_for_generate_documentation do 23 | require 'parser/ruby25' 24 | 25 | ok = true 26 | YARD::Registry.load! 27 | cops = RuboCop::Cop::Registry.global 28 | cops.each do |cop| 29 | examples = YARD::Registry.all(:class).find do |code_object| 30 | next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge 31 | 32 | break code_object.tags('example') 33 | end 34 | 35 | examples.to_a.each do |example| 36 | buffer = Parser::Source::Buffer.new('', 1) 37 | buffer.source = example.text 38 | parser = Parser::Ruby25.new(RuboCop::AST::Builder.new) 39 | parser.diagnostics.all_errors_are_fatal = true 40 | parser.parse(buffer) 41 | rescue Parser::SyntaxError => e 42 | path = example.object.file 43 | puts "#{path}: Syntax Error in an example. #{e}" 44 | ok = false 45 | end 46 | end 47 | abort unless ok 48 | end 49 | -------------------------------------------------------------------------------- /tasks/create_release_notes.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | desc 'Create release notes for the most recent version.' 4 | task :create_release_notes do 5 | CreateReleaseNotes.call 6 | end 7 | 8 | # Create release notes from the most recent version in the CHANGELOG.md file. 9 | module CreateReleaseNotes 10 | module_function 11 | 12 | def call 13 | release_notes = new_version_changes.strip 14 | contributor_links = user_links(release_notes) 15 | 16 | File.open('relnotes.md', 'w') do |file| 17 | file << release_notes 18 | file << "\n\n" 19 | file << contributor_links 20 | file << "\n" 21 | end 22 | end 23 | 24 | def new_version_changes 25 | changelog = File.read('CHANGELOG.md') 26 | _, _, new_changes, _older_changes = changelog.split(/^## .*$/, 4) 27 | new_changes 28 | end 29 | 30 | def user_links(text) 31 | names = text.scan(/\[@(\S+)\]/).map(&:first).uniq.sort 32 | names.map { |name| "[@#{name}]: https://github.com/#{name}" }.join("\n") 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /tasks/cut_release.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bump' 4 | 5 | namespace :cut_release do 6 | def update_file(path) 7 | content = File.read(path) 8 | File.write(path, yield(content)) 9 | end 10 | 11 | %w[major minor patch pre].each do |release_type| 12 | desc "Cut a new #{release_type} release and update documents." 13 | task release_type do 14 | run(release_type) 15 | end 16 | end 17 | 18 | def version_sans_patch(version) 19 | version.split('.').take(2).join('.') 20 | end 21 | 22 | # Replace `<>` (and variations) with version being cut. 23 | def update_cop_versions(version) 24 | update_file('config/default.yml') do |default| 25 | default.gsub(/['"]?<<\s*next\s*>>['"]?/i, 26 | "'#{version_sans_patch(version)}'") 27 | end 28 | RuboCop::ConfigLoader.default_configuration = nil # invalidate loaded conf 29 | end 30 | 31 | def update_docs(version) 32 | update_file('docs/antora.yml') do |antora_metadata| 33 | antora_metadata.sub('version: ~', 34 | "version: '#{version_sans_patch(version)}'") 35 | end 36 | end 37 | 38 | def add_header_to_changelog(version) 39 | update_file('CHANGELOG.md') do |changelog| 40 | changelog.sub("## Master (Unreleased)\n\n", 41 | "\\0## #{version} (#{Time.now.strftime('%F')})\n\n") 42 | end 43 | end 44 | 45 | def run(release_type) 46 | old_version = Bump::Bump.current 47 | Bump::Bump.run(release_type, commit: false, bundle: false, tag: false) 48 | new_version = Bump::Bump.current 49 | 50 | update_cop_versions(new_version) 51 | `bundle exec rake generate_cops_documentation` 52 | update_docs(new_version) if %w[major minor].include?(release_type) 53 | add_header_to_changelog(new_version) 54 | 55 | puts "Changed version from #{old_version} to #{new_version}." 56 | end 57 | end 58 | --------------------------------------------------------------------------------