├── .codespellignore ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── codespell.yml │ ├── danger.yml │ ├── linting.yml │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── .yamllint ├── .yardopts ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dangerfile ├── Gemfile ├── MIT-LICENSE.md ├── README.md ├── Rakefile ├── config └── default.yml ├── docs ├── antora.yml └── modules │ └── ROOT │ ├── nav.adoc │ └── pages │ ├── cops.adoc │ ├── cops_factorybot.adoc │ ├── development.adoc │ ├── index.adoc │ ├── installation.adoc │ └── usage.adoc ├── lib ├── rubocop-factory_bot.rb └── rubocop │ ├── cop │ ├── factory_bot │ │ ├── association_style.rb │ │ ├── attribute_defined_statically.rb │ │ ├── consistent_parentheses_style.rb │ │ ├── create_list.rb │ │ ├── excessive_create_list.rb │ │ ├── factory_association_with_strategy.rb │ │ ├── factory_class_name.rb │ │ ├── factory_name_style.rb │ │ ├── id_sequence.rb │ │ ├── mixin │ │ │ └── configurable_explicit_only.rb │ │ ├── redundant_factory_option.rb │ │ └── syntax_methods.rb │ └── factory_bot_cops.rb │ └── factory_bot │ ├── config_formatter.rb │ ├── cop │ └── generator.rb │ ├── description_extractor.rb │ ├── factory_bot.rb │ ├── language.rb │ ├── plugin.rb │ └── version.rb ├── rubocop-factory_bot.gemspec ├── spec ├── project │ ├── changelog_spec.rb │ └── default_config_spec.rb ├── rubocop │ ├── cop │ │ └── factory_bot │ │ │ ├── association_style_spec.rb │ │ │ ├── attribute_defined_statically_spec.rb │ │ │ ├── consistent_parentheses_style_spec.rb │ │ │ ├── create_list_spec.rb │ │ │ ├── excessive_create_list_spec.rb │ │ │ ├── factory_association_with_strategy_spec.rb │ │ │ ├── factory_class_name_spec.rb │ │ │ ├── factory_name_style_spec.rb │ │ │ ├── id_sequence_spec.rb │ │ │ ├── redundant_factory_option_spec.rb │ │ │ └── syntax_methods_spec.rb │ └── factory_bot │ │ ├── config_formatter_spec.rb │ │ └── description_extractor_spec.rb └── spec_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/danger.yml: -------------------------------------------------------------------------------- 1 | name: Danger 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 | danger: 9 | name: Danger 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - uses: ruby/setup-ruby@v1 16 | with: 17 | bundler-cache: true 18 | ruby-version: "3.4" 19 | - name: Run Danger 20 | run: bundle exec danger 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.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 | prism: 81 | runs-on: ubuntu-latest 82 | name: Prism 83 | steps: 84 | - uses: actions/checkout@v4 85 | - name: Use prism parser 86 | run: | 87 | cat << EOF > Gemfile.local 88 | gem 'prism' 89 | EOF 90 | - name: set up Ruby 91 | uses: ruby/setup-ruby@v1 92 | with: 93 | # Specify the minimum Ruby version 2.7 required for Prism to run. 94 | ruby-version: 2.7 95 | bundler-cache: true 96 | - name: spec 97 | env: 98 | PARSER_ENGINE: parser_prism 99 | run: NO_COVERAGE=true bundle exec rake 100 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: master 5 | paths: lib/rubocop/factory_bot/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 FactoryBot $(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-factory_bot 5 | - rubocop-performance 6 | - rubocop-rake 7 | - rubocop-rspec 8 | - rubocop-internal_affairs 9 | 10 | AllCops: 11 | DisplayCopNames: true 12 | TargetRubyVersion: 2.7 13 | NewCops: disable 14 | Exclude: 15 | - 'vendor/**/*' 16 | - 'spec/fixtures/**/*' 17 | - 'tmp/**/*' 18 | - 'spec/smoke_tests/**/*.rb' 19 | 20 | InternalAffairs/OnSendWithoutOnCSend: 21 | Enabled: false 22 | 23 | Layout/HashAlignment: 24 | EnforcedHashRocketStyle: 25 | - key 26 | - table 27 | EnforcedColonStyle: 28 | - key 29 | - table 30 | 31 | Layout/LineLength: 32 | Max: 80 # default: 120 33 | AllowedPatterns: 34 | - '^\s*# .*https?:\/\/.+\[.+\]\.?$' # Allow long asciidoc links 35 | 36 | Layout/MultilineMethodCallIndentation: 37 | EnforcedStyle: indented 38 | 39 | Layout/MultilineOperationIndentation: 40 | EnforcedStyle: indented 41 | 42 | Lint/InterpolationCheck: 43 | Exclude: 44 | - spec/**/*.rb 45 | 46 | # When the `edge-rubocop` build is red, and we decide to disable the cop, 47 | # the rest of the builds become red if the cop has not yet been released. 48 | # Instead of waiting for RuboCop releases to make `edge-rubocop` green, 49 | # we prefer keeping disable directives here and there and check if they 50 | # are still needed once in a while. 51 | Lint/RedundantCopDisableDirective: 52 | Enabled: false 53 | 54 | Metrics/BlockLength: 55 | Exclude: 56 | - rubocop-rspec.gemspec 57 | - Rakefile 58 | - '**/*.rake' 59 | 60 | Naming/FileName: 61 | Exclude: 62 | - lib/rubocop-factory_bot.rb 63 | 64 | Naming/InclusiveLanguage: 65 | Enabled: true 66 | CheckStrings: true 67 | FlaggedTerms: 68 | ' a offense': 69 | Suggestions: 70 | - an offense 71 | auto-correct: 72 | Suggestions: 73 | - autocorrect 74 | auto_correct: 75 | Suggestions: 76 | - autocorrect 77 | ' a violation': 78 | Suggestions: 79 | - an offense 80 | behaviour: 81 | Suggestions: 82 | - behavior 83 | offence: 84 | Suggestions: 85 | - offense 86 | 'does not registers': 87 | Suggestions: 88 | - does not register 89 | violation: 90 | Suggestions: 91 | - offense 92 | 'register no offense': 93 | Suggestions: 94 | - registers no offense 95 | 96 | RSpec: 97 | Language: 98 | Expectations: 99 | - expect_correction 100 | - expect_no_offenses 101 | - expect_offense 102 | 103 | RSpec/ExampleLength: 104 | CountAsOne: 105 | - heredoc 106 | Max: 11 107 | 108 | RSpec/DescribeClass: 109 | Exclude: 110 | - spec/project/**/*.rb 111 | 112 | RSpec/MultipleExpectations: 113 | Max: 2 114 | 115 | Style/FormatStringToken: 116 | Exclude: 117 | - spec/rubocop/**/*.rb 118 | 119 | Style/RequireOrder: 120 | Enabled: true 121 | 122 | RSpec/SpecFilePathFormat: 123 | Exclude: 124 | - spec/rubocop/cop/rspec/mixin/**/*.rb 125 | 126 | # Enable some of RuboCop's pending cops. 127 | 128 | Layout/LineContinuationSpacing: 129 | Enabled: true 130 | Layout/LineEndStringConcatenationIndentation: 131 | Enabled: true 132 | Lint/AmbiguousOperatorPrecedence: 133 | Enabled: true 134 | Lint/NonAtomicFileOperation: 135 | Enabled: true 136 | Style/EmptyHeredoc: 137 | Enabled: true 138 | Style/RedundantHeredocDelimiterQuotes: 139 | Enabled: true 140 | Style/RedundantStringEscape: 141 | Enabled: true 142 | Style/ReturnNilInPredicateMethodDefinition: 143 | Enabled: true 144 | 145 | # Enable pending rubocop-performance cops. 146 | 147 | Performance/AncestorsInclude: 148 | Enabled: true 149 | Performance/BlockGivenWithExplicitBlock: 150 | Enabled: true 151 | Performance/CollectionLiteralInLoop: 152 | Enabled: true 153 | Performance/ConstantRegexp: 154 | Enabled: true 155 | Performance/MapCompact: 156 | Enabled: true 157 | Performance/MapMethodChain: 158 | Enabled: true 159 | Performance/MethodObjectAsBlock: 160 | Enabled: true 161 | Performance/RedundantEqualityComparisonBlock: 162 | Enabled: true 163 | Performance/RedundantSortBlock: 164 | Enabled: true 165 | Performance/RedundantSplitRegexpArgument: 166 | Enabled: true 167 | Performance/RedundantStringChars: 168 | Enabled: true 169 | Performance/ReverseFirst: 170 | Enabled: true 171 | Performance/SortReverse: 172 | Enabled: true 173 | Performance/Squeeze: 174 | Enabled: true 175 | Performance/StringIdentifierArgument: 176 | Enabled: true 177 | Performance/StringInclude: 178 | Enabled: true 179 | Performance/Sum: 180 | Enabled: true 181 | -------------------------------------------------------------------------------- /.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: 99.60, branch: 95.32 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.27.1 (2025-03-12) 6 | 7 | - Fix incorrect plugin version. ([@koic]) 8 | 9 | ## 2.27.0 (2025-03-06) 10 | 11 | - Fix a false positive for `FactoryBot/ConsistentParenthesesStyle` when using traits and omitting hash values. ([@thejonroberts]) 12 | - Make RuboCop FactoryBot work as a RuboCop plugin. ([@bquorning]) 13 | 14 | ## 2.26.1 (2024-06-12) 15 | 16 | - Bump RuboCop requirement to +1.61. ([@ydah]) 17 | 18 | ## 2.26.0 (2024-06-08) 19 | 20 | - Fix a false positive for `FactoryBot/AssociationStyle` when using nested factories with traits. ([@jaydorsey]) 21 | - Support `AutoCorrect: contextual` option for LSP. ([@ydah]) 22 | 23 | ## 2.25.1 (2024-01-08) 24 | 25 | - Fix a false positive for `FactoryBot/CreateList` when create call does have method calls and repeat multiple times with other argument. ([@ydah]) 26 | - Fix an error occurred for `FactoryBot/IdSequence` when `sequence` with non-symbol argument or without argument. ([@ydah]) 27 | 28 | ## 2.25.0 (2024-01-04) 29 | 30 | - Fix a false positive for `FactoryBot/FactoryNameStyle` when namespaced models. ([@ydah]) 31 | - Add new `FactoryBot/ExcessiveCreateList` cop. ([@ddieulivol]) 32 | - Fix a false positive for `FactoryBot/ConsistentParenthesesStyle` when hash pinning. ([@ydah]) 33 | 34 | ## 2.24.0 (2023-09-18) 35 | 36 | - Fix `FactoryBot/AssociationStyle` cop to ignore explicit associations with `strategy: :build`. ([@pirj]) 37 | - Change `FactoryBot/CreateList` so that it is not an offense if not repeated multiple times. ([@ydah]) 38 | - Fix a false positive for `FactoryBot/AssociationStyle` when `association` is called in trait block and column name is keyword. ([@ydah]) 39 | - Fix a false positive for `FactoryBot/AssociationStyle` when `EnforcedStyle: Explicit` and using trait within trait. ([@ydah]) 40 | - Change `FactoryBot/AssociationStyle`, `FactoryBot/AttributeDefinedStatically`, `FactoryBot/CreateList` and `FactoryBot/FactoryClassName` to work with minitest style directory. ([@ydah]) 41 | - Add `FactoryBot/IdSequence` cop. ([@owst]) 42 | 43 | ## 2.23.1 (2023-05-15) 44 | 45 | - Fix `FactoryBot/AssociationStyle` cop for a blockless `factory`. ([@pirj]) 46 | 47 | ## 2.23.0 (2023-05-15) 48 | 49 | - Add `FactoryBot/FactoryAssociationWithStrategy` cop. ([@morissetcl]) 50 | - Mark `FactoryBot/CreateList` as `SafeAutoCorrect: false`. ([@r7kamura]) 51 | - Change `FactoryBot/CreateList` so that it considers `times.map`. ([@r7kamura]) 52 | - Add `FactoryBot/RedundantFactoryOption` cop. ([@r7kamura]) 53 | - Add `ExplicitOnly` configuration option to `FactoryBot/ConsistentParenthesesStyle`, `FactoryBot/CreateList` and `FactoryBot/FactoryNameStyle`. ([@ydah]) 54 | - Change `FactoryBot/CreateList` so that it checks same factory calls in an Array. ([@r7kamura]) 55 | - Add `FactoryBot/AssociationStyle` cop. ([@r7kamura]) 56 | 57 | ## 2.22.0 (2023-05-04) 58 | 59 | - Extracted from `rubocop-rspec` into a separate repository for easier use with Minitest/Cucumber. ([@ydah]) 60 | 61 | ## Previously (see [rubocop-rspec's changelist](https://github.com/rubocop/rubocop-rspec/blob/70a97b1895ce4b9bcd6ff336d5d343ddc6175fe6/CHANGELOG.md) for details) 62 | 63 | - Fix a false positive for `RSpec/FactoryBot/ConsistentParenthesesStyle` inside `&&`, `||` and `:?` when `omit_parentheses` is on. ([@dmitrytsepelev]) 64 | - Add new `RSpec/FactoryBot/FactoryNameStyle` cop. ([@ydah]) 65 | - Fix wrong autocorrection in `n_times` style on `RSpec/FactoryBot/CreateList`. ([@r7kamura]) 66 | - Fix a false positive for `RSpec/FactoryBot/ConsistentParenthesesStyle` when using `generate` with multiple arguments. ([@ydah]) 67 | - Fix `RSpec/FactoryBot/ConsistentParenthesesStyle` to ignore calls without the first positional argument. ([@pirj]) 68 | - Fix `RSpec/FactoryBot/ConsistentParenthesesStyle` to ignore calls inside a Hash or an Array. ([@pirj]) 69 | - Fix an incorrect autocorrect for `FactoryBot/ConsistentParenthesesStyle` with `omit_parentheses` option when method name and first argument are not on same line. ([@ydah]) 70 | - Add `RSpec/FactoryBot/ConsistentParenthesesStyle` cop. ([@Liberatys]) 71 | - Support `Array.new(n)` on `RSpec/FactoryBot/CreateList` cop. ([@r7kamura]) 72 | - Fixed false offense detection in `FactoryBot/CreateList` when a n.times block is including method calls in the factory create arguments. ([@ngouy]) 73 | - Fix error in `RSpec/RSpec/FactoryBot/CreateList` cop for empty block. ([@tejasbubane]) 74 | - Fix `RSpec/FactoryBot/SyntaxMethods` and `RSpec/Capybara/FeatureMethods` to inspect shared groups. ([@pirj]) 75 | - Add new `RSpec/FactoryBot/SyntaxMethods` cop. ([@leoarnold]) 76 | - Change namespace of several cops (`Capybara/*` -> `RSpec/Capybara/*`, `FactoryBot/*` -> `RSpec/FactoryBot/*`, `Rails/*` -> `RSpec/Rails/*`). ([@pirj], [@bquorning]) 77 | - Fix `FactoryBot/AttributeDefinedStatically` to allow `#traits_for_enum` without a block. ([@harrylewis]) 78 | - Improve the performance of `FactoryBot/AttributeDefinedStatically`, `RSpec/InstanceVariable`, `RSpec/LetSetup`, `RSpec/NestedGroups` and `RSpec/ReturnFromStub`. ([@andrykonchin]) 79 | - Improve message and description of `FactoryBot/FactoryClassName`. ([@ybiquitous]) 80 | - Fix `FactoryBot/FactoryClassName` to ignore `Hash` and `OpenStruct`. ([@jfragoulis]) 81 | - Add `FactoryBot/FactoryClassName` cop. ([@jfragoulis]) 82 | - Fix `FactoryBot/AttributeDefinedStatically` not working with an explicit receiver. ([@composerinteralia]) 83 | - Fix `FactoryBot/CreateList` autocorrect crashing when the factory is called with a block=. ([@Darhazer]) 84 | - `FactoryBot/CreateList` now ignores `times` blocks with an argument. ([@Darhazer]) 85 | - Fix `FactoryBot/AttributeDefinedStatically` not working when there is a non-symbol key. ([@vzvu3k6k]) 86 | - Fix false negative in `FactoryBot/AttributeDefinedStatically` when attribute is defined on `self`. ([@Darhazer]) 87 | - `RSpec/FactoryBot` cops will now also inspect the `spec/factories.rb` path by default. ([@bquorning]) 88 | - Add `FactoryBot/AttributeDefinedStatically` cop to help FactoryBot users with the deprecation of static attributes. ([@composerinteralia], [@seanpdoyle]) 89 | - Remove `FactoryBot/DynamicAttributeDefinedStatically` and `FactoryBot/StaticAttributeDefinedDynamically` cops. ([@composerinteralia]) 90 | - Fix `FactoryBot/DynamicAttributeDefinedStatically` false positive when using symbol proc argument for a sequence. ([@tdeo]) 91 | - Add `FactoryBot/CreateList` cop. ([@Darhazer]) 92 | - Fix `FactoryBot/StaticAttributeDefinedDynamically` to handle empty block. ([@abrom]) 93 | - Fix false positive in `FactoryBot/DynamicAttributeDefinedStatically` when a before/after callback has a symbol proc argument. ([@abrom]) 94 | - Fix `FactoryBot/DynamicAttributeDefinedStatically` to handle dynamic attributes inside arrays/hashes. ([@abrom]) 95 | - Add `FactoryBot/StaticAttributeDefinedDynamically` (based on dynamic attribute cop). ([@abrom]) 96 | - Rename namespace `FactoryGirl` to `FactoryBot` following original library update. ([@walf443]) 97 | - Add `RSpec/FactoryGirl` namespace including the first cop for factories: `FactoryGirl/DynamicAttributeDefinedStatically`. ([@jonatas]) 98 | 99 | 100 | 101 | [@abrom]: https://github.com/abrom 102 | [@andrykonchin]: https://github.com/andrykonchin 103 | [@bquorning]: https://github.com/bquorning 104 | [@composerinteralia]: https://github.com/composerinteralia 105 | [@darhazer]: https://github.com/Darhazer 106 | [@ddieulivol]: https://github.com/ddieulivol 107 | [@dmitrytsepelev]: https://github.com/dmitrytsepelev 108 | [@harrylewis]: https://github.com/harrylewis 109 | [@jaydorsey]: https://github.com/jaydorsey 110 | [@jfragoulis]: https://github.com/jfragoulis 111 | [@jonatas]: https://github.com/jonatas 112 | [@koic]: https://github.com/koic 113 | [@leoarnold]: https://github.com/leoarnold 114 | [@liberatys]: https://github.com/Liberatys 115 | [@morissetcl]: https://github.com/morissetcl 116 | [@ngouy]: https://github.com/ngouy 117 | [@owst]: https://github.com/owst 118 | [@pirj]: https://github.com/pirj 119 | [@r7kamura]: https://github.com/r7kamura 120 | [@seanpdoyle]: https://github.com/seanpdoyle 121 | [@tdeo]: https://github.com/tdeo 122 | [@tejasbubane]: https://github.com/tejasbubane 123 | [@thejonroberts]: https://github.com/thejonroberts 124 | [@vzvu3k6k]: https://github.com/vzvu3k6k 125 | [@walf443]: https://github.com/walf443 126 | [@ybiquitous]: https://github.com/ybiquitous 127 | [@ydah]: https://github.com/ydah 128 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | diff = git.diff_for_file('config/default.yml') 4 | if diff && diff.patch =~ /^\+\s*Enabled: true$/ 5 | warn(<<~MESSAGE) 6 | There is a cop that became `Enabled: true` due to this pull request. 7 | Please review the diff and make sure there are no issues. 8 | MESSAGE 9 | end 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'bump' 8 | gem 'danger' 9 | gem 'rack' 10 | gem 'rake' 11 | gem 'rspec', '~> 3.11' 12 | gem 'rubocop-performance', '~> 1.24' 13 | gem 'rubocop-rake', '~> 0.7' 14 | gem 'rubocop-rspec', '~> 3.5' 15 | gem 'simplecov', '>= 0.19' 16 | gem 'yard' 17 | 18 | local_gemfile = 'Gemfile.local' 19 | eval_gemfile(local_gemfile) if File.exist?(local_gemfile) 20 | -------------------------------------------------------------------------------- /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 factory_bot 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-factory_bot.svg)](https://rubygems.org/gems/rubocop-factory_bot) 5 | ![CI](https://github.com/rubocop/rubocop-factory_bot/workflows/CI/badge.svg) 6 | 7 | [factory_bot](https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md)-specific analysis for your projects, as an extension to 8 | [RuboCop](https://github.com/rubocop/rubocop). 9 | 10 | ## Installation 11 | 12 | Just install the `rubocop-factory_bot` gem 13 | 14 | ```bash 15 | gem install rubocop-factory_bot 16 | ``` 17 | 18 | or if you use bundler put this in your `Gemfile` 19 | 20 | ```ruby 21 | gem 'rubocop-factory_bot', require: false 22 | ``` 23 | 24 | ## Usage 25 | 26 | You need to tell RuboCop to load the factory_bot extension. There are three 27 | ways to do this: 28 | 29 | ### RuboCop configuration file 30 | 31 | Put this into your `.rubocop.yml`. 32 | 33 | ```yaml 34 | plugins: rubocop-factory_bot 35 | ``` 36 | 37 | Alternatively, use the following array notation when specifying multiple extensions. 38 | 39 | ```yaml 40 | plugins: 41 | - rubocop-other-extension 42 | - rubocop-factory_bot 43 | ``` 44 | 45 | Now you can run `rubocop` and it will automatically load the RuboCop factory_bot 46 | cops together with the standard cops. 47 | 48 | > [!NOTE] 49 | > The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. 50 | 51 | ### Command line 52 | 53 | ```bash 54 | rubocop --plugin rubocop-factory_bot 55 | ``` 56 | 57 | ### Rake task 58 | 59 | ```ruby 60 | RuboCop::RakeTask.new do |task| 61 | task.plugins << 'rubocop-factory_bot' 62 | end 63 | ``` 64 | 65 | ## Documentation 66 | 67 | You can read more about RuboCop factory_bot in its [official manual](https://docs.rubocop.org/rubocop-factory_bot). 68 | 69 | ## The Cops 70 | 71 | All cops are located under 72 | [`lib/rubocop/cop/factory_bot`](lib/rubocop/cop/factory_bot), and contain 73 | examples/documentation. 74 | 75 | In your `.rubocop.yml`, you may treat the factory_bot cops just like any other 76 | cop. For example: 77 | 78 | ```yaml 79 | FactoryBot/AttributeDefinedStatically: 80 | Exclude: 81 | - spec/factories/my_factory.rb 82 | ``` 83 | 84 | ## Contributing 85 | 86 | Checkout the [contribution guidelines](.github/CONTRIBUTING.md). 87 | 88 | ## License 89 | 90 | `rubocop-factory_bot` is MIT licensed. [See the accompanying file](MIT-LICENSE.md) for 91 | the full text. 92 | -------------------------------------------------------------------------------- /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-factory_bot' 33 | require 'rubocop/factory_bot/config_formatter' 34 | require 'rubocop/factory_bot/description_extractor' 35 | 36 | glob = File.join('lib', 'rubocop', 'cop', 'factory_bot', '*.rb') 37 | YARD::Tags::Library.define_tag('Cop Safety Information', :safety) 38 | YARD.parse(Dir[glob], []) 39 | 40 | descriptions = 41 | RuboCop::FactoryBot::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::FactoryBot::ConfigFormatter.new(current_config, descriptions).dump 53 | ) 54 | end 55 | 56 | desc 'Confirm config/default.yml is up to date' 57 | task confirm_config: :build_config do 58 | _, stdout, _, process = 59 | Open3.popen3('git diff --exit-code config/default.yml') 60 | 61 | raise <<~ERROR unless process.value.success? 62 | default.yml is out of sync: 63 | 64 | #{stdout.read} 65 | Please run `rake build_config` 66 | ERROR 67 | end 68 | 69 | desc 'Confirm documentation is up to date' 70 | task confirm_documentation: :generate_cops_documentation do 71 | _, _, _, process = 72 | Open3.popen3('git diff --exit-code docs/') 73 | 74 | unless process.value.success? 75 | raise 'Please run `rake generate_cops_documentation` ' \ 76 | 'and add docs/ to the commit.' 77 | end 78 | end 79 | 80 | task default: %i[build_config spec 81 | internal_investigation 82 | confirm_config 83 | documentation_syntax_check 84 | confirm_documentation] 85 | 86 | desc 'Generate a new cop template' 87 | task :new_cop, [:cop] do |_task, args| 88 | require 'rubocop' 89 | require_relative 'lib/rubocop/factory_bot/cop/generator' 90 | 91 | cop_name = args.fetch(:cop) do 92 | warn "usage: bundle exec rake 'new_cop[Department/Name]'" 93 | exit! 94 | end 95 | 96 | generator = RuboCop::FactoryBot::Cop::Generator.new(cop_name) 97 | generator.write_source 98 | generator.write_spec 99 | generator.inject_require( 100 | root_file_path: 'lib/rubocop/cop/factory_bot_cops.rb' 101 | ) 102 | generator.inject_config 103 | 104 | puts generator.todo 105 | end 106 | -------------------------------------------------------------------------------- /config/default.yml: -------------------------------------------------------------------------------- 1 | --- 2 | FactoryBot: 3 | Enabled: true 4 | Include: 5 | - "**/spec/factories.rb" 6 | - "**/spec/factories/**/*.rb" 7 | - "**/test/factories.rb" 8 | - "**/test/factories/**/*.rb" 9 | - "**/features/support/factories/**/*.rb" 10 | DocumentationBaseURL: https://docs.rubocop.org/rubocop-factory_bot 11 | 12 | FactoryBot/AssociationStyle: 13 | Description: Use a consistent style to define associations. 14 | Enabled: pending 15 | Safe: false 16 | VersionAdded: '2.23' 17 | VersionChanged: '2.24' 18 | EnforcedStyle: implicit 19 | SupportedStyles: 20 | - explicit 21 | - implicit 22 | NonImplicitAssociationMethodNames: ~ 23 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/AssociationStyle 24 | 25 | FactoryBot/AttributeDefinedStatically: 26 | Description: Always declare attribute values as blocks. 27 | Enabled: true 28 | VersionAdded: '1.28' 29 | VersionChanged: '2.24' 30 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/AttributeDefinedStatically 31 | 32 | FactoryBot/ConsistentParenthesesStyle: 33 | Description: Use a consistent style for parentheses in factory_bot calls. 34 | Enabled: pending 35 | Include: 36 | - "**/*_spec.rb" 37 | - "**/spec/**/*" 38 | - "**/test/**/*" 39 | - "**/features/support/factories/**/*.rb" 40 | EnforcedStyle: require_parentheses 41 | SupportedStyles: 42 | - require_parentheses 43 | - omit_parentheses 44 | ExplicitOnly: false 45 | VersionAdded: '2.14' 46 | VersionChanged: '2.23' 47 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/ConsistentParenthesesStyle 48 | 49 | FactoryBot/CreateList: 50 | Description: Checks for create_list usage. 51 | Enabled: true 52 | AutoCorrect: contextual 53 | Include: 54 | - "**/*_spec.rb" 55 | - "**/spec/**/*" 56 | - "**/test/**/*" 57 | - "**/features/support/factories/**/*.rb" 58 | EnforcedStyle: create_list 59 | SupportedStyles: 60 | - create_list 61 | - n_times 62 | ExplicitOnly: false 63 | SafeAutoCorrect: false 64 | VersionAdded: '1.25' 65 | VersionChanged: '2.26' 66 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/CreateList 67 | 68 | FactoryBot/ExcessiveCreateList: 69 | Description: Check for excessive model creation in a list. 70 | Enabled: pending 71 | Include: 72 | - "**/*_spec.rb" 73 | - "**/spec/**/*" 74 | - "**/test/**/*" 75 | - "**/features/support/factories/**/*.rb" 76 | MaxAmount: 10 77 | VersionAdded: '2.25' 78 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/ExcessiveCreateList 79 | 80 | FactoryBot/FactoryAssociationWithStrategy: 81 | Description: Use definition in factory association instead of hard coding a strategy. 82 | Enabled: pending 83 | Include: 84 | - "**/*_spec.rb" 85 | - "**/spec/**/*" 86 | - "**/test/**/*" 87 | - "**/features/support/factories/**/*.rb" 88 | VersionAdded: '2.23' 89 | VersionChanged: '2.23' 90 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryAssociationWithStrategy 91 | 92 | FactoryBot/FactoryClassName: 93 | Description: Use string value when setting the class attribute explicitly. 94 | Enabled: true 95 | VersionAdded: '1.37' 96 | VersionChanged: '2.24' 97 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryClassName 98 | 99 | FactoryBot/FactoryNameStyle: 100 | Description: Checks for name style for argument of FactoryBot::Syntax::Methods. 101 | Enabled: pending 102 | Include: 103 | - "**/*_spec.rb" 104 | - "**/spec/**/*" 105 | - "**/test/**/*" 106 | - "**/features/support/factories/**/*.rb" 107 | EnforcedStyle: symbol 108 | SupportedStyles: 109 | - symbol 110 | - string 111 | ExplicitOnly: false 112 | VersionAdded: '2.16' 113 | VersionChanged: '2.23' 114 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryNameStyle 115 | 116 | FactoryBot/IdSequence: 117 | Description: Do not create a FactoryBot sequence for an id column. 118 | Enabled: pending 119 | VersionAdded: '2.24' 120 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/IdSequence 121 | 122 | FactoryBot/RedundantFactoryOption: 123 | Description: Checks for redundant `factory` option. 124 | Enabled: pending 125 | Include: 126 | - "**/*_spec.rb" 127 | - "**/spec/**/*" 128 | - "**/test/**/*" 129 | - "**/features/support/factories/**/*.rb" 130 | VersionAdded: '2.23' 131 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/RedundantFactoryOption 132 | 133 | FactoryBot/SyntaxMethods: 134 | Description: Use shorthands from `FactoryBot::Syntax::Methods` in your specs. 135 | Enabled: pending 136 | Include: 137 | - "**/*_spec.rb" 138 | - "**/spec/**/*" 139 | - "**/test/**/*" 140 | - "**/features/support/factories/**/*.rb" 141 | SafeAutoCorrect: false 142 | VersionAdded: '2.7' 143 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/SyntaxMethods 144 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: rubocop-factory_bot 2 | title: RuboCop factory_bot 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_factorybot.adoc[FactoryBot] 8 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/cops.adoc: -------------------------------------------------------------------------------- 1 | // START_COP_LIST 2 | 3 | === Department xref:cops_factorybot.adoc[FactoryBot] 4 | 5 | * xref:cops_factorybot.adoc#factorybotassociationstyle[FactoryBot/AssociationStyle] 6 | * xref:cops_factorybot.adoc#factorybotattributedefinedstatically[FactoryBot/AttributeDefinedStatically] 7 | * xref:cops_factorybot.adoc#factorybotconsistentparenthesesstyle[FactoryBot/ConsistentParenthesesStyle] 8 | * xref:cops_factorybot.adoc#factorybotcreatelist[FactoryBot/CreateList] 9 | * xref:cops_factorybot.adoc#factorybotexcessivecreatelist[FactoryBot/ExcessiveCreateList] 10 | * xref:cops_factorybot.adoc#factorybotfactoryassociationwithstrategy[FactoryBot/FactoryAssociationWithStrategy] 11 | * xref:cops_factorybot.adoc#factorybotfactoryclassname[FactoryBot/FactoryClassName] 12 | * xref:cops_factorybot.adoc#factorybotfactorynamestyle[FactoryBot/FactoryNameStyle] 13 | * xref:cops_factorybot.adoc#factorybotidsequence[FactoryBot/IdSequence] 14 | * xref:cops_factorybot.adoc#factorybotredundantfactoryoption[FactoryBot/RedundantFactoryOption] 15 | * xref:cops_factorybot.adoc#factorybotsyntaxmethods[FactoryBot/SyntaxMethods] 16 | 17 | // END_COP_LIST 18 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/cops_factorybot.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 | = FactoryBot 8 | 9 | [#factorybotassociationstyle] 10 | == FactoryBot/AssociationStyle 11 | 12 | |=== 13 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 14 | 15 | | Pending 16 | | No 17 | | Always (Unsafe) 18 | | 2.23 19 | | 2.24 20 | |=== 21 | 22 | Use a consistent style to define associations. 23 | 24 | [#safety-factorybotassociationstyle] 25 | === Safety 26 | 27 | This cop may cause false-positives in `EnforcedStyle: explicit` 28 | case. It recognizes any method call that has no arguments as an 29 | implicit association but it might be a user-defined trait call. 30 | 31 | [#examples-factorybotassociationstyle] 32 | === Examples 33 | 34 | [#enforcedstyle_-implicit-_default_-factorybotassociationstyle] 35 | ==== EnforcedStyle: implicit (default) 36 | 37 | [source,ruby] 38 | ---- 39 | # bad 40 | factory :post do 41 | association :user 42 | end 43 | 44 | # good 45 | factory :post do 46 | user 47 | end 48 | 49 | # bad 50 | factory :post do 51 | association :user, :author 52 | end 53 | 54 | # good 55 | factory :post do 56 | user factory: %i[user author] 57 | end 58 | ---- 59 | 60 | [#enforcedstyle_-explicit-factorybotassociationstyle] 61 | ==== EnforcedStyle: explicit 62 | 63 | [source,ruby] 64 | ---- 65 | # bad 66 | factory :post do 67 | user 68 | end 69 | 70 | # good 71 | factory :post do 72 | association :user 73 | end 74 | 75 | # bad 76 | factory :post do 77 | user factory: %i[user author] 78 | end 79 | 80 | # good 81 | factory :post do 82 | association :user, :author 83 | end 84 | 85 | # good (NonImplicitAssociationMethodNames: ['email']) 86 | sequence :email do |n| 87 | "person#{n}@example.com" 88 | end 89 | 90 | factory :user do 91 | email 92 | end 93 | ---- 94 | 95 | [#configurable-attributes-factorybotassociationstyle] 96 | === Configurable attributes 97 | 98 | |=== 99 | | Name | Default value | Configurable values 100 | 101 | | EnforcedStyle 102 | | `implicit` 103 | | `explicit`, `implicit` 104 | 105 | | NonImplicitAssociationMethodNames 106 | | `` 107 | | 108 | |=== 109 | 110 | [#references-factorybotassociationstyle] 111 | === References 112 | 113 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/AssociationStyle 114 | 115 | [#factorybotattributedefinedstatically] 116 | == FactoryBot/AttributeDefinedStatically 117 | 118 | |=== 119 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 120 | 121 | | Enabled 122 | | Yes 123 | | Always 124 | | 1.28 125 | | 2.24 126 | |=== 127 | 128 | Always declare attribute values as blocks. 129 | 130 | [#examples-factorybotattributedefinedstatically] 131 | === Examples 132 | 133 | [source,ruby] 134 | ---- 135 | # bad 136 | kind [:active, :rejected].sample 137 | 138 | # good 139 | kind { [:active, :rejected].sample } 140 | 141 | # bad 142 | closed_at 1.day.from_now 143 | 144 | # good 145 | closed_at { 1.day.from_now } 146 | 147 | # bad 148 | count 1 149 | 150 | # good 151 | count { 1 } 152 | ---- 153 | 154 | [#references-factorybotattributedefinedstatically] 155 | === References 156 | 157 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/AttributeDefinedStatically 158 | 159 | [#factorybotconsistentparenthesesstyle] 160 | == FactoryBot/ConsistentParenthesesStyle 161 | 162 | |=== 163 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 164 | 165 | | Pending 166 | | Yes 167 | | Always 168 | | 2.14 169 | | 2.23 170 | |=== 171 | 172 | Use a consistent style for parentheses in factory_bot calls. 173 | 174 | [#examples-factorybotconsistentparenthesesstyle] 175 | === Examples 176 | 177 | [#_enforcedstyle_-require_parentheses_-_default_-factorybotconsistentparenthesesstyle] 178 | ==== `EnforcedStyle: require_parentheses` (default) 179 | 180 | [source,ruby] 181 | ---- 182 | # bad 183 | create :user 184 | build :login 185 | 186 | # good 187 | create(:user) 188 | build(:login) 189 | ---- 190 | 191 | [#_enforcedstyle_-omit_parentheses_-factorybotconsistentparenthesesstyle] 192 | ==== `EnforcedStyle: omit_parentheses` 193 | 194 | [source,ruby] 195 | ---- 196 | # bad 197 | create(:user) 198 | build(:login) 199 | 200 | # good 201 | create :user 202 | build :login 203 | 204 | # also good 205 | # when method name and first argument are not on same line 206 | create( 207 | :user 208 | ) 209 | build( 210 | :user, 211 | name: 'foo' 212 | ) 213 | ---- 214 | 215 | [#_explicitonly_-false_-_default_-factorybotconsistentparenthesesstyle] 216 | ==== `ExplicitOnly: false` (default) 217 | 218 | [source,ruby] 219 | ---- 220 | # bad - with `EnforcedStyle: require_parentheses` 221 | FactoryBot.create :user 222 | build :user 223 | 224 | # good - with `EnforcedStyle: require_parentheses` 225 | FactoryBot.create(:user) 226 | build(:user) 227 | ---- 228 | 229 | [#_explicitonly_-true_-factorybotconsistentparenthesesstyle] 230 | ==== `ExplicitOnly: true` 231 | 232 | [source,ruby] 233 | ---- 234 | # bad - with `EnforcedStyle: require_parentheses` 235 | FactoryBot.create :user 236 | FactoryBot.build :user 237 | 238 | # good - with `EnforcedStyle: require_parentheses` 239 | FactoryBot.create(:user) 240 | FactoryBot.build(:user) 241 | create :user 242 | build :user 243 | ---- 244 | 245 | [#configurable-attributes-factorybotconsistentparenthesesstyle] 246 | === Configurable attributes 247 | 248 | |=== 249 | | Name | Default value | Configurable values 250 | 251 | | Include 252 | | `+**/*_spec.rb+`, `+**/spec/**/*+`, `+**/test/**/*+`, `+**/features/support/factories/**/*.rb+` 253 | | Array 254 | 255 | | EnforcedStyle 256 | | `require_parentheses` 257 | | `require_parentheses`, `omit_parentheses` 258 | 259 | | ExplicitOnly 260 | | `false` 261 | | Boolean 262 | |=== 263 | 264 | [#references-factorybotconsistentparenthesesstyle] 265 | === References 266 | 267 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/ConsistentParenthesesStyle 268 | 269 | [#factorybotcreatelist] 270 | == FactoryBot/CreateList 271 | 272 | |=== 273 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 274 | 275 | | Enabled 276 | | Yes 277 | | Command-line only (Unsafe) 278 | | 1.25 279 | | 2.26 280 | |=== 281 | 282 | Checks for create_list usage. 283 | 284 | This cop can be configured using the `EnforcedStyle` option 285 | 286 | [#safety-factorybotcreatelist] 287 | === Safety 288 | 289 | This cop's autocorrection is unsafe because replacing `n.times` to 290 | `create_list` changes its returned value. 291 | 292 | [#examples-factorybotcreatelist] 293 | === Examples 294 | 295 | [#_enforcedstyle_-create_list_-_default_-factorybotcreatelist] 296 | ==== `EnforcedStyle: create_list` (default) 297 | 298 | [source,ruby] 299 | ---- 300 | # bad 301 | 3.times { create :user } 302 | 3.times.map { create :user } 303 | [create(:user), create(:user), create(:user)] 304 | Array.new(3) { create :user } 305 | 306 | # good 307 | create_list :user, 3 308 | 309 | # bad 310 | 3.times { create :user, age: 18 } 311 | 312 | # good - index is used to alter the created models attributes 313 | 3.times { |n| create :user, age: n } 314 | 315 | # good - contains a method call, may return different values 316 | 3.times { create :user, age: rand } 317 | ---- 318 | 319 | [#_enforcedstyle_-n_times_-factorybotcreatelist] 320 | ==== `EnforcedStyle: n_times` 321 | 322 | [source,ruby] 323 | ---- 324 | # bad 325 | create_list :user, 3 326 | [create(:user), create(:user), create(:user)] 327 | 328 | # good 329 | 3.times.map { create :user } 330 | ---- 331 | 332 | [#_explicitonly_-false_-_default_-factorybotcreatelist] 333 | ==== `ExplicitOnly: false` (default) 334 | 335 | [source,ruby] 336 | ---- 337 | # bad - with `EnforcedStyle: create_list` 338 | 3.times { FactoryBot.create :user } 339 | 3.times { create :user } 340 | 341 | # good - with `EnforcedStyle: create_list` 342 | FactoryBot.create_list :user, 3 343 | create_list :user, 3 344 | ---- 345 | 346 | [#_explicitonly_-true_-factorybotcreatelist] 347 | ==== `ExplicitOnly: true` 348 | 349 | [source,ruby] 350 | ---- 351 | # bad - with `EnforcedStyle: create_list` 352 | 3.times { FactoryBot.create :user } 353 | 354 | # good - with `EnforcedStyle: create_list` 355 | FactoryBot.create_list :user, 3 356 | create_list :user, 3 357 | 3.times { create :user } 358 | ---- 359 | 360 | [#configurable-attributes-factorybotcreatelist] 361 | === Configurable attributes 362 | 363 | |=== 364 | | Name | Default value | Configurable values 365 | 366 | | Include 367 | | `+**/*_spec.rb+`, `+**/spec/**/*+`, `+**/test/**/*+`, `+**/features/support/factories/**/*.rb+` 368 | | Array 369 | 370 | | EnforcedStyle 371 | | `create_list` 372 | | `create_list`, `n_times` 373 | 374 | | ExplicitOnly 375 | | `false` 376 | | Boolean 377 | |=== 378 | 379 | [#references-factorybotcreatelist] 380 | === References 381 | 382 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/CreateList 383 | 384 | [#factorybotexcessivecreatelist] 385 | == FactoryBot/ExcessiveCreateList 386 | 387 | |=== 388 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 389 | 390 | | Pending 391 | | Yes 392 | | No 393 | | 2.25 394 | | - 395 | |=== 396 | 397 | Check for excessive model creation in a list. 398 | 399 | [#examples-factorybotexcessivecreatelist] 400 | === Examples 401 | 402 | [#maxamount_-10-_default_-factorybotexcessivecreatelist] 403 | ==== MaxAmount: 10 (default) 404 | 405 | [source,ruby] 406 | ---- 407 | # We do not allow more than 10 items to be created 408 | 409 | # bad 410 | create_list(:merge_request, 1000, state: :opened) 411 | 412 | # good 413 | create_list(:merge_request, 10, state: :opened) 414 | ---- 415 | 416 | [#maxamount_-20-factorybotexcessivecreatelist] 417 | ==== MaxAmount: 20 418 | 419 | [source,ruby] 420 | ---- 421 | # We do not allow more than 20 items to be created 422 | 423 | # bad 424 | create_list(:merge_request, 1000, state: :opened) 425 | 426 | # good 427 | create_list(:merge_request, 15, state: :opened) 428 | ---- 429 | 430 | [#configurable-attributes-factorybotexcessivecreatelist] 431 | === Configurable attributes 432 | 433 | |=== 434 | | Name | Default value | Configurable values 435 | 436 | | Include 437 | | `+**/*_spec.rb+`, `+**/spec/**/*+`, `+**/test/**/*+`, `+**/features/support/factories/**/*.rb+` 438 | | Array 439 | 440 | | MaxAmount 441 | | `10` 442 | | Integer 443 | |=== 444 | 445 | [#references-factorybotexcessivecreatelist] 446 | === References 447 | 448 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/ExcessiveCreateList 449 | 450 | [#factorybotfactoryassociationwithstrategy] 451 | == FactoryBot/FactoryAssociationWithStrategy 452 | 453 | |=== 454 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 455 | 456 | | Pending 457 | | Yes 458 | | No 459 | | 2.23 460 | | 2.23 461 | |=== 462 | 463 | Use definition in factory association instead of hard coding a strategy. 464 | 465 | [#examples-factorybotfactoryassociationwithstrategy] 466 | === Examples 467 | 468 | [source,ruby] 469 | ---- 470 | # bad - only works for one strategy 471 | factory :foo do 472 | profile { create(:profile) } 473 | end 474 | 475 | # good - implicit 476 | factory :foo do 477 | profile 478 | end 479 | 480 | # good - explicit 481 | factory :foo do 482 | association :profile 483 | end 484 | 485 | # good - inline 486 | factory :foo do 487 | profile { association :profile } 488 | end 489 | ---- 490 | 491 | [#configurable-attributes-factorybotfactoryassociationwithstrategy] 492 | === Configurable attributes 493 | 494 | |=== 495 | | Name | Default value | Configurable values 496 | 497 | | Include 498 | | `+**/*_spec.rb+`, `+**/spec/**/*+`, `+**/test/**/*+`, `+**/features/support/factories/**/*.rb+` 499 | | Array 500 | |=== 501 | 502 | [#references-factorybotfactoryassociationwithstrategy] 503 | === References 504 | 505 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryAssociationWithStrategy 506 | 507 | [#factorybotfactoryclassname] 508 | == FactoryBot/FactoryClassName 509 | 510 | |=== 511 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 512 | 513 | | Enabled 514 | | Yes 515 | | Always 516 | | 1.37 517 | | 2.24 518 | |=== 519 | 520 | Use string value when setting the class attribute explicitly. 521 | 522 | This cop would promote faster tests by lazy-loading of 523 | application files. Also, this could help you suppress potential bugs 524 | in combination with external libraries by avoiding a preload of 525 | application files from the factory files. 526 | 527 | [#examples-factorybotfactoryclassname] 528 | === Examples 529 | 530 | [source,ruby] 531 | ---- 532 | # bad 533 | factory :foo, class: Foo do 534 | end 535 | 536 | # good 537 | factory :foo, class: 'Foo' do 538 | end 539 | ---- 540 | 541 | [#references-factorybotfactoryclassname] 542 | === References 543 | 544 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryClassName 545 | 546 | [#factorybotfactorynamestyle] 547 | == FactoryBot/FactoryNameStyle 548 | 549 | |=== 550 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 551 | 552 | | Pending 553 | | Yes 554 | | Always 555 | | 2.16 556 | | 2.23 557 | |=== 558 | 559 | Checks for name style for argument of FactoryBot::Syntax::Methods. 560 | 561 | [#examples-factorybotfactorynamestyle] 562 | === Examples 563 | 564 | [#enforcedstyle_-symbol-_default_-factorybotfactorynamestyle] 565 | ==== EnforcedStyle: symbol (default) 566 | 567 | [source,ruby] 568 | ---- 569 | # bad 570 | create('user') 571 | build "user", username: "NAME" 572 | 573 | # good 574 | create(:user) 575 | build :user, username: "NAME" 576 | 577 | # good - namespaced models 578 | create('users/internal') 579 | ---- 580 | 581 | [#enforcedstyle_-string-factorybotfactorynamestyle] 582 | ==== EnforcedStyle: string 583 | 584 | [source,ruby] 585 | ---- 586 | # bad 587 | create(:user) 588 | build :user, username: "NAME" 589 | 590 | # good 591 | create('user') 592 | build "user", username: "NAME" 593 | ---- 594 | 595 | [#_explicitonly_-false_-_default_-factorybotfactorynamestyle] 596 | ==== `ExplicitOnly: false` (default) 597 | 598 | [source,ruby] 599 | ---- 600 | # bad - with `EnforcedStyle: symbol` 601 | FactoryBot.create('user') 602 | create('user') 603 | 604 | # good - with `EnforcedStyle: symbol` 605 | FactoryBot.create(:user) 606 | create(:user) 607 | ---- 608 | 609 | [#_explicitonly_-true_-factorybotfactorynamestyle] 610 | ==== `ExplicitOnly: true` 611 | 612 | [source,ruby] 613 | ---- 614 | # bad - with `EnforcedStyle: symbol` 615 | FactoryBot.create(:user) 616 | FactoryBot.build "user", username: "NAME" 617 | 618 | # good - with `EnforcedStyle: symbol` 619 | FactoryBot.create('user') 620 | FactoryBot.build "user", username: "NAME" 621 | FactoryBot.create(:user) 622 | create(:user) 623 | ---- 624 | 625 | [#configurable-attributes-factorybotfactorynamestyle] 626 | === Configurable attributes 627 | 628 | |=== 629 | | Name | Default value | Configurable values 630 | 631 | | Include 632 | | `+**/*_spec.rb+`, `+**/spec/**/*+`, `+**/test/**/*+`, `+**/features/support/factories/**/*.rb+` 633 | | Array 634 | 635 | | EnforcedStyle 636 | | `symbol` 637 | | `symbol`, `string` 638 | 639 | | ExplicitOnly 640 | | `false` 641 | | Boolean 642 | |=== 643 | 644 | [#references-factorybotfactorynamestyle] 645 | === References 646 | 647 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryNameStyle 648 | 649 | [#factorybotidsequence] 650 | == FactoryBot/IdSequence 651 | 652 | |=== 653 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 654 | 655 | | Pending 656 | | Yes 657 | | Always 658 | | 2.24 659 | | - 660 | |=== 661 | 662 | Do not create a FactoryBot sequence for an id column. 663 | 664 | [#examples-factorybotidsequence] 665 | === Examples 666 | 667 | [source,ruby] 668 | ---- 669 | # bad - can lead to conflicts between FactoryBot and DB sequences 670 | factory :foo do 671 | sequence :id 672 | end 673 | 674 | # good - a non-id column 675 | factory :foo do 676 | sequence :some_non_id_column 677 | end 678 | ---- 679 | 680 | [#references-factorybotidsequence] 681 | === References 682 | 683 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/IdSequence 684 | 685 | [#factorybotredundantfactoryoption] 686 | == FactoryBot/RedundantFactoryOption 687 | 688 | |=== 689 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 690 | 691 | | Pending 692 | | Yes 693 | | Always 694 | | 2.23 695 | | - 696 | |=== 697 | 698 | Checks for redundant `factory` option. 699 | 700 | [#examples-factorybotredundantfactoryoption] 701 | === Examples 702 | 703 | [source,ruby] 704 | ---- 705 | # bad 706 | association :user, factory: :user 707 | 708 | # good 709 | association :user 710 | ---- 711 | 712 | [#configurable-attributes-factorybotredundantfactoryoption] 713 | === Configurable attributes 714 | 715 | |=== 716 | | Name | Default value | Configurable values 717 | 718 | | Include 719 | | `+**/*_spec.rb+`, `+**/spec/**/*+`, `+**/test/**/*+`, `+**/features/support/factories/**/*.rb+` 720 | | Array 721 | |=== 722 | 723 | [#references-factorybotredundantfactoryoption] 724 | === References 725 | 726 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/RedundantFactoryOption 727 | 728 | [#factorybotsyntaxmethods] 729 | == FactoryBot/SyntaxMethods 730 | 731 | |=== 732 | | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed 733 | 734 | | Pending 735 | | Yes 736 | | Always (Unsafe) 737 | | 2.7 738 | | - 739 | |=== 740 | 741 | Use shorthands from `FactoryBot::Syntax::Methods` in your specs. 742 | 743 | [#safety-factorybotsyntaxmethods] 744 | === Safety 745 | 746 | The autocorrection is marked as unsafe because the cop 747 | cannot verify whether you already include 748 | `FactoryBot::Syntax::Methods` in your test suite. 749 | 750 | If you're using Rails, add the following configuration to 751 | `spec/support/factory_bot.rb` and be sure to require that file in 752 | `rails_helper.rb`: 753 | 754 | [source,ruby] 755 | ---- 756 | RSpec.configure do |config| 757 | config.include FactoryBot::Syntax::Methods 758 | end 759 | ---- 760 | 761 | If you're not using Rails: 762 | 763 | [source,ruby] 764 | ---- 765 | RSpec.configure do |config| 766 | config.include FactoryBot::Syntax::Methods 767 | 768 | config.before(:suite) do 769 | FactoryBot.find_definitions 770 | end 771 | end 772 | ---- 773 | 774 | [#examples-factorybotsyntaxmethods] 775 | === Examples 776 | 777 | [source,ruby] 778 | ---- 779 | # bad 780 | FactoryBot.create(:bar) 781 | FactoryBot.build(:bar) 782 | FactoryBot.attributes_for(:bar) 783 | 784 | # good 785 | create(:bar) 786 | build(:bar) 787 | attributes_for(:bar) 788 | ---- 789 | 790 | [#configurable-attributes-factorybotsyntaxmethods] 791 | === Configurable attributes 792 | 793 | |=== 794 | | Name | Default value | Configurable values 795 | 796 | | Include 797 | | `+**/*_spec.rb+`, `+**/spec/**/*+`, `+**/test/**/*+`, `+**/features/support/factories/**/*.rb+` 798 | | Array 799 | |=== 800 | 801 | [#references-factorybotsyntaxmethods] 802 | === References 803 | 804 | * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/SyntaxMethods 805 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/development.adoc: -------------------------------------------------------------------------------- 1 | = Development 2 | 3 | This page describes considerations when developing factory_bot-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[FactoryBot/CopName]' 15 | [create] lib/rubocop/cop/factory_bot/cop_name.rb 16 | [create] spec/rubocop/cop/factory_bot/cop_name_spec.rb 17 | [modify] lib/rubocop/cop/factory_bot_cops.rb - `require_relative 'factory_bot/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 FactoryBot/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 | 46 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = RuboCop factory_bot 2 | 3 | https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md[factory_bot]-specific analysis for your projects, as an extension to 4 | https://github.com/rubocop/rubocop[RuboCop]. 5 | 6 | RuboCop factory_bot 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 factory_bot functionality 15 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/installation.adoc: -------------------------------------------------------------------------------- 1 | = Installation 2 | 3 | Just install the `rubocop-factory_bot` gem 4 | 5 | [source,bash] 6 | ---- 7 | gem install rubocop-factory_bot 8 | ---- 9 | 10 | or if you use bundler put this in your `Gemfile` 11 | 12 | [source,ruby] 13 | ---- 14 | gem 'rubocop-factory_bot' 15 | ---- 16 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/usage.adoc: -------------------------------------------------------------------------------- 1 | = Usage 2 | 3 | You need to tell RuboCop to load the factory_bot 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-factory_bot 12 | ---- 13 | 14 | or, if you are using several extensions: 15 | 16 | ---- 17 | plugins: 18 | - rubocop-factory_bot 19 | - rubocop-performance 20 | ---- 21 | 22 | Now you can run `rubocop` and it will automatically load the RuboCop factory_bot 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-factory_bot 32 | ---- 33 | 34 | == Rake task 35 | 36 | [source,ruby] 37 | ---- 38 | RuboCop::RakeTask.new do |task| 39 | task.plugins << 'rubocop-factory_bot' 40 | end 41 | ---- 42 | 43 | == Inspecting non-default file paths 44 | 45 | By default, `rubocop-factory_bot` only inspects code within below: 46 | 47 | [source,yaml] 48 | ---- 49 | FactoryBot: 50 | Include: 51 | - "**/spec/factories.rb" 52 | - "**/spec/factories/**/*.rb" 53 | - "**/test/factories.rb" 54 | - "**/test/factories/**/*.rb" 55 | - "**/features/support/factories/**/*.rb" 56 | ---- 57 | 58 | You can override this setting in your config file by setting `Include`: 59 | 60 | [source,yaml] 61 | ---- 62 | # Inspect files in `my_factory/` directory 63 | FactoryBot: 64 | Include: 65 | - '**/my_factory/**/*' 66 | ---- 67 | 68 | [source,yaml] 69 | ---- 70 | # Inspect only files ending with `_my_factory.rb` 71 | FactoryBot: 72 | Include: 73 | - '**/*_my_factory.rb' 74 | ---- 75 | 76 | 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. 77 | -------------------------------------------------------------------------------- /lib/rubocop-factory_bot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'yaml' 5 | 6 | require 'rubocop' 7 | 8 | require_relative 'rubocop/factory_bot/factory_bot' 9 | require_relative 'rubocop/factory_bot/language' 10 | require_relative 'rubocop/factory_bot/plugin' 11 | require_relative 'rubocop/factory_bot/version' 12 | 13 | require_relative 'rubocop/cop/factory_bot/mixin/configurable_explicit_only' 14 | 15 | require_relative 'rubocop/cop/factory_bot_cops' 16 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/association_style.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Use a consistent style to define associations. 7 | # 8 | # @safety 9 | # This cop may cause false-positives in `EnforcedStyle: explicit` 10 | # case. It recognizes any method call that has no arguments as an 11 | # implicit association but it might be a user-defined trait call. 12 | # 13 | # @example EnforcedStyle: implicit (default) 14 | # # bad 15 | # factory :post do 16 | # association :user 17 | # end 18 | # 19 | # # good 20 | # factory :post do 21 | # user 22 | # end 23 | # 24 | # # bad 25 | # factory :post do 26 | # association :user, :author 27 | # end 28 | # 29 | # # good 30 | # factory :post do 31 | # user factory: %i[user author] 32 | # end 33 | # 34 | # @example EnforcedStyle: explicit 35 | # # bad 36 | # factory :post do 37 | # user 38 | # end 39 | # 40 | # # good 41 | # factory :post do 42 | # association :user 43 | # end 44 | # 45 | # # bad 46 | # factory :post do 47 | # user factory: %i[user author] 48 | # end 49 | # 50 | # # good 51 | # factory :post do 52 | # association :user, :author 53 | # end 54 | # 55 | # # good (NonImplicitAssociationMethodNames: ['email']) 56 | # sequence :email do |n| 57 | # "person#{n}@example.com" 58 | # end 59 | # 60 | # factory :user do 61 | # email 62 | # end 63 | class AssociationStyle < ::RuboCop::Cop::Base # rubocop:disable Metrics/ClassLength 64 | extend AutoCorrector 65 | 66 | include ConfigurableEnforcedStyle 67 | 68 | DEFAULT_NON_IMPLICIT_ASSOCIATION_METHOD_NAMES = %w[ 69 | association 70 | factory 71 | sequence 72 | skip_create 73 | traits_for_enum 74 | ].freeze 75 | 76 | RESTRICT_ON_SEND = %i[factory trait].freeze 77 | KEYWORDS = %i[alias and begin break case class def defined? do 78 | else elsif end ensure false for if in module 79 | next nil not or redo rescue retry return self 80 | super then true undef unless until when while 81 | yield __FILE__ __LINE__ __ENCODING__].freeze 82 | 83 | def on_send(node) 84 | bad_associations_in(node).each do |association| 85 | add_offense( 86 | association, 87 | message: "Use #{style} style to define associations." 88 | ) do |corrector| 89 | autocorrect(corrector, association) 90 | end 91 | end 92 | end 93 | 94 | private 95 | 96 | # @!method explicit_association?(node) 97 | def_node_matcher :explicit_association?, <<~PATTERN 98 | (send nil? :association sym ...) 99 | PATTERN 100 | 101 | # @!method with_strategy_build_option?(node) 102 | def_node_matcher :with_strategy_build_option?, <<~PATTERN 103 | (send nil? :association sym ... 104 | (hash <(pair (sym :strategy) (sym :build)) ...>) 105 | ) 106 | PATTERN 107 | 108 | # @!method implicit_association?(node) 109 | def_node_matcher :implicit_association?, <<~PATTERN 110 | (send nil? !#non_implicit_association_method_name? ...) 111 | PATTERN 112 | 113 | # @!method factory_option_matcher(node) 114 | def_node_matcher :factory_option_matcher, <<~PATTERN 115 | (send 116 | nil? 117 | :association 118 | ... 119 | (hash 120 | < 121 | (pair 122 | (sym :factory) 123 | { 124 | (sym $_) | 125 | (array (sym $_)*) 126 | } 127 | ) 128 | ... 129 | > 130 | ) 131 | ) 132 | PATTERN 133 | 134 | # @!method trait_names_from_explicit(node) 135 | def_node_matcher :trait_names_from_explicit, <<~PATTERN 136 | (send nil? :association _ (sym $_)* ...) 137 | PATTERN 138 | 139 | # @!method association_names(node) 140 | def_node_search :association_names, <<~PATTERN 141 | (send nil? :association $...) 142 | PATTERN 143 | 144 | # @!method trait_name(node) 145 | def_node_search :trait_name, <<~PATTERN 146 | (send nil? :trait (sym $_) ) 147 | PATTERN 148 | 149 | def autocorrect(corrector, node) 150 | if style == :explicit 151 | autocorrect_to_explicit_style(corrector, node) 152 | else 153 | autocorrect_to_implicit_style(corrector, node) 154 | end 155 | end 156 | 157 | def autocorrect_to_explicit_style(corrector, node) 158 | arguments = [ 159 | ":#{node.method_name}", 160 | *node.arguments.map(&:source) 161 | ] 162 | corrector.replace(node, "association #{arguments.join(', ')}") 163 | end 164 | 165 | def autocorrect_to_implicit_style(corrector, node) 166 | source = node.first_argument.value.to_s 167 | options = options_for_autocorrect_to_implicit_style(node) 168 | unless options.empty? 169 | rest = options.map { |option| option.join(': ') }.join(', ') 170 | source += " #{rest}" 171 | end 172 | corrector.replace(node, source) 173 | end 174 | 175 | def bad?(node) 176 | if style == :explicit 177 | implicit_association?(node) && 178 | !trait_within_trait?(node) 179 | else 180 | explicit_association?(node) && 181 | !with_strategy_build_option?(node) && 182 | !keyword?(node) 183 | end 184 | end 185 | 186 | def keyword?(node) 187 | association_names(node).any? do |associations| 188 | associations.any? do |association| 189 | next unless association.sym_type? 190 | 191 | KEYWORDS.include?(association.value) 192 | end 193 | end 194 | end 195 | 196 | def bad_associations_in(node) 197 | children_of_factory_block(node).select do |child| 198 | bad?(child) 199 | end 200 | end 201 | 202 | def children_of_factory_block(node) 203 | block = node.block_node 204 | return [] unless block 205 | return [] unless block.body 206 | 207 | if block.body.begin_type? 208 | block.body.children 209 | else 210 | [block.body] 211 | end 212 | end 213 | 214 | def factory_names_from_explicit(node) 215 | trait_names = trait_names_from_explicit(node) 216 | factory_names = Array(factory_option_matcher(node)) 217 | result = factory_names + trait_names 218 | if factory_names.empty? && !trait_names.empty? 219 | result.prepend(node.first_argument.value) 220 | end 221 | result 222 | end 223 | 224 | def non_implicit_association_method_name?(method_name) 225 | non_implicit_association_method_names.include?(method_name.to_s) 226 | end 227 | 228 | def non_implicit_association_method_names 229 | DEFAULT_NON_IMPLICIT_ASSOCIATION_METHOD_NAMES + 230 | (cop_config['NonImplicitAssociationMethodNames'] || []) 231 | end 232 | 233 | def options_from_explicit(node) 234 | return {} unless node.last_argument.hash_type? 235 | 236 | node.last_argument.pairs.inject({}) do |options, pair| 237 | options.merge(pair.key.value => pair.value.source) 238 | end 239 | end 240 | 241 | def options_for_autocorrect_to_implicit_style(node) 242 | options = options_from_explicit(node) 243 | factory_names = factory_names_from_explicit(node) 244 | unless factory_names.empty? 245 | options[:factory] = "%i[#{factory_names.join(' ')}]" 246 | end 247 | options 248 | end 249 | 250 | def trait_within_trait?(node) 251 | factory_node = node.ancestors.reverse.find do |ancestor| 252 | ancestor.method?(:factory) if ancestor.block_type? 253 | end 254 | 255 | trait_name(factory_node).include?(node.method_name) 256 | end 257 | end 258 | end 259 | end 260 | end 261 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/attribute_defined_statically.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Always declare attribute values as blocks. 7 | # 8 | # @example 9 | # # bad 10 | # kind [:active, :rejected].sample 11 | # 12 | # # good 13 | # kind { [:active, :rejected].sample } 14 | # 15 | # # bad 16 | # closed_at 1.day.from_now 17 | # 18 | # # good 19 | # closed_at { 1.day.from_now } 20 | # 21 | # # bad 22 | # count 1 23 | # 24 | # # good 25 | # count { 1 } 26 | # 27 | class AttributeDefinedStatically < ::RuboCop::Cop::Base 28 | extend AutoCorrector 29 | 30 | MSG = 'Use a block to declare attribute values.' 31 | 32 | # @!method value_matcher(node) 33 | def_node_matcher :value_matcher, <<~PATTERN 34 | (send _ !#reserved_method? $...) 35 | PATTERN 36 | 37 | # @!method factory_attributes(node) 38 | def_node_matcher :factory_attributes, <<~PATTERN 39 | (block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } ) 40 | PATTERN 41 | 42 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 43 | attributes = factory_attributes(node) || [] 44 | attributes = [attributes] unless attributes.is_a?(Array) # rubocop:disable Style/ArrayCoercion, Lint/RedundantCopDisableDirective 45 | 46 | attributes.each do |attribute| 47 | next unless offensive_receiver?(attribute.receiver, node) 48 | next if proc?(attribute) || association?(attribute.first_argument) 49 | 50 | add_offense(attribute) do |corrector| 51 | autocorrect(corrector, attribute) 52 | end 53 | end 54 | end 55 | 56 | private 57 | 58 | def autocorrect(corrector, node) 59 | if node.parenthesized? 60 | autocorrect_replacing_parens(corrector, node) 61 | else 62 | autocorrect_without_parens(corrector, node) 63 | end 64 | end 65 | 66 | def offensive_receiver?(receiver, node) 67 | receiver.nil? || 68 | receiver.self_type? || 69 | receiver_matches_first_block_argument?(receiver, node) 70 | end 71 | 72 | def receiver_matches_first_block_argument?(receiver, node) 73 | first_block_argument = node.first_argument 74 | 75 | !first_block_argument.nil? && 76 | receiver.lvar_type? && 77 | receiver.node_parts == first_block_argument.node_parts 78 | end 79 | 80 | def proc?(attribute) 81 | value_matcher(attribute).to_a.all?(&:block_pass_type?) 82 | end 83 | 84 | # @!method association?(node) 85 | def_node_matcher :association?, '(hash <(pair (sym :factory) _) ...>)' 86 | 87 | def autocorrect_replacing_parens(corrector, node) 88 | left_braces, right_braces = braces(node) 89 | 90 | corrector.replace(node.location.begin, " #{left_braces}") 91 | corrector.replace(node.location.end, right_braces) 92 | end 93 | 94 | def autocorrect_without_parens(corrector, node) 95 | left_braces, right_braces = braces(node) 96 | 97 | argument = node.first_argument 98 | expression = argument.source_range 99 | corrector.insert_before(expression, left_braces) 100 | corrector.insert_after(expression, right_braces) 101 | end 102 | 103 | def braces(node) 104 | if value_hash_without_braces?(node.first_argument) 105 | ['{ { ', ' } }'] 106 | else 107 | ['{ ', ' }'] 108 | end 109 | end 110 | 111 | def value_hash_without_braces?(node) 112 | node.hash_type? && !node.braces? 113 | end 114 | 115 | def reserved_method?(method_name) 116 | RuboCop::FactoryBot.reserved_methods.include?(method_name) 117 | end 118 | 119 | def attribute_defining_method?(method_name) 120 | RuboCop::FactoryBot.attribute_defining_methods 121 | .include?(method_name) 122 | end 123 | end 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Use a consistent style for parentheses in factory_bot calls. 7 | # 8 | # @example `EnforcedStyle: require_parentheses` (default) 9 | # 10 | # # bad 11 | # create :user 12 | # build :login 13 | # 14 | # # good 15 | # create(:user) 16 | # build(:login) 17 | # 18 | # @example `EnforcedStyle: omit_parentheses` 19 | # 20 | # # bad 21 | # create(:user) 22 | # build(:login) 23 | # 24 | # # good 25 | # create :user 26 | # build :login 27 | # 28 | # # also good 29 | # # when method name and first argument are not on same line 30 | # create( 31 | # :user 32 | # ) 33 | # build( 34 | # :user, 35 | # name: 'foo' 36 | # ) 37 | # 38 | # @example `ExplicitOnly: false` (default) 39 | # 40 | # # bad - with `EnforcedStyle: require_parentheses` 41 | # FactoryBot.create :user 42 | # build :user 43 | # 44 | # # good - with `EnforcedStyle: require_parentheses` 45 | # FactoryBot.create(:user) 46 | # build(:user) 47 | # 48 | # @example `ExplicitOnly: true` 49 | # 50 | # # bad - with `EnforcedStyle: require_parentheses` 51 | # FactoryBot.create :user 52 | # FactoryBot.build :user 53 | # 54 | # # good - with `EnforcedStyle: require_parentheses` 55 | # FactoryBot.create(:user) 56 | # FactoryBot.build(:user) 57 | # create :user 58 | # build :user 59 | # 60 | class ConsistentParenthesesStyle < ::RuboCop::Cop::Base 61 | extend AutoCorrector 62 | include ConfigurableEnforcedStyle 63 | include ConfigurableExplicitOnly 64 | 65 | MSG_REQUIRE_PARENS = 'Prefer method call with parentheses' 66 | MSG_OMIT_PARENS = 'Prefer method call without parentheses' 67 | FACTORY_CALLS = RuboCop::FactoryBot::Language::METHODS 68 | RESTRICT_ON_SEND = FACTORY_CALLS 69 | 70 | # @!method factory_call(node) 71 | def_node_matcher :factory_call, <<~PATTERN 72 | (send 73 | #factory_call? %FACTORY_CALLS 74 | {sym str send lvar} _* 75 | ) 76 | PATTERN 77 | 78 | # @!method omit_hash_value?(node) 79 | def_node_matcher :omit_hash_value?, <<~PATTERN 80 | (send 81 | #factory_call? %FACTORY_CALLS 82 | {sym str send lvar} _* 83 | (hash 84 | 85 | ) 86 | ) 87 | PATTERN 88 | 89 | def self.autocorrect_incompatible_with 90 | [Style::MethodCallWithArgsParentheses] 91 | end 92 | 93 | def on_send(node) 94 | return if ambiguous_without_parentheses?(node) 95 | 96 | factory_call(node) { register_offense(node) } 97 | end 98 | 99 | private 100 | 101 | def register_offense(node) 102 | return if node.method?(:generate) && node.arguments.count > 1 103 | 104 | register_offense_with_parentheses(node) 105 | register_offense_without_parentheses(node) 106 | end 107 | 108 | def register_offense_with_parentheses(node) 109 | return if style == :require_parentheses || !node.parenthesized? 110 | return unless same_line?(node, node.first_argument) 111 | return if omit_hash_value?(node) 112 | 113 | add_offense(node.loc.selector, 114 | message: MSG_OMIT_PARENS) do |corrector| 115 | remove_parentheses(corrector, node) 116 | end 117 | end 118 | 119 | def register_offense_without_parentheses(node) 120 | return if style == :omit_parentheses || node.parenthesized? 121 | 122 | add_offense(node.loc.selector, 123 | message: MSG_REQUIRE_PARENS) do |corrector| 124 | add_parentheses(node, corrector) 125 | end 126 | end 127 | 128 | AMBIGUOUS_TYPES = %i[send pair array and or if].freeze 129 | 130 | def ambiguous_without_parentheses?(node) 131 | node.parent && AMBIGUOUS_TYPES.include?(node.parent.type) 132 | end 133 | 134 | def remove_parentheses(corrector, node) 135 | corrector.replace(node.location.begin, ' ') 136 | corrector.remove(node.location.end) 137 | end 138 | end 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/create_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Checks for create_list usage. 7 | # 8 | # This cop can be configured using the `EnforcedStyle` option 9 | # 10 | # @safety 11 | # This cop's autocorrection is unsafe because replacing `n.times` to 12 | # `create_list` changes its returned value. 13 | # 14 | # @example `EnforcedStyle: create_list` (default) 15 | # # bad 16 | # 3.times { create :user } 17 | # 3.times.map { create :user } 18 | # [create(:user), create(:user), create(:user)] 19 | # Array.new(3) { create :user } 20 | # 21 | # # good 22 | # create_list :user, 3 23 | # 24 | # # bad 25 | # 3.times { create :user, age: 18 } 26 | # 27 | # # good - index is used to alter the created models attributes 28 | # 3.times { |n| create :user, age: n } 29 | # 30 | # # good - contains a method call, may return different values 31 | # 3.times { create :user, age: rand } 32 | # 33 | # @example `EnforcedStyle: n_times` 34 | # # bad 35 | # create_list :user, 3 36 | # [create(:user), create(:user), create(:user)] 37 | # 38 | # # good 39 | # 3.times.map { create :user } 40 | # 41 | # @example `ExplicitOnly: false` (default) 42 | # 43 | # # bad - with `EnforcedStyle: create_list` 44 | # 3.times { FactoryBot.create :user } 45 | # 3.times { create :user } 46 | # 47 | # # good - with `EnforcedStyle: create_list` 48 | # FactoryBot.create_list :user, 3 49 | # create_list :user, 3 50 | # 51 | # @example `ExplicitOnly: true` 52 | # 53 | # # bad - with `EnforcedStyle: create_list` 54 | # 3.times { FactoryBot.create :user } 55 | # 56 | # # good - with `EnforcedStyle: create_list` 57 | # FactoryBot.create_list :user, 3 58 | # create_list :user, 3 59 | # 3.times { create :user } 60 | # 61 | class CreateList < ::RuboCop::Cop::Base # rubocop:disable Metrics/ClassLength 62 | extend AutoCorrector 63 | include ConfigurableEnforcedStyle 64 | include RuboCop::FactoryBot::Language 65 | include ConfigurableExplicitOnly 66 | 67 | MSG_CREATE_LIST = 'Prefer create_list.' 68 | MSG_N_TIMES = 'Prefer %s.times.map.' 69 | RESTRICT_ON_SEND = %i[create_list].freeze 70 | 71 | # @!method repeat_count(node) 72 | def_node_matcher :repeat_count, <<~PATTERN 73 | (block 74 | { 75 | (send (const {nil? cbase} :Array) :new (int $_)) # Array.new(3) { create(:user) } 76 | (send (int $_) :times) # 3.times { create(:user) } 77 | (send (send (int $_) :times) :map) # 3.times.map { create(:user) } 78 | } 79 | ...) 80 | PATTERN 81 | 82 | # @!method block_with_arg_and_used?(node) 83 | def_node_matcher :block_with_arg_and_used?, <<~PATTERN 84 | (block 85 | _ 86 | (args (arg _value)) 87 | `_value 88 | ) 89 | PATTERN 90 | 91 | # @!method arguments_include_method_call?(node) 92 | def_node_matcher :arguments_include_method_call?, <<~PATTERN 93 | (send #factory_call? :create sym ... `(send ...)) 94 | PATTERN 95 | 96 | # @!method factory_call(node) 97 | def_node_matcher :factory_call, <<~PATTERN 98 | (send #factory_call? :create sym ...) 99 | PATTERN 100 | 101 | # @!method factory_list_call(node) 102 | def_node_matcher :factory_list_call, <<~PATTERN 103 | (send #factory_call? :create_list (sym _) (int $_) ...) 104 | PATTERN 105 | 106 | # @!method factory_calls_in_array?(node) 107 | def_node_search :factory_calls_in_array?, <<~PATTERN 108 | (array #factory_call+) 109 | PATTERN 110 | 111 | def on_array(node) 112 | return unless same_factory_calls_in_array?(node) 113 | return if node.values.size < 2 114 | 115 | add_offense( 116 | node, 117 | message: preferred_message_for_array(node) 118 | ) do |corrector| 119 | autocorrect_same_factory_calls_in_array(corrector, node) 120 | end 121 | end 122 | 123 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, Metrics/CyclomaticComplexity 124 | return unless style == :create_list 125 | return unless repeat_multiple_time?(node) 126 | return if block_with_arg_and_used?(node) 127 | return unless node.body 128 | return if arguments_include_method_call?(node.body) 129 | return unless contains_only_factory?(node.body) 130 | 131 | add_offense(node.send_node, message: MSG_CREATE_LIST) do |corrector| 132 | CreateListCorrector.new(node.send_node).call(corrector) 133 | end 134 | end 135 | 136 | def on_send(node) 137 | return unless style == :n_times 138 | 139 | factory_list_call(node) do |count| 140 | next if count < 2 141 | 142 | message = format(MSG_N_TIMES, number: count) 143 | add_offense(node.loc.selector, message: message) do |corrector| 144 | TimesCorrector.new(node).call(corrector) 145 | end 146 | end 147 | end 148 | 149 | private 150 | 151 | def repeat_multiple_time?(node) 152 | return false unless (count = repeat_count(node)) 153 | 154 | count > 1 155 | end 156 | 157 | # For ease of modification, it is replaced with the `n_times` style, 158 | # but if it is not appropriate for the configured style, 159 | # it will be replaced in the subsequent autocorrection. 160 | def autocorrect_same_factory_calls_in_array(corrector, node) 161 | corrector.replace( 162 | node, 163 | format( 164 | '%s.times.map { %s }', 165 | count: node.children.count, 166 | factory_call: node.children.first.source 167 | ) 168 | ) 169 | end 170 | 171 | def contains_only_factory?(node) 172 | if node.block_type? 173 | factory_call(node.send_node) 174 | else 175 | factory_call(node) 176 | end 177 | end 178 | 179 | def preferred_message_for_array(node) 180 | if style == :create_list && 181 | !arguments_include_method_call?(node.children.first) 182 | MSG_CREATE_LIST 183 | else 184 | format(MSG_N_TIMES, number: node.children.count) 185 | end 186 | end 187 | 188 | def same_factory_calls_in_array?(node) 189 | factory_calls_in_array?(node) && 190 | node.children.map(&:source).uniq.one? 191 | end 192 | 193 | # :nodoc 194 | module Corrector 195 | private 196 | 197 | def build_options_string(options) 198 | options.map(&:source).join(', ') 199 | end 200 | 201 | def format_method_call(node, method, arguments) 202 | if node.block_type? || node.parenthesized? 203 | "#{method}(#{arguments})" 204 | else 205 | "#{method} #{arguments}" 206 | end 207 | end 208 | 209 | def format_receiver(receiver) 210 | return '' unless receiver 211 | 212 | "#{receiver.source}." 213 | end 214 | end 215 | 216 | # :nodoc 217 | class TimesCorrector 218 | include Corrector 219 | 220 | def initialize(node) 221 | @node = node 222 | end 223 | 224 | def call(corrector) 225 | replacement = generate_n_times_block(node) 226 | corrector.replace(node.block_node || node, replacement) 227 | end 228 | 229 | private 230 | 231 | attr_reader :node 232 | 233 | def generate_n_times_block(node) 234 | factory, count, *options = node.arguments 235 | 236 | arguments = factory.source 237 | options = build_options_string(options) 238 | arguments += ", #{options}" unless options.empty? 239 | 240 | replacement = format_receiver(node.receiver) 241 | replacement += format_method_call(node, 'create', arguments) 242 | replacement += " #{factory_call_block_source}" if node.block_node 243 | "#{count.source}.times.map { #{replacement} }" 244 | end 245 | 246 | def factory_call_block_source 247 | node.block_node.location.begin.with( 248 | end_pos: node.block_node.location.end.end_pos 249 | ).source 250 | end 251 | end 252 | 253 | # :nodoc: 254 | class CreateListCorrector 255 | include Corrector 256 | 257 | def initialize(node) 258 | @node = node.parent 259 | end 260 | 261 | def call(corrector) 262 | replacement = if node.body.block_type? 263 | call_with_block_replacement(node) 264 | else 265 | call_replacement(node) 266 | end 267 | 268 | corrector.replace(node, replacement) 269 | end 270 | 271 | private 272 | 273 | attr_reader :node 274 | 275 | def call_with_block_replacement(node) 276 | block = node.body 277 | arguments = build_arguments(block, count_from(node)) 278 | replacement = format_receiver(block.receiver) 279 | replacement += format_method_call(block, 'create_list', arguments) 280 | replacement += format_block(block) 281 | replacement 282 | end 283 | 284 | def build_arguments(node, count) 285 | factory, *options = *node.send_node.arguments 286 | 287 | arguments = ":#{factory.value}, #{count}" 288 | options = build_options_string(options) 289 | arguments += ", #{options}" unless options.empty? 290 | arguments 291 | end 292 | 293 | def call_replacement(node) 294 | block = node.body 295 | factory, *options = *block.arguments 296 | 297 | arguments = "#{factory.source}, #{count_from(node)}" 298 | options = build_options_string(options) 299 | arguments += ", #{options}" unless options.empty? 300 | 301 | replacement = format_receiver(block.receiver) 302 | replacement += format_method_call(block, 'create_list', arguments) 303 | replacement 304 | end 305 | 306 | def count_from(node) 307 | count_node = 308 | case node.method_name 309 | when :map 310 | node.receiver.receiver 311 | when :new 312 | node.send_node.first_argument 313 | when :times 314 | node.receiver 315 | end 316 | count_node.source 317 | end 318 | 319 | def format_block(node) 320 | if node.body.begin_type? 321 | format_multiline_block(node) 322 | else 323 | format_singleline_block(node) 324 | end 325 | end 326 | 327 | def format_multiline_block(node) 328 | indent = ' ' * node.body.loc.column 329 | indent_end = ' ' * node.parent.loc.column 330 | " do #{node.arguments.source}\n" \ 331 | "#{indent}#{node.body.source}\n" \ 332 | "#{indent_end}end" 333 | end 334 | 335 | def format_singleline_block(node) 336 | " { #{node.arguments.source} #{node.body.source} }" 337 | end 338 | end 339 | end 340 | end 341 | end 342 | end 343 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/excessive_create_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Check for excessive model creation in a list. 7 | # 8 | # @example MaxAmount: 10 (default) 9 | # # We do not allow more than 10 items to be created 10 | # 11 | # # bad 12 | # create_list(:merge_request, 1000, state: :opened) 13 | # 14 | # # good 15 | # create_list(:merge_request, 10, state: :opened) 16 | # 17 | # @example MaxAmount: 20 18 | # # We do not allow more than 20 items to be created 19 | # 20 | # # bad 21 | # create_list(:merge_request, 1000, state: :opened) 22 | # 23 | # # good 24 | # create_list(:merge_request, 15, state: :opened) 25 | # 26 | class ExcessiveCreateList < ::RuboCop::Cop::Base 27 | include ConfigurableExplicitOnly 28 | 29 | MESSAGE = 30 | 'Avoid using `create_list` with more than %s items.' 31 | 32 | # @!method create_list?(node) 33 | def_node_matcher :create_list?, <<~PATTERN 34 | (send #factory_call? :create_list {sym str} $(int _) ...) 35 | PATTERN 36 | 37 | RESTRICT_ON_SEND = %i[create_list].freeze 38 | 39 | def on_send(node) 40 | number_node = create_list?(node) 41 | return unless number_node 42 | 43 | max_amount = cop_config['MaxAmount'] 44 | return if number_node.value <= max_amount 45 | 46 | add_offense(number_node, message: 47 | format(MESSAGE, max_amount: max_amount)) 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/factory_association_with_strategy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Use definition in factory association instead of hard coding a strategy. 7 | # 8 | # @example 9 | # # bad - only works for one strategy 10 | # factory :foo do 11 | # profile { create(:profile) } 12 | # end 13 | # 14 | # # good - implicit 15 | # factory :foo do 16 | # profile 17 | # end 18 | # 19 | # # good - explicit 20 | # factory :foo do 21 | # association :profile 22 | # end 23 | # 24 | # # good - inline 25 | # factory :foo do 26 | # profile { association :profile } 27 | # end 28 | # 29 | class FactoryAssociationWithStrategy < ::RuboCop::Cop::Base 30 | MSG = 'Use an implicit, explicit or inline definition instead of ' \ 31 | 'hard coding a strategy for setting association within factory.' 32 | 33 | HARDCODED = Set.new(%i[create build build_stubbed]).freeze 34 | 35 | # @!method factory_declaration(node) 36 | def_node_matcher :factory_declaration, <<~PATTERN 37 | (block (send nil? {:factory :trait} ...) 38 | ... 39 | ) 40 | PATTERN 41 | 42 | # @!method factory_strategy_association(node) 43 | def_node_matcher :factory_strategy_association, <<~PATTERN 44 | (block 45 | (send nil? _association_name) 46 | (args) 47 | < $(send nil? HARDCODED ...) ... > 48 | ) 49 | PATTERN 50 | 51 | def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler 52 | factory_declaration(node) do 53 | node.each_node do |statement| 54 | factory_strategy_association(statement) do |hardcoded_association| 55 | add_offense(hardcoded_association) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/factory_class_name.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Use string value when setting the class attribute explicitly. 7 | # 8 | # This cop would promote faster tests by lazy-loading of 9 | # application files. Also, this could help you suppress potential bugs 10 | # in combination with external libraries by avoiding a preload of 11 | # application files from the factory files. 12 | # 13 | # @example 14 | # # bad 15 | # factory :foo, class: Foo do 16 | # end 17 | # 18 | # # good 19 | # factory :foo, class: 'Foo' do 20 | # end 21 | # 22 | class FactoryClassName < ::RuboCop::Cop::Base 23 | extend AutoCorrector 24 | 25 | MSG = "Pass '%s' string instead of `%s` " \ 26 | 'constant.' 27 | ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze 28 | RESTRICT_ON_SEND = %i[factory].freeze 29 | 30 | # @!method class_name(node) 31 | def_node_matcher :class_name, <<~PATTERN 32 | (send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>)) 33 | PATTERN 34 | 35 | def on_send(node) 36 | class_name(node) do |cn| 37 | next if allowed?(cn.const_name) 38 | 39 | msg = format(MSG, class_name: cn.const_name) 40 | add_offense(cn, message: msg) do |corrector| 41 | corrector.replace(cn, "'#{cn.source}'") 42 | end 43 | end 44 | end 45 | 46 | private 47 | 48 | def allowed?(const_name) 49 | ALLOWED_CONSTANTS.include?(const_name) 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/factory_name_style.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Checks for name style for argument of FactoryBot::Syntax::Methods. 7 | # 8 | # @example EnforcedStyle: symbol (default) 9 | # # bad 10 | # create('user') 11 | # build "user", username: "NAME" 12 | # 13 | # # good 14 | # create(:user) 15 | # build :user, username: "NAME" 16 | # 17 | # # good - namespaced models 18 | # create('users/internal') 19 | # 20 | # @example EnforcedStyle: string 21 | # # bad 22 | # create(:user) 23 | # build :user, username: "NAME" 24 | # 25 | # # good 26 | # create('user') 27 | # build "user", username: "NAME" 28 | # 29 | # @example `ExplicitOnly: false` (default) 30 | # 31 | # # bad - with `EnforcedStyle: symbol` 32 | # FactoryBot.create('user') 33 | # create('user') 34 | # 35 | # # good - with `EnforcedStyle: symbol` 36 | # FactoryBot.create(:user) 37 | # create(:user) 38 | # 39 | # @example `ExplicitOnly: true` 40 | # 41 | # # bad - with `EnforcedStyle: symbol` 42 | # FactoryBot.create(:user) 43 | # FactoryBot.build "user", username: "NAME" 44 | # 45 | # # good - with `EnforcedStyle: symbol` 46 | # FactoryBot.create('user') 47 | # FactoryBot.build "user", username: "NAME" 48 | # FactoryBot.create(:user) 49 | # create(:user) 50 | # 51 | class FactoryNameStyle < ::RuboCop::Cop::Base 52 | extend AutoCorrector 53 | include ConfigurableEnforcedStyle 54 | include RuboCop::FactoryBot::Language 55 | include ConfigurableExplicitOnly 56 | 57 | MSG = 'Use %s to refer to a factory.' 58 | FACTORY_CALLS = RuboCop::FactoryBot::Language::METHODS 59 | RESTRICT_ON_SEND = FACTORY_CALLS 60 | 61 | # @!method factory_call(node) 62 | def_node_matcher :factory_call, <<~PATTERN 63 | (send 64 | #factory_call? %FACTORY_CALLS 65 | ${str sym} ... 66 | ) 67 | PATTERN 68 | 69 | def on_send(node) 70 | factory_call(node) do |name| 71 | if offense_for_symbol_style?(name) 72 | register_offense(name, name.value.to_sym.inspect) 73 | elsif offense_for_string_style?(name) 74 | register_offense(name, name.value.to_s.inspect) 75 | end 76 | end 77 | end 78 | 79 | private 80 | 81 | def offense_for_symbol_style?(name) 82 | name.str_type? && style == :symbol && !namespaced?(name) 83 | end 84 | 85 | def offense_for_string_style?(name) 86 | name.sym_type? && style == :string 87 | end 88 | 89 | def namespaced?(name) 90 | name.value.include?('/') 91 | end 92 | 93 | def register_offense(name, prefer) 94 | add_offense(name, 95 | message: format(MSG, prefer: style.to_s)) do |corrector| 96 | corrector.replace(name, prefer) 97 | end 98 | end 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/id_sequence.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Do not create a FactoryBot sequence for an id column. 7 | # 8 | # @example 9 | # # bad - can lead to conflicts between FactoryBot and DB sequences 10 | # factory :foo do 11 | # sequence :id 12 | # end 13 | # 14 | # # good - a non-id column 15 | # factory :foo do 16 | # sequence :some_non_id_column 17 | # end 18 | # 19 | class IdSequence < ::RuboCop::Cop::Base 20 | extend AutoCorrector 21 | include RangeHelp 22 | include RuboCop::FactoryBot::Language 23 | 24 | MSG = 'Do not create a sequence for an id attribute' 25 | RESTRICT_ON_SEND = %i[sequence].freeze 26 | 27 | def on_send(node) 28 | return unless node.receiver.nil? || factory_bot?(node.receiver) 29 | return unless node.first_argument&.sym_type? && 30 | node.first_argument.value == :id 31 | 32 | add_offense(node) do |corrector| 33 | range_to_remove = range_by_whole_lines( 34 | node.source_range, 35 | include_final_newline: true 36 | ) 37 | 38 | corrector.remove(range_to_remove) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/mixin/configurable_explicit_only.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Handles `ExplicitOnly` configuration parameters. 7 | module ConfigurableExplicitOnly 8 | include RuboCop::FactoryBot::Language 9 | 10 | def factory_call?(node) 11 | return factory_bot?(node) if explicit_only? 12 | 13 | factory_bot?(node) || node.nil? 14 | end 15 | 16 | def explicit_only? 17 | cop_config['ExplicitOnly'] 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/redundant_factory_option.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Checks for redundant `factory` option. 7 | # 8 | # @example 9 | # # bad 10 | # association :user, factory: :user 11 | # 12 | # # good 13 | # association :user 14 | class RedundantFactoryOption < ::RuboCop::Cop::Base 15 | extend AutoCorrector 16 | 17 | include RangeHelp 18 | 19 | MSG = 'Remove redundant `factory` option.' 20 | 21 | RESTRICT_ON_SEND = %i[association].freeze 22 | 23 | # @!method association_with_a_factory_option(node) 24 | def_node_matcher :association_with_a_factory_option, <<~PATTERN 25 | (send nil? :association 26 | (sym $_association_name) 27 | ... 28 | (hash 29 | <$(pair 30 | (sym :factory) 31 | { 32 | (sym $_factory_name) 33 | (array (sym $_factory_name)) 34 | } 35 | ) 36 | ... 37 | > 38 | ) 39 | ) 40 | PATTERN 41 | 42 | def on_send(node) 43 | association_with_a_factory_option(node) do 44 | |association_name, factory_option, factory_name| 45 | next if association_name != factory_name 46 | 47 | add_offense(factory_option) do |corrector| 48 | autocorrect(corrector, factory_option) 49 | end 50 | end 51 | end 52 | 53 | private 54 | 55 | def autocorrect(corrector, node) 56 | corrector.remove( 57 | range_with_surrounding_comma( 58 | range_with_surrounding_space( 59 | node.source_range, 60 | side: :left 61 | ), 62 | :left 63 | ) 64 | ) 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot/syntax_methods.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module Cop 5 | module FactoryBot 6 | # Use shorthands from `FactoryBot::Syntax::Methods` in your specs. 7 | # 8 | # @safety 9 | # The autocorrection is marked as unsafe because the cop 10 | # cannot verify whether you already include 11 | # `FactoryBot::Syntax::Methods` in your test suite. 12 | # 13 | # If you're using Rails, add the following configuration to 14 | # `spec/support/factory_bot.rb` and be sure to require that file in 15 | # `rails_helper.rb`: 16 | # 17 | # [source,ruby] 18 | # ---- 19 | # RSpec.configure do |config| 20 | # config.include FactoryBot::Syntax::Methods 21 | # end 22 | # ---- 23 | # 24 | # If you're not using Rails: 25 | # 26 | # [source,ruby] 27 | # ---- 28 | # RSpec.configure do |config| 29 | # config.include FactoryBot::Syntax::Methods 30 | # 31 | # config.before(:suite) do 32 | # FactoryBot.find_definitions 33 | # end 34 | # end 35 | # ---- 36 | # 37 | # @example 38 | # # bad 39 | # FactoryBot.create(:bar) 40 | # FactoryBot.build(:bar) 41 | # FactoryBot.attributes_for(:bar) 42 | # 43 | # # good 44 | # create(:bar) 45 | # build(:bar) 46 | # attributes_for(:bar) 47 | # 48 | class SyntaxMethods < ::RuboCop::Cop::Base 49 | extend AutoCorrector 50 | include RangeHelp 51 | include RuboCop::FactoryBot::Language 52 | 53 | MSG = 'Use `%s` from `FactoryBot::Syntax::Methods`.' 54 | 55 | RESTRICT_ON_SEND = RuboCop::FactoryBot::Language::METHODS 56 | 57 | # @!method spec_group?(node) 58 | def_node_matcher :spec_group?, <<~PATTERN 59 | (block 60 | (send 61 | {(const {nil? cbase} :RSpec) nil?} 62 | { 63 | :describe :context :feature :example_group 64 | :xdescribe :xcontext :xfeature 65 | :fdescribe :fcontext :ffeature 66 | :shared_examples :shared_examples_for 67 | :shared_context 68 | } 69 | ...) 70 | ...) 71 | PATTERN 72 | 73 | def on_send(node) 74 | return unless factory_bot?(node.receiver) 75 | 76 | return unless inside_example_group?(node) 77 | 78 | message = format(MSG, method: node.method_name) 79 | 80 | add_offense(crime_scene(node), message: message) do |corrector| 81 | corrector.remove(offense(node)) 82 | end 83 | end 84 | 85 | private 86 | 87 | def crime_scene(node) 88 | range_between( 89 | node.source_range.begin_pos, 90 | node.loc.selector.end_pos 91 | ) 92 | end 93 | 94 | def offense(node) 95 | range_between( 96 | node.source_range.begin_pos, 97 | node.loc.selector.begin_pos 98 | ) 99 | end 100 | 101 | def inside_example_group?(node) 102 | return spec_group?(node) if example_group_root?(node) 103 | 104 | root = node.ancestors.find { |parent| example_group_root?(parent) } 105 | 106 | spec_group?(root) 107 | end 108 | 109 | def example_group_root?(node) 110 | node.parent.nil? || example_group_root_with_siblings?(node.parent) 111 | end 112 | 113 | def example_group_root_with_siblings?(node) 114 | node.begin_type? && node.parent.nil? 115 | end 116 | end 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/rubocop/cop/factory_bot_cops.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'factory_bot/association_style' 4 | require_relative 'factory_bot/attribute_defined_statically' 5 | require_relative 'factory_bot/consistent_parentheses_style' 6 | require_relative 'factory_bot/create_list' 7 | require_relative 'factory_bot/excessive_create_list' 8 | require_relative 'factory_bot/factory_association_with_strategy' 9 | require_relative 'factory_bot/factory_class_name' 10 | require_relative 'factory_bot/factory_name_style' 11 | require_relative 'factory_bot/id_sequence' 12 | require_relative 'factory_bot/redundant_factory_option' 13 | require_relative 'factory_bot/syntax_methods' 14 | -------------------------------------------------------------------------------- /lib/rubocop/factory_bot/config_formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | 5 | module RuboCop 6 | module FactoryBot 7 | # Builds a YAML config file from two config hashes 8 | class ConfigFormatter 9 | EXTENSION_ROOT_DEPARTMENT = %r{^(FactoryBot/)}.freeze 10 | SUBDEPARTMENTS = [].freeze 11 | AMENDMENTS = [].freeze 12 | COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-factory_bot/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/factory_bot/cop/generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module FactoryBot 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/factory_bot/description_extractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module FactoryBot 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(&: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 cops 23 | class CodeObject 24 | RUBOCOP_COP_CLASS_NAME = 'RuboCop::Cop::Base' 25 | 26 | def initialize(yardoc) 27 | @yardoc = yardoc 28 | end 29 | 30 | # Test if the YARD code object documents a concrete cop class 31 | # 32 | # @return [Boolean] 33 | def cop? 34 | cop_subclass? && !abstract? 35 | end 36 | 37 | # Configuration for the documented cop that would live in default.yml 38 | # 39 | # @return [Hash] 40 | def configuration 41 | { cop_name => { 'Description' => description } } 42 | end 43 | 44 | private 45 | 46 | def cop_name 47 | Object.const_get(documented_constant).cop_name 48 | end 49 | 50 | def description 51 | yardoc.docstring.split("\n\n").first.to_s 52 | end 53 | 54 | def documented_constant 55 | yardoc.to_s 56 | end 57 | 58 | def cop_subclass? 59 | yardoc.superclass.path == RUBOCOP_COP_CLASS_NAME 60 | end 61 | 62 | def abstract? 63 | yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') } 64 | end 65 | 66 | attr_reader :yardoc 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/rubocop/factory_bot/factory_bot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | # RuboCop factory_bot project namespace 5 | module FactoryBot 6 | ATTRIBUTE_DEFINING_METHODS = %i[ 7 | factory 8 | ignore 9 | trait 10 | traits_for_enum 11 | transient 12 | ].freeze 13 | 14 | UNPROXIED_METHODS = %i[ 15 | __send__ 16 | __id__ 17 | nil? 18 | send 19 | object_id 20 | extend 21 | instance_eval 22 | initialize 23 | block_given? 24 | raise 25 | caller 26 | method 27 | ].freeze 28 | 29 | DEFINITION_PROXY_METHODS = %i[ 30 | add_attribute 31 | after 32 | association 33 | before 34 | callback 35 | ignore 36 | initialize_with 37 | sequence 38 | skip_create 39 | to_create 40 | ].freeze 41 | 42 | RESERVED_METHODS = 43 | DEFINITION_PROXY_METHODS + 44 | UNPROXIED_METHODS + 45 | ATTRIBUTE_DEFINING_METHODS 46 | 47 | private_constant( 48 | :ATTRIBUTE_DEFINING_METHODS, 49 | :UNPROXIED_METHODS, 50 | :DEFINITION_PROXY_METHODS, 51 | :RESERVED_METHODS 52 | ) 53 | 54 | def self.attribute_defining_methods 55 | ATTRIBUTE_DEFINING_METHODS 56 | end 57 | 58 | def self.reserved_methods 59 | RESERVED_METHODS 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/rubocop/factory_bot/language.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module FactoryBot 5 | # Contains node matchers for common factory_bot DSL. 6 | module Language 7 | extend RuboCop::NodePattern::Macros 8 | 9 | METHODS = %i[ 10 | attributes_for 11 | attributes_for_list 12 | attributes_for_pair 13 | build 14 | build_list 15 | build_pair 16 | build_stubbed 17 | build_stubbed_list 18 | build_stubbed_pair 19 | create 20 | create_list 21 | create_pair 22 | generate 23 | generate_list 24 | null 25 | null_list 26 | null_pair 27 | ].to_set.freeze 28 | 29 | # @!method factory_bot?(node) 30 | def_node_matcher :factory_bot?, <<~PATTERN 31 | (const {nil? cbase} {:FactoryGirl :FactoryBot}) 32 | PATTERN 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/rubocop/factory_bot/plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'lint_roller' 4 | 5 | module RuboCop 6 | module FactoryBot 7 | # A plugin that integrates RuboCop FactoryBot with RuboCop's plugin system. 8 | class Plugin < LintRoller::Plugin 9 | # :nocov: 10 | def about 11 | LintRoller::About.new( 12 | name: 'rubocop-factory_bot', 13 | version: Version::STRING, 14 | homepage: 'https://github.com/rubocop/rubocop-factory_bot', 15 | description: 'Code style checking for FactoryBot test 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 | obsoletion = project_root.join('config', 'obsoletion.yml') 28 | ConfigObsoletion.files << obsoletion if obsoletion.exist? 29 | 30 | LintRoller::Rules.new( 31 | type: :path, 32 | config_format: :rubocop, 33 | value: project_root.join('config/default.yml') 34 | ) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rubocop/factory_bot/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RuboCop 4 | module FactoryBot 5 | # Version information for the factory_bot RuboCop plugin. 6 | module Version 7 | STRING = '2.27.1' 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /rubocop-factory_bot.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path('lib', __dir__) 4 | require 'rubocop/factory_bot/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'rubocop-factory_bot' 8 | spec.summary = 'Code style checking for factory_bot files' 9 | spec.description = <<~DESCRIPTION 10 | Code style checking for factory_bot files. 11 | A plugin for the RuboCop code style enforcing & linting tool. 12 | DESCRIPTION 13 | spec.homepage = 'https://github.com/rubocop/rubocop-factory_bot' 14 | spec.authors = ['John Backus', 'Ian MacLeod', 'Phil Pirozhkov', 15 | 'Maxim Krizhanovsky', 'Andrew Bromwich'] 16 | spec.licenses = ['MIT'] 17 | 18 | spec.version = RuboCop::FactoryBot::Version::STRING 19 | spec.platform = Gem::Platform::RUBY 20 | spec.required_ruby_version = '>= 2.7.0' 21 | 22 | spec.require_paths = ['lib'] 23 | spec.files = Dir[ 24 | 'lib/**/*', 25 | 'config/*', 26 | '*.md' 27 | ] 28 | spec.extra_rdoc_files = ['MIT-LICENSE.md', 'README.md'] 29 | 30 | spec.metadata = { 31 | 'changelog_uri' => 'https://github.com/rubocop/rubocop-factory_bot/blob/master/CHANGELOG.md', 32 | 'documentation_uri' => 'https://docs.rubocop.org/rubocop-factory_bot/', 33 | 'rubygems_mfa_required' => 'true', 34 | 'default_lint_roller_plugin' => 'RuboCop::FactoryBot::Plugin' 35 | } 36 | 37 | spec.add_dependency 'lint_roller', '~> 1.1' 38 | spec.add_dependency 'rubocop', '~> 1.72', '>= 1.72.1' 39 | end 40 | -------------------------------------------------------------------------------- /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 | 'factory_bot' => 'FactoryBot' 11 | } 12 | end 13 | 14 | let(:cop_names) do 15 | glob = SpecHelper::ROOT.join('lib', 'rubocop', 'cop', 'factory_bot', '*.rb') 16 | 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 | end 23 | 24 | let(:config_keys) do 25 | cop_names + namespaces.values 26 | end 27 | 28 | let(:unsafe_cops) do 29 | require 'yard' 30 | YARD::Registry.load! 31 | YARD::Registry.all(:class).select do |example| 32 | example.tags.any? { |tag| tag.tag_name == 'safety' } 33 | end 34 | end 35 | 36 | let(:unsafe_cop_names) do 37 | unsafe_cops.map do |cop| 38 | dept_and_cop_names = 39 | cop.path.split('::')[2..] # Drop `RuboCop::Cop` from class name. 40 | dept_and_cop_names.join('/') 41 | end 42 | end 43 | 44 | def cop_configuration(config_key) 45 | cop_names.map do |cop_name| 46 | cop_config = default_config[cop_name] 47 | 48 | cop_config.fetch(config_key) do 49 | raise "Expected #{cop_name} to have #{config_key} configuration key" 50 | end 51 | end 52 | end 53 | 54 | it 'has configuration for all cops' do 55 | expect(default_config.keys) 56 | .to match_array(config_keys) 57 | end 58 | 59 | it 'sorts configuration keys alphabetically with nested namespaces last' do 60 | keys = default_config.keys.select { |key| key.start_with?('FactoryBot') } 61 | namespaced_keys = keys.select do |key| 62 | key.start_with?(*(namespaces.values - ['FactoryBot'])) 63 | end 64 | 65 | expected = keys.sort_by do |key| 66 | namespaced = namespaced_keys.include?(key) ? 1 : 0 67 | "#{namespaced} #{key}" 68 | end 69 | 70 | keys.each_with_index do |key, idx| 71 | expect(key).to eq expected[idx] 72 | end 73 | end 74 | 75 | it 'has descriptions for all cops' do 76 | expect(cop_configuration('Description')).to all(be_a(String)) 77 | end 78 | 79 | it 'does not have newlines in cop descriptions' do 80 | cop_configuration('Description').each do |value| 81 | expect(value).not_to include("\n") 82 | end 83 | end 84 | 85 | it 'ends every description with a period' do 86 | expect(cop_configuration('Description')).to all(end_with('.')) 87 | end 88 | 89 | it 'includes a valid Enabled for every cop' do 90 | expect(cop_configuration('Enabled')) 91 | .to all be(true).or(be(false)).or(eq('pending')) 92 | end 93 | 94 | it 'does not include unnecessary `SafeAutoCorrect: false`' do 95 | cop_names.each do |cop_name| 96 | next unless default_config.dig(cop_name, 'Safe') == false 97 | 98 | safe_autocorrect = default_config.dig(cop_name, 'SafeAutoCorrect') 99 | 100 | expect(safe_autocorrect).not_to( 101 | be(false), 102 | "`#{cop_name}` has unnecessary `SafeAutoCorrect: false` config." 103 | ) 104 | end 105 | end 106 | 107 | it 'is expected that all cops documented with `@safety` are `Safe: false`' \ 108 | ' or `SafeAutoCorrect: false`' do 109 | unsafe_cop_names.each do |cop_name| 110 | unsafe = default_config[cop_name]['Safe'] == false || 111 | default_config[cop_name]['SafeAutoCorrect'] == false 112 | expect(unsafe).to( 113 | be(true), 114 | "`#{cop_name}` cop should be set `Safe: false` or " \ 115 | '`SafeAutoCorrect: false` because `@safety` YARD tag exists.' 116 | ) 117 | YARD::Registry.clear 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/association_style_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::AssociationStyle do 4 | def inspected_source_filename 5 | 'spec/factories.rb' 6 | end 7 | 8 | let(:cop_config) do 9 | { 'EnforcedStyle' => enforced_style } 10 | end 11 | 12 | context 'when EnforcedStyle is :implicit' do 13 | let(:enforced_style) { :implicit } 14 | 15 | context 'when factory block is empty' do 16 | it 'does not register an offense' do 17 | expect_no_offenses(<<~RUBY) 18 | FactoryBot.define do 19 | factory :user do 20 | end 21 | end 22 | RUBY 23 | end 24 | end 25 | 26 | context 'with when factory has no block' do 27 | it 'does not register an offense' do 28 | expect_no_offenses(<<~RUBY) 29 | FactoryBot.define do 30 | factory :user 31 | factory :admin_user, parent: :user 32 | end 33 | RUBY 34 | end 35 | end 36 | 37 | context 'when implicit style is used' do 38 | it 'does not register an offense' do 39 | expect_no_offenses(<<~RUBY) 40 | FactoryBot.define do 41 | factory :article do 42 | user 43 | end 44 | end 45 | RUBY 46 | end 47 | end 48 | 49 | context 'when `association` is called in attribute block' do 50 | it 'does not register an offense' do 51 | expect_no_offenses(<<~RUBY) 52 | factory :article do 53 | author do 54 | association :user 55 | end 56 | end 57 | RUBY 58 | end 59 | end 60 | 61 | context 'when `association` has only 1 argument' do 62 | it 'registers and corrects an offense' do 63 | expect_offense(<<~RUBY) 64 | factory :article do 65 | association :user 66 | ^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 67 | end 68 | RUBY 69 | 70 | expect_correction(<<~RUBY) 71 | factory :article do 72 | user 73 | end 74 | RUBY 75 | end 76 | end 77 | 78 | context 'when `association` is called in trait block' do 79 | it 'registers and corrects an offense' do 80 | expect_offense(<<~RUBY) 81 | factory :article do 82 | trait :with_user do 83 | association :user 84 | ^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 85 | end 86 | end 87 | RUBY 88 | 89 | expect_correction(<<~RUBY) 90 | factory :article do 91 | trait :with_user do 92 | user 93 | end 94 | end 95 | RUBY 96 | end 97 | end 98 | 99 | context 'when `association` is called with trait' do 100 | it 'registers and corrects an offense' do 101 | expect_offense(<<~RUBY) 102 | factory :article do 103 | association :user, :admin 104 | ^^^^^^^^^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 105 | end 106 | RUBY 107 | 108 | expect_correction(<<~RUBY) 109 | factory :article do 110 | user factory: %i[user admin] 111 | end 112 | RUBY 113 | end 114 | end 115 | 116 | context 'when `association` is called with factory option' do 117 | it 'registers and corrects an offense' do 118 | expect_offense(<<~RUBY) 119 | factory :article do 120 | association :author, factory: :user 121 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 122 | end 123 | RUBY 124 | 125 | expect_correction(<<~RUBY) 126 | factory :article do 127 | author factory: %i[user] 128 | end 129 | RUBY 130 | end 131 | end 132 | 133 | context 'when `association` is called with array factory option' do 134 | it 'registers and corrects an offense' do 135 | expect_offense(<<~RUBY) 136 | factory :article do 137 | association :author, factory: %i[user] 138 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 139 | end 140 | RUBY 141 | 142 | expect_correction(<<~RUBY) 143 | factory :article do 144 | author factory: %i[user] 145 | end 146 | RUBY 147 | end 148 | end 149 | 150 | context 'when `association` is called with trait arguments and factory' \ 151 | 'option' do 152 | it 'registers and corrects an offense' do 153 | expect_offense(<<~RUBY) 154 | factory :article do 155 | association :author, :admin, factory: :user 156 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 157 | end 158 | RUBY 159 | 160 | expect_correction(<<~RUBY) 161 | factory :article do 162 | author factory: %i[user admin] 163 | end 164 | RUBY 165 | end 166 | end 167 | 168 | context 'with `strategy: :build` option' do 169 | # the `strategy: :build` option cannot be used with implicit association 170 | it 'does not register an offense' do 171 | expect_no_offenses(<<~RUBY) 172 | factory :article do 173 | association :user, strategy: :build 174 | association :reviewer, factory: :user, strategy: :build 175 | association :tag, :pop, strategy: :build 176 | end 177 | RUBY 178 | end 179 | end 180 | 181 | context 'when `association` is called in trait block ' \ 182 | 'and column name is keyword' do 183 | it 'does not register an offense' do 184 | expect_no_offenses(<<~RUBY) 185 | factory :article do 186 | trait :with_class do 187 | association :alias 188 | association :and, factory: :user 189 | association :foo, :__FILE__ 190 | end 191 | end 192 | RUBY 193 | end 194 | end 195 | 196 | context 'when `association` is called in trait block ' \ 197 | 'and factory option is keyword' do 198 | it 'registers and corrects an offense' do 199 | expect_offense(<<~RUBY) 200 | factory :article do 201 | trait :with_class do 202 | association :foo, factory: :alias 203 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 204 | association :bar, factory: :and 205 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 206 | association :baz, factory: :__FILE__ 207 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use implicit style to define associations. 208 | end 209 | end 210 | RUBY 211 | 212 | expect_correction(<<~RUBY) 213 | factory :article do 214 | trait :with_class do 215 | foo factory: %i[alias] 216 | bar factory: %i[and] 217 | baz factory: %i[__FILE__] 218 | end 219 | end 220 | RUBY 221 | end 222 | end 223 | end 224 | 225 | context 'when EnforcedStyle is :explicit' do 226 | let(:enforced_style) { :explicit } 227 | 228 | context 'when explicit style is used' do 229 | it 'does not register an offense' do 230 | expect_no_offenses(<<~RUBY) 231 | factory :article do 232 | association :user 233 | end 234 | RUBY 235 | end 236 | end 237 | 238 | context 'when implicit association is used without any arguments' do 239 | it 'registers and corrects an offense' do 240 | expect_offense(<<~RUBY) 241 | factory :article do 242 | user 243 | ^^^^ Use explicit style to define associations. 244 | end 245 | RUBY 246 | 247 | expect_correction(<<~RUBY) 248 | factory :article do 249 | association :user 250 | end 251 | RUBY 252 | end 253 | end 254 | 255 | context 'when implicit association is used with arguments' do 256 | it 'registers and corrects an offense' do 257 | expect_offense(<<~RUBY) 258 | factory :article do 259 | author factory: :user 260 | ^^^^^^^^^^^^^^^^^^^^^ Use explicit style to define associations. 261 | end 262 | RUBY 263 | 264 | expect_correction(<<~RUBY) 265 | factory :article do 266 | association :author, factory: :user 267 | end 268 | RUBY 269 | end 270 | end 271 | 272 | context 'when implicit association has factory and traits' do 273 | it 'registers and corrects an offense' do 274 | expect_offense(<<~RUBY) 275 | factory :article do 276 | author factory: %i[user admin] 277 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use explicit style to define associations. 278 | end 279 | RUBY 280 | 281 | expect_correction(<<~RUBY) 282 | factory :article do 283 | association :author, factory: %i[user admin] 284 | end 285 | RUBY 286 | end 287 | end 288 | 289 | context 'when default non implicit association method name is used' do 290 | it 'does not register an offense' do 291 | expect_no_offenses(<<~RUBY) 292 | factory :article do 293 | skip_create 294 | end 295 | RUBY 296 | end 297 | end 298 | 299 | context 'when custom non implicit association method name is used' do 300 | let(:cop_config) do 301 | { 'NonImplicitAssociationMethods' => %w[email] } 302 | end 303 | 304 | it 'does not register an offense' do 305 | expect_no_offenses(<<~'RUBY') 306 | sequence(:email) { |n| "person#{n}@example.com" } 307 | 308 | factory :user do 309 | email 310 | 311 | skip_create 312 | end 313 | RUBY 314 | end 315 | end 316 | 317 | context 'when implicit association is called in trait block' do 318 | it 'registers and corrects an offense' do 319 | expect_offense(<<~RUBY) 320 | factory :article do 321 | trait :with_user do 322 | user 323 | ^^^^ Use explicit style to define associations. 324 | end 325 | end 326 | RUBY 327 | 328 | expect_correction(<<~RUBY) 329 | factory :article do 330 | trait :with_user do 331 | association :user 332 | end 333 | end 334 | RUBY 335 | end 336 | end 337 | 338 | context 'when using trait within trait' do 339 | it 'does not register an offense' do 340 | expect_no_offenses(<<~RUBY) 341 | factory :order do 342 | trait :completed do 343 | completed_at { 3.days.ago } 344 | end 345 | 346 | trait :refunded do 347 | completed 348 | refunded_at { 1.day.ago } 349 | end 350 | end 351 | RUBY 352 | end 353 | end 354 | 355 | context 'when factory inside a factory' do 356 | it 'does not register an offense' do 357 | expect_no_offenses(<<~RUBY) 358 | factory :order do 359 | trait :completed do 360 | completed_at { 3.days.ago } 361 | end 362 | 363 | factory :order_with_refund do 364 | trait :refunded do 365 | completed 366 | refunded_at { 1.day.ago } 367 | end 368 | end 369 | end 370 | RUBY 371 | end 372 | end 373 | 374 | context 'when use an association with the same name as trait' do 375 | it 'registers and corrects an offense' do 376 | expect_offense(<<~RUBY) 377 | factory :order do 378 | trait :completed do 379 | completed_at { 3.days.ago } 380 | end 381 | end 382 | 383 | factory :order_with_refund do 384 | trait :refunded do 385 | completed 386 | ^^^^^^^^^ Use explicit style to define associations. 387 | refunded_at { 1.day.ago } 388 | end 389 | end 390 | RUBY 391 | 392 | expect_correction(<<~RUBY) 393 | factory :order do 394 | trait :completed do 395 | completed_at { 3.days.ago } 396 | end 397 | end 398 | 399 | factory :order_with_refund do 400 | trait :refunded do 401 | association :completed 402 | refunded_at { 1.day.ago } 403 | end 404 | end 405 | RUBY 406 | end 407 | end 408 | 409 | context 'when factory inside a factory with inline traits' do 410 | it 'does not register an offense' do 411 | expect_no_offenses(<<~RUBY) 412 | factory :order do 413 | traits :completed do 414 | completed_at { 3.days.ago } 415 | end 416 | 417 | factory :order_completed, traits: [:completed] 418 | end 419 | RUBY 420 | end 421 | end 422 | end 423 | end 424 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/attribute_defined_statically_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::AttributeDefinedStatically do 4 | it 'registers an offense for offending code' do 5 | expect_offense(<<~RUBY) 6 | FactoryBot.define do 7 | factory :post do 8 | title "Something" 9 | ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 10 | comments_count 0 11 | ^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 12 | tag Tag::MAGIC 13 | ^^^^^^^^^^^^^^ Use a block to declare attribute values. 14 | recent_statuses [] 15 | ^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 16 | status([:draft, :published].sample) 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 18 | published_at 1.day.from_now 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 20 | created_at(1.day.ago) 21 | ^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 22 | update_times [Time.current] 23 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 24 | meta_tags(foo: Time.current) 25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 26 | other_tags({ foo: Time.current }) 27 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 28 | options color: :blue 29 | ^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 30 | other_options Tag::MAGIC => :magic 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 32 | end 33 | end 34 | RUBY 35 | 36 | expect_correction(<<~RUBY) 37 | FactoryBot.define do 38 | factory :post do 39 | title { "Something" } 40 | comments_count { 0 } 41 | tag { Tag::MAGIC } 42 | recent_statuses { [] } 43 | status { [:draft, :published].sample } 44 | published_at { 1.day.from_now } 45 | created_at { 1.day.ago } 46 | update_times { [Time.current] } 47 | meta_tags { { foo: Time.current } } 48 | other_tags { { foo: Time.current } } 49 | options { { color: :blue } } 50 | other_options { { Tag::MAGIC => :magic } } 51 | end 52 | end 53 | RUBY 54 | end 55 | 56 | it 'registers an offense in a trait' do 57 | expect_offense(<<~RUBY) 58 | FactoryBot.define do 59 | factory :post do 60 | trait :published do 61 | title "Something" 62 | ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 63 | published_at 1.day.from_now 64 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 65 | end 66 | end 67 | end 68 | RUBY 69 | 70 | expect_correction(<<~RUBY) 71 | FactoryBot.define do 72 | factory :post do 73 | trait :published do 74 | title { "Something" } 75 | published_at { 1.day.from_now } 76 | end 77 | end 78 | end 79 | RUBY 80 | end 81 | 82 | it 'registers an offense in a transient block' do 83 | expect_offense(<<~RUBY) 84 | FactoryBot.define do 85 | factory :post do 86 | transient do 87 | title "Something" 88 | ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 89 | published_at 1.day.from_now 90 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 91 | end 92 | end 93 | end 94 | RUBY 95 | 96 | expect_correction(<<~RUBY) 97 | FactoryBot.define do 98 | factory :post do 99 | transient do 100 | title { "Something" } 101 | published_at { 1.day.from_now } 102 | end 103 | end 104 | end 105 | RUBY 106 | end 107 | 108 | it 'registers an offense for an attribute defined on `self`' do 109 | expect_offense(<<~RUBY) 110 | FactoryBot.define do 111 | factory :post do 112 | self.start { Date.today } 113 | self.end Date.tomorrow 114 | ^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 115 | end 116 | end 117 | RUBY 118 | 119 | expect_correction(<<~RUBY) 120 | FactoryBot.define do 121 | factory :post do 122 | self.start { Date.today } 123 | self.end { Date.tomorrow } 124 | end 125 | end 126 | RUBY 127 | end 128 | 129 | it 'registers an offense for attributes defined on explicit receiver' do 130 | expect_offense(<<~RUBY) 131 | FactoryBot.define do 132 | factory :post do |post_definition| 133 | post_definition.end Date.tomorrow 134 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 135 | post_definition.trait :published do |published_definition| 136 | published_definition.published_at 1.day.from_now 137 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. 138 | end 139 | end 140 | end 141 | RUBY 142 | 143 | expect_correction(<<~RUBY) 144 | FactoryBot.define do 145 | factory :post do |post_definition| 146 | post_definition.end { Date.tomorrow } 147 | post_definition.trait :published do |published_definition| 148 | published_definition.published_at { 1.day.from_now } 149 | end 150 | end 151 | end 152 | RUBY 153 | end 154 | 155 | it 'accepts valid factory definitions' do 156 | expect_no_offenses(<<~RUBY) 157 | FactoryBot.define do 158 | factory :post do 159 | trait :published do 160 | published_at { 1.day.from_now } 161 | end 162 | created_at { 1.day.ago } 163 | status { :draft } 164 | comments_count { 0 } 165 | title { "Static" } 166 | description { FFaker::Lorem.paragraph(10) } 167 | recent_statuses { [] } 168 | tags { { like_count: 2 } } 169 | 170 | before(:create, &:initialize_something) 171 | after(:create, &:rebuild_cache) 172 | end 173 | end 174 | RUBY 175 | end 176 | 177 | it 'does not add offense if out of factory_bot block' do 178 | expect_no_offenses(<<~RUBY) 179 | status [:draft, :published].sample 180 | published_at 1.day.from_now 181 | created_at 1.day.ago 182 | update_times [Time.current] 183 | meta_tags(foo: Time.current) 184 | RUBY 185 | end 186 | 187 | it 'does not add offense if method called on another object' do 188 | expect_no_offenses(<<~RUBY) 189 | FactoryBot.define do 190 | factory :post do |post_definition| 191 | Registrar.register :post_factory 192 | end 193 | end 194 | RUBY 195 | end 196 | 197 | it 'does not add offense if method called on a local variable' do 198 | expect_no_offenses(<<~RUBY) 199 | FactoryBot.define do 200 | factory :post do |post_definition| 201 | local = Registrar 202 | local.register :post_factory 203 | end 204 | end 205 | RUBY 206 | end 207 | 208 | it 'accepts valid association definitions' do 209 | expect_no_offenses(<<~RUBY) 210 | FactoryBot.define do 211 | factory :post do 212 | author age: 42, factory: :user 213 | end 214 | end 215 | RUBY 216 | end 217 | 218 | it 'accepts valid sequence definition' do 219 | expect_no_offenses(<<~RUBY) 220 | FactoryBot.define do 221 | factory :post do 222 | sequence :negative_numbers, &:-@ 223 | end 224 | end 225 | RUBY 226 | end 227 | 228 | it 'accepts valid traits_for_enum definition' do 229 | expect_no_offenses(<<~RUBY) 230 | FactoryBot.define do 231 | factory :post do 232 | traits_for_enum :status, [:draft, :published] 233 | end 234 | end 235 | RUBY 236 | end 237 | end 238 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/consistent_parentheses_style_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::ConsistentParenthesesStyle do 4 | let(:cop_config) do 5 | { 'EnforcedStyle' => enforced_style, 'ExplicitOnly' => explicit_only } 6 | end 7 | let(:explicit_only) { false } 8 | 9 | context 'when EnforcedStyle is :enforce_parentheses' do 10 | let(:enforced_style) { :require_parentheses } 11 | 12 | context 'with create' do 13 | it 'flags the call to use parentheses' do 14 | expect_offense(<<~RUBY) 15 | create :user 16 | ^^^^^^ Prefer method call with parentheses 17 | RUBY 18 | 19 | expect_correction(<<~RUBY) 20 | create(:user) 21 | RUBY 22 | end 23 | end 24 | 25 | context 'with multiline method calls' do 26 | it 'expects parentheses around multiline call' do 27 | expect_offense(<<~RUBY) 28 | create :user, 29 | ^^^^^^ Prefer method call with parentheses 30 | username: "PETER", 31 | peter: "USERNAME" 32 | RUBY 33 | 34 | expect_correction(<<~RUBY) 35 | create(:user, 36 | username: "PETER", 37 | peter: "USERNAME") 38 | RUBY 39 | end 40 | end 41 | 42 | context 'with build' do 43 | it 'flags the call to use parentheses' do 44 | expect_offense(<<~RUBY) 45 | build :user 46 | ^^^^^ Prefer method call with parentheses 47 | RUBY 48 | 49 | expect_correction(<<~RUBY) 50 | build(:user) 51 | RUBY 52 | end 53 | end 54 | 55 | context 'with mixed tests' do 56 | it 'flags the call to use parentheses' do 57 | expect_offense(<<~RUBY) 58 | build_list :user, 10 59 | ^^^^^^^^^^ Prefer method call with parentheses 60 | build_list "user", 10 61 | ^^^^^^^^^^ Prefer method call with parentheses 62 | create_list :user, 10 63 | ^^^^^^^^^^^ Prefer method call with parentheses 64 | build_stubbed :user 65 | ^^^^^^^^^^^^^ Prefer method call with parentheses 66 | build_stubbed_list :user, 10 67 | ^^^^^^^^^^^^^^^^^^ Prefer method call with parentheses 68 | build factory 69 | ^^^^^ Prefer method call with parentheses 70 | RUBY 71 | 72 | expect_correction(<<~RUBY) 73 | build_list(:user, 10) 74 | build_list("user", 10) 75 | create_list(:user, 10) 76 | build_stubbed(:user) 77 | build_stubbed_list(:user, 10) 78 | build(factory) 79 | RUBY 80 | end 81 | end 82 | 83 | context 'with nested calling' do 84 | it 'flags the call to use parentheses' do 85 | expect_offense(<<~RUBY) 86 | build :user, build(:yester) 87 | ^^^^^ Prefer method call with parentheses 88 | RUBY 89 | 90 | expect_correction(<<~RUBY) 91 | build(:user, build(:yester)) 92 | RUBY 93 | end 94 | 95 | it 'works in a bigger context' do 96 | expect_offense(<<~RUBY) 97 | context 'with context' do 98 | let(:build) { create :user, build(:user) } 99 | ^^^^^^ Prefer method call with parentheses 100 | 101 | it 'test in test' do 102 | user = create :user, first: name, peter: miller 103 | ^^^^^^ Prefer method call with parentheses 104 | end 105 | 106 | let(:build) { create :user, build(:user, create(:user, create(:first_name))) } 107 | ^^^^^^ Prefer method call with parentheses 108 | end 109 | RUBY 110 | 111 | expect_correction(<<~RUBY) 112 | context 'with context' do 113 | let(:build) { create(:user, build(:user)) } 114 | 115 | it 'test in test' do 116 | user = create(:user, first: name, peter: miller) 117 | end 118 | 119 | let(:build) { create(:user, build(:user, create(:user, create(:first_name)))) } 120 | end 121 | RUBY 122 | end 123 | end 124 | 125 | context 'with already valid usage of parentheses' do 126 | it 'does not flag as invalid - create' do 127 | expect_no_offenses(<<~RUBY) 128 | create(:user) 129 | RUBY 130 | end 131 | 132 | it 'does not flag as invalid - build' do 133 | expect_no_offenses(<<~RUBY) 134 | build(:user) 135 | RUBY 136 | end 137 | end 138 | 139 | it 'flags the call with an explicit receiver' do 140 | expect_offense(<<~RUBY) 141 | FactoryBot.create :user 142 | ^^^^^^ Prefer method call with parentheses 143 | RUBY 144 | end 145 | 146 | it 'ignores factory_bot DSL methods without a first positional argument' do 147 | expect_no_offenses(<<~RUBY) 148 | create 149 | create foo: :bar 150 | RUBY 151 | end 152 | 153 | it 'dose not register an offense when using `generate` ' \ 154 | 'with not a one argument' do 155 | expect_no_offenses(<<~RUBY) 156 | generate 157 | generate :foo, :bar 158 | RUBY 159 | end 160 | end 161 | 162 | context 'when EnforcedStyle is :omit_parentheses' do 163 | let(:enforced_style) { :omit_parentheses } 164 | 165 | context 'with create' do 166 | it 'flags the call to not use parentheses' do 167 | expect_offense(<<~RUBY) 168 | create(:user) 169 | ^^^^^^ Prefer method call without parentheses 170 | RUBY 171 | 172 | expect_correction(<<~RUBY) 173 | create :user 174 | RUBY 175 | end 176 | end 177 | 178 | context 'with nest call' do 179 | it 'inner call is ignored and not fixed' do 180 | expect_no_offenses(<<~RUBY) 181 | puts(1, create(:user)) 182 | RUBY 183 | end 184 | end 185 | 186 | context 'with multiline method calls' do 187 | it 'removes parentheses around multiline call' do 188 | expect_offense(<<~RUBY) 189 | create(:user, 190 | ^^^^^^ Prefer method call without parentheses 191 | username: "PETER", 192 | peter: "USERNAME") 193 | RUBY 194 | 195 | expect_correction(<<~RUBY) 196 | create :user, 197 | username: "PETER", 198 | peter: "USERNAME" 199 | RUBY 200 | end 201 | end 202 | 203 | %w[&& ||].each do |operator| 204 | context "with #{operator}" do 205 | it 'does not flag the call' do 206 | expect_no_offenses(<<~RUBY) 207 | can_create_user? #{operator} create(:user) 208 | RUBY 209 | end 210 | end 211 | end 212 | 213 | context 'with ternary operator' do 214 | it 'does not flag the call' do 215 | expect_no_offenses(<<~RUBY) 216 | can_create_user? ? create(:user) : nil 217 | RUBY 218 | end 219 | end 220 | 221 | context 'with mixed tests' do 222 | it 'flags the call not to use parentheses' do 223 | expect_offense(<<~RUBY) 224 | build_list(:user, 10) 225 | ^^^^^^^^^^ Prefer method call without parentheses 226 | build_list("user", 10) 227 | ^^^^^^^^^^ Prefer method call without parentheses 228 | create_list(:user, 10) 229 | ^^^^^^^^^^^ Prefer method call without parentheses 230 | build_stubbed(:user) 231 | ^^^^^^^^^^^^^ Prefer method call without parentheses 232 | build_stubbed_list(:user, 10) 233 | ^^^^^^^^^^^^^^^^^^ Prefer method call without parentheses 234 | build(factory) 235 | ^^^^^ Prefer method call without parentheses 236 | RUBY 237 | 238 | expect_correction(<<~RUBY) 239 | build_list :user, 10 240 | build_list "user", 10 241 | create_list :user, 10 242 | build_stubbed :user 243 | build_stubbed_list :user, 10 244 | build factory 245 | RUBY 246 | end 247 | end 248 | 249 | context 'with build' do 250 | it 'flags the call to not use parentheses' do 251 | expect_offense(<<~RUBY) 252 | build(:user) 253 | ^^^^^ Prefer method call without parentheses 254 | RUBY 255 | 256 | expect_correction(<<~RUBY) 257 | build :user 258 | RUBY 259 | end 260 | end 261 | 262 | context 'with nested calling' do 263 | it 'flags the call to use parentheses' do 264 | expect_offense(<<~RUBY) 265 | build(:user, build(:yester)) 266 | ^^^^^ Prefer method call without parentheses 267 | RUBY 268 | 269 | expect_correction(<<~RUBY) 270 | build :user, build(:yester) 271 | RUBY 272 | end 273 | end 274 | 275 | context 'with nested calling that does not require fixing' do 276 | it 'does not flag the nested call' do 277 | expect_no_offenses(<<~RUBY) 278 | build :user, build(:yester) 279 | RUBY 280 | end 281 | end 282 | 283 | context 'when is a part of a hash' do 284 | it 'does not flag the call' do 285 | expect_no_offenses(<<~RUBY) 286 | build :user, home: build(:address) 287 | RUBY 288 | end 289 | end 290 | 291 | context 'when is a part of an array' do 292 | it 'does not flag the call' do 293 | expect_no_offenses(<<~RUBY) 294 | users = [ 295 | build(:user), 296 | build(:user) 297 | ] 298 | RUBY 299 | end 300 | end 301 | 302 | context 'with already valid usage of parentheses' do 303 | it 'does not flag as invalid - create' do 304 | expect_no_offenses(<<~RUBY) 305 | create :user 306 | RUBY 307 | end 308 | 309 | it 'does not flag as invalid - build' do 310 | expect_no_offenses(<<~RUBY) 311 | build :user 312 | RUBY 313 | end 314 | end 315 | 316 | it 'works in a bigger context' do 317 | expect_offense(<<~RUBY) 318 | RSpec.describe Context do 319 | let(:build) { create(:user, build(:user)) } 320 | ^^^^^^ Prefer method call without parentheses 321 | 322 | it 'test in test' do 323 | user = create(:user, first: name, peter: miller) 324 | ^^^^^^ Prefer method call without parentheses 325 | end 326 | 327 | let(:build) { create(:user, build(:user, create(:user, create(:first_name)))) } 328 | ^^^^^^ Prefer method call without parentheses 329 | end 330 | RUBY 331 | 332 | expect_correction(<<~RUBY) 333 | RSpec.describe Context do 334 | let(:build) { create :user, build(:user) } 335 | 336 | it 'test in test' do 337 | user = create :user, first: name, peter: miller 338 | end 339 | 340 | let(:build) { create :user, build(:user, create(:user, create(:first_name))) } 341 | end 342 | RUBY 343 | end 344 | 345 | context 'when create and first argument are on same line' do 346 | it 'registers an offense' do 347 | expect_offense(<<~RUBY) 348 | create(:user, 349 | ^^^^^^ Prefer method call without parentheses 350 | name: 'foo' 351 | ) 352 | RUBY 353 | 354 | expect_correction(<<~RUBY) 355 | create :user, 356 | name: 'foo' 357 | 358 | RUBY 359 | end 360 | end 361 | 362 | context 'when create and first argument are not on same line' do 363 | it 'does not register an offense' do 364 | expect_no_offenses(<<~RUBY) 365 | create( 366 | :user 367 | ) 368 | RUBY 369 | end 370 | end 371 | 372 | context 'when create and some argument are not on same line' do 373 | it 'does not register an offense' do 374 | expect_no_offenses(<<~RUBY) 375 | create( 376 | :user, 377 | name: 'foo' 378 | ) 379 | RUBY 380 | end 381 | end 382 | 383 | it 'flags the call with an explicit receiver' do 384 | expect_offense(<<~RUBY) 385 | FactoryBot.create(:user) 386 | ^^^^^^ Prefer method call without parentheses 387 | RUBY 388 | 389 | expect_correction(<<~RUBY) 390 | FactoryBot.create :user 391 | RUBY 392 | end 393 | 394 | it 'ignores factory_bot DSL methods without a first positional argument' do 395 | expect_no_offenses(<<~RUBY) 396 | create() 397 | create(foo: :bar) 398 | RUBY 399 | end 400 | 401 | it 'dose not register an offense when using `generate` ' \ 402 | 'with not a one argument' do 403 | expect_no_offenses(<<~RUBY) 404 | generate() 405 | generate(:foo, :bar) 406 | RUBY 407 | end 408 | 409 | context 'when TargetRubyVersion >= 3.1', :ruby31 do 410 | it 'does not register an offense when using `create` ' \ 411 | 'with pinned hash argument' do 412 | expect_no_offenses(<<~RUBY) 413 | create(:user, name:) 414 | create(:user, name:, client:) 415 | create(:user, :trait, name:, client:) 416 | RUBY 417 | end 418 | 419 | it 'does not register an offense when using `create` ' \ 420 | 'with pinned hash argument and other unpinned args' do 421 | expect_no_offenses(<<~RUBY) 422 | create(:user, client:, name: 'foo') 423 | create(:user, client: 'foo', name:) 424 | create(:user, :trait, client:, name: 'foo') 425 | create(:user, :trait, client: 'foo', name:) 426 | RUBY 427 | end 428 | 429 | it 'registers an offense when using `create` ' \ 430 | 'with unpinned hash argument' do 431 | expect_offense(<<~RUBY) 432 | create(:user, name: 'foo') 433 | ^^^^^^ Prefer method call without parentheses 434 | create(:user, :trait, name: 'foo') 435 | ^^^^^^ Prefer method call without parentheses 436 | RUBY 437 | 438 | expect_correction(<<~RUBY) 439 | create :user, name: 'foo' 440 | create :user, :trait, name: 'foo' 441 | RUBY 442 | end 443 | 444 | it 'registers an offense when using `create` ' \ 445 | 'with method call has pinned hash argument' do 446 | expect_offense(<<~RUBY) 447 | create(:user, foo(name:)) 448 | ^^^^^^ Prefer method call without parentheses 449 | create(:user, :trait, foo(name:)) 450 | ^^^^^^ Prefer method call without parentheses 451 | RUBY 452 | 453 | expect_correction(<<~RUBY) 454 | create :user, foo(name:) 455 | create :user, :trait, foo(name:) 456 | RUBY 457 | end 458 | end 459 | end 460 | 461 | context 'when ExplicitOnly is false' do 462 | let(:enforced_style) { :require_parentheses } 463 | let(:explicit_only) { false } 464 | 465 | it 'registers an offense when using `create` with an explicit receiver' do 466 | expect_offense(<<~RUBY) 467 | FactoryBot.create :user 468 | ^^^^^^ Prefer method call with parentheses 469 | RUBY 470 | 471 | expect_correction(<<~RUBY) 472 | FactoryBot.create(:user) 473 | RUBY 474 | end 475 | 476 | it 'registers an offense when using `create` with no explicit receiver' do 477 | expect_offense(<<~RUBY) 478 | create :user 479 | ^^^^^^ Prefer method call with parentheses 480 | RUBY 481 | 482 | expect_correction(<<~RUBY) 483 | create(:user) 484 | RUBY 485 | end 486 | end 487 | 488 | context 'when ExplicitOnly is true' do 489 | let(:enforced_style) { :require_parentheses } 490 | let(:explicit_only) { true } 491 | 492 | it 'registers an offense when using `create` with an explicit receiver' do 493 | expect_offense(<<~RUBY) 494 | FactoryBot.create :user 495 | ^^^^^^ Prefer method call with parentheses 496 | RUBY 497 | 498 | expect_correction(<<~RUBY) 499 | FactoryBot.create(:user) 500 | RUBY 501 | end 502 | 503 | it 'dose not register an offense when using `create` ' \ 504 | 'with no explicit receiver' do 505 | expect_no_offenses(<<~RUBY) 506 | create :user 507 | RUBY 508 | end 509 | end 510 | end 511 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/create_list_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::CreateList do 4 | let(:cop_config) do 5 | { 'EnforcedStyle' => enforced_style, 'ExplicitOnly' => explicit_only } 6 | end 7 | let(:explicit_only) { false } 8 | 9 | context 'when EnforcedStyle is :create_list' do 10 | let(:enforced_style) { :create_list } 11 | 12 | it 'flags usage of n.times with no arguments' do 13 | expect_offense(<<~RUBY) 14 | 3.times { create :user } 15 | ^^^^^^^ Prefer create_list. 16 | RUBY 17 | 18 | expect_correction(<<~RUBY) 19 | create_list :user, 3 20 | RUBY 21 | end 22 | 23 | it 'ignores usage of 1.times' do 24 | expect_no_offenses(<<~RUBY) 25 | 1.times { create :user } 26 | RUBY 27 | end 28 | 29 | it 'flags usage of n.times when FactoryGirl.create is used' do 30 | expect_offense(<<~RUBY) 31 | 3.times { FactoryGirl.create :user } 32 | ^^^^^^^ Prefer create_list. 33 | RUBY 34 | 35 | expect_correction(<<~RUBY) 36 | FactoryGirl.create_list :user, 3 37 | RUBY 38 | end 39 | 40 | it 'flags usage of n.times when FactoryBot.create is used' do 41 | expect_offense(<<~RUBY) 42 | 3.times { FactoryBot.create :user } 43 | ^^^^^^^ Prefer create_list. 44 | RUBY 45 | 46 | expect_correction(<<~RUBY) 47 | FactoryBot.create_list :user, 3 48 | RUBY 49 | end 50 | 51 | it 'ignores create method of other object' do 52 | expect_no_offenses(<<~RUBY) 53 | 3.times { SomeFactory.create :user } 54 | RUBY 55 | end 56 | 57 | it 'ignores create in other block' do 58 | expect_no_offenses(<<~RUBY) 59 | allow(User).to receive(:create) { create :user } 60 | RUBY 61 | end 62 | 63 | it 'ignores n.times with n as argument' do 64 | expect_no_offenses(<<~RUBY) 65 | 3.times { |n| create :user, position: n } 66 | RUBY 67 | end 68 | 69 | it 'flags n.times when create call doesn\'t have method calls' do 70 | expect_offense(<<~RUBY) 71 | 3.times { |n| create :user, :active } 72 | ^^^^^^^ Prefer create_list. 73 | 3.times { |n| create :user, password: '123' } 74 | ^^^^^^^ Prefer create_list. 75 | 3.times { |n| create :user, :active, password: '123' } 76 | ^^^^^^^ Prefer create_list. 77 | RUBY 78 | end 79 | 80 | it 'ignores n.times when create call does have method calls ' \ 81 | 'and repeat multiple times' do 82 | expect_no_offenses(<<~RUBY) 83 | 3.times { |n| create :user, repositories_count: rand } 84 | RUBY 85 | end 86 | 87 | it 'ignores n.times when create call does have method calls ' \ 88 | 'and repeat multiple times with other argument' do 89 | expect_no_offenses(<<~RUBY) 90 | 3.times { |n| create :user, :admin, repositories_count: rand } 91 | RUBY 92 | end 93 | 94 | it 'ignores n.times when create call does have method calls ' \ 95 | 'and not repeat' do 96 | expect_no_offenses(<<~RUBY) 97 | 1.times { |n| create :user, repositories_count: rand } 98 | RUBY 99 | end 100 | 101 | it 'ignores n.times when there is no create call inside' do 102 | expect_no_offenses(<<~RUBY) 103 | 3.times { do_something } 104 | RUBY 105 | end 106 | 107 | it 'ignores empty n.times' do 108 | expect_no_offenses(<<~RUBY) 109 | 3.times {} 110 | RUBY 111 | end 112 | 113 | it 'ignores n.times when there is other calls but create' do 114 | expect_no_offenses(<<~RUBY) 115 | used_passwords = [] 116 | 3.times do 117 | u = create :user 118 | expect(used_passwords).not_to include(u.password) 119 | used_passwords << u.password 120 | end 121 | RUBY 122 | end 123 | 124 | it 'flags FactoryGirl.create calls with a block' do 125 | expect_offense(<<~RUBY) 126 | 3.times do 127 | ^^^^^^^ Prefer create_list. 128 | create(:user) { |user| create :account, user: user } 129 | end 130 | RUBY 131 | 132 | expect_correction(<<~RUBY) 133 | create_list(:user, 3) { |user| create :account, user: user } 134 | RUBY 135 | end 136 | 137 | it 'flags usage of n.times with arguments' do 138 | expect_offense(<<~RUBY) 139 | 5.times { create(:user, :trait) } 140 | ^^^^^^^ Prefer create_list. 141 | RUBY 142 | 143 | expect_correction(<<~RUBY) 144 | create_list(:user, 5, :trait) 145 | RUBY 146 | end 147 | 148 | it 'flags usage of n.times with block argument' do 149 | expect_offense(<<~RUBY) 150 | 3.times do 151 | ^^^^^^^ Prefer create_list. 152 | create(:user, :trait) { |user| create :account, user: user } 153 | end 154 | RUBY 155 | 156 | expect_correction(<<~RUBY) 157 | create_list(:user, 3, :trait) { |user| create :account, user: user } 158 | RUBY 159 | end 160 | 161 | it 'flags usage of n.times with nested block arguments' do 162 | expect_offense(<<~RUBY) 163 | 3.times do 164 | ^^^^^^^ Prefer create_list. 165 | create(:user, :trait) do |user| 166 | create :account, user: user 167 | create :profile, user: user 168 | end 169 | end 170 | RUBY 171 | 172 | expect_correction(<<~RUBY) 173 | create_list(:user, 3, :trait) do |user| 174 | create :account, user: user 175 | create :profile, user: user 176 | end 177 | RUBY 178 | end 179 | 180 | it 'flags usage of n.times.map' do 181 | expect_offense(<<~RUBY) 182 | 3.times.map { create :user } 183 | ^^^^^^^^^^^ Prefer create_list. 184 | RUBY 185 | 186 | expect_correction(<<~RUBY) 187 | create_list :user, 3 188 | RUBY 189 | end 190 | 191 | it 'ignores n.times.map when create call does have method calls' do 192 | expect_no_offenses(<<~RUBY) 193 | 3.times.map { create :user, repositories_count: rand } 194 | RUBY 195 | end 196 | 197 | it 'flags usage of Array.new(n) with no arguments' do 198 | expect_offense(<<~RUBY) 199 | Array.new(3) { create(:user) } 200 | ^^^^^^^^^^^^ Prefer create_list. 201 | RUBY 202 | 203 | expect_correction(<<~RUBY) 204 | create_list(:user, 3) 205 | RUBY 206 | end 207 | 208 | it 'flags usage of Array.new(n) with block argument' do 209 | expect_offense(<<~RUBY) 210 | Array.new(3) do 211 | ^^^^^^^^^^^^ Prefer create_list. 212 | create(:user) { |user| create(:account, user: user) } 213 | end 214 | RUBY 215 | 216 | expect_correction(<<~RUBY) 217 | create_list(:user, 3) { |user| create(:account, user: user) } 218 | RUBY 219 | end 220 | 221 | context 'with empty array' do 222 | it 'does not register an offense' do 223 | expect_no_offenses(<<~RUBY) 224 | [] 225 | RUBY 226 | end 227 | end 228 | 229 | context 'with different `create` nodes in array' do 230 | it 'does not register an offense' do 231 | expect_no_offenses(<<~RUBY) 232 | [create(:user), create(:user, age: 18)] 233 | RUBY 234 | end 235 | end 236 | 237 | context 'with one `create` node in array' do 238 | it 'does not register an offense' do 239 | expect_no_offenses(<<~RUBY) 240 | [create(:user)] 241 | RUBY 242 | end 243 | end 244 | 245 | context 'with same `create` nodes in array' do 246 | it 'registers and corrects an offense' do 247 | expect_offense(<<~RUBY) 248 | [create(:user), create(:user)] 249 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer create_list. 250 | RUBY 251 | 252 | expect_correction(<<~RUBY) 253 | create_list(:user, 2) 254 | RUBY 255 | end 256 | end 257 | 258 | context 'with same `create` nodes in array with method calls' do 259 | it 'registers and corrects an offense' do 260 | expect_offense(<<~RUBY) 261 | [create(:user, point: rand), create(:user, point: rand)] 262 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer 2.times.map. 263 | RUBY 264 | 265 | expect_correction(<<~RUBY) 266 | 2.times.map { create(:user, point: rand) } 267 | RUBY 268 | end 269 | end 270 | 271 | context 'when ExplicitOnly is false' do 272 | let(:explicit_only) { false } 273 | 274 | it 'registers an offense when using n.times with no arguments ' \ 275 | 'and an explicit receiver' do 276 | expect_offense(<<~RUBY) 277 | 3.times { FactoryBot.create :user } 278 | ^^^^^^^ Prefer create_list. 279 | RUBY 280 | 281 | expect_correction(<<~RUBY) 282 | FactoryBot.create_list :user, 3 283 | RUBY 284 | end 285 | 286 | it 'registers an offense when using n.times with no arguments ' \ 287 | 'and no explicit receiver' do 288 | expect_offense(<<~RUBY) 289 | 3.times { create :user } 290 | ^^^^^^^ Prefer create_list. 291 | RUBY 292 | 293 | expect_correction(<<~RUBY) 294 | create_list :user, 3 295 | RUBY 296 | end 297 | end 298 | 299 | context 'when ExplicitOnly is true' do 300 | let(:explicit_only) { true } 301 | 302 | it 'registers an offense when using n.times with no arguments ' \ 303 | 'and an explicit receiver' do 304 | expect_offense(<<~RUBY) 305 | 3.times { FactoryBot.create :user } 306 | ^^^^^^^ Prefer create_list. 307 | RUBY 308 | 309 | expect_correction(<<~RUBY) 310 | FactoryBot.create_list :user, 3 311 | RUBY 312 | end 313 | 314 | it 'dose not register an offense when using n.times with no arguments ' \ 315 | 'and no explicit receiver' do 316 | expect_no_offenses(<<~RUBY) 317 | 3.times { create :user } 318 | RUBY 319 | end 320 | end 321 | end 322 | 323 | context 'when EnforcedStyle is :n_times' do 324 | let(:enforced_style) { :n_times } 325 | 326 | it 'flags usage of create_list' do 327 | expect_offense(<<~RUBY) 328 | create_list :user, 3 329 | ^^^^^^^^^^^ Prefer 3.times.map. 330 | RUBY 331 | 332 | expect_correction(<<~RUBY) 333 | 3.times.map { create :user } 334 | RUBY 335 | end 336 | 337 | it 'ignores create_list :user, 1' do 338 | expect_no_offenses(<<~RUBY) 339 | create_list :user, 1 340 | RUBY 341 | end 342 | 343 | it 'flags usage of create_list with argument' do 344 | expect_offense(<<~RUBY) 345 | create_list(:user, 3, :trait) 346 | ^^^^^^^^^^^ Prefer 3.times.map. 347 | RUBY 348 | 349 | expect_correction(<<~RUBY) 350 | 3.times.map { create(:user, :trait) } 351 | RUBY 352 | end 353 | 354 | it 'flags usage of create_list with keyword arguments' do 355 | expect_offense(<<~RUBY) 356 | create_list :user, 3, :trait, key: val 357 | ^^^^^^^^^^^ Prefer 3.times.map. 358 | RUBY 359 | 360 | expect_correction(<<~RUBY) 361 | 3.times.map { create :user, :trait, key: val } 362 | RUBY 363 | end 364 | 365 | it 'flags usage of FactoryGirl.create_list' do 366 | expect_offense(<<~RUBY) 367 | FactoryGirl.create_list :user, 3 368 | ^^^^^^^^^^^ Prefer 3.times.map. 369 | RUBY 370 | 371 | expect_correction(<<~RUBY) 372 | 3.times.map { FactoryGirl.create :user } 373 | RUBY 374 | end 375 | 376 | it 'flags usage of FactoryGirl.create_list with a block' do 377 | expect_offense(<<~RUBY) 378 | FactoryGirl.create_list(:user, 3) { |user| user.points = rand(1000) } 379 | ^^^^^^^^^^^ Prefer 3.times.map. 380 | RUBY 381 | 382 | expect_correction(<<~RUBY) 383 | 3.times.map { FactoryGirl.create(:user) { |user| user.points = rand(1000) } } 384 | RUBY 385 | end 386 | 387 | it 'ignores create method of other object' do 388 | expect_no_offenses(<<~RUBY) 389 | SomeFactory.create_list :user, 3 390 | RUBY 391 | end 392 | 393 | context 'when ExplicitOnly is false' do 394 | let(:explicit_only) { false } 395 | 396 | it 'registers an offense when using create_list ' \ 397 | 'with no arguments and an explicit receiver' do 398 | expect_offense(<<~RUBY) 399 | FactoryBot.create_list :user, 3 400 | ^^^^^^^^^^^ Prefer 3.times.map. 401 | RUBY 402 | 403 | expect_correction(<<~RUBY) 404 | 3.times.map { FactoryBot.create :user } 405 | RUBY 406 | end 407 | 408 | it 'registers an offense when using create_list ' \ 409 | 'with no arguments and no explicit receiver' do 410 | expect_offense(<<~RUBY) 411 | create_list :user, 3 412 | ^^^^^^^^^^^ Prefer 3.times.map. 413 | RUBY 414 | 415 | expect_correction(<<~RUBY) 416 | 3.times.map { create :user } 417 | RUBY 418 | end 419 | end 420 | 421 | context 'when ExplicitOnly is true' do 422 | let(:explicit_only) { true } 423 | 424 | it 'registers an offense when using create_list ' \ 425 | 'with no arguments and an explicit receiver' do 426 | expect_offense(<<~RUBY) 427 | FactoryBot.create_list :user, 3 428 | ^^^^^^^^^^^ Prefer 3.times.map. 429 | RUBY 430 | 431 | expect_correction(<<~RUBY) 432 | 3.times.map { FactoryBot.create :user } 433 | RUBY 434 | end 435 | 436 | it 'dose not register an offense when using create_list ' \ 437 | 'with no arguments and no explicit receiver' do 438 | expect_no_offenses(<<~RUBY) 439 | create_list :user, 3 440 | RUBY 441 | end 442 | end 443 | 444 | context 'when Ruby 2.7', :ruby27 do 445 | it 'ignores n.times with numblock' do 446 | expect_no_offenses(<<~RUBY) 447 | 3.times { create :user, position: _1 } 448 | RUBY 449 | end 450 | end 451 | 452 | context 'with same `create` nodes in array' do 453 | it 'registers and corrects an offense' do 454 | expect_offense(<<~RUBY) 455 | [create(:user), create(:user)] 456 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer 2.times.map. 457 | RUBY 458 | 459 | expect_correction(<<~RUBY) 460 | 2.times.map { create(:user) } 461 | RUBY 462 | end 463 | end 464 | 465 | context 'with same `create` nodes in array with method calls' do 466 | it 'registers and corrects an offense' do 467 | expect_offense(<<~RUBY) 468 | [create(:user, point: rand), create(:user, point: rand)] 469 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer 2.times.map. 470 | RUBY 471 | 472 | expect_correction(<<~RUBY) 473 | 2.times.map { create(:user, point: rand) } 474 | RUBY 475 | end 476 | end 477 | end 478 | end 479 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/excessive_create_list_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::ExcessiveCreateList do 4 | let(:cop_config) do 5 | { 'MaxAmount' => max_amount } 6 | end 7 | 8 | let(:max_amount) { 10 } 9 | 10 | it 'ignores code that does not contain create_list' do 11 | expect_no_offenses(<<~RUBY) 12 | expect(true).to be_truthy 13 | RUBY 14 | end 15 | 16 | it 'ignores create_list with non-integer value' do 17 | expect_no_offenses(<<~RUBY) 18 | create_list(:merge_requests, value) 19 | RUBY 20 | end 21 | 22 | it 'ignores create_list with less than 10 items' do 23 | expect_no_offenses(<<~RUBY) 24 | create_list(:merge_requests, 9) 25 | RUBY 26 | end 27 | 28 | it 'ignores create_list for 10 items' do 29 | expect_no_offenses(<<~RUBY) 30 | create_list(:merge_requests, 10) 31 | RUBY 32 | end 33 | 34 | it 'registers an offense for create_list for more than 10 items' do 35 | expect_offense(<<~RUBY) 36 | create_list(:merge_requests, 11) 37 | ^^ Avoid using `create_list` with more than 10 items. 38 | RUBY 39 | end 40 | 41 | it 'registers an offense for FactoryBot.create_list' do 42 | expect_offense(<<~RUBY) 43 | FactoryBot.create_list(:merge_requests, 11) 44 | ^^ Avoid using `create_list` with more than 10 items. 45 | RUBY 46 | end 47 | 48 | context 'when create_list has the factory name as a string' do 49 | it 'registers an offense' do 50 | expect_offense(<<~RUBY) 51 | FactoryBot.create_list('warehouse/user', 11) 52 | ^^ Avoid using `create_list` with more than 10 items. 53 | RUBY 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/factory_association_with_strategy_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::FactoryAssociationWithStrategy do 4 | context 'when passing a hardcoded strategy' do 5 | context 'when passing a `create` strategy' do 6 | it 'flags the strategy' do 7 | expect_offense(<<~RUBY) 8 | factory :foo, class: 'FOOO' do 9 | profile { create(:profile) } 10 | ^^^^^^^^^^^^^^^^ Use an implicit, explicit or inline definition instead of hard coding a strategy for setting association within factory. 11 | end 12 | RUBY 13 | end 14 | end 15 | 16 | context 'when passing a `build` strategy' do 17 | it 'flags the strategy' do 18 | expect_offense(<<~RUBY) 19 | factory :foo do 20 | profile { build(:profile) } 21 | ^^^^^^^^^^^^^^^ Use an implicit, explicit or inline definition instead of hard coding a strategy for setting association within factory. 22 | end 23 | RUBY 24 | end 25 | end 26 | 27 | context 'when passing a `build_stubbed` strategy' do 28 | it 'flags the strategy' do 29 | expect_offense(<<~RUBY) 30 | factory :foo do 31 | profile { build_stubbed(:profile) } 32 | ^^^^^^^^^^^^^^^^^^^^^^^ Use an implicit, explicit or inline definition instead of hard coding a strategy for setting association within factory. 33 | end 34 | RUBY 35 | end 36 | end 37 | 38 | context 'when passing an additional argument' do 39 | it 'flags the strategy' do 40 | expect_offense(<<~RUBY) 41 | factory :foo do 42 | profile { build_stubbed(:profile, :qualified) } 43 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use an implicit, explicit or inline definition instead of hard coding a strategy for setting association within factory. 44 | end 45 | RUBY 46 | end 47 | end 48 | 49 | context 'when having multiple hardcoded strategies' do 50 | it 'flags all the strategies' do 51 | expect_offense(<<~RUBY) 52 | factory :foo do 53 | profile { build_stubbed(:profile) } 54 | ^^^^^^^^^^^^^^^^^^^^^^^ Use an implicit, explicit or inline definition instead of hard coding a strategy for setting association within factory. 55 | 56 | area { create(:area) } 57 | ^^^^^^^^^^^^^ Use an implicit, explicit or inline definition instead of hard coding a strategy for setting association within factory. 58 | end 59 | RUBY 60 | end 61 | end 62 | 63 | context 'when inside a transient block' do 64 | # Using an association inside of the `transient` block is not supported, 65 | # as it would initialize the association as if it was outside of the 66 | # `transient` block. But if the referenced factory is backed by 67 | # `ActiveModel::Model` and declares `skip_create`, it can be used. 68 | # Otherwise, there is usually a better way than building a model 69 | # instance that is not directly referenced. 70 | it 'flags the strategy' do 71 | expect_offense(<<~RUBY) 72 | factory :foo do 73 | transient do 74 | profile { create(:profile, :qualified) } 75 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use an implicit, explicit or inline definition instead of hard coding a strategy for setting association within factory. 76 | account { association(:fiscal_year) } # No offense 77 | end 78 | end 79 | RUBY 80 | end 81 | end 82 | end 83 | 84 | context 'when passing a block who does not use strategy' do 85 | context 'when passing an inline association' do 86 | it 'does not flag' do 87 | expect_no_offenses(<<~RUBY) 88 | factory :foo do 89 | profile { association :profile } 90 | end 91 | RUBY 92 | end 93 | end 94 | 95 | context 'when passing an implicit association' do 96 | it 'does not flag' do 97 | expect_no_offenses(<<~RUBY) 98 | factory :foo do 99 | profile 100 | end 101 | RUBY 102 | end 103 | end 104 | 105 | context 'when passing an explicit association' do 106 | it 'does not flag' do 107 | expect_no_offenses(<<~RUBY) 108 | factory :foo do 109 | association :profile 110 | end 111 | RUBY 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/factory_class_name_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::FactoryClassName do 4 | context 'when passing block' do 5 | it 'flags passing a class' do 6 | expect_offense(<<~RUBY) 7 | factory :foo, class: Foo do 8 | ^^^ Pass 'Foo' string instead of `Foo` constant. 9 | end 10 | RUBY 11 | 12 | expect_correction(<<~RUBY) 13 | factory :foo, class: 'Foo' do 14 | end 15 | RUBY 16 | end 17 | 18 | it 'flags passing a class from global namespace' do 19 | expect_offense(<<~RUBY) 20 | factory :foo, class: ::Foo do 21 | ^^^^^ Pass 'Foo' string instead of `Foo` constant. 22 | end 23 | RUBY 24 | 25 | expect_correction(<<~RUBY) 26 | factory :foo, class: '::Foo' do 27 | end 28 | RUBY 29 | end 30 | 31 | it 'flags passing a subclass' do 32 | expect_offense(<<~RUBY) 33 | factory :foo, class: Foo::Bar do 34 | ^^^^^^^^ Pass 'Foo::Bar' string instead of `Foo::Bar` constant. 35 | end 36 | RUBY 37 | 38 | expect_correction(<<~RUBY) 39 | factory :foo, class: 'Foo::Bar' do 40 | end 41 | RUBY 42 | end 43 | 44 | it 'ignores passing class name' do 45 | expect_no_offenses(<<~RUBY) 46 | factory :foo, class: 'Foo' do 47 | end 48 | RUBY 49 | end 50 | 51 | it 'ignores passing Hash' do 52 | expect_no_offenses(<<~RUBY) 53 | factory :foo, class: Hash do 54 | end 55 | RUBY 56 | end 57 | 58 | it 'ignores passing OpenStruct' do 59 | expect_no_offenses(<<~RUBY) 60 | factory :foo, class: OpenStruct do 61 | end 62 | RUBY 63 | end 64 | end 65 | 66 | context 'when not passing block' do 67 | it 'flags passing a class' do 68 | expect_offense(<<~RUBY) 69 | factory :foo, class: Foo 70 | ^^^ Pass 'Foo' string instead of `Foo` constant. 71 | RUBY 72 | 73 | expect_correction(<<~RUBY) 74 | factory :foo, class: 'Foo' 75 | RUBY 76 | end 77 | 78 | it 'ignores passing class name' do 79 | expect_no_offenses(<<~RUBY) 80 | factory :foo, class: 'Foo' 81 | RUBY 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/factory_name_style_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::FactoryNameStyle do 4 | let(:cop_config) do 5 | { 'EnforcedStyle' => enforced_style, 'ExplicitOnly' => explicit_only } 6 | end 7 | let(:explicit_only) { false } 8 | 9 | context 'when EnforcedStyle is :symbol' do 10 | let(:enforced_style) { :symbol } 11 | 12 | it 'registers an offense when using `create` with string name' do 13 | expect_offense(<<~RUBY) 14 | create('user') 15 | ^^^^^^ Use symbol to refer to a factory. 16 | RUBY 17 | 18 | expect_correction(<<~RUBY) 19 | create(:user) 20 | RUBY 21 | end 22 | 23 | it 'registers an offense when using `create` with string name and ' \ 24 | 'multiline method calls' do 25 | expect_offense(<<~RUBY) 26 | create('user', 27 | ^^^^^^ Use symbol to refer to a factory. 28 | username: "PETER", 29 | peter: "USERNAME") 30 | RUBY 31 | 32 | expect_correction(<<~RUBY) 33 | create(:user, 34 | username: "PETER", 35 | peter: "USERNAME") 36 | RUBY 37 | end 38 | 39 | it 'registers an offense when using `build` with string name' do 40 | expect_offense(<<~RUBY) 41 | build 'user' 42 | ^^^^^^ Use symbol to refer to a factory. 43 | RUBY 44 | 45 | expect_correction(<<~RUBY) 46 | build :user 47 | RUBY 48 | end 49 | 50 | it 'registers an offense when using `create` with an explicit receiver' do 51 | expect_offense(<<~RUBY) 52 | FactoryBot.create('user') 53 | ^^^^^^ Use symbol to refer to a factory. 54 | RUBY 55 | 56 | expect_correction(<<~RUBY) 57 | FactoryBot.create(:user) 58 | RUBY 59 | end 60 | 61 | it 'does not register an offense when using `create` with symbol name`' do 62 | expect_no_offenses(<<~RUBY) 63 | create(:user) 64 | RUBY 65 | end 66 | 67 | it 'does not register an offense when using `build` with symbol name`' do 68 | expect_no_offenses(<<~RUBY) 69 | build(:user) 70 | RUBY 71 | end 72 | 73 | it 'does not register an offense when using `create` ' \ 74 | 'with string interpolation name`' do 75 | expect_no_offenses(<<~RUBY) 76 | create("user_\#{type}") 77 | RUBY 78 | end 79 | 80 | it 'does not register an offense when using `build` ' \ 81 | 'with string interpolation name`' do 82 | expect_no_offenses(<<~RUBY) 83 | build("user_\#{'a'}") 84 | RUBY 85 | end 86 | 87 | it 'does not register an offense when using `create` ' \ 88 | 'with keyword argument' do 89 | expect_no_offenses(<<~RUBY) 90 | create user: :foo 91 | RUBY 92 | end 93 | 94 | it 'does not register an offense when using `build` ' \ 95 | 'with keyword argument' do 96 | expect_no_offenses(<<~RUBY) 97 | build user: :foo 98 | RUBY 99 | end 100 | 101 | it 'does not register an offense when using `create` ' \ 102 | 'with a method call when string include /' do 103 | expect_no_offenses(<<~RUBY) 104 | create("users/internal") 105 | RUBY 106 | end 107 | end 108 | 109 | context 'when EnforcedStyle is :string' do 110 | let(:enforced_style) { :string } 111 | 112 | it 'registers an offense when using `create` with symbol name' do 113 | expect_offense(<<~RUBY) 114 | create(:user) 115 | ^^^^^ Use string to refer to a factory. 116 | RUBY 117 | 118 | expect_correction(<<~RUBY) 119 | create("user") 120 | RUBY 121 | end 122 | 123 | it 'registers an offense when using `create` with symbol name and ' \ 124 | 'multiline method calls' do 125 | expect_offense(<<~RUBY) 126 | create(:user, 127 | ^^^^^ Use string to refer to a factory. 128 | username: "PETER", 129 | peter: "USERNAME") 130 | RUBY 131 | 132 | expect_correction(<<~RUBY) 133 | create("user", 134 | username: "PETER", 135 | peter: "USERNAME") 136 | RUBY 137 | end 138 | 139 | it 'registers an offense when using `build` with symbol name' do 140 | expect_offense(<<~RUBY) 141 | build :user 142 | ^^^^^ Use string to refer to a factory. 143 | RUBY 144 | 145 | expect_correction(<<~RUBY) 146 | build "user" 147 | RUBY 148 | end 149 | 150 | it 'registers an offense when using `create` with an explicit receiver' do 151 | expect_offense(<<~RUBY) 152 | FactoryBot.create(:user) 153 | ^^^^^ Use string to refer to a factory. 154 | RUBY 155 | 156 | expect_correction(<<~RUBY) 157 | FactoryBot.create("user") 158 | RUBY 159 | end 160 | 161 | it 'registers an offense when using `create` with a method call' do 162 | expect_offense(<<~RUBY) 163 | do_something create(:user) 164 | ^^^^^ Use string to refer to a factory. 165 | RUBY 166 | 167 | expect_correction(<<~RUBY) 168 | do_something create("user") 169 | RUBY 170 | end 171 | 172 | it 'registers an offense when using `build` with a method call' do 173 | expect_offense(<<~RUBY) 174 | do_something build(:user) 175 | ^^^^^ Use string to refer to a factory. 176 | RUBY 177 | 178 | expect_correction(<<~RUBY) 179 | do_something build("user") 180 | RUBY 181 | end 182 | 183 | it 'does not register an offense when using `create` with string name`' do 184 | expect_no_offenses(<<~RUBY) 185 | create('user') 186 | RUBY 187 | end 188 | 189 | it 'does not register an offense when using `build` with string name`' do 190 | expect_no_offenses(<<~RUBY) 191 | build('user') 192 | RUBY 193 | end 194 | 195 | it 'does not register an offense when using `create` ' \ 196 | 'with a local variable' do 197 | expect_no_offenses(<<~RUBY) 198 | create(user) 199 | RUBY 200 | end 201 | 202 | it 'does not register an offense when using `build` ' \ 203 | 'with a local variable' do 204 | expect_no_offenses(<<~RUBY) 205 | build(user) 206 | RUBY 207 | end 208 | 209 | it 'does not register an offense when using `create` ' \ 210 | 'with string interpolation name`' do 211 | expect_no_offenses(<<~RUBY) 212 | create("user_\#{type}") 213 | RUBY 214 | end 215 | 216 | it 'does not register an offense when using `build` ' \ 217 | 'with string interpolation name`' do 218 | expect_no_offenses(<<~RUBY) 219 | build("user_\#{'a'}") 220 | RUBY 221 | end 222 | 223 | it 'does not register an offense when using `create` ' \ 224 | 'with keyword argument' do 225 | expect_no_offenses(<<~RUBY) 226 | create user: :foo 227 | RUBY 228 | end 229 | 230 | it 'does not register an offense when using `build` ' \ 231 | 'with keyword argument' do 232 | expect_no_offenses(<<~RUBY) 233 | build user: :foo 234 | RUBY 235 | end 236 | end 237 | 238 | context 'when ExplicitOnly is false' do 239 | let(:enforced_style) { :symbol } 240 | let(:explicit_only) { false } 241 | 242 | it 'registers an offense when using `create` ' \ 243 | 'with string name and an explicit receiver' do 244 | expect_offense(<<~RUBY) 245 | FactoryBot.create('user') 246 | ^^^^^^ Use symbol to refer to a factory. 247 | RUBY 248 | 249 | expect_correction(<<~RUBY) 250 | FactoryBot.create(:user) 251 | RUBY 252 | end 253 | 254 | it 'registers an offense when using `create` ' \ 255 | 'with string name and no explicit receiver' do 256 | expect_offense(<<~RUBY) 257 | create('user') 258 | ^^^^^^ Use symbol to refer to a factory. 259 | RUBY 260 | 261 | expect_correction(<<~RUBY) 262 | create(:user) 263 | RUBY 264 | end 265 | end 266 | 267 | context 'when ExplicitOnly is true' do 268 | let(:enforced_style) { :symbol } 269 | let(:explicit_only) { true } 270 | 271 | it 'registers an offense when using `create` ' \ 272 | 'with string name and an explicit receiver' do 273 | expect_offense(<<~RUBY) 274 | FactoryBot.create('user') 275 | ^^^^^^ Use symbol to refer to a factory. 276 | RUBY 277 | 278 | expect_correction(<<~RUBY) 279 | FactoryBot.create(:user) 280 | RUBY 281 | end 282 | 283 | it 'dose not register an offense when using `create` ' \ 284 | 'with string name and no explicit receiver' do 285 | expect_no_offenses(<<~RUBY) 286 | create('user') 287 | RUBY 288 | end 289 | end 290 | end 291 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/id_sequence_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::IdSequence do 4 | it 'registers an offense with no block' do 5 | expect_offense(<<~RUBY) 6 | FactoryBot.define do 7 | factory :post do 8 | summary { "A summary" } 9 | sequence :id 10 | ^^^^^^^^^^^^ Do not create a sequence for an id attribute 11 | title { "A title" } 12 | end 13 | end 14 | RUBY 15 | 16 | expect_correction(<<~RUBY) 17 | FactoryBot.define do 18 | factory :post do 19 | summary { "A summary" } 20 | title { "A title" } 21 | end 22 | end 23 | RUBY 24 | end 25 | 26 | it 'registers an offense with a default value' do 27 | expect_offense(<<~RUBY) 28 | FactoryBot.define do 29 | factory :post do 30 | summary { "A summary" } 31 | sequence(:id, 1000) 32 | ^^^^^^^^^^^^^^^^^^^ Do not create a sequence for an id attribute 33 | title { "A title" } 34 | end 35 | end 36 | RUBY 37 | 38 | expect_correction(<<~RUBY) 39 | FactoryBot.define do 40 | factory :post do 41 | summary { "A summary" } 42 | title { "A title" } 43 | end 44 | end 45 | RUBY 46 | end 47 | 48 | it 'registers an offense across multiple lines' do 49 | expect_offense(<<~RUBY) 50 | FactoryBot.define do 51 | factory :post do 52 | summary { "A summary" } 53 | sequence( 54 | ^^^^^^^^^ Do not create a sequence for an id attribute 55 | :id, 56 | 1000 57 | ) 58 | title { "A title" } 59 | end 60 | end 61 | RUBY 62 | 63 | expect_correction(<<~RUBY) 64 | FactoryBot.define do 65 | factory :post do 66 | summary { "A summary" } 67 | title { "A title" } 68 | end 69 | end 70 | RUBY 71 | end 72 | 73 | it 'registers an offense with a Enumerable of values' do 74 | expect_offense(<<~RUBY) 75 | FactoryBot.define do 76 | factory :post do 77 | summary { "A summary" } 78 | sequence(:id, (1..10).cycle) 79 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not create a sequence for an id attribute 80 | title { "A title" } 81 | end 82 | end 83 | RUBY 84 | 85 | expect_correction(<<~RUBY) 86 | FactoryBot.define do 87 | factory :post do 88 | summary { "A summary" } 89 | title { "A title" } 90 | end 91 | end 92 | RUBY 93 | end 94 | 95 | it 'does not register an offense for a non-id sequence' do 96 | expect_no_offenses(<<~RUBY) 97 | FactoryBot.define do 98 | factory :post do 99 | summary { "A summary" } 100 | sequence :something_else 101 | title { "A title" } 102 | end 103 | end 104 | RUBY 105 | end 106 | 107 | it 'does not register an offense for a `sequence` with non-symbol argment' do 108 | expect_no_offenses(<<~RUBY) 109 | FactoryBot.define do 110 | sequence(id) 111 | end 112 | RUBY 113 | end 114 | 115 | it 'does not register an offense for a `sequence` without argument' do 116 | expect_no_offenses(<<~RUBY) 117 | FactoryBot.define do 118 | sequence 119 | end 120 | RUBY 121 | end 122 | 123 | it 'does not register an offense for a `sequence` with receiver' do 124 | expect_no_offenses(<<~RUBY) 125 | FactoryBot.define do 126 | foo.sequence(:id) 127 | end 128 | RUBY 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/redundant_factory_option_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::RedundantFactoryOption do 4 | context 'when `association` has no factory option' do 5 | it 'registers no offense' do 6 | expect_no_offenses(<<~RUBY) 7 | association :user 8 | RUBY 9 | end 10 | end 11 | 12 | context 'when `association` has no factory option but other option' do 13 | it 'registers no offense' do 14 | expect_no_offenses(<<~RUBY) 15 | association :user, strtaegy: :build 16 | RUBY 17 | end 18 | end 19 | 20 | context 'when `association` has non-redundant factory option' do 21 | it 'registers no offense' do 22 | expect_no_offenses(<<~RUBY) 23 | association :author, factory: :user 24 | RUBY 25 | end 26 | end 27 | 28 | context 'when `association` has non-redundant factory option in Array' do 29 | it 'registers no offense' do 30 | expect_no_offenses(<<~RUBY) 31 | association :user, factory: %i[user admin] 32 | RUBY 33 | end 34 | end 35 | 36 | context 'when `association` has redundant factory option' do 37 | it 'registers offense' do 38 | expect_offense(<<~RUBY) 39 | association :user, factory: :user 40 | ^^^^^^^^^^^^^^ Remove redundant `factory` option. 41 | RUBY 42 | 43 | expect_correction(<<~RUBY) 44 | association :user 45 | RUBY 46 | end 47 | end 48 | 49 | context 'when `association` has redundant factory option in Array' do 50 | it 'registers offense' do 51 | expect_offense(<<~RUBY) 52 | association :user, factory: %i[user] 53 | ^^^^^^^^^^^^^^^^^ Remove redundant `factory` option. 54 | RUBY 55 | 56 | expect_correction(<<~RUBY) 57 | association :user 58 | RUBY 59 | end 60 | end 61 | 62 | context 'when `association` has redundant factory option with traits' do 63 | it 'registers offense' do 64 | expect_offense(<<~RUBY) 65 | association :user, :admin, factory: :user 66 | ^^^^^^^^^^^^^^ Remove redundant `factory` option. 67 | RUBY 68 | 69 | expect_correction(<<~RUBY) 70 | association :user, :admin 71 | RUBY 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/rubocop/cop/factory_bot/syntax_methods_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RuboCop::Cop::FactoryBot::SyntaxMethods do 4 | described_class::RESTRICT_ON_SEND.each do |method| 5 | it 'does not register an offense when used outside an example group' do 6 | expect_no_offenses(<<~RUBY) 7 | FactoryBot.#{method}(:bar) 8 | RUBY 9 | end 10 | 11 | it "does not register an offense for `#{method}`" do 12 | expect_no_offenses(<<~RUBY) 13 | RSpec.describe Foo do 14 | let(:bar) { #{method}(:bar) } 15 | end 16 | RUBY 17 | end 18 | 19 | it "registers an offense for `FactoryBot.#{method}`" do 20 | expect_offense(<<~RUBY, method: method) 21 | describe Foo do 22 | let(:bar) { FactoryBot.%{method}(:bar) } 23 | ^^^^^^^^^^^^{method} Use `%{method}` from `FactoryBot::Syntax::Methods`. 24 | end 25 | RUBY 26 | 27 | expect_correction(<<~RUBY) 28 | describe Foo do 29 | let(:bar) { #{method}(:bar) } 30 | end 31 | RUBY 32 | end 33 | 34 | it "registers an offense for `FactoryBot.#{method}` in a shared group" do 35 | expect_offense(<<~RUBY, method: method) 36 | shared_examples_for Foo do 37 | let(:bar) { FactoryBot.%{method}(:bar) } 38 | ^^^^^^^^^^^^{method} Use `%{method}` from `FactoryBot::Syntax::Methods`. 39 | end 40 | RUBY 41 | 42 | expect_correction(<<~RUBY) 43 | shared_examples_for Foo do 44 | let(:bar) { #{method}(:bar) } 45 | end 46 | RUBY 47 | end 48 | 49 | it "registers an offense for `::FactoryBot.#{method}`" do 50 | expect_offense(<<~RUBY, method: method) 51 | RSpec.describe Foo do 52 | let(:bar) { ::FactoryBot.%{method}(:bar) } 53 | ^^^^^^^^^^^^^^{method} Use `%{method}` from `FactoryBot::Syntax::Methods`. 54 | end 55 | RUBY 56 | 57 | expect_correction(<<~RUBY) 58 | RSpec.describe Foo do 59 | let(:bar) { #{method}(:bar) } 60 | end 61 | RUBY 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/rubocop/factory_bot/config_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubocop/factory_bot/config_formatter' 4 | 5 | RSpec.describe RuboCop::FactoryBot::ConfigFormatter do 6 | let(:config) do 7 | { 8 | 'AllCops' => { 9 | 'Setting' => 'forty two' 10 | }, 11 | 'FactoryBot/Foo' => { 12 | 'Config' => 2, 13 | 'Enabled' => true 14 | }, 15 | 'FactoryBot/Bar' => { 16 | 'Enabled' => true, 17 | 'Nullable' => nil 18 | }, 19 | 'FactoryBot/Baz' => { 20 | 'Enabled' => true, 21 | 'StyleGuide' => '#buzz' 22 | } 23 | } 24 | end 25 | 26 | let(:descriptions) do 27 | { 28 | 'FactoryBot/Foo' => { 29 | 'Description' => 'Blah' 30 | }, 31 | 'FactoryBot/Bar' => { 32 | 'Description' => 'Wow' 33 | }, 34 | 'FactoryBot/Baz' => { 35 | 'Description' => 'Woof' 36 | } 37 | } 38 | end 39 | 40 | it 'builds a YAML dump with spacing between cops' do 41 | formatter = described_class.new(config, descriptions) 42 | 43 | expect(formatter.dump).to eql(<<~YAML) 44 | --- 45 | AllCops: 46 | Setting: forty two 47 | 48 | FactoryBot/Foo: 49 | Config: 2 50 | Enabled: true 51 | Description: Blah 52 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/Foo 53 | 54 | FactoryBot/Bar: 55 | Enabled: true 56 | Nullable: ~ 57 | Description: Wow 58 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/Bar 59 | 60 | FactoryBot/Baz: 61 | Enabled: true 62 | StyleGuide: "#buzz" 63 | Description: Woof 64 | Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/Baz 65 | YAML 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/rubocop/factory_bot/description_extractor_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yard' 4 | 5 | require 'rubocop/factory_bot/description_extractor' 6 | 7 | RSpec.describe RuboCop::FactoryBot::DescriptionExtractor do 8 | let(:yardocs) do 9 | YARD.parse_string(<<~RUBY) 10 | # This is not a cop 11 | class RuboCop::Cop::Mixin::Sneaky 12 | end 13 | 14 | # This is not a concrete cop 15 | # 16 | # @abstract 17 | class RuboCop::Cop::FactoryBot::Base 18 | end 19 | 20 | # Checks foo 21 | # 22 | # Some description 23 | # 24 | # @note only works with foo 25 | class RuboCop::Cop::FactoryBot::Foo < RuboCop::Cop::Base 26 | # Hello 27 | def bar 28 | end 29 | 30 | # :nodoc: 31 | class HelperClassForFoo 32 | end 33 | end 34 | 35 | class RuboCop::Cop::FactoryBot::Undocumented < RuboCop::Cop::Base 36 | # Hello 37 | def bar 38 | end 39 | end 40 | RUBY 41 | 42 | YARD::Registry.all(:class) 43 | end 44 | 45 | let(:temp_class) do 46 | temp = RuboCop::Cop::Base.dup 47 | temp.class_exec do 48 | class << self 49 | undef inherited 50 | def inherited(*) end # rubocop:disable Lint/MissingSuper 51 | end 52 | end 53 | temp 54 | end 55 | 56 | def stub_cop_const(name) 57 | stub_const("RuboCop::Cop::FactoryBot::#{name}", Class.new(temp_class)) 58 | end 59 | 60 | before do 61 | stub_cop_const('Foo') 62 | stub_cop_const('Undocumented') 63 | end 64 | 65 | it 'builds a hash of descriptions' do 66 | expect(described_class.new(yardocs).to_h).to eql( 67 | 'FactoryBot/Foo' => { 'Description' => 'Checks foo' }, 68 | 'FactoryBot/Undocumented' => { 'Description' => '' } 69 | ) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubocop' 4 | require 'rubocop/rspec/support' # `expect_offense` etc 5 | 6 | require 'simplecov' unless ENV['NO_COVERAGE'] 7 | 8 | module SpecHelper 9 | ROOT = Pathname.new(__dir__).parent.freeze 10 | end 11 | 12 | spec_helper_glob = 13 | '{support,shared,../lib/rubocop/factory_bot/shared_contexts}/*.rb' 14 | Dir 15 | .glob(File.expand_path(spec_helper_glob, __dir__)) 16 | .sort 17 | .each { |file| require file } 18 | 19 | RSpec.configure do |config| 20 | # Set metadata so smoke tests are run on all cop specs 21 | config.define_derived_metadata(file_path: %r{/spec/rubocop/cop/}) do |meta| 22 | meta[:type] = :cop_spec 23 | end 24 | 25 | # Include config shared context for all cop specs 26 | config.define_derived_metadata(type: :cop_spec) do |meta| 27 | meta[:config] = true 28 | end 29 | 30 | config.order = :random 31 | 32 | # Run focused tests with `fdescribe`, `fit`, `:focus` etc. 33 | config.filter_run_when_matching :focus 34 | 35 | if ENV['PARSER_ENGINE'] == 'parser_prism' 36 | config.filter_run_excluding broken_on: :prism 37 | end 38 | 39 | # We should address configuration warnings when we upgrade 40 | config.raise_errors_for_deprecations! 41 | 42 | # RSpec gives helpful warnings when you are doing something wrong. 43 | # We should take their advice! 44 | config.raise_on_warning = true 45 | end 46 | 47 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 48 | 49 | require 'rubocop-factory_bot' 50 | -------------------------------------------------------------------------------- /tasks/cops_documentation.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubocop' 4 | require 'rubocop-factory_bot' 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[FactoryBot], 17 | plugin_name: 'rubocop-factory_bot' 18 | ) 19 | generator.call 20 | end 21 | 22 | desc 'Syntax check for the documentation comments' 23 | task documentation_syntax_check: :yard_for_generate_documentation do 24 | require 'parser/ruby25' 25 | 26 | ok = true 27 | YARD::Registry.load! 28 | cops = RuboCop::Cop::Registry.global 29 | cops.each do |cop| 30 | examples = YARD::Registry.all(:class).find do |code_object| 31 | next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge 32 | 33 | break code_object.tags('example') 34 | end 35 | 36 | examples.to_a.each do |example| 37 | buffer = Parser::Source::Buffer.new('', 1) 38 | buffer.source = example.text 39 | parser = Parser::Ruby25.new(RuboCop::AST::Builder.new) 40 | parser.diagnostics.all_errors_are_fatal = true 41 | parser.parse(buffer) 42 | rescue Parser::SyntaxError => e 43 | path = example.object.file 44 | puts "#{path}: Syntax Error in an example. #{e}" 45 | ok = false 46 | end 47 | end 48 | abort unless ok 49 | end 50 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------