diff --git a/.circleci/config.yml b/.circleci/config.yml index 1f0dded2..d770e773 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,13 +25,12 @@ jobs: circleci step halt fi - run: make image - - run: echo "$GCR_JSON_KEY" | docker login -u _json_key --password-stdin us.gcr.io + - run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - run: name: Push image to GCR command: | - docker tag $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME \ - us.gcr.io/code-climate/codeclimate-rubocop:b$CIRCLE_BUILD_NUM - docker push us.gcr.io/code-climate/codeclimate-rubocop:b$CIRCLE_BUILD_NUM + make release RELEASE_TAG="b$CIRCLE_BUILD_NUM" + make release RELEASE_TAG="$(echo $CIRCLE_BRANCH | grep -oP 'channel/\K[\w\-]+')" workflows: version: 2 @@ -39,6 +38,7 @@ workflows: jobs: - test - release_images: + context: Quality requires: - test filters: @@ -46,4 +46,4 @@ workflows: only: /master|channel\/[\w-]+/ notify: webhooks: - - url: https://cc-slack-proxy.herokuapp.com/circle \ No newline at end of file + - url: https://cc-slack-proxy.herokuapp.com/circle diff --git a/.gitignore b/.gitignore index e69de29b..9c7fbda1 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,3 @@ +# Ignore bundler config. +/.bundle +/vendor/bundle diff --git a/.rubocop.yml b/.rubocop.yml index af4241ce..510e51f2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,18 +2,21 @@ inherit_from: base_rubocop.yml require: rubocop-rspec -Style/FileName: +Layout/MultilineOperationIndentation: + Enabled: false + +Naming/FileName: Exclude: - 'bin/codeclimate-rubocop' Style/StringLiterals: Enabled: false -Style/TrailingCommaInLiteral: +Style/TrailingCommaInArrayLiteral: Enabled: false -Style/TrailingCommaInArguments: +Style/TrailingCommaInHashLiteral: Enabled: false -Style/MultilineOperationIndentation: +Style/TrailingCommaInArguments: Enabled: false diff --git a/.ruby-version b/.ruby-version index 0bee604d..37c2961c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.3 +2.7.2 diff --git a/DEVELOPERS.md b/DEVELOPERS.md index fb7b423c..68b34df8 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -1,4 +1,4 @@ -Information for engine developers & contributors +# Information for engine developers & contributors ## Testing local changes @@ -13,7 +13,7 @@ against real repos using the Code Climate CLI. If you are upgrading to a new version of RuboCop, please re-run the doc scraper to get any new or updated docs: -``` +```sh docker run \ --rm --volume "$PWD:/usr/src/app" --user root --workdir /usr/src/app \ codeclimate/codeclimate-rubocop bundle exec rake docs:scrape @@ -26,7 +26,7 @@ You should add any new cops without docs that fail specs to When doing QA, it's good to confirm the engine will run successfully when using the default inferred configuration: -``` +```sh rm .rubocop.yml codeclimate init --upgrade codeclimate analyze -e rubocop diff --git a/Dockerfile b/Dockerfile index 2f4cdb6b..dbeeca17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,7 @@ -FROM ruby:2.4-alpine +FROM ruby:2.7-alpine3.11 + +LABEL name="Ruby-2.7" \ + version="1.0" WORKDIR /usr/src/app @@ -6,12 +9,12 @@ RUN adduser -u 9000 -D app COPY Gemfile Gemfile.lock /usr/src/app/ -RUN apk add --update build-base && \ - gem install bundler && \ - bundle install --quiet -j 4 --without=test && \ +RUN apk add --update build-base git && \ + gem install bundler -v 2.0.2 && \ + bundle install --quiet -j 4 && \ chown -R app:app /usr/local/bundle && \ rm -fr ~/.gem ~/.bundle ~/.wh..gem && \ - apk del build-base + gem cleanup COPY . /usr/src/app RUN chown -R app:app . diff --git a/Gemfile b/Gemfile index 6ca59850..7e0b4966 100644 --- a/Gemfile +++ b/Gemfile @@ -3,13 +3,23 @@ source 'https://rubygems.org' gem "activesupport", require: false -gem "mry", "~> 0.52.0", require: false -gem "parser", "~> 2.4.0" +gem "mry", require: false +gem "parser" gem "pry", require: false -gem "rubocop", "~> 0.52.1", require: false -gem "rubocop-migrations", require: false +gem "rubocop", "1.18.3", require: false +gem "rubocop-i18n", require: false +gem "rubocop-graphql", require: false +gem "rubocop-minitest", require: false +gem "rubocop-performance", require: false +gem "rubocop-rails", require: false +gem "rubocop-rake", require: false gem "rubocop-rspec", require: false +gem "rubocop-sequel", require: false +gem "rubocop-sorbet", require: false +gem "rubocop-thread_safety", require: false gem "safe_yaml" +gem "syntax_tree", require: false +gem "test-prof", require: false group :test do gem "rake" diff --git a/Gemfile.lock b/Gemfile.lock index 6231ee3c..de0e374a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,75 +1,116 @@ GEM remote: https://rubygems.org/ specs: - activesupport (5.1.4) + activesupport (6.1.4) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) - minitest (~> 5.1) - tzinfo (~> 1.1) - ast (2.3.0) - coderay (1.1.2) - concurrent-ruby (1.0.5) - diff-lcs (1.3) - i18n (0.9.1) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + ast (2.4.2) + coderay (1.1.3) + concurrent-ruby (1.1.9) + diff-lcs (1.4.4) + i18n (1.8.10) concurrent-ruby (~> 1.0) - method_source (0.9.0) - minitest (5.10.3) - mry (0.52.0.0) + method_source (1.0.0) + minitest (5.14.4) + mry (0.78.0.0) rubocop (>= 0.41.0) - parallel (1.12.1) - parser (2.4.0.2) - ast (~> 2.3) - powerpack (0.1.1) - pry (0.11.3) - coderay (~> 1.1.0) - method_source (~> 0.9.0) + parallel (1.20.1) + parser (3.0.1.1) + ast (~> 2.4.1) + prettier_print (1.2.1) + pry (0.14.1) + coderay (~> 1.1) + method_source (~> 1.0) + rack (2.2.3) rainbow (3.0.0) - rake (12.3.0) - rspec (3.7.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-core (3.7.0) - rspec-support (~> 3.7.0) - rspec-expectations (3.7.0) + rake (13.0.4) + regexp_parser (2.1.1) + rexml (3.2.5) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-mocks (3.7.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-support (3.7.0) - rubocop (0.52.1) + rspec-support (~> 3.10.0) + rspec-support (3.10.2) + rubocop (1.18.3) parallel (~> 1.10) - parser (>= 2.4.0.2, < 3.0) - powerpack (~> 0.1) + parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.7.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-migrations (0.1.2) - rubocop (~> 0.41) - rubocop-rspec (1.21.0) - rubocop (>= 0.52.0) - ruby-progressbar (1.9.0) - safe_yaml (1.0.4) - thread_safe (0.3.6) - tzinfo (1.2.4) - thread_safe (~> 0.1) - unicode-display_width (1.3.0) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.7.0) + parser (>= 3.0.1.1) + rubocop-graphql (0.9.0) + rubocop (>= 0.87, < 2) + rubocop-i18n (3.0.0) + rubocop (~> 1.0) + rubocop-minitest (0.14.0) + rubocop (>= 0.90, < 2.0) + rubocop-performance (1.11.3) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rails (2.11.2) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.7.0, < 2.0) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-rspec (2.4.0) + rubocop (~> 1.0) + rubocop-ast (>= 1.1.0) + rubocop-sequel (0.2.0) + rubocop (~> 1.0) + rubocop-sorbet (0.6.2) + rubocop + rubocop-thread_safety (0.4.2) + rubocop (>= 0.53.0) + ruby-progressbar (1.11.0) + safe_yaml (1.0.5) + syntax_tree (6.1.1) + prettier_print (>= 1.2.0) + test-prof (1.0.6) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + unicode-display_width (2.0.0) + zeitwerk (2.4.2) PLATFORMS ruby DEPENDENCIES activesupport - mry (~> 0.52.0) - parser (~> 2.4.0) + mry + parser pry rake rspec - rubocop (~> 0.52.1) - rubocop-migrations + rubocop (= 1.18.3) + rubocop-graphql + rubocop-i18n + rubocop-minitest + rubocop-performance + rubocop-rails + rubocop-rake rubocop-rspec + rubocop-sequel + rubocop-sorbet + rubocop-thread_safety safe_yaml + syntax_tree + test-prof BUNDLED WITH - 1.16.1 + 2.1.4 diff --git a/Makefile b/Makefile index a05b627a..8d97838d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ -.PHONY: image test docs bundle +.PHONY: image test docs bundle release IMAGE_NAME ?= codeclimate/codeclimate-rubocop +RELEASE_REGISTRY ?= codeclimate + +ifndef RELEASE_TAG +override RELEASE_TAG = latest +endif image: docker build --rm -t $(IMAGE_NAME) . @@ -19,4 +24,8 @@ bundle: docker run --rm \ --entrypoint /bin/sh \ --volume $(PWD):/usr/src/app \ - $(IMAGE_NAME) -c "cd /usr/src/app && bundle $(BUNDLE_ARGS)" \ No newline at end of file + $(IMAGE_NAME) -c "cd /usr/src/app && bundle $(BUNDLE_ARGS)" + +release: + docker tag $(IMAGE_NAME) $(RELEASE_REGISTRY)/codeclimate-rubocop:$(RELEASE_TAG) + docker push $(RELEASE_REGISTRY)/codeclimate-rubocop:$(RELEASE_TAG) diff --git a/README.md b/README.md index e9132e77..d28f7466 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [](https://codeclimate.com/github/codeclimate/codeclimate-rubocop) -`codeclimate-rubocop` is a Code Climate engine that wraps the [RuboCop](https://github.com/bbatsov/rubocop) static analysis tool. You can run it on your command line using the Code Climate CLI, or on our hosted analysis platform. +`codeclimate-rubocop` is a Code Climate engine that wraps the [RuboCop](https://github.com/rubocop-hq/rubocop) static analysis tool. You can run it on your command line using the Code Climate CLI, or on our hosted analysis platform. -RuboCop helps you enforce many of the guidelines outlined in the community [Ruby Style Guide](https://github.com/bbatsov/ruby-style-guide). Most aspects of its behavior can be tweaked via various [configuration options](https://github.com/bbatsov/rubocop/blob/master/config/default.yml), which are set in a **.rubocop.yml** file. +RuboCop helps you enforce many of the guidelines outlined in the community [Ruby Style Guide](https://github.com/rubocop-hq/ruby-style-guide). Most aspects of its behavior can be tweaked via various [configuration options](https://github.com/rubocop-hq/rubocop/blob/master/config/default.yml), which are set in a **.rubocop.yml** file. You can find some basic setup instructions and links to the RuboCop OSS project below. For additional Code Climate-specific config details, check out our [RuboCop engine documentation][cc-docs-rubocop]. @@ -16,7 +16,7 @@ You can find some basic setup instructions and links to the RuboCop OSS project ### Need help? -For help with RuboCop, [check out their documentation](https://github.com/bbatsov/rubocop). +For help with RuboCop, [check out their documentation](https://github.com/rubocop-hq/rubocop). If you're running into a Code Climate issue, first check out [our RuboCop engine docs][cc-docs-rubocop] and look over this project's [GitHub Issues](https://github.com/codeclimate/codeclimate-rubocop/issues), as your question may have already been covered. If not, [go ahead and open a support ticket with us](https://codeclimate.com/help). diff --git a/base_rubocop.yml b/base_rubocop.yml index de75ed56..a5305d0d 100644 --- a/base_rubocop.yml +++ b/base_rubocop.yml @@ -1,22 +1,43 @@ ################################################################################ -# Metrics +# Layout ################################################################################ -Metrics/LineLength: +Layout/LineLength: Enabled: false +# Disallow extra spacing for token alignment +Layout/ExtraSpacing: + AllowForAlignment: false + +Layout/DotPosition: + EnforcedStyle: trailing + +################################################################################ +# Metrics +################################################################################ + Metrics/AbcSize: Enabled: false ################################################################################ -# Style +# Naming ################################################################################ # Executables are conventionally named bin/foo-bar -Style/FileName: +Naming/FileName: Exclude: - bin/**/* +# We have common cases where has_ and have_ make sense +Naming/PredicateName: + Enabled: true + ForbiddenPrefixes: + - is_ + +################################################################################ +# Style +################################################################################ + # We don't (currently) document our code Style/Documentation: Enabled: false @@ -32,7 +53,10 @@ Style/StringLiteralsInInterpolation: Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma -Style/TrailingCommaInLiteral: +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: comma # We avoid GuardClause because it can result in "suprise return" @@ -47,9 +71,6 @@ Style/IfUnlessModifier: Style/SignalException: EnforcedStyle: only_raise -Style/DotPosition: - EnforcedStyle: trailing - # Common globals we allow Style/GlobalVars: AllowedVariables: @@ -62,12 +83,6 @@ Style/GlobalVars: Style/SpecialGlobalVars: EnforcedStyle: use_perl_names -# We have common cases where has_ and have_ make sense -Style/PredicateName: - Enabled: true - NamePrefixBlacklist: - - is_ - # We use %w[ ], not %w( ) because the former looks like an array Style/PercentLiteralDelimiters: PreferredDelimiters: @@ -87,27 +102,6 @@ Style/Next: Style/ModuleFunction: Enabled: false -# Disallow extra spacing for token alignment -Style/ExtraSpacing: - AllowForAlignment: false - -################################################################################ -# Performance -################################################################################ - -Performance/RedundantMerge: - Enabled: false - -################################################################################ -# Rails - disable things because we're primarily non-rails -################################################################################ - -Rails/Delegate: - Enabled: false - -Rails/TimeZone: - Enabled: false - ################################################################################ # Specs - be more lenient on length checks and block styles ################################################################################ diff --git a/bin/newver b/bin/newver index e1931deb..5c23f5ab 100755 --- a/bin/newver +++ b/bin/newver @@ -7,20 +7,27 @@ if [ $# -eq 0 ]; then fi NEWVER="$1" +previous_channel() { + cat <<EOM | ruby + version = "${NEWVER}".match(/(\d+).(\d+).(\d+)/) + minor = version[2].to_i - 1 + puts "channel/rubocop-#{version[1]}-#{minor}" +EOM +} + git checkout Gemfile* +git fetch origin +git checkout $(previous_channel) git checkout -b "newver/${NEWVER}" ed Gemfile <<-EDITS -/gem "parser",/c -gem "parser" -. /gem "rubocop",/c -gem "rubocop", "~> ${NEWVER}", require: false +gem "rubocop", "${NEWVER}", require: false . wq EDITS -make bundle -e BUNDLE_ARGS="update rubocop" +make bundle -e BUNDLE_ARGS="update rubocop rubocop-rspec rubocop-migrations" make image make docs sudo chown -R $(id -u):$(id -g) config diff --git a/config/contents/bundler/duplicated_gem.md b/config/contents/bundler/duplicated_gem.md index 48232730..029c38e0 100644 --- a/config/contents/bundler/duplicated_gem.md +++ b/config/contents/bundler/duplicated_gem.md @@ -19,4 +19,13 @@ A Gem's requirements should be listed only once in a Gemfile. end # good - gem 'rubocop', groups: [:development, :test] \ No newline at end of file + gem 'rubocop', groups: [:development, :test] + + # good - conditional declaration + if Dir.exist?(local) + gem 'rubocop', path: local + elsif ENV['RUBOCOP_VERSION'] == 'master' + gem 'rubocop', git: 'https://github.com/rubocop/rubocop.git' + else + gem 'rubocop', '~> 0.90.0' + end diff --git a/config/contents/bundler/gem_comment.md b/config/contents/bundler/gem_comment.md new file mode 100644 index 00000000..bdab6e07 --- /dev/null +++ b/config/contents/bundler/gem_comment.md @@ -0,0 +1,76 @@ +Each gem in the Gemfile should have a comment explaining +its purpose in the project, or the reason for its version +or source. + +The optional "OnlyFor" configuration array +can be used to only register offenses when the gems +use certain options or have version specifiers. + +When "version_specifiers" is included, a comment +will be enforced if the gem has any version specifier. + +When "restrictive_version_specifiers" is included, a comment +will be enforced if the gem has a version specifier that +holds back the version of the gem. + +For any other value in the array, a comment will be enforced for +a gem if an option by the same name is present. +A useful use case is to enforce a comment when using +options that change the source of a gem: + +- `bitbucket` +- `gist` +- `git` +- `github` +- `source` + +For a full list of options supported by bundler, +see https://bundler.io/man/gemfile.5.html +. + +### Example: OnlyFor: [] (default) + # bad + + gem 'foo' + + # good + + # Helpers for the foo things. + gem 'foo' + +### Example: OnlyFor: ['version_specifiers'] + # bad + + gem 'foo', '< 2.1' + + # good + + # Version 2.1 introduces breaking change baz + gem 'foo', '< 2.1' + +### Example: OnlyFor: ['restrictive_version_specifiers'] + # bad + + gem 'foo', '< 2.1' + + # good + + gem 'foo', '>= 1.0' + + # Version 2.1 introduces breaking change baz + gem 'foo', '< 2.1' + +### Example: OnlyFor: ['version_specifiers', 'github'] + # bad + + gem 'foo', github: 'some_account/some_fork_of_foo' + + gem 'bar', '< 2.1' + + # good + + # Using this fork because baz + gem 'foo', github: 'some_account/some_fork_of_foo' + + # Version 2.1 introduces breaking change baz + gem 'bar', '< 2.1' diff --git a/config/contents/bundler/gem_version.md b/config/contents/bundler/gem_version.md new file mode 100644 index 00000000..853cbdae --- /dev/null +++ b/config/contents/bundler/gem_version.md @@ -0,0 +1,46 @@ +Enforce that Gem version specifications or a commit reference (branch, +ref, or tag) are either required or forbidden. + +### Example: EnforcedStyle: required (default) + # bad + gem 'rubocop' + + # good + gem 'rubocop', '~> 1.12' + + # good + gem 'rubocop', '>= 1.10.0' + + # good + gem 'rubocop', '>= 1.5.0', '< 1.10.0' + + # good + gem 'rubocop', branch: 'feature-branch' + + # good + gem 'rubocop', ref: '74b5bfbb2c4b6fd6cdbbc7254bd7084b36e0c85b' + + # good + gem 'rubocop', tag: 'v1.17.0' + +### Example: EnforcedStyle: forbidden + # good + gem 'rubocop' + + # bad + gem 'rubocop', '~> 1.12' + + # bad + gem 'rubocop', '>= 1.10.0' + + # bad + gem 'rubocop', '>= 1.5.0', '< 1.10.0' + + # bad + gem 'rubocop', branch: 'feature-branch' + + # bad + gem 'rubocop', ref: '74b5bfbb2c4b6fd6cdbbc7254bd7084b36e0c85b' + + # bad + gem 'rubocop', tag: 'v1.17.0' diff --git a/config/contents/bundler/insecure_protocol_source.md b/config/contents/bundler/insecure_protocol_source.md index b405c76f..2218ee3c 100644 --- a/config/contents/bundler/insecure_protocol_source.md +++ b/config/contents/bundler/insecure_protocol_source.md @@ -1,4 +1,4 @@ -The symbol argument `:gemcutter`, `:rubygems` and `:rubyforge` +The symbol argument `:gemcutter`, `:rubygems`, and `:rubyforge` are deprecated. So please change your source to URL string that 'https://rubygems.org' if possible, or 'http://rubygems.org' if not. @@ -8,8 +8,8 @@ most use cases HTTPS will be fine. However, it don't replace all `sources` of `http://` with `https://`. For example, when specifying an internal gem server using HTTP on the -intranet, a use case where HTTPS can not be specified was considered. -Consider using HTTP only if you can not use HTTPS. +intranet, a use case where HTTPS cannot be specified was considered. +Consider using HTTP only if you cannot use HTTPS. ### Example: # bad diff --git a/config/contents/gemspec/date_assignment.md b/config/contents/gemspec/date_assignment.md new file mode 100644 index 00000000..d58bfd09 --- /dev/null +++ b/config/contents/gemspec/date_assignment.md @@ -0,0 +1,15 @@ +This cop checks that `date =` is not used in gemspec file. +It is set automatically when the gem is packaged. + +### Example: + + # bad + Gem::Specification.new do |spec| + s.name = 'your_cool_gem_name' + spec.date = Time.now.strftime('%Y-%m-%d') + end + + # good + Gem::Specification.new do |spec| + s.name = 'your_cool_gem_name' + end diff --git a/config/contents/gemspec/duplicated_assignment.md b/config/contents/gemspec/duplicated_assignment.md index cfcb10ab..6afaac86 100644 --- a/config/contents/gemspec/duplicated_assignment.md +++ b/config/contents/gemspec/duplicated_assignment.md @@ -3,7 +3,7 @@ in a gemspec. Assigning to an attribute with the same name using `spec.foo =` will be an unintended usage. On the other hand, duplication of methods such -as `spec.requirements`, `spec.add_runtime_dependency` and others are +as `spec.requirements`, `spec.add_runtime_dependency`, and others are permitted because it is the intended use of appending values. ### Example: diff --git a/config/contents/gemspec/required_ruby_version.md b/config/contents/gemspec/required_ruby_version.md index 1d761a46..d8941d27 100644 --- a/config/contents/gemspec/required_ruby_version.md +++ b/config/contents/gemspec/required_ruby_version.md @@ -1,14 +1,14 @@ -Checks that `required_ruby_version` of gemspec and `TargetRubyVersion` -of .rubocop.yml are equal. +Checks that `required_ruby_version` of gemspec is specified and +equal to `TargetRubyVersion` of .rubocop.yml. Thereby, RuboCop to perform static analysis working on the version required by gemspec. ### Example: - # When `TargetRubyVersion` of .rubocop.yml is `2.3`. + # When `TargetRubyVersion` of .rubocop.yml is `2.5`. # bad Gem::Specification.new do |spec| - spec.required_ruby_version = '>= 2.2.0' + # no `required_ruby_version` specified end # bad @@ -16,17 +16,28 @@ required by gemspec. spec.required_ruby_version = '>= 2.4.0' end - # good + # bad Gem::Specification.new do |spec| - spec.required_ruby_version = '>= 2.3.0' + spec.required_ruby_version = '>= 2.6.0' end # good Gem::Specification.new do |spec| - spec.required_ruby_version = '>= 2.3' + spec.required_ruby_version = '>= 2.5.0' end # good Gem::Specification.new do |spec| - spec.required_ruby_version = ['>= 2.3.0', '< 2.5.0'] + spec.required_ruby_version = '>= 2.5' + end + + # accepted but not recommended + Gem::Specification.new do |spec| + spec.required_ruby_version = ['>= 2.5.0', '< 2.7.0'] + end + + # accepted but not recommended, since + # Ruby does not really follow semantic versionning + Gem::Specification.new do |spec| + spec.required_ruby_version = '~> 2.5' end \ No newline at end of file diff --git a/config/contents/gemspec/ruby_version_globals_usage.md b/config/contents/gemspec/ruby_version_globals_usage.md new file mode 100644 index 00000000..f570beb9 --- /dev/null +++ b/config/contents/gemspec/ruby_version_globals_usage.md @@ -0,0 +1,21 @@ +Checks that `RUBY_VERSION` constant is not used in gemspec. +Using `RUBY_VERSION` is dangerous because value of the +constant is determined by `rake release`. +It's possible to have dependency based on ruby version used +to execute `rake release` and not user's ruby version. + +### Example: + + # bad + Gem::Specification.new do |spec| + if RUBY_VERSION >= '2.5' + spec.add_runtime_dependency 'gem_a' + else + spec.add_runtime_dependency 'gem_b' + end + end + + # good + Gem::Specification.new do |spec| + spec.add_runtime_dependency 'gem_a' + end diff --git a/config/contents/layout/access_modifier_indentation.md b/config/contents/layout/access_modifier_indentation.md index a91ed86b..096ab569 100644 --- a/config/contents/layout/access_modifier_indentation.md +++ b/config/contents/layout/access_modifier_indentation.md @@ -1,5 +1,6 @@ -Modifiers should be indented as deep as method definitions, or as deep -as the class/module keyword, depending on configuration. +Bare access modifiers (those not applying to specific methods) should be +indented as deep as method definitions, or as deep as the class/module +keyword, depending on configuration. ### Example: EnforcedStyle: indent (default) # bad diff --git a/config/contents/layout/align_arguments.md b/config/contents/layout/align_arguments.md new file mode 100644 index 00000000..d90af9ec --- /dev/null +++ b/config/contents/layout/align_arguments.md @@ -0,0 +1,34 @@ +Here we check if the arguments on a multi-line method +definition are aligned. + +### Example: EnforcedStyle: with_first_argument (default) + # good + + foo :bar, + :baz + + foo( + :bar, + :baz + ) + + # bad + + foo :bar, + :baz + + foo( + :bar, + :baz + ) + +### Example: EnforcedStyle: with_fixed_indentation + # good + + foo :bar, + :baz + + # bad + + foo :bar, + :baz \ No newline at end of file diff --git a/config/contents/layout/align_hash.md b/config/contents/layout/align_hash.md index 1842159b..8193d99e 100644 --- a/config/contents/layout/align_hash.md +++ b/config/contents/layout/align_hash.md @@ -2,7 +2,7 @@ Check that the keys, separators, and values of a multi-line hash literal are aligned according to configuration. The configuration options are: - - key (left align keys) + - key (left align keys, one space before hash rockets and values) - separator (align hash rockets and colons, right align keys) - table (left align keys, hash rockets, and values) @@ -12,83 +12,160 @@ can also be configured. The options are: - always_inspect - always_ignore - ignore_implicit (without curly braces) - - ignore_explicit (with curly braces) -### Example: +Alternatively you can specify multiple allowed styles. That's done by +passing a list of styles to EnforcedStyles. - # EnforcedHashRocketStyle: key (default) - # EnforcedColonStyle: key (default) - - # good +### Example: EnforcedHashRocketStyle: key (default) + # bad { - foo: bar, - ba: baz + :foo => bar, + :ba => baz + } + { + :foo => bar, + :ba => baz } + + # good { :foo => bar, :ba => baz } +### Example: EnforcedHashRocketStyle: separator # bad { - foo: bar, - ba: baz + :foo => bar, + :ba => baz } { :foo => bar, - :ba => baz + :ba => baz } -### Example: - - # EnforcedHashRocketStyle: separator - # EnforcedColonStyle: separator - - #good - { - foo: bar, - ba: baz - } + # good { :foo => bar, :ba => baz } - #bad - { - foo: bar, - ba: baz - } +### Example: EnforcedHashRocketStyle: table + # bad { :foo => bar, - :ba => baz + :ba => baz } + + # good { :foo => bar, :ba => baz } -### Example: +### Example: EnforcedColonStyle: key (default) + # bad + { + foo: bar, + ba: baz + } + { + foo: bar, + ba: baz + } - # EnforcedHashRocketStyle: table - # EnforcedColonStyle: table + # good + { + foo: bar, + ba: baz + } - #good +### Example: EnforcedColonStyle: separator + # bad { foo: bar, - ba: baz + ba: baz } + + # good { - :foo => bar, - :ba => baz + foo: bar, + ba: baz } - #bad +### Example: EnforcedColonStyle: table + # bad { foo: bar, ba: baz } + + # good { - :foo => bar, - :ba => baz - } \ No newline at end of file + foo: bar, + ba: baz + } + +### Example: EnforcedLastArgumentHashStyle: always_inspect (default) + # Inspect both implicit and explicit hashes. + + # bad + do_something(foo: 1, + bar: 2) + + # bad + do_something({foo: 1, + bar: 2}) + + # good + do_something(foo: 1, + bar: 2) + + # good + do_something( + foo: 1, + bar: 2 + ) + + # good + do_something({foo: 1, + bar: 2}) + + # good + do_something({ + foo: 1, + bar: 2 + }) + +### Example: EnforcedLastArgumentHashStyle: always_ignore + # Ignore both implicit and explicit hashes. + + # good + do_something(foo: 1, + bar: 2) + + # good + do_something({foo: 1, + bar: 2}) + +### Example: EnforcedLastArgumentHashStyle: ignore_implicit + # Ignore only implicit hashes. + + # bad + do_something({foo: 1, + bar: 2}) + + # good + do_something(foo: 1, + bar: 2) + +### Example: EnforcedLastArgumentHashStyle: ignore_explicit + # Ignore only explicit hashes. + + # bad + do_something(foo: 1, + bar: 2) + + # good + do_something({foo: 1, + bar: 2}) diff --git a/config/contents/layout/align_parameters.md b/config/contents/layout/align_parameters.md index c6848abc..ec3d2d29 100644 --- a/config/contents/layout/align_parameters.md +++ b/config/contents/layout/align_parameters.md @@ -1,24 +1,65 @@ Here we check if the parameters on a multi-line method call or definition are aligned. +To set the alignment of the first argument, use the cop +FirstParameterIndentation. + ### Example: EnforcedStyle: with_first_parameter (default) # good - foo :bar, - :baz + def foo(bar, + baz) + 123 + end + + def foo( + bar, + baz + ) + 123 + end + + # bad + + def foo(bar, + baz) + 123 + end # bad - foo :bar, - :baz + def foo( + bar, + baz) + 123 + end ### Example: EnforcedStyle: with_fixed_indentation # good - foo :bar, - :baz + def foo(bar, + baz) + 123 + end + + def foo( + bar, + baz + ) + 123 + end + + # bad + + def foo(bar, + baz) + 123 + end # bad - foo :bar, - :baz \ No newline at end of file + def foo( + bar, + baz) + 123 + end \ No newline at end of file diff --git a/config/contents/layout/argument_alignment.md b/config/contents/layout/argument_alignment.md new file mode 100644 index 00000000..4165f3e6 --- /dev/null +++ b/config/contents/layout/argument_alignment.md @@ -0,0 +1,40 @@ +Here we check if the arguments on a multi-line method +definition are aligned. + +### Example: EnforcedStyle: with_first_argument (default) + # good + + foo :bar, + :baz, + key: value + + foo( + :bar, + :baz, + key: value + ) + + # bad + + foo :bar, + :baz, + key: value + + foo( + :bar, + :baz, + key: value + ) + +### Example: EnforcedStyle: with_fixed_indentation + # good + + foo :bar, + :baz, + key: value + + # bad + + foo :bar, + :baz, + key: value \ No newline at end of file diff --git a/config/contents/layout/array_alignment.md b/config/contents/layout/array_alignment.md new file mode 100644 index 00000000..c80a71f9 --- /dev/null +++ b/config/contents/layout/array_alignment.md @@ -0,0 +1,30 @@ +Here we check if the elements of a multi-line array literal are +aligned. + +### Example: EnforcedStyle: with_first_element (default) + # good + + array = [1, 2, 3, + 4, 5, 6] + array = ['run', + 'forrest', + 'run'] + + # bad + + array = [1, 2, 3, + 4, 5, 6] + array = ['run', + 'forrest', + 'run'] + +### Example: EnforcedStyle: with_fixed_indentation + # good + + array = [1, 2, 3, + 4, 5, 6] + + # bad + + array = [1, 2, 3, + 4, 5, 6] \ No newline at end of file diff --git a/config/contents/layout/assignment_indentation.md b/config/contents/layout/assignment_indentation.md new file mode 100644 index 00000000..0b5d5d42 --- /dev/null +++ b/config/contents/layout/assignment_indentation.md @@ -0,0 +1,18 @@ +This cop checks the indentation of the first line of the +right-hand-side of a multi-line assignment. + +### Example: + # bad + value = + if foo + 'bar' + end + + # good + value = + if foo + 'bar' + end + +The indentation of the remaining lines can be corrected with +other cops such as `IndentationConsistency` and `EndAlignment`. \ No newline at end of file diff --git a/config/contents/layout/begin_end_alignment.md b/config/contents/layout/begin_end_alignment.md new file mode 100644 index 00000000..75989b42 --- /dev/null +++ b/config/contents/layout/begin_end_alignment.md @@ -0,0 +1,34 @@ +This cop checks whether the end keyword of `begin` is aligned properly. + +Two modes are supported through the `EnforcedStyleAlignWith` configuration +parameter. If it's set to `start_of_line` (which is the default), the +`end` shall be aligned with the start of the line where the `begin` +keyword is. If it's set to `begin`, the `end` shall be aligned with the +`begin` keyword. + +`Layout/EndAlignment` cop aligns with keywords (e.g. `if`, `while`, `case`) +by default. On the other hand, `||= begin` that this cop targets tends to +align with the start of the line, it defaults to `EnforcedStyleAlignWith: start_of_line`. +These style can be configured by each cop. + +### Example: EnforcedStyleAlignWith: start_of_line (default) + # bad + foo ||= begin + do_something + end + + # good + foo ||= begin + do_something + end + +### Example: EnforcedStyleAlignWith: begin + # bad + foo ||= begin + do_something + end + + # good + foo ||= begin + do_something + end diff --git a/config/contents/layout/block_alignment.md b/config/contents/layout/block_alignment.md new file mode 100644 index 00000000..e87adc1b --- /dev/null +++ b/config/contents/layout/block_alignment.md @@ -0,0 +1,58 @@ +This cop checks whether the end keywords are aligned properly for do +end blocks. + +Three modes are supported through the `EnforcedStyleAlignWith` +configuration parameter: + +`start_of_block` : the `end` shall be aligned with the +start of the line where the `do` appeared. + +`start_of_line` : the `end` shall be aligned with the +start of the line where the expression started. + +`either` (which is the default) : the `end` is allowed to be in either +location. The autofixer will default to `start_of_line`. + +### Example: EnforcedStyleAlignWith: either (default) + # bad + + foo.bar + .each do + baz + end + + # good + + variable = lambda do |i| + i + end + +### Example: EnforcedStyleAlignWith: start_of_block + # bad + + foo.bar + .each do + baz + end + + # good + + foo.bar + .each do + baz + end + +### Example: EnforcedStyleAlignWith: start_of_line + # bad + + foo.bar + .each do + baz + end + + # good + + foo.bar + .each do + baz + end \ No newline at end of file diff --git a/config/contents/layout/case_indentation.md b/config/contents/layout/case_indentation.md index e707f42a..b2d7550b 100644 --- a/config/contents/layout/case_indentation.md +++ b/config/contents/layout/case_indentation.md @@ -1,7 +1,7 @@ -This cop checks how the *when*s of a *case* expression -are indented in relation to its *case* or *end* keyword. +This cop checks how the `when` and `in`s of a `case` expression +are indented in relation to its `case` or `end` keyword. -It will register a separate offense for each misaligned *when*. +It will register a separate offense for each misaligned `when` and `in`. ### Example: # If Layout/EndAlignment is set to keyword style (default) @@ -17,6 +17,13 @@ It will register a separate offense for each misaligned *when*. y / 3 end + case n + in pattern + x * 2 + else + y / 3 + end + # good for all styles case n when 0 @@ -25,6 +32,13 @@ It will register a separate offense for each misaligned *when*. y / 3 end + case n + in pattern + x * 2 + else + y / 3 + end + ### Example: EnforcedStyle: case (default) # if EndAlignment is set to other style such as # start_of_line (as shown below), then *when* alignment @@ -38,6 +52,13 @@ It will register a separate offense for each misaligned *when*. y / 3 end + a = case n + in pattern + x * 2 + else + y / 3 + end + # good a = case n when 0 @@ -46,6 +67,13 @@ It will register a separate offense for each misaligned *when*. y / 3 end + a = case n + in pattern + x * 2 + else + y / 3 + end + ### Example: EnforcedStyle: end # bad a = case n @@ -55,10 +83,24 @@ It will register a separate offense for each misaligned *when*. y / 3 end + a = case n + in pattern + x * 2 + else + y / 3 + end + # good a = case n when 0 x * 2 else y / 3 + end + + a = case n + in pattern + x * 2 + else + y / 3 end \ No newline at end of file diff --git a/config/contents/layout/class_structure.md b/config/contents/layout/class_structure.md index b4691836..10773803 100644 --- a/config/contents/layout/class_structure.md +++ b/config/contents/layout/class_structure.md @@ -3,47 +3,65 @@ Checks if the code style follows the ExpectedOrder configuration: `Categories` allows us to map macro names into a category. Consider an example of code style that covers the following order: -- Constants -- Associations (has_one, has_many) -- Attributes (attr_accessor, attr_writer, attr_reader) -- Initializer -- Instance methods -- Protected methods -- Private methods + +* Module inclusion (include, prepend, extend) +* Constants +* Associations (has_one, has_many) +* Public attribute macros (attr_accessor, attr_writer, attr_reader) +* Other macros (validates, validate) +* Public class methods +* Initializer +* Public instance methods +* Protected attribute macros (attr_accessor, attr_writer, attr_reader) +* Protected instance methods +* Private attribute macros (attr_accessor, attr_writer, attr_reader) +* Private instance methods You can configure the following order: -```yaml +[source,yaml] +---- Layout/ClassStructure: - Categories: - module_inclusion: - - include - - prepend - - extend ExpectedOrder: - - module_inclusion - - constants - - public_class_methods - - initializer - - public_methods - - protected_methods - - private_methods - -``` + - module_inclusion + - constants + - association + - public_attribute_macros + - public_delegate + - macros + - public_class_methods + - initializer + - public_methods + - protected_attribute_macros + - protected_methods + - private_attribute_macros + - private_delegate + - private_methods +---- + Instead of putting all literals in the expected order, is also -possible to group categories of macros. +possible to group categories of macros. Visibility levels are handled +automatically. -```yaml +[source,yaml] +---- Layout/ClassStructure: Categories: association: - has_many - has_one - attribute: + attribute_macros: - attr_accessor - attr_reader - attr_writer -``` + macros: + - validates + - validate + module_inclusion: + - include + - prepend + - extend +---- ### Example: # bad @@ -68,12 +86,15 @@ possible to group categories of macros. # constants are next SOME_CONSTANT = 20 - # afterwards we have attribute macros + # afterwards we have public attribute macros attr_reader :name # followed by other macros (if any) validates :name + # then we have public delegate macros + delegate :to_s, to: :name + # public class methods are next in line def self.some_method end @@ -86,16 +107,24 @@ possible to group categories of macros. def some_method end - # protected and private methods are grouped near the end + # protected attribute macros and methods go next protected + attr_reader :protected_name + def some_protected_method end + # private attribute macros, delegate macros and methods + # are grouped near the end private + attr_reader :private_name + + delegate :some_private_delegate, to: :name + def some_private_method end end -@see https://github.com/bbatsov/ruby-style-guide#consistent-classes \ No newline at end of file +@see https://rubystyle.guide#consistent-classes \ No newline at end of file diff --git a/config/contents/layout/closing_heredoc_indentation.md b/config/contents/layout/closing_heredoc_indentation.md new file mode 100644 index 00000000..6a78ff4d --- /dev/null +++ b/config/contents/layout/closing_heredoc_indentation.md @@ -0,0 +1,42 @@ + +Checks the indentation of here document closings. + +### Example: + + # bad + class Foo + def bar + <<~SQL + 'Hi' + SQL + end + end + + # good + class Foo + def bar + <<~SQL + 'Hi' + SQL + end + end + + # bad + + # heredoc contents is before closing heredoc. + foo arg, + <<~EOS + Hi + EOS + + # good + foo arg, + <<~EOS + Hi + EOS + + # good + foo arg, + <<~EOS + Hi + EOS diff --git a/config/contents/layout/closing_parenthesis_indentation.md b/config/contents/layout/closing_parenthesis_indentation.md index f7092715..ac4fbef4 100644 --- a/config/contents/layout/closing_parenthesis_indentation.md +++ b/config/contents/layout/closing_parenthesis_indentation.md @@ -1,23 +1,64 @@ -This cops checks the indentation of hanging closing parentheses in +This cop checks the indentation of hanging closing parentheses in method calls, method definitions, and grouped expressions. A hanging closing parenthesis means `)` preceded by a line break. ### Example: - # good: when x is on its own line, indent this way - func( - x, - y - ) + # bad + some_method( + a, + b + ) - # good: when x follows opening parenthesis, align parentheses - a = b * (x + - y - ) + some_method( + a, b + ) - # bad - def func( - x, - y + some_method(a, b, c ) - end \ No newline at end of file + + some_method(a, + b, + c + ) + + some_method(a, + x: 1, + y: 2 + ) + + # Scenario 1: When First Parameter Is On Its Own Line + + # good: when first param is on a new line, right paren is *always* + # outdented by IndentationWidth + some_method( + a, + b + ) + + # good + some_method( + a, b + ) + + # Scenario 2: When First Parameter Is On The Same Line + + # good: when all other params are also on the same line, outdent + # right paren by IndentationWidth + some_method(a, b, c + ) + + # good: when all other params are on multiple lines, but are lined + # up, align right paren with left paren + some_method(a, + b, + c + ) + + # good: when other params are not lined up on multiple lines, outdent + # right paren by IndentationWidth + some_method(a, + x: 1, + y: 2 + ) + diff --git a/config/contents/layout/comment_indentation.md b/config/contents/layout/comment_indentation.md index 34dd43a9..3d2c7c1c 100644 --- a/config/contents/layout/comment_indentation.md +++ b/config/contents/layout/comment_indentation.md @@ -1,4 +1,4 @@ -This cops checks the indentation of comments. +This cop checks the indentation of comments. ### Example: # bad diff --git a/config/contents/layout/condition_position.md b/config/contents/layout/condition_position.md new file mode 100644 index 00000000..5f4557fd --- /dev/null +++ b/config/contents/layout/condition_position.md @@ -0,0 +1,19 @@ +This cop checks for conditions that are not on the same line as +if/while/until. + +### Example: + + # bad + + if + some_condition + do_something + end + +### Example: + + # good + + if some_condition + do_something + end \ No newline at end of file diff --git a/config/contents/layout/def_end_alignment.md b/config/contents/layout/def_end_alignment.md new file mode 100644 index 00000000..05d6a6bc --- /dev/null +++ b/config/contents/layout/def_end_alignment.md @@ -0,0 +1,30 @@ +This cop checks whether the end keywords of method definitions are +aligned properly. + +Two modes are supported through the EnforcedStyleAlignWith configuration +parameter. If it's set to `start_of_line` (which is the default), the +`end` shall be aligned with the start of the line where the `def` +keyword is. If it's set to `def`, the `end` shall be aligned with the +`def` keyword. + +### Example: EnforcedStyleAlignWith: start_of_line (default) + # bad + + private def foo + end + + # good + + private def foo + end + +### Example: EnforcedStyleAlignWith: def + # bad + + private def foo + end + + # good + + private def foo + end \ No newline at end of file diff --git a/config/contents/layout/dot_position.md b/config/contents/layout/dot_position.md index ffabb960..4f25b42f 100644 --- a/config/contents/layout/dot_position.md +++ b/config/contents/layout/dot_position.md @@ -3,7 +3,7 @@ This cop checks the . position in multi-line method calls. ### Example: EnforcedStyle: leading (default) # bad something. - mehod + method # good something @@ -16,4 +16,4 @@ This cop checks the . position in multi-line method calls. # good something. - mehod \ No newline at end of file + method \ No newline at end of file diff --git a/config/contents/layout/else_alignment.md b/config/contents/layout/else_alignment.md index decaeb7d..ea5c1996 100644 --- a/config/contents/layout/else_alignment.md +++ b/config/contents/layout/else_alignment.md @@ -1,5 +1,5 @@ -This cops checks the alignment of else keywords. Normally they should -be aligned with an if/unless/while/until/begin/def keyword, but there +This cop checks the alignment of else keywords. Normally they should +be aligned with an if/unless/while/until/begin/def/rescue keyword, but there are special cases when they should follow the same rules as the alignment of end. diff --git a/config/contents/layout/empty_comment.md b/config/contents/layout/empty_comment.md new file mode 100644 index 00000000..5c89b7b3 --- /dev/null +++ b/config/contents/layout/empty_comment.md @@ -0,0 +1,56 @@ +This cop checks empty comment. + +### Example: + # bad + + # + class Foo + end + + # good + + # + # Description of `Foo` class. + # + class Foo + end + +### Example: AllowBorderComment: true (default) + # good + + def foo + end + + ################# + + def bar + end + +### Example: AllowBorderComment: false + # bad + + def foo + end + + ################# + + def bar + end + +### Example: AllowMarginComment: true (default) + # good + + # + # Description of `Foo` class. + # + class Foo + end + +### Example: AllowMarginComment: false + # bad + + # + # Description of `Foo` class. + # + class Foo + end diff --git a/config/contents/layout/empty_line_after_guard_clause.md b/config/contents/layout/empty_line_after_guard_clause.md new file mode 100644 index 00000000..cb271393 --- /dev/null +++ b/config/contents/layout/empty_line_after_guard_clause.md @@ -0,0 +1,32 @@ +This cop enforces empty line after guard clause + +### Example: + + # bad + def foo + return if need_return? + bar + end + + # good + def foo + return if need_return? + + bar + end + + # good + def foo + return if something? + return if something_different? + + bar + end + + # also good + def foo + if something? + do_something + return if need_return? + end + end \ No newline at end of file diff --git a/config/contents/layout/empty_line_after_multiline_condition.md b/config/contents/layout/empty_line_after_multiline_condition.md new file mode 100644 index 00000000..7539b861 --- /dev/null +++ b/config/contents/layout/empty_line_after_multiline_condition.md @@ -0,0 +1,47 @@ +This cop enforces empty line after multiline condition. + +### Example: + # bad + if multiline && + condition + do_something + end + + # good + if multiline && + condition + + do_something + end + + # bad + case x + when foo, + bar + do_something + end + + # good + case x + when foo, + bar + + do_something + end + + # bad + begin + do_something + rescue FooError, + BarError + handle_error + end + + # good + begin + do_something + rescue FooError, + BarError + + handle_error + end diff --git a/config/contents/layout/empty_line_between_defs.md b/config/contents/layout/empty_line_between_defs.md index c3b150aa..100f67b8 100644 --- a/config/contents/layout/empty_line_between_defs.md +++ b/config/contents/layout/empty_line_between_defs.md @@ -1,14 +1,15 @@ -This cop checks whether method definitions are -separated by one empty line. +This cop checks whether class/module/method definitions are +separated by one or more empty lines. -`NumberOfEmptyLines` can be and integer (e.g. 1 by default) or -an array (e.g. [1, 2]) to specificy a minimum and a maximum of -empty lines. +`NumberOfEmptyLines` can be an integer (default is 1) or +an array (e.g. [1, 2]) to specify a minimum and maximum +number of empty lines permitted. -`AllowAdjacentOneLineDefs` can be used to configure is adjacent -one line methods definitions are an offense +`AllowAdjacentOneLineDefs` configures whether adjacent +one-line definitions are considered an offense. -### Example: +### Example: EmptyLineBetweenMethodDefs: true (default) + # checks for empty lines between method definitions. # bad def a @@ -23,4 +24,57 @@ one line methods definitions are an offense end def b - end \ No newline at end of file + end + +### Example: EmptyLineBetweenClassDefs: true (default) + # checks for empty lines between class definitions. + + # bad + class A + end + class B + end + def b + end + +### Example: + + # good + class A + end + + class B + end + + def b + end + +### Example: EmptyLineBetweenModuleDefs: true (default) + # checks for empty lines between module definitions. + + # bad + module A + end + module B + end + def b + end + +### Example: + + # good + module A + end + + module B + end + + def b + end + +### Example: AllowAdjacentOneLineDefs: true + + # good + class ErrorA < BaseError; end + class ErrorB < BaseError; end + class ErrorC < BaseError; end diff --git a/config/contents/layout/empty_lines.md b/config/contents/layout/empty_lines.md index b8b4d751..268015d8 100644 --- a/config/contents/layout/empty_lines.md +++ b/config/contents/layout/empty_lines.md @@ -1,4 +1,4 @@ -This cops checks for two or more consecutive blank lines. +This cop checks for two or more consecutive blank lines. ### Example: diff --git a/config/contents/layout/empty_lines_around_access_modifier.md b/config/contents/layout/empty_lines_around_access_modifier.md index acd6558f..f5e88696 100644 --- a/config/contents/layout/empty_lines_around_access_modifier.md +++ b/config/contents/layout/empty_lines_around_access_modifier.md @@ -1,6 +1,6 @@ Access modifiers should be surrounded by blank lines. -### Example: +### Example: EnforcedStyle: around (default) # bad class Foo @@ -16,4 +16,21 @@ Access modifiers should be surrounded by blank lines. private def baz; end - end \ No newline at end of file + end + +### Example: EnforcedStyle: only_before + + # bad + class Foo + def bar; end + private + def baz; end + end + + # good + class Foo + def bar; end + + private + def baz; end + end diff --git a/config/contents/layout/empty_lines_around_arguments.md b/config/contents/layout/empty_lines_around_arguments.md index d7a8a22d..8ebdbcc4 100644 --- a/config/contents/layout/empty_lines_around_arguments.md +++ b/config/contents/layout/empty_lines_around_arguments.md @@ -1,4 +1,4 @@ -This cops checks if empty lines exist around the arguments +This cop checks if empty lines exist around the arguments of a method invocation. ### Example: diff --git a/config/contents/layout/empty_lines_around_attribute_accessor.md b/config/contents/layout/empty_lines_around_attribute_accessor.md new file mode 100644 index 00000000..78401856 --- /dev/null +++ b/config/contents/layout/empty_lines_around_attribute_accessor.md @@ -0,0 +1,56 @@ +Checks for a newline after an attribute accessor or a group of them. +`alias` syntax and `alias_method`, `public`, `protected`, and `private` methods are allowed +by default. These are customizable with `AllowAliasSyntax` and `AllowedMethods` options. + +### Example: + # bad + attr_accessor :foo + def do_something + end + + # good + attr_accessor :foo + + def do_something + end + + # good + attr_accessor :foo + attr_reader :bar + attr_writer :baz + attr :qux + + def do_something + end + +### Example: AllowAliasSyntax: true (default) + # good + attr_accessor :foo + alias :foo? :foo + + def do_something + end + +### Example: AllowAliasSyntax: false + # bad + attr_accessor :foo + alias :foo? :foo + + def do_something + end + + # good + attr_accessor :foo + + alias :foo? :foo + + def do_something + end + +### Example: AllowedMethods: ['private'] + # good + attr_accessor :foo + private :foo + + def do_something + end diff --git a/config/contents/layout/empty_lines_around_begin_body.md b/config/contents/layout/empty_lines_around_begin_body.md index 742ac0d6..31f8736b 100644 --- a/config/contents/layout/empty_lines_around_begin_body.md +++ b/config/contents/layout/empty_lines_around_begin_body.md @@ -1,4 +1,4 @@ -This cops checks if empty lines exist around the bodies of begin-end +This cop checks if empty lines exist around the bodies of begin-end blocks. ### Example: diff --git a/config/contents/layout/empty_lines_around_block_body.md b/config/contents/layout/empty_lines_around_block_body.md index 77cf3168..79120428 100644 --- a/config/contents/layout/empty_lines_around_block_body.md +++ b/config/contents/layout/empty_lines_around_block_body.md @@ -1,4 +1,4 @@ -This cops checks if empty lines around the bodies of blocks match +This cop checks if empty lines around the bodies of blocks match the configuration. ### Example: EnforcedStyle: empty_lines diff --git a/config/contents/layout/empty_lines_around_class_body.md b/config/contents/layout/empty_lines_around_class_body.md index 3bec0bca..a0253690 100644 --- a/config/contents/layout/empty_lines_around_class_body.md +++ b/config/contents/layout/empty_lines_around_class_body.md @@ -1,4 +1,4 @@ -This cops checks if empty lines around the bodies of classes match +This cop checks if empty lines around the bodies of classes match the configuration. ### Example: EnforcedStyle: empty_lines @@ -31,6 +31,26 @@ the configuration. end +### Example: EnforcedStyle: beginning_only + # good + + class Foo + + def bar + # ... + end + end + +### Example: EnforcedStyle: ending_only + # good + + class Foo + def bar + # ... + end + + end + ### Example: EnforcedStyle: no_empty_lines (default) # good diff --git a/config/contents/layout/empty_lines_around_exception_handling_keywords.md b/config/contents/layout/empty_lines_around_exception_handling_keywords.md index 7b47f400..9933b3ba 100644 --- a/config/contents/layout/empty_lines_around_exception_handling_keywords.md +++ b/config/contents/layout/empty_lines_around_exception_handling_keywords.md @@ -1,4 +1,4 @@ -This cops checks if empty lines exist around the bodies of `begin` +This cop checks if empty lines exist around the bodies of `begin` sections. This cop doesn't check empty lines at `begin` body beginning/end and around method definition body. `Style/EmptyLinesAroundBeginBody` or `Style/EmptyLinesAroundMethodBody` diff --git a/config/contents/layout/empty_lines_around_method_body.md b/config/contents/layout/empty_lines_around_method_body.md index 4ba0275d..a096fd35 100644 --- a/config/contents/layout/empty_lines_around_method_body.md +++ b/config/contents/layout/empty_lines_around_method_body.md @@ -1,4 +1,4 @@ -This cops checks if empty lines exist around the bodies of methods. +This cop checks if empty lines exist around the bodies of methods. ### Example: diff --git a/config/contents/layout/empty_lines_around_module_body.md b/config/contents/layout/empty_lines_around_module_body.md index b2f40a14..9da03d84 100644 --- a/config/contents/layout/empty_lines_around_module_body.md +++ b/config/contents/layout/empty_lines_around_module_body.md @@ -1,4 +1,4 @@ -This cops checks if empty lines around the bodies of modules match +This cop checks if empty lines around the bodies of modules match the configuration. ### Example: EnforcedStyle: empty_lines diff --git a/config/contents/layout/end_alignment.md b/config/contents/layout/end_alignment.md new file mode 100644 index 00000000..65d2cbfd --- /dev/null +++ b/config/contents/layout/end_alignment.md @@ -0,0 +1,69 @@ +This cop checks whether the end keywords are aligned properly. + +Three modes are supported through the `EnforcedStyleAlignWith` +configuration parameter: + +If it's set to `keyword` (which is the default), the `end` +shall be aligned with the start of the keyword (if, class, etc.). + +If it's set to `variable` the `end` shall be aligned with the +left-hand-side of the variable assignment, if there is one. + +If it's set to `start_of_line`, the `end` shall be aligned with the +start of the line where the matching keyword appears. + +This `Layout/EndAlignment` cop aligns with keywords (e.g. `if`, `while`, `case`) +by default. On the other hand, `Layout/BeginEndAlignment` cop aligns with +`EnforcedStyleAlignWith: start_of_line` by default due to `||= begin` tends +to align with the start of the line. These style can be configured by each cop. + +### Example: EnforcedStyleAlignWith: keyword (default) + # bad + + variable = if true + end + + # good + + variable = if true + end + + variable = + if true + end + +### Example: EnforcedStyleAlignWith: variable + # bad + + variable = if true + end + + # good + + variable = if true + end + + variable = + if true + end + +### Example: EnforcedStyleAlignWith: start_of_line + # bad + + variable = if true + end + + puts(if true + end) + + # good + + variable = if true + end + + puts(if true + end) + + variable = + if true + end \ No newline at end of file diff --git a/config/contents/layout/end_of_line.md b/config/contents/layout/end_of_line.md new file mode 100644 index 00000000..eb966a5e --- /dev/null +++ b/config/contents/layout/end_of_line.md @@ -0,0 +1,33 @@ +This cop checks for Windows-style line endings in the source code. + +### Example: EnforcedStyle: native (default) + # The `native` style means that CR+LF (Carriage Return + Line Feed) is + # enforced on Windows, and LF is enforced on other platforms. + + # bad + puts 'Hello' # Return character is LF on Windows. + puts 'Hello' # Return character is CR+LF on other than Windows. + + # good + puts 'Hello' # Return character is CR+LF on Windows. + puts 'Hello' # Return character is LF on other than Windows. + +### Example: EnforcedStyle: lf + # The `lf` style means that LF (Line Feed) is enforced on + # all platforms. + + # bad + puts 'Hello' # Return character is CR+LF on all platfoms. + + # good + puts 'Hello' # Return character is LF on all platfoms. + +### Example: EnforcedStyle: crlf + # The `crlf` style means that CR+LF (Carriage Return + Line Feed) is + # enforced on all platforms. + + # bad + puts 'Hello' # Return character is LF on all platfoms. + + # good + puts 'Hello' # Return character is CR+LF on all platfoms. diff --git a/config/contents/layout/extra_spacing.md b/config/contents/layout/extra_spacing.md index a9c73cdc..bcb0f188 100644 --- a/config/contents/layout/extra_spacing.md +++ b/config/contents/layout/extra_spacing.md @@ -6,9 +6,20 @@ This cop checks for extra/unnecessary whitespace. name = "RuboCop" # Some comment and an empty line - website += "/bbatsov/rubocop" unless cond + website += "/rubocop/rubocop" unless cond puts "rubocop" if debug # bad for any configuration set_app("RuboCop") - website = "https://github.com/bbatsov/rubocop" \ No newline at end of file + website = "https://github.com/rubocop/rubocop" + + # good only if AllowBeforeTrailingComments is true + object.method(arg) # this is a comment + + # good even if AllowBeforeTrailingComments is false or not set + object.method(arg) # this is a comment + + # good with either AllowBeforeTrailingComments or AllowForAlignment + object.method(arg) # this is a comment + another_object.method(arg) # this is another comment + some_object.method(arg) # this is some comment \ No newline at end of file diff --git a/config/contents/layout/first_argument_indentation.md b/config/contents/layout/first_argument_indentation.md new file mode 100644 index 00000000..19fd4bc2 --- /dev/null +++ b/config/contents/layout/first_argument_indentation.md @@ -0,0 +1,140 @@ +This cop checks the indentation of the first argument in a method call. +Arguments after the first one are checked by `Layout/ArgumentAlignment`, +not by this cop. + +For indenting the first parameter of method _definitions_, check out +`Layout/FirstParameterIndentation`. + +This cop will respect `Layout/ArgumentAlignment` and will not work when +`EnforcedStyle: with_fixed_indentation` is specified for `Layout/ArgumentAlignment`. + +### Example: + + # bad + some_method( + first_param, + second_param) + + foo = some_method( + first_param, + second_param) + + foo = some_method(nested_call( + nested_first_param), + second_param) + + foo = some_method( + nested_call( + nested_first_param), + second_param) + + some_method nested_call( + nested_first_param), + second_param + +### Example: EnforcedStyle: consistent + # The first argument should always be indented one step more than the + # preceding line. + + # good + some_method( + first_param, + second_param) + + foo = some_method( + first_param, + second_param) + + foo = some_method(nested_call( + nested_first_param), + second_param) + + foo = some_method( + nested_call( + nested_first_param), + second_param) + + some_method nested_call( + nested_first_param), + second_param + +### Example: EnforcedStyle: consistent_relative_to_receiver + # The first argument should always be indented one level relative to + # the parent that is receiving the argument + + # good + some_method( + first_param, + second_param) + + foo = some_method( + first_param, + second_param) + + foo = some_method(nested_call( + nested_first_param), + second_param) + + foo = some_method( + nested_call( + nested_first_param), + second_param) + + some_method nested_call( + nested_first_param), + second_params + +### Example: EnforcedStyle: special_for_inner_method_call + # The first argument should normally be indented one step more than + # the preceding line, but if it's a argument for a method call that + # is itself a argument in a method call, then the inner argument + # should be indented relative to the inner method. + + # good + some_method( + first_param, + second_param) + + foo = some_method( + first_param, + second_param) + + foo = some_method(nested_call( + nested_first_param), + second_param) + + foo = some_method( + nested_call( + nested_first_param), + second_param) + + some_method nested_call( + nested_first_param), + second_param + +### Example: EnforcedStyle: special_for_inner_method_call_in_parentheses (default) + # Same as `special_for_inner_method_call` except that the special rule + # only applies if the outer method call encloses its arguments in + # parentheses. + + # good + some_method( + first_param, + second_param) + + foo = some_method( + first_param, + second_param) + + foo = some_method(nested_call( + nested_first_param), + second_param) + + foo = some_method( + nested_call( + nested_first_param), + second_param) + + some_method nested_call( + nested_first_param), + second_param diff --git a/config/contents/layout/first_array_element_indentation.md b/config/contents/layout/first_array_element_indentation.md new file mode 100644 index 00000000..8d729c29 --- /dev/null +++ b/config/contents/layout/first_array_element_indentation.md @@ -0,0 +1,76 @@ +This cop checks the indentation of the first element in an array literal +where the opening bracket and the first element are on separate lines. +The other elements' indentations are handled by the ArrayAlignment cop. + +By default, array literals that are arguments in a method call with +parentheses, and where the opening square bracket of the array is on the +same line as the opening parenthesis of the method call, shall have +their first element indented one step (two spaces) more than the +position inside the opening parenthesis. + +Other array literals shall have their first element indented one step +more than the start of the line where the opening square bracket is. + +This default style is called 'special_inside_parentheses'. Alternative +styles are 'consistent' and 'align_brackets'. Here are examples: + +### Example: EnforcedStyle: special_inside_parentheses (default) + # The `special_inside_parentheses` style enforces that the first + # element in an array literal where the opening bracket and first + # element are on separate lines is indented one step (two spaces) more + # than the position inside the opening parenthesis. + + #bad + array = [ + :value + ] + and_in_a_method_call([ + :no_difference + ]) + + #good + array = [ + :value + ] + but_in_a_method_call([ + :its_like_this + ]) + +### Example: EnforcedStyle: consistent + # The `consistent` style enforces that the first element in an array + # literal where the opening bracket and the first element are on + # separate lines is indented the same as an array literal which is not + # defined inside a method call. + + #bad + # consistent + array = [ + :value + ] + but_in_a_method_call([ + :its_like_this + ]) + + #good + array = [ + :value + ] + and_in_a_method_call([ + :no_difference + ]) + +### Example: EnforcedStyle: align_brackets + # The `align_brackets` style enforces that the opening and closing + # brackets are indented to the same position. + + #bad + # align_brackets + and_now_for_something = [ + :completely_different + ] + + #good + # align_brackets + and_now_for_something = [ + :completely_different + ] \ No newline at end of file diff --git a/config/contents/layout/first_hash_element_indentation.md b/config/contents/layout/first_hash_element_indentation.md new file mode 100644 index 00000000..c47b386b --- /dev/null +++ b/config/contents/layout/first_hash_element_indentation.md @@ -0,0 +1,74 @@ +This cop checks the indentation of the first key in a hash literal +where the opening brace and the first key are on separate lines. The +other keys' indentations are handled by the HashAlignment cop. + +By default, Hash literals that are arguments in a method call with +parentheses, and where the opening curly brace of the hash is on the +same line as the opening parenthesis of the method call, shall have +their first key indented one step (two spaces) more than the position +inside the opening parenthesis. + +Other hash literals shall have their first key indented one step more +than the start of the line where the opening curly brace is. + +This default style is called 'special_inside_parentheses'. Alternative +styles are 'consistent' and 'align_braces'. Here are examples: + +### Example: EnforcedStyle: special_inside_parentheses (default) + # The `special_inside_parentheses` style enforces that the first key + # in a hash literal where the opening brace and the first key are on + # separate lines is indented one step (two spaces) more than the + # position inside the opening parentheses. + + # bad + hash = { + key: :value + } + and_in_a_method_call({ + no: :difference + }) + + # good + special_inside_parentheses + hash = { + key: :value + } + but_in_a_method_call({ + its_like: :this + }) + +### Example: EnforcedStyle: consistent + # The `consistent` style enforces that the first key in a hash + # literal where the opening brace and the first key are on + # separate lines is indented the same as a hash literal which is not + # defined inside a method call. + + # bad + hash = { + key: :value + } + but_in_a_method_call({ + its_like: :this + }) + + # good + hash = { + key: :value + } + and_in_a_method_call({ + no: :difference + }) + +### Example: EnforcedStyle: align_braces + # The `align_brackets` style enforces that the opening and closing + # braces are indented to the same position. + + # bad + and_now_for_something = { + completely: :different + } + + # good + and_now_for_something = { + completely: :different + } \ No newline at end of file diff --git a/config/contents/layout/first_parameter_indentation.md b/config/contents/layout/first_parameter_indentation.md index cd4dc6f0..1c86def4 100644 --- a/config/contents/layout/first_parameter_indentation.md +++ b/config/contents/layout/first_parameter_indentation.md @@ -1,15 +1,38 @@ -This cop checks the indentation of the first parameter in a method call. -Parameters after the first one are checked by Style/AlignParameters, not -by this cop. +This cop checks the indentation of the first parameter in a method +definition. Parameters after the first one are checked by +Layout/ParameterAlignment, not by this cop. + +For indenting the first argument of method _calls_, check out +Layout/FirstArgumentIndentation, which supports options related to +nesting that are irrelevant for method _definitions_. ### Example: # bad - some_method( + def some_method( first_param, second_param) + 123 + end + +### Example: EnforcedStyle: consistent (default) + # The first parameter should always be indented one step more than the + # preceding line. # good - some_method( + def some_method( first_param, - second_param) \ No newline at end of file + second_param) + 123 + end + +### Example: EnforcedStyle: align_parentheses + # The first parameter should always be indented one step more than the + # opening parenthesis. + + # good + def some_method( + first_param, + second_param) + 123 + end \ No newline at end of file diff --git a/config/contents/layout/hash_alignment.md b/config/contents/layout/hash_alignment.md new file mode 100644 index 00000000..cc191293 --- /dev/null +++ b/config/contents/layout/hash_alignment.md @@ -0,0 +1,171 @@ +Check that the keys, separators, and values of a multi-line hash +literal are aligned according to configuration. The configuration +options are: + +* key (left align keys, one space before hash rockets and values) +* separator (align hash rockets and colons, right align keys) +* table (left align keys, hash rockets, and values) + +The treatment of hashes passed as the last argument to a method call +can also be configured. The options are: + +* always_inspect +* always_ignore +* ignore_implicit (without curly braces) + +Alternatively you can specify multiple allowed styles. That's done by +passing a list of styles to EnforcedStyles. + +### Example: EnforcedHashRocketStyle: key (default) + # bad + { + :foo => bar, + :ba => baz + } + { + :foo => bar, + :ba => baz + } + + # good + { + :foo => bar, + :ba => baz + } + +### Example: EnforcedHashRocketStyle: separator + # bad + { + :foo => bar, + :ba => baz + } + { + :foo => bar, + :ba => baz + } + + # good + { + :foo => bar, + :ba => baz + } + +### Example: EnforcedHashRocketStyle: table + # bad + { + :foo => bar, + :ba => baz + } + + # good + { + :foo => bar, + :ba => baz + } + +### Example: EnforcedColonStyle: key (default) + # bad + { + foo: bar, + ba: baz + } + { + foo: bar, + ba: baz + } + + # good + { + foo: bar, + ba: baz + } + +### Example: EnforcedColonStyle: separator + # bad + { + foo: bar, + ba: baz + } + + # good + { + foo: bar, + ba: baz + } + +### Example: EnforcedColonStyle: table + # bad + { + foo: bar, + ba: baz + } + + # good + { + foo: bar, + ba: baz + } + +### Example: EnforcedLastArgumentHashStyle: always_inspect (default) + # Inspect both implicit and explicit hashes. + + # bad + do_something(foo: 1, + bar: 2) + + # bad + do_something({foo: 1, + bar: 2}) + + # good + do_something(foo: 1, + bar: 2) + + # good + do_something( + foo: 1, + bar: 2 + ) + + # good + do_something({foo: 1, + bar: 2}) + + # good + do_something({ + foo: 1, + bar: 2 + }) + +### Example: EnforcedLastArgumentHashStyle: always_ignore + # Ignore both implicit and explicit hashes. + + # good + do_something(foo: 1, + bar: 2) + + # good + do_something({foo: 1, + bar: 2}) + +### Example: EnforcedLastArgumentHashStyle: ignore_implicit + # Ignore only implicit hashes. + + # bad + do_something({foo: 1, + bar: 2}) + + # good + do_something(foo: 1, + bar: 2) + +### Example: EnforcedLastArgumentHashStyle: ignore_explicit + # Ignore only explicit hashes. + + # bad + do_something(foo: 1, + bar: 2) + + # good + do_something({foo: 1, + bar: 2}) diff --git a/config/contents/layout/heredoc_argument_closing_parenthesis.md b/config/contents/layout/heredoc_argument_closing_parenthesis.md new file mode 100644 index 00000000..1d03cdb0 --- /dev/null +++ b/config/contents/layout/heredoc_argument_closing_parenthesis.md @@ -0,0 +1,46 @@ +This cop checks for the placement of the closing parenthesis +in a method call that passes a HEREDOC string as an argument. +It should be placed at the end of the line containing the +opening HEREDOC tag. + +### Example: + # bad + + foo(<<-SQL + bar + SQL + ) + + foo(<<-SQL, 123, <<-NOSQL, + bar + SQL + baz + NOSQL + ) + + foo( + bar(<<-SQL + baz + SQL + ), + 123, + ) + + # good + + foo(<<-SQL) + bar + SQL + + foo(<<-SQL, 123, <<-NOSQL) + bar + SQL + baz + NOSQL + + foo( + bar(<<-SQL), + baz + SQL + 123, + ) diff --git a/config/contents/layout/heredoc_indentation.md b/config/contents/layout/heredoc_indentation.md new file mode 100644 index 00000000..8ba772be --- /dev/null +++ b/config/contents/layout/heredoc_indentation.md @@ -0,0 +1,17 @@ +This cop checks the indentation of the here document bodies. The bodies +are indented one step. + +Note: When ``Layout/LineLength``'s `AllowHeredoc` is false (not default), + this cop does not add any offenses for long here documents to + avoid `Layout/LineLength`'s offenses. + +### Example: + # bad + <<-RUBY + something + RUBY + + # good + <<~RUBY + something + RUBY diff --git a/config/contents/layout/indent_first_array_element.md b/config/contents/layout/indent_first_array_element.md new file mode 100644 index 00000000..2649cb28 --- /dev/null +++ b/config/contents/layout/indent_first_array_element.md @@ -0,0 +1,76 @@ +This cop checks the indentation of the first element in an array literal +where the opening bracket and the first element are on separate lines. +The other elements' indentations are handled by the AlignArray cop. + +By default, array literals that are arguments in a method call with +parentheses, and where the opening square bracket of the array is on the +same line as the opening parenthesis of the method call, shall have +their first element indented one step (two spaces) more than the +position inside the opening parenthesis. + +Other array literals shall have their first element indented one step +more than the start of the line where the opening square bracket is. + +This default style is called 'special_inside_parentheses'. Alternative +styles are 'consistent' and 'align_brackets'. Here are examples: + +### Example: EnforcedStyle: special_inside_parentheses (default) + # The `special_inside_parentheses` style enforces that the first + # element in an array literal where the opening bracket and first + # element are on seprate lines is indented one step (two spaces) more + # than the position inside the opening parenthesis. + + #bad + array = [ + :value + ] + and_in_a_method_call([ + :no_difference + ]) + + #good + array = [ + :value + ] + but_in_a_method_call([ + :its_like_this + ]) + +### Example: EnforcedStyle: consistent + # The `consistent` style enforces that the first element in an array + # literal where the opening bracket and the first element are on + # seprate lines is indented the same as an array literal which is not + # defined inside a method call. + + #bad + # consistent + array = [ + :value + ] + but_in_a_method_call([ + :its_like_this + ]) + + #good + array = [ + :value + ] + and_in_a_method_call([ + :no_difference + ]) + +### Example: EnforcedStyle: align_brackets + # The `align_brackets` style enforces that the opening and closing + # brackets are indented to the same position. + + #bad + # align_brackets + and_now_for_something = [ + :completely_different + ] + + #good + # align_brackets + and_now_for_something = [ + :completely_different + ] \ No newline at end of file diff --git a/config/contents/layout/indent_first_hash_element.md b/config/contents/layout/indent_first_hash_element.md new file mode 100644 index 00000000..083b97da --- /dev/null +++ b/config/contents/layout/indent_first_hash_element.md @@ -0,0 +1,74 @@ +This cop checks the indentation of the first key in a hash literal +where the opening brace and the first key are on separate lines. The +other keys' indentations are handled by the AlignHash cop. + +By default, Hash literals that are arguments in a method call with +parentheses, and where the opening curly brace of the hash is on the +same line as the opening parenthesis of the method call, shall have +their first key indented one step (two spaces) more than the position +inside the opening parenthesis. + +Other hash literals shall have their first key indented one step more +than the start of the line where the opening curly brace is. + +This default style is called 'special_inside_parentheses'. Alternative +styles are 'consistent' and 'align_braces'. Here are examples: + +### Example: EnforcedStyle: special_inside_parentheses (default) + # The `special_inside_parentheses` style enforces that the first key + # in a hash literal where the opening brace and the first key are on + # separate lines is indented one step (two spaces) more than the + # position inside the opening parentheses. + + # bad + hash = { + key: :value + } + and_in_a_method_call({ + no: :difference + }) + + # good + special_inside_parentheses + hash = { + key: :value + } + but_in_a_method_call({ + its_like: :this + }) + +### Example: EnforcedStyle: consistent + # The `consistent` style enforces that the first key in a hash + # literal where the opening brace and the first key are on + # separate lines is indented the same as a hash literal which is not + # defined inside a method call. + + # bad + hash = { + key: :value + } + but_in_a_method_call({ + its_like: :this + }) + + # good + hash = { + key: :value + } + and_in_a_method_call({ + no: :difference + }) + +### Example: EnforcedStyle: align_braces + # The `align_brackets` style enforces that the opening and closing + # braces are indented to the same position. + + # bad + and_now_for_something = { + completely: :different + } + + # good + and_now_for_something = { + completely: :different + } \ No newline at end of file diff --git a/config/contents/layout/indent_first_parameter.md b/config/contents/layout/indent_first_parameter.md new file mode 100644 index 00000000..107b4f45 --- /dev/null +++ b/config/contents/layout/indent_first_parameter.md @@ -0,0 +1,38 @@ +This cop checks the indentation of the first parameter in a method +definition. Parameters after the first one are checked by +Layout/AlignParameters, not by this cop. + +For indenting the first argument of method *calls*, check out +Layout/IndentFirstArgument, which supports options related to +nesting that are irrelevant for method *definitions*. + +### Example: + + # bad + def some_method( + first_param, + second_param) + 123 + end + +### Example: EnforcedStyle: consistent (default) + # The first parameter should always be indented one step more than the + # preceding line. + + # good + def some_method( + first_param, + second_param) + 123 + end + +### Example: EnforcedStyle: align_parentheses + # The first parameter should always be indented one step more than the + # opening parenthesis. + + # good + def some_method( + first_param, + second_param) + 123 + end \ No newline at end of file diff --git a/config/contents/layout/indent_heredoc.md b/config/contents/layout/indent_heredoc.md index e23f7367..d9b5968d 100644 --- a/config/contents/layout/indent_heredoc.md +++ b/config/contents/layout/indent_heredoc.md @@ -1,14 +1,13 @@ -This cops checks the indentation of the here document bodies. The bodies +This cop checks the indentation of the here document bodies. The bodies are indented one step. In Ruby 2.3 or newer, squiggly heredocs (`<<~`) should be used. If you use the older rubies, you should introduce some library to your project (e.g. ActiveSupport, Powerpack or Unindent). -Note: When `Metrics/LineLength`'s `AllowHeredoc` is false(not default), +Note: When `Metrics/LineLength`'s `AllowHeredoc` is false (not default), this cop does not add any offenses for long here documents to avoid `Metrics/LineLength`'s offenses. -### Example: - +### Example: EnforcedStyle: squiggly (default) # bad <<-RUBY something @@ -21,9 +20,26 @@ Note: When `Metrics/LineLength`'s `AllowHeredoc` is false(not default), something RUBY +### Example: EnforcedStyle: active_support # good # When EnforcedStyle is active_support, bad code is auto-corrected to # the following code. <<-RUBY.strip_heredoc something - RUBY \ No newline at end of file + RUBY + +### Example: EnforcedStyle: powerpack + # good + # When EnforcedStyle is powerpack, bad code is auto-corrected to + # the following code. + <<-RUBY.strip_indent + something + RUBY + +### Example: EnforcedStyle: unindent + # good + # When EnforcedStyle is unindent, bad code is auto-corrected to + # the following code. + <<-RUBY.unindent + something + RUBY diff --git a/config/contents/layout/indentation_consistency.md b/config/contents/layout/indentation_consistency.md index 82c3b002..8a2b98b5 100644 --- a/config/contents/layout/indentation_consistency.md +++ b/config/contents/layout/indentation_consistency.md @@ -1,10 +1,115 @@ -This cops checks for inconsistent indentation. +This cop checks for inconsistent indentation. -### Example: +The difference between `indented_internal_methods` and `normal` is +that the `indented_internal_methods` style prescribes that in +classes and modules the `protected` and `private` modifier keywords +shall be indented the same as public methods and that protected and +private members shall be indented one step more than the modifiers. +Other than that, both styles mean that entities on the same logical +depth shall have the same indentation. +### Example: EnforcedStyle: normal (default) + # bad class A def test puts 'hello' puts 'world' end + end + + # bad + class A + def test + puts 'hello' + puts 'world' + end + + protected + + def foo + end + + private + + def bar + end + end + + # good + class A + def test + puts 'hello' + puts 'world' + end + end + + # good + class A + def test + puts 'hello' + puts 'world' + end + + protected + + def foo + end + + private + + def bar + end + end + +### Example: EnforcedStyle: indented_internal_methods + # bad + class A + def test + puts 'hello' + puts 'world' + end + end + + # bad + class A + def test + puts 'hello' + puts 'world' + end + + protected + + def foo + end + + private + + def bar + end + end + + # good + class A + def test + puts 'hello' + puts 'world' + end + end + + # good + class A + def test + puts 'hello' + puts 'world' + end + + protected + + def foo + end + + private + + def bar + end end \ No newline at end of file diff --git a/config/contents/layout/indentation_style.md b/config/contents/layout/indentation_style.md new file mode 100644 index 00000000..65b63f85 --- /dev/null +++ b/config/contents/layout/indentation_style.md @@ -0,0 +1,28 @@ +This cop checks that the indentation method is consistent. +Either tabs only or spaces only are used for indentation. + +### Example: EnforcedStyle: spaces (default) + # bad + # This example uses a tab to indent bar. + def foo + bar + end + + # good + # This example uses spaces to indent bar. + def foo + bar + end + +### Example: EnforcedStyle: tabs + # bad + # This example uses spaces to indent bar. + def foo + bar + end + + # good + # This example uses a tab to indent bar. + def foo + bar + end \ No newline at end of file diff --git a/config/contents/layout/indentation_width.md b/config/contents/layout/indentation_width.md index 8f8c6b1c..0aadfc49 100644 --- a/config/contents/layout/indentation_width.md +++ b/config/contents/layout/indentation_width.md @@ -1,4 +1,4 @@ -This cops checks for indentation that doesn't use the specified number +This cop checks for indentation that doesn't use the specified number of spaces. See also the IndentationConsistency cop which is the companion to this diff --git a/config/contents/layout/initial_indentation.md b/config/contents/layout/initial_indentation.md new file mode 100644 index 00000000..d74d08a2 --- /dev/null +++ b/config/contents/layout/initial_indentation.md @@ -0,0 +1,13 @@ +This cop checks for indentation of the first non-blank non-comment +line in a file. + +### Example: + # bad + class A + def foo; end + end + + # good + class A + def foo; end + end diff --git a/config/contents/layout/leading_blank_lines.md b/config/contents/layout/leading_blank_lines.md new file mode 100644 index 00000000..83b90ffc --- /dev/null +++ b/config/contents/layout/leading_blank_lines.md @@ -0,0 +1,24 @@ +This cop checks for unnecessary leading blank lines at the beginning +of a file. + +### Example: + + # bad + # (start of file) + + class Foo + end + + # bad + # (start of file) + + # a comment + + # good + # (start of file) + class Foo + end + + # good + # (start of file) + # a comment \ No newline at end of file diff --git a/config/contents/layout/leading_comment_space.md b/config/contents/layout/leading_comment_space.md index d37fab71..850de1f3 100644 --- a/config/contents/layout/leading_comment_space.md +++ b/config/contents/layout/leading_comment_space.md @@ -10,4 +10,36 @@ or rackup options. #Some comment # good - # Some comment \ No newline at end of file + # Some comment + +### Example: AllowDoxygenCommentStyle: false (default) + + # bad + + #** + # Some comment + # Another line of comment + #* + +### Example: AllowDoxygenCommentStyle: true + + # good + + #** + # Some comment + # Another line of comment + #* + +### Example: AllowGemfileRubyComment: false (default) + + # bad + + #ruby=2.7.0 + #ruby-gemset=myproject + +### Example: AllowGemfileRubyComment: true + + # good + + #ruby=2.7.0 + #ruby-gemset=myproject diff --git a/config/contents/layout/leading_empty_lines.md b/config/contents/layout/leading_empty_lines.md new file mode 100644 index 00000000..83b90ffc --- /dev/null +++ b/config/contents/layout/leading_empty_lines.md @@ -0,0 +1,24 @@ +This cop checks for unnecessary leading blank lines at the beginning +of a file. + +### Example: + + # bad + # (start of file) + + class Foo + end + + # bad + # (start of file) + + # a comment + + # good + # (start of file) + class Foo + end + + # good + # (start of file) + # a comment \ No newline at end of file diff --git a/config/contents/layout/line_end_string_concatenation_indentation.md b/config/contents/layout/line_end_string_concatenation_indentation.md new file mode 100644 index 00000000..f1394605 --- /dev/null +++ b/config/contents/layout/line_end_string_concatenation_indentation.md @@ -0,0 +1,53 @@ +This cop checks the indentation of the next line after a line that ends with a string +literal and a backslash. + +If `EnforcedStyle: aligned` is set, the concatenated string parts shall be aligned with the +first part. There are some exceptions, such as implicit return values, where the +concatenated string parts shall be indented regardless of `EnforcedStyle` configuration. + +If `EnforcedStyle: indented` is set, it's the second line that shall be indented one step +more than the first line. Lines 3 and forward shall be aligned with line 2. Here too there +are exceptions. Values in a hash literal are always aligned. + +### Example: + # bad + def some_method + 'x' \ + 'y' \ + 'z' + end + + my_hash = { + first: 'a message' \ + 'in two parts' + } + + # good + def some_method + 'x' \ + 'y' \ + 'z' + end + + my_hash = { + first: 'a message' \ + 'in two parts' + } + +### Example: EnforcedStyle: aligned (default) + # bad + puts 'x' \ + 'y' + + # good + puts 'x' \ + 'y' + +### Example: EnforcedStyle: indented + # bad + result = 'x' \ + 'y' + + # good + result = 'x' \ + 'y' diff --git a/config/contents/layout/line_length.md b/config/contents/layout/line_length.md new file mode 100644 index 00000000..2ffab3a5 --- /dev/null +++ b/config/contents/layout/line_length.md @@ -0,0 +1,53 @@ +This cop checks the length of lines in the source code. +The maximum length is configurable. +The tab size is configured in the `IndentationWidth` +of the `Layout/IndentationStyle` cop. +It also ignores a shebang line by default. + +This cop has some autocorrection capabilities. +It can programmatically shorten certain long lines by +inserting line breaks into expressions that can be safely +split across lines. These include arrays, hashes, and +method calls with argument lists. + +If autocorrection is enabled, the following Layout cops +are recommended to further format the broken lines. +(Many of these are enabled by default.) + +* ArgumentAlignment +* BlockAlignment +* BlockDelimiters +* BlockEndNewline +* ClosingParenthesisIndentation +* FirstArgumentIndentation +* FirstArrayElementIndentation +* FirstHashElementIndentation +* FirstParameterIndentation +* HashAlignment +* IndentationWidth +* MultilineArrayLineBreaks +* MultilineBlockLayout +* MultilineHashBraceLayout +* MultilineHashKeyLineBreaks +* MultilineMethodArgumentLineBreaks +* ParameterAlignment + +Together, these cops will pretty print hashes, arrays, +method calls, etc. For example, let's say the max columns +is 25: + +### Example: + + # bad + {foo: "0000000000", bar: "0000000000", baz: "0000000000"} + + # good + {foo: "0000000000", + bar: "0000000000", baz: "0000000000"} + + # good (with recommended cops enabled) + { + foo: "0000000000", + bar: "0000000000", + baz: "0000000000", + } \ No newline at end of file diff --git a/config/contents/layout/multiline_array_brace_layout.md b/config/contents/layout/multiline_array_brace_layout.md index 8ae6e771..8c0a1b94 100644 --- a/config/contents/layout/multiline_array_brace_layout.md +++ b/config/contents/layout/multiline_array_brace_layout.md @@ -1,5 +1,5 @@ This cop checks that the closing brace in an array literal is either -on the same line as the last array element, or a new line. +on the same line as the last array element or on a new line. When using the `symmetrical` (default) style: diff --git a/config/contents/layout/multiline_array_line_breaks.md b/config/contents/layout/multiline_array_line_breaks.md new file mode 100644 index 00000000..4550b7c3 --- /dev/null +++ b/config/contents/layout/multiline_array_line_breaks.md @@ -0,0 +1,17 @@ +This cop ensures that each item in a multi-line array +starts on a separate line. + +### Example: + + # bad + [ + a, b, + c + ] + + # good + [ + a, + b, + c + ] \ No newline at end of file diff --git a/config/contents/layout/multiline_assignment_layout.md b/config/contents/layout/multiline_assignment_layout.md index dc574f60..8d15855f 100644 --- a/config/contents/layout/multiline_assignment_layout.md +++ b/config/contents/layout/multiline_assignment_layout.md @@ -25,4 +25,29 @@ after the assignment operator. # good foo = if expression 'bar' - end \ No newline at end of file + end + +### Example: SupportedTypes: ['block', 'case', 'class', 'if', 'kwbegin', 'module'] (default) + # good + foo = + if expression + 'bar' + end + + # good + foo = + [1].map do |i| + i + 1 + end + +### Example: SupportedTypes: ['block'] + # good + foo = if expression + 'bar' + end + + # good + foo = + [1].map do |i| + 'bar' * i + end diff --git a/config/contents/layout/multiline_block_layout.md b/config/contents/layout/multiline_block_layout.md index 7a28a3d8..77134167 100644 --- a/config/contents/layout/multiline_block_layout.md +++ b/config/contents/layout/multiline_block_layout.md @@ -1,6 +1,8 @@ This cop checks whether the multiline do end blocks have a newline after the start of the block. Additionally, it checks whether the block -arguments, if any, are on the same line as the start of the block. +arguments, if any, are on the same line as the start of the +block. Putting block arguments on separate lines, because the whole +line would otherwise be too long, is accepted. ### Example: # bad @@ -29,4 +31,15 @@ arguments, if any, are on the same line as the start of the block. blah { |i| foo(i) bar(i) + } + + # good + blah { | + long_list, + of_parameters, + that_would_not, + fit_on_one_line + | + foo(i) + bar(i) } \ No newline at end of file diff --git a/config/contents/layout/multiline_hash_key_line_breaks.md b/config/contents/layout/multiline_hash_key_line_breaks.md new file mode 100644 index 00000000..9da05489 --- /dev/null +++ b/config/contents/layout/multiline_hash_key_line_breaks.md @@ -0,0 +1,17 @@ +This cop ensures that each key in a multi-line hash +starts on a separate line. + +### Example: + + # bad + { + a: 1, b: 2, + c: 3 + } + + # good + { + a: 1, + b: 2, + c: 3 + } \ No newline at end of file diff --git a/config/contents/layout/multiline_method_argument_line_breaks.md b/config/contents/layout/multiline_method_argument_line_breaks.md new file mode 100644 index 00000000..b80dbb68 --- /dev/null +++ b/config/contents/layout/multiline_method_argument_line_breaks.md @@ -0,0 +1,16 @@ +This cop ensures that each argument in a multi-line method call +starts on a separate line. + +### Example: + + # bad + foo(a, b, + c + ) + + # good + foo( + a, + b, + c + ) \ No newline at end of file diff --git a/config/contents/layout/multiline_method_call_brace_layout.md b/config/contents/layout/multiline_method_call_brace_layout.md index 9a0bd593..9f695398 100644 --- a/config/contents/layout/multiline_method_call_brace_layout.md +++ b/config/contents/layout/multiline_method_call_brace_layout.md @@ -21,32 +21,65 @@ When using the `same_line` style: The closing brace of a multi-line method call must be on the same line as the last argument of the call. -### Example: - - # symmetrical: bad - # new_line: good - # same_line: bad - foo(a, - b - ) - - # symmetrical: bad - # new_line: bad - # same_line: good - foo( - a, - b) - - # symmetrical: good - # new_line: bad - # same_line: good - foo(a, - b) - - # symmetrical: good - # new_line: good - # same_line: bad - foo( - a, - b - ) \ No newline at end of file +### Example: EnforcedStyle: symmetrical (default) + # bad + foo(a, + b + ) + + # bad + foo( + a, + b) + + # good + foo(a, + b) + + # good + foo( + a, + b + ) + +### Example: EnforcedStyle: new_line + # bad + foo( + a, + b) + + # bad + foo(a, + b) + + # good + foo(a, + b + ) + + # good + foo( + a, + b + ) + +### Example: EnforcedStyle: same_line + # bad + foo(a, + b + ) + + # bad + foo( + a, + b + ) + + # good + foo( + a, + b) + + # good + foo(a, + b) \ No newline at end of file diff --git a/config/contents/layout/multiline_method_call_indentation.md b/config/contents/layout/multiline_method_call_indentation.md index add6c3f0..cde63232 100644 --- a/config/contents/layout/multiline_method_call_indentation.md +++ b/config/contents/layout/multiline_method_call_indentation.md @@ -1,7 +1,7 @@ This cop checks the indentation of the method name part in method calls that span more than one line. -### Example: EnforcedStyle: aligned +### Example: EnforcedStyle: aligned (default) # bad while myvariable .b diff --git a/config/contents/layout/multiline_method_definition_brace_layout.md b/config/contents/layout/multiline_method_definition_brace_layout.md index b390c935..3572739c 100644 --- a/config/contents/layout/multiline_method_definition_brace_layout.md +++ b/config/contents/layout/multiline_method_definition_brace_layout.md @@ -21,36 +21,77 @@ When using the `same_line` style: The closing brace of a multi-line method definition must be on the same line as the last parameter of the definition. -### Example: - - # symmetrical: bad - # new_line: good - # same_line: bad - def foo(a, - b - ) - end - - # symmetrical: bad - # new_line: bad - # same_line: good - def foo( - a, - b) - end - - # symmetrical: good - # new_line: bad - # same_line: good - def foo(a, - b) - end - - # symmetrical: good - # new_line: good - # same_line: bad - def foo( - a, - b - ) - end \ No newline at end of file +### Example: EnforcedStyle: symmetrical (default) + # bad + def foo(a, + b + ) + end + + # bad + def foo( + a, + b) + end + + # good + def foo(a, + b) + end + + # good + def foo( + a, + b + ) + end + +### Example: EnforcedStyle: new_line + # bad + def foo( + a, + b) + end + + # bad + def foo(a, + b) + end + + # good + def foo(a, + b + ) + end + + # good + def foo( + a, + b + ) + end + +### Example: EnforcedStyle: same_line + # bad + def foo(a, + b + ) + end + + # bad + def foo( + a, + b + ) + end + + # good + def foo( + a, + b) + end + + # good + def foo(a, + b) + end \ No newline at end of file diff --git a/config/contents/layout/multiline_operation_indentation.md b/config/contents/layout/multiline_operation_indentation.md index 9af39df0..54fbe17d 100644 --- a/config/contents/layout/multiline_operation_indentation.md +++ b/config/contents/layout/multiline_operation_indentation.md @@ -1,15 +1,37 @@ This cop checks the indentation of the right hand side operand in binary operations that span more than one line. -### Example: +The `aligned` style checks that operators are aligned if they are part +of an `if` or `while` condition, a `return` statement, etc. In other +contexts, the second operand should be indented regardless of enforced +style. + +### Example: EnforcedStyle: aligned (default) # bad if a + - b - something + b + something && + something_else end # good if a + b - something - end \ No newline at end of file + something && + something_else + end + +### Example: EnforcedStyle: indented + # bad + if a + + b + something && + something_else + end + + # good + if a + + b + something && + something_else + end diff --git a/config/contents/layout/parameter_alignment.md b/config/contents/layout/parameter_alignment.md new file mode 100644 index 00000000..ec3d2d29 --- /dev/null +++ b/config/contents/layout/parameter_alignment.md @@ -0,0 +1,65 @@ +Here we check if the parameters on a multi-line method call or +definition are aligned. + +To set the alignment of the first argument, use the cop +FirstParameterIndentation. + +### Example: EnforcedStyle: with_first_parameter (default) + # good + + def foo(bar, + baz) + 123 + end + + def foo( + bar, + baz + ) + 123 + end + + # bad + + def foo(bar, + baz) + 123 + end + + # bad + + def foo( + bar, + baz) + 123 + end + +### Example: EnforcedStyle: with_fixed_indentation + # good + + def foo(bar, + baz) + 123 + end + + def foo( + bar, + baz + ) + 123 + end + + # bad + + def foo(bar, + baz) + 123 + end + + # bad + + def foo( + bar, + baz) + 123 + end \ No newline at end of file diff --git a/config/contents/layout/redundant_line_break.md b/config/contents/layout/redundant_line_break.md new file mode 100644 index 00000000..87f7cd86 --- /dev/null +++ b/config/contents/layout/redundant_line_break.md @@ -0,0 +1,38 @@ +This cop checks whether certain expressions, e.g. method calls, that could fit +completely on a single line, are broken up into multiple lines unnecessarily. + +### Example: any configuration + # bad + foo( + a, + b + ) + + puts 'string that fits on ' \ + 'a single line' + + things + .select { |thing| thing.cond? } + .join('-') + + # good + foo(a, b) + + puts 'string that fits on a single line' + + things.select { |thing| thing.cond? }.join('-') + +### Example: InspectBlocks: false (default) + # good + foo(a) do |x| + puts x + end + +### Example: InspectBlocks: true + # bad + foo(a) do |x| + puts x + end + + # good + foo(a) { |x| puts x } diff --git a/config/contents/layout/single_line_block_chain.md b/config/contents/layout/single_line_block_chain.md new file mode 100644 index 00000000..efc475d0 --- /dev/null +++ b/config/contents/layout/single_line_block_chain.md @@ -0,0 +1,15 @@ +This cop checks if method calls are chained onto single line blocks. It considers that a +line break before the dot improves the readability of the code. + +### Example: + # bad + example.select { |item| item.cond? }.join('-') + + # good + example.select { |item| item.cond? } + .join('-') + + # good (not a concern for this cop) + example.select do |item| + item.cond? + end.join('-') diff --git a/config/contents/layout/space_around_block_parameters.md b/config/contents/layout/space_around_block_parameters.md index f51f5673..14ba567e 100644 --- a/config/contents/layout/space_around_block_parameters.md +++ b/config/contents/layout/space_around_block_parameters.md @@ -1,4 +1,6 @@ -Checks the spacing inside and after block parameters pipes. +Checks the spacing inside and after block parameters pipes. Line breaks +inside parameter pipes are checked by `Layout/MultilineBlockLayout` and +not by this cop. ### Example: EnforcedStyleInsidePipes: no_space (default) # bad diff --git a/config/contents/layout/space_around_equals_in_parameter_default.md b/config/contents/layout/space_around_equals_in_parameter_default.md index 8eec9282..bfc5bd91 100644 --- a/config/contents/layout/space_around_equals_in_parameter_default.md +++ b/config/contents/layout/space_around_equals_in_parameter_default.md @@ -1,6 +1,7 @@ Checks that the equals signs in parameter default assignments have or don't have surrounding space depending on configuration. -### Example: + +### Example: EnforcedStyle: space (default) # bad def some_method(arg1=:default, arg2=nil, arg3=[]) # do something... @@ -9,4 +10,15 @@ have or don't have surrounding space depending on configuration. # good def some_method(arg1 = :default, arg2 = nil, arg3 = []) # do something... + end + +### Example: EnforcedStyle: no_space + # bad + def some_method(arg1 = :default, arg2 = nil, arg3 = []) + # do something... + end + + # good + def some_method(arg1=:default, arg2=nil, arg3=[]) + # do something... end \ No newline at end of file diff --git a/config/contents/layout/space_around_method_call_operator.md b/config/contents/layout/space_around_method_call_operator.md new file mode 100644 index 00000000..37ef0ce6 --- /dev/null +++ b/config/contents/layout/space_around_method_call_operator.md @@ -0,0 +1,30 @@ +Checks method call operators to not have spaces around them. + +### Example: + # bad + foo. bar + foo .bar + foo . bar + foo. bar .buzz + foo + . bar + . buzz + foo&. bar + foo &.bar + foo &. bar + foo &. bar&. buzz + RuboCop:: Cop + RuboCop:: Cop:: Cop + :: RuboCop::Cop + + # good + foo.bar + foo.bar.buzz + foo + .bar + .buzz + foo&.bar + foo&.bar&.buzz + RuboCop::Cop + RuboCop::Cop::Cop + ::RuboCop::Cop diff --git a/config/contents/layout/space_around_operators.md b/config/contents/layout/space_around_operators.md index 83ce14a2..dea89ac5 100644 --- a/config/contents/layout/space_around_operators.md +++ b/config/contents/layout/space_around_operators.md @@ -1,15 +1,47 @@ -Checks that operators have space around them, except for ** -which should not have surrounding space. +Checks that operators have space around them, except for ** which +should or shouldn't have surrounding space depending on configuration. +It allows vertical alignment consisting of one or more whitespace +around operators. + +This cop has `AllowForAlignment` option. When `true`, allows most +uses of extra spacing if the intent is to align with an operator on +the previous or next line, not counting empty lines or comment lines. ### Example: # bad total = 3*4 "apple"+"juice" my_number = 38/4 - a ** b # good total = 3 * 4 "apple" + "juice" my_number = 38 / 4 - a**b \ No newline at end of file + +### Example: AllowForAlignment: true (default) + # good + { + 1 => 2, + 11 => 3 + } + +### Example: AllowForAlignment: false + # bad + { + 1 => 2, + 11 => 3 + } + +### Example: EnforcedStyleForExponentOperator: no_space (default) + # bad + a ** b + + # good + a**b + +### Example: EnforcedStyleForExponentOperator: space + # bad + a**b + + # good + a ** b \ No newline at end of file diff --git a/config/contents/layout/space_before_block_braces.md b/config/contents/layout/space_before_block_braces.md index a0dc9c14..7640b085 100644 --- a/config/contents/layout/space_before_block_braces.md +++ b/config/contents/layout/space_before_block_braces.md @@ -1,7 +1,7 @@ Checks that block braces have or don't have a space before the opening brace depending on configuration. -### Example: +### Example: EnforcedStyle: space (default) # bad foo.map{ |a| a.bar.to_s @@ -10,4 +10,29 @@ brace depending on configuration. # good foo.map { |a| a.bar.to_s - } \ No newline at end of file + } + +### Example: EnforcedStyle: no_space + # bad + foo.map { |a| + a.bar.to_s + } + + # good + foo.map{ |a| + a.bar.to_s + } + +### Example: EnforcedStyleForEmptyBraces: space (default) + # bad + 7.times{} + + # good + 7.times {} + +### Example: EnforcedStyleForEmptyBraces: no_space + # bad + 7.times {} + + # good + 7.times{} \ No newline at end of file diff --git a/config/contents/layout/space_before_brackets.md b/config/contents/layout/space_before_brackets.md new file mode 100644 index 00000000..5b48e8fd --- /dev/null +++ b/config/contents/layout/space_before_brackets.md @@ -0,0 +1,10 @@ +Checks for space between the name of a receiver and a left +brackets. + +### Example: + + # bad + collection [index_or_key] + + # good + collection[index_or_key] diff --git a/config/contents/layout/space_before_comma.md b/config/contents/layout/space_before_comma.md index 1bc65886..3dda1d58 100644 --- a/config/contents/layout/space_before_comma.md +++ b/config/contents/layout/space_before_comma.md @@ -9,4 +9,4 @@ Checks for comma (,) preceded by space. # good [1, 2, 3] a(1, 2) - each { |a, b| } \ No newline at end of file + each { |a, b| } diff --git a/config/contents/layout/space_in_lambda_literal.md b/config/contents/layout/space_in_lambda_literal.md index d60382e2..d420fac4 100644 --- a/config/contents/layout/space_in_lambda_literal.md +++ b/config/contents/layout/space_in_lambda_literal.md @@ -1,5 +1,5 @@ -This cop checks for spaces between -> and opening parameter -brace in lambda literals. +This cop checks for spaces between `->` and opening parameter +parenthesis (`(`) in lambda literals. ### Example: EnforcedStyle: require_no_space (default) # bad diff --git a/config/contents/layout/space_inside_array_literal_brackets.md b/config/contents/layout/space_inside_array_literal_brackets.md index 8aca1b29..618ed7fd 100644 --- a/config/contents/layout/space_inside_array_literal_brackets.md +++ b/config/contents/layout/space_inside_array_literal_brackets.md @@ -11,7 +11,7 @@ surrounding space depending on configuration. # good array = [ a, b, c, d ] -### Example: EnforcedStyle: no_space +### Example: EnforcedStyle: no_space (default) # The `no_space` style enforces that array literals have # no surrounding space. @@ -28,6 +28,36 @@ surrounding space depending on configuration. # bad array = [ a, [ b, c ] ] + array = [ + [ a ], + [ b, c ] + ] # good - array = [ a, [ b, c ]] \ No newline at end of file + array = [ a, [ b, c ]] + array = [[ a ], + [ b, c ]] + +### Example: EnforcedStyleForEmptyBrackets: no_space (default) + # The `no_space` EnforcedStyleForEmptyBrackets style enforces that + # empty array brackets do not contain spaces. + + # bad + foo = [ ] + bar = [ ] + + # good + foo = [] + bar = [] + +### Example: EnforcedStyleForEmptyBrackets: space + # The `space` EnforcedStyleForEmptyBrackets style enforces that + # empty array brackets contain exactly one space. + + # bad + foo = [] + bar = [ ] + + # good + foo = [ ] + bar = [ ] diff --git a/config/contents/layout/space_inside_block_braces.md b/config/contents/layout/space_inside_block_braces.md index 10975fbe..6738f3eb 100644 --- a/config/contents/layout/space_inside_block_braces.md +++ b/config/contents/layout/space_inside_block_braces.md @@ -38,7 +38,7 @@ configuration. ### Example: EnforcedStyleForEmptyBraces: space # The `space` EnforcedStyleForEmptyBraces style enforces that - # block braces have at least a spece in between when empty. + # block braces have at least a space in between when empty. # bad some_array.each {} @@ -60,7 +60,7 @@ configuration. # good [1, 2, 3].each { |n| n * 2 } -### Example: SpaceBeforeBlockParameters: true +### Example: SpaceBeforeBlockParameters: false # The SpaceBeforeBlockParameters style set to `false` enforces that # there is no space between `{` and `|`. Overrides `EnforcedStyle` # if there is a conflict. diff --git a/config/contents/layout/space_inside_hash_literal_braces.md b/config/contents/layout/space_inside_hash_literal_braces.md index 566643cd..d9e48b24 100644 --- a/config/contents/layout/space_inside_hash_literal_braces.md +++ b/config/contents/layout/space_inside_hash_literal_braces.md @@ -1,7 +1,7 @@ Checks that braces used for hash literals have or don't have surrounding space depending on configuration. -### Example: EnforcedStyle: space +### Example: EnforcedStyle: space (default) # The `space` style enforces that hash literals have # surrounding space. @@ -28,6 +28,32 @@ surrounding space depending on configuration. # bad h = { a: { b: 2 } } + foo = { { a: 1 } => { b: { c: 2 } } } # good - h = { a: { b: 2 }} \ No newline at end of file + h = { a: { b: 2 }} + foo = {{ a: 1 } => { b: { c: 2 }}} + +### Example: EnforcedStyleForEmptyBraces: no_space (default) + # The `no_space` EnforcedStyleForEmptyBraces style enforces that + # empty hash braces do not contain spaces. + + # bad + foo = { } + bar = { } + + # good + foo = {} + bar = {} + +### Example: EnforcedStyleForEmptyBraces: space + # The `space` EnforcedStyleForEmptyBraces style enforces that + # empty hash braces contain space. + + # bad + foo = {} + + # good + foo = { } + foo = { } + foo = { } diff --git a/config/contents/layout/space_inside_parens.md b/config/contents/layout/space_inside_parens.md index 21342814..e6f96af2 100644 --- a/config/contents/layout/space_inside_parens.md +++ b/config/contents/layout/space_inside_parens.md @@ -1,10 +1,29 @@ Checks for spaces inside ordinary round parentheses. -### Example: +### Example: EnforcedStyle: no_space (default) + # The `no_space` style enforces that parentheses do not have spaces. + # bad f( 3) g = (a + 3 ) + f( ) # good f(3) - g = (a + 3) \ No newline at end of file + g = (a + 3) + f() + +### Example: EnforcedStyle: space + # The `space` style enforces that parentheses have a space at the + # beginning and end. + # Note: Empty parentheses should not have spaces. + + # bad + f(3) + g = (a + 3) + y( ) + + # good + f( 3 ) + g = ( a + 3 ) + y() diff --git a/config/contents/layout/space_inside_reference_brackets.md b/config/contents/layout/space_inside_reference_brackets.md index d75ed237..8ee084ed 100644 --- a/config/contents/layout/space_inside_reference_brackets.md +++ b/config/contents/layout/space_inside_reference_brackets.md @@ -23,4 +23,27 @@ surrounding space depending on configuration. # good hash[ :key ] - array[ index ] \ No newline at end of file + array[ index ] + + +### Example: EnforcedStyleForEmptyBrackets: no_space (default) + # The `no_space` EnforcedStyleForEmptyBrackets style enforces that + # empty reference brackets do not contain spaces. + + # bad + foo[ ] + foo[ ] + + # good + foo[] + +### Example: EnforcedStyleForEmptyBrackets: space + # The `space` EnforcedStyleForEmptyBrackets style enforces that + # empty reference brackets contain exactly one space. + + # bad + foo[] + foo[ ] + + # good + foo[ ] diff --git a/config/contents/layout/tab.md b/config/contents/layout/tab.md new file mode 100644 index 00000000..ecca5491 --- /dev/null +++ b/config/contents/layout/tab.md @@ -0,0 +1,14 @@ +This cop checks for tabs inside the source code. + +### Example: + # bad + # This example uses a tab to indent bar. + def foo + bar + end + + # good + # This example uses spaces to indent bar. + def foo + bar + end diff --git a/config/contents/layout/trailing_blank_lines.md b/config/contents/layout/trailing_blank_lines.md new file mode 100644 index 00000000..913590a2 --- /dev/null +++ b/config/contents/layout/trailing_blank_lines.md @@ -0,0 +1,33 @@ +This cop looks for trailing blank lines and a final newline in the +source code. + +### Example: EnforcedStyle: final_blank_line + # `final_blank_line` looks for one blank line followed by a new line + # at the end of files. + + # bad + class Foo; end + # EOF + + # bad + class Foo; end # EOF + + # good + class Foo; end + + # EOF + +### Example: EnforcedStyle: final_newline (default) + # `final_newline` looks for one newline at the end of files. + + # bad + class Foo; end + + # EOF + + # bad + class Foo; end # EOF + + # good + class Foo; end + # EOF diff --git a/config/contents/layout/trailing_empty_lines.md b/config/contents/layout/trailing_empty_lines.md new file mode 100644 index 00000000..913590a2 --- /dev/null +++ b/config/contents/layout/trailing_empty_lines.md @@ -0,0 +1,33 @@ +This cop looks for trailing blank lines and a final newline in the +source code. + +### Example: EnforcedStyle: final_blank_line + # `final_blank_line` looks for one blank line followed by a new line + # at the end of files. + + # bad + class Foo; end + # EOF + + # bad + class Foo; end # EOF + + # good + class Foo; end + + # EOF + +### Example: EnforcedStyle: final_newline (default) + # `final_newline` looks for one newline at the end of files. + + # bad + class Foo; end + + # EOF + + # bad + class Foo; end # EOF + + # good + class Foo; end + # EOF diff --git a/config/contents/layout/trailing_whitespace.md b/config/contents/layout/trailing_whitespace.md new file mode 100644 index 00000000..f815bb2b --- /dev/null +++ b/config/contents/layout/trailing_whitespace.md @@ -0,0 +1,35 @@ +This cop looks for trailing whitespace in the source code. + +### Example: + # The line in this example contains spaces after the 0. + # bad + x = 0 + + # The line in this example ends directly after the 0. + # good + x = 0 + +### Example: AllowInHeredoc: false (default) + # The line in this example contains spaces after the 0. + # bad + code = <<~RUBY + x = 0 + RUBY + + # ok + code = <<~RUBY + x = 0 #{} + RUBY + + # good + trailing_whitespace = ' ' + code = <<~RUBY + x = 0#{trailing_whitespace} + RUBY + +### Example: AllowInHeredoc: true + # The line in this example contains spaces after the 0. + # good + code = <<~RUBY + x = 0 + RUBY diff --git a/config/contents/lint/ambiguous_assignment.md b/config/contents/lint/ambiguous_assignment.md new file mode 100644 index 00000000..f57b029e --- /dev/null +++ b/config/contents/lint/ambiguous_assignment.md @@ -0,0 +1,14 @@ +This cop checks for mistyped shorthand assignments. + +### Example: + # bad + x =- y + x =+ y + x =* y + x =! y + + # good + x -= y # or x = -y + x += y # or x = +y + x *= y # or x = *y + x != y # or x = !y diff --git a/config/contents/lint/ambiguous_block_association.md b/config/contents/lint/ambiguous_block_association.md index 455ef0d2..37a392c9 100644 --- a/config/contents/lint/ambiguous_block_association.md +++ b/config/contents/lint/ambiguous_block_association.md @@ -1,6 +1,8 @@ This cop checks for ambiguous block association with method when param passed without parentheses. +This cop can customize ignored methods with `IgnoredMethods`. + ### Example: # bad @@ -10,6 +12,8 @@ when param passed without parentheses. # good # With parentheses, there's no ambiguity. + some_method(a { |val| puts val }) + # or (different meaning) some_method(a) { |val| puts val } # good @@ -18,4 +22,9 @@ when param passed without parentheses. # good # Lambda arguments require no disambiguation - foo = ->(bar) { bar.baz } \ No newline at end of file + foo = ->(bar) { bar.baz } + +### Example: IgnoredMethods: [change] + + # good + expect { do_something }.to change { object.attribute } \ No newline at end of file diff --git a/config/contents/lint/assignment_in_condition.md b/config/contents/lint/assignment_in_condition.md index 60a37861..b652212c 100644 --- a/config/contents/lint/assignment_in_condition.md +++ b/config/contents/lint/assignment_in_condition.md @@ -1,18 +1,30 @@ This cop checks for assignments in the conditions of if/while/until. -### Example: +`AllowSafeAssignment` option for safe assignment. +By safe assignment we mean putting parentheses around +an assignment to indicate "I know I'm using an assignment +as a condition. It's not a mistake." +### Example: # bad - if some_var = true do_something end -### Example: + # good + if some_var == true + do_something + end +### Example: AllowSafeAssignment: true (default) # good + if (some_var = true) + do_something + end - if some_var == true +### Example: AllowSafeAssignment: false + # bad + if (some_var = true) do_something - end \ No newline at end of file + end diff --git a/config/contents/lint/big_decimal_new.md b/config/contents/lint/big_decimal_new.md new file mode 100644 index 00000000..ff6a0326 --- /dev/null +++ b/config/contents/lint/big_decimal_new.md @@ -0,0 +1,10 @@ +`BigDecimal.new()` is deprecated since BigDecimal 1.3.3. +This cop identifies places where `BigDecimal.new()` +can be replaced by `BigDecimal()`. + +### Example: + # bad + BigDecimal.new(123.456, 3) + + # good + BigDecimal(123.456, 3) diff --git a/config/contents/lint/binary_operator_with_identical_operands.md b/config/contents/lint/binary_operator_with_identical_operands.md new file mode 100644 index 00000000..cc871f7f --- /dev/null +++ b/config/contents/lint/binary_operator_with_identical_operands.md @@ -0,0 +1,28 @@ +This cop checks for places where binary operator has identical operands. + +It covers arithmetic operators: `+`, `-`, `*`, `/`, `%`, `**`; +comparison operators: `==`, `===`, `=~`, `>`, `>=`, `<`, `<=`; +bitwise operators: `|`, `^`, `&`, `<<`, `>>`; +boolean operators: `&&`, `||` +and "spaceship" operator - `<=>`. + +This cop is marked as unsafe as it does not consider side effects when calling methods +and thus can generate false positives: + if wr.take_char == '\0' && wr.take_char == '\0' + +### Example: + # bad + x / x + x.top >= x.top + + if a.x != 0 && a.x != 0 + do_something + end + + def childs? + left_child || left_child + end + + # good + x + x + 1 << 1 diff --git a/config/contents/lint/constant_definition_in_block.md b/config/contents/lint/constant_definition_in_block.md new file mode 100644 index 00000000..7884b8bd --- /dev/null +++ b/config/contents/lint/constant_definition_in_block.md @@ -0,0 +1,57 @@ +Do not define constants within a block, since the block's scope does not +isolate or namespace the constant in any way. + +If you are trying to define that constant once, define it outside of +the block instead, or use a variable or method if defining the constant +in the outer scope would be problematic. + +For meta-programming, use `const_set`. + +### Example: + # bad + task :lint do + FILES_TO_LINT = Dir['lib/*.rb'] + end + + # bad + describe 'making a request' do + class TestRequest; end + end + + # bad + module M + extend ActiveSupport::Concern + included do + LIST = [] + end + end + + # good + task :lint do + files_to_lint = Dir['lib/*.rb'] + end + + # good + describe 'making a request' do + let(:test_request) { Class.new } + # see also `stub_const` for RSpec + end + + # good + module M + extend ActiveSupport::Concern + included do + const_set(:LIST, []) + end + end + +### Example: AllowedMethods: ['enums'] (default) + # good + + # `enums` for Typed Enums via `T::Enum` in Sorbet. + # https://sorbet.org/docs/tenum + class TestEnum < T::Enum + enums do + Foo = new("foo") + end + end diff --git a/config/contents/lint/constant_resolution.md b/config/contents/lint/constant_resolution.md new file mode 100644 index 00000000..efa3b31e --- /dev/null +++ b/config/contents/lint/constant_resolution.md @@ -0,0 +1,51 @@ +Check that certain constants are fully qualified. + +This is not enabled by default because it would mark a lot of offenses +unnecessarily. + +Generally, gems should fully qualify all constants to avoid conflicts with +the code that uses the gem. Enable this cop without using `Only`/`Ignore` + +Large projects will over time end up with one or two constant names that +are problematic because of a conflict with a library or just internally +using the same name a namespace and a class. To avoid too many unnecessary +offenses, Enable this cop with `Only: [The, Constant, Names, Causing, Issues]` + +### Example: + # By default checks every constant + + # bad + User + + # bad + User::Login + + # good + ::User + + # good + ::User::Login + +### Example: Only: ['Login'] + # Restrict this cop to only being concerned about certain constants + + # bad + Login + + # good + ::Login + + # good + User::Login + +### Example: Ignore: ['Login'] + # Restrict this cop not being concerned about certain constants + + # bad + User + + # good + ::User::Login + + # good + Login diff --git a/config/contents/lint/debugger.md b/config/contents/lint/debugger.md index a66591c9..dad1bd13 100644 --- a/config/contents/lint/debugger.md +++ b/config/contents/lint/debugger.md @@ -1,4 +1,18 @@ -This cop checks for calls to debugger or pry. +This cop checks for debug calls (such as `debugger` or `binding.pry`) that should +not be kept for production code. + +The cop can be configured using `DebuggerMethods`. By default, a number of gems +debug entrypoints are configured (`Kernel`, `Byebug`, `Capybara`, `Pry`, `Rails`, +and `WebConsole`). Additional methods can be added. + +Specific default groups can be disabled if necessary: + +[source,yaml] +---- +Lint/Debugger: + WebConsole: ~ +---- + ### Example: @@ -26,4 +40,12 @@ This cop checks for calls to debugger or pry. def some_method do_something + end + +### Example: DebuggerMethods: [my_debugger] + + # bad (ok during development) + + def some_method + my_debugger end \ No newline at end of file diff --git a/config/contents/lint/deprecated_class_methods.md b/config/contents/lint/deprecated_class_methods.md index 0ede887f..5d57fb9c 100644 --- a/config/contents/lint/deprecated_class_methods.md +++ b/config/contents/lint/deprecated_class_methods.md @@ -5,9 +5,17 @@ This cop checks for uses of the deprecated class method usages. # bad File.exists?(some_path) + Dir.exists?(some_path) + iterator? + Socket.gethostbyname(host) + Socket.gethostbyaddr(host) ### Example: # good - File.exist?(some_path) \ No newline at end of file + File.exist?(some_path) + Dir.exist?(some_path) + block_given? + Addrinfo.getaddrinfo(nodename, service) + Addrinfo.tcp(host, port).getnameinfo \ No newline at end of file diff --git a/config/contents/lint/deprecated_constants.md b/config/contents/lint/deprecated_constants.md new file mode 100644 index 00000000..2b389fc5 --- /dev/null +++ b/config/contents/lint/deprecated_constants.md @@ -0,0 +1,26 @@ +This cop checks for deprecated constants. + +It has `DeprecatedConstants` config. If there is an alternative method, you can set +alternative value as `Alternative`. And you can set the deprecated version as +`DeprecatedVersion`. These options can be omitted if they are not needed. + + DeprecatedConstants: + 'DEPRECATED_CONSTANT': + Alternative: 'alternative_value' + DeprecatedVersion: 'deprecated_version' + +By default, `NIL`, `TRUE`, `FALSE` and `Random::DEFAULT` are configured. + +### Example: + + # bad + NIL + TRUE + FALSE + Random::DEFAULT # Return value of Ruby 2 is `Random` instance, Ruby 3.0 is `Random` class. + + # good + nil + true + false + Random.new # `::DEFAULT` has been deprecated in Ruby 3, `.new` is compatible with Ruby 2. diff --git a/config/contents/lint/deprecated_open_ssl_constant.md b/config/contents/lint/deprecated_open_ssl_constant.md new file mode 100644 index 00000000..96571a48 --- /dev/null +++ b/config/contents/lint/deprecated_open_ssl_constant.md @@ -0,0 +1,33 @@ +Algorithmic constants for `OpenSSL::Cipher` and `OpenSSL::Digest` +deprecated since OpenSSL version 2.2.0. Prefer passing a string +instead. + +### Example: + + # Example for OpenSSL::Cipher instantiation. + + # bad + OpenSSL::Cipher::AES.new(128, :GCM) + + # good + OpenSSL::Cipher.new('aes-128-gcm') + +### Example: + + # Example for OpenSSL::Digest instantiation. + + # bad + OpenSSL::Digest::SHA256.new + + # good + OpenSSL::Digest.new('SHA256') + +### Example: + + # Example for ::Digest inherited class methods. + + # bad + OpenSSL::Digest::SHA256.digest('foo') + + # good + OpenSSL::Digest.digest('SHA256', 'foo') diff --git a/config/contents/lint/disjunctive_assignment_in_constructor.md b/config/contents/lint/disjunctive_assignment_in_constructor.md new file mode 100644 index 00000000..bc75f9a5 --- /dev/null +++ b/config/contents/lint/disjunctive_assignment_in_constructor.md @@ -0,0 +1,19 @@ +This cop checks constructors for disjunctive assignments that should +be plain assignments. + +So far, this cop is only concerned with disjunctive assignment of +instance variables. + +In ruby, an instance variable is nil until a value is assigned, so the +disjunction is unnecessary. A plain assignment has the same effect. + +### Example: + # bad + def initialize + @x ||= 1 + end + + # good + def initialize + @x = 1 + end \ No newline at end of file diff --git a/config/contents/lint/duplicate_branch.md b/config/contents/lint/duplicate_branch.md new file mode 100644 index 00000000..c9f491fd --- /dev/null +++ b/config/contents/lint/duplicate_branch.md @@ -0,0 +1,79 @@ +This cop checks that there are no repeated bodies +within `if/unless`, `case-when` and `rescue` constructs. + +With `IgnoreLiteralBranches: true`, branches are not registered +as offenses if they return a basic literal value (string, symbol, +integer, float, rational, complex, `true`, `false`, or `nil`), or +return an array, hash, regexp or range that only contains one of +the above basic literal values. + +With `IgnoreConstantBranches: true`, branches are not registered +as offenses if they return a constant value. + +### Example: + # bad + if foo + do_foo + do_something_else + elsif bar + do_foo + do_something_else + end + + # good + if foo || bar + do_foo + do_something_else + end + + # bad + case x + when foo + do_foo + when bar + do_foo + else + do_something_else + end + + # good + case x + when foo, bar + do_foo + else + do_something_else + end + + # bad + begin + do_something + rescue FooError + handle_error + rescue BarError + handle_error + end + + # good + begin + do_something + rescue FooError, BarError + handle_error + end + +### Example: IgnoreLiteralBranches: true + # good + case size + when "small" then 100 + when "medium" then 250 + when "large" then 1000 + else 250 + end + +### Example: IgnoreConstantBranches: true + # good + case size + when "small" then SMALL_SIZE + when "medium" then MEDIUM_SIZE + when "large" then LARGE_SIZE + else MEDIUM_SIZE + end diff --git a/config/contents/lint/duplicate_elsif_condition.md b/config/contents/lint/duplicate_elsif_condition.md new file mode 100644 index 00000000..7f9f68b3 --- /dev/null +++ b/config/contents/lint/duplicate_elsif_condition.md @@ -0,0 +1,16 @@ +This cop checks that there are no repeated conditions used in if 'elsif'. + +### Example: + # bad + if x == 1 + do_something + elsif x == 1 + do_something_else + end + + # good + if x == 1 + do_something + elsif x == 2 + do_something_else + end diff --git a/config/contents/lint/duplicate_hash_key.md b/config/contents/lint/duplicate_hash_key.md new file mode 100644 index 00000000..6bab0890 --- /dev/null +++ b/config/contents/lint/duplicate_hash_key.md @@ -0,0 +1,15 @@ +This cop checks for duplicated keys in hash literals. + +This cop mirrors a warning in Ruby 2.2. + +### Example: + + # bad + + hash = { food: 'apple', food: 'orange' } + +### Example: + + # good + + hash = { food: 'apple', other_food: 'orange' } \ No newline at end of file diff --git a/config/contents/lint/duplicate_methods.md b/config/contents/lint/duplicate_methods.md index a38d165e..ae8c942f 100644 --- a/config/contents/lint/duplicate_methods.md +++ b/config/contents/lint/duplicate_methods.md @@ -5,11 +5,11 @@ definitions. # bad - def duplicated + def foo 1 end - def duplicated + def foo 2 end @@ -17,20 +17,30 @@ definitions. # bad - def duplicated + def foo 1 end - alias duplicated other_duplicated + alias foo bar ### Example: # good - def duplicated + def foo 1 end - def other_duplicated + def bar 2 - end \ No newline at end of file + end + +### Example: + + # good + + def foo + 1 + end + + alias bar foo \ No newline at end of file diff --git a/config/contents/lint/duplicate_regexp_character_class_element.md b/config/contents/lint/duplicate_regexp_character_class_element.md new file mode 100644 index 00000000..f20875a2 --- /dev/null +++ b/config/contents/lint/duplicate_regexp_character_class_element.md @@ -0,0 +1,15 @@ +This cop checks for duplicate elements in Regexp character classes. + +### Example: + + # bad + r = /[xyx]/ + + # bad + r = /[0-9x0-9]/ + + # good + r = /[xy]/ + + # good + r = /[0-9x]/ \ No newline at end of file diff --git a/config/contents/lint/duplicate_require.md b/config/contents/lint/duplicate_require.md new file mode 100644 index 00000000..ff7dd3de --- /dev/null +++ b/config/contents/lint/duplicate_require.md @@ -0,0 +1,15 @@ +This cop checks for duplicate `require`s and `require_relative`s. + +### Example: + # bad + require 'foo' + require 'bar' + require 'foo' + + # good + require 'foo' + require 'bar' + + # good + require 'foo' + require_relative 'foo' diff --git a/config/contents/lint/duplicate_rescue_exception.md b/config/contents/lint/duplicate_rescue_exception.md new file mode 100644 index 00000000..e430c4f6 --- /dev/null +++ b/config/contents/lint/duplicate_rescue_exception.md @@ -0,0 +1,21 @@ +This cop checks that there are no repeated exceptions +used in 'rescue' expressions. + +### Example: + # bad + begin + something + rescue FirstException + handle_exception + rescue FirstException + handle_other_exception + end + + # good + begin + something + rescue FirstException + handle_exception + rescue SecondException + handle_other_exception + end diff --git a/config/contents/lint/else_layout.md b/config/contents/lint/else_layout.md index ba728d37..9ed76256 100644 --- a/config/contents/lint/else_layout.md +++ b/config/contents/lint/else_layout.md @@ -1,7 +1,11 @@ -This cop checks for odd else block layout - like -having an expression on the same line as the else keyword, +This cop checks for odd `else` block layout - like +having an expression on the same line as the `else` keyword, which is usually a mistake. +Its auto-correction tweaks layout to keep the syntax. So, this auto-correction +is compatible correction for bad case syntax, but if your code makes a mistake +with `elsif` and `else`, you will have to correct it manually. + ### Example: # bad @@ -16,9 +20,18 @@ which is usually a mistake. # good + # This code is compatible with the bad case. It will be auto-corrected like this. if something # ... else do_this do_that + end + + # This code is incompatible with the bad case. + # If `do_this` is a condition, `elsif` should be used instead of `else`. + if something + # ... + elsif do_this + do_that end \ No newline at end of file diff --git a/config/contents/lint/empty_block.md b/config/contents/lint/empty_block.md new file mode 100644 index 00000000..dcd86cb2 --- /dev/null +++ b/config/contents/lint/empty_block.md @@ -0,0 +1,56 @@ +This cop checks for blocks without a body. +Such empty blocks are typically an oversight or we should provide a comment +be clearer what we're aiming for. + +Empty lambdas and procs are ignored by default. + +NOTE: For backwards compatibility, the configuration that allows/disallows +empty lambdas and procs is called `AllowEmptyLambdas`, even though it also +applies to procs. + +### Example: + # bad + items.each { |item| } + + # good + items.each { |item| puts item } + +### Example: AllowComments: true (default) + # good + items.each do |item| + # TODO: implement later (inner comment) + end + + items.each { |item| } # TODO: implement later (inline comment) + +### Example: AllowComments: false + # bad + items.each do |item| + # TODO: implement later (inner comment) + end + + items.each { |item| } # TODO: implement later (inline comment) + +### Example: AllowEmptyLambdas: true (default) + # good + allow(subject).to receive(:callable).and_return(-> {}) + + placeholder = lambda do + end + (callable || placeholder).call + + proc { } + + Proc.new { } + +### Example: AllowEmptyLambdas: false + # bad + allow(subject).to receive(:callable).and_return(-> {}) + + placeholder = lambda do + end + (callable || placeholder).call + + proc { } + + Proc.new { } diff --git a/config/contents/lint/empty_class.md b/config/contents/lint/empty_class.md new file mode 100644 index 00000000..a4c86992 --- /dev/null +++ b/config/contents/lint/empty_class.md @@ -0,0 +1,65 @@ +This cop checks for classes and metaclasses without a body. +Such empty classes and metaclasses are typically an oversight or we should provide a comment +to be clearer what we're aiming for. + +### Example: + # bad + class Foo + end + + class Bar + class << self + end + end + + class << obj + end + + # good + class Foo + def do_something + # ... code + end + end + + class Bar + class << self + attr_reader :bar + end + end + + class << obj + attr_reader :bar + end + +### Example: AllowComments: false (default) + # bad + class Foo + # TODO: implement later + end + + class Bar + class << self + # TODO: implement later + end + end + + class << obj + # TODO: implement later + end + +### Example: AllowComments: true + # good + class Foo + # TODO: implement later + end + + class Bar + class << self + # TODO: implement later + end + end + + class << obj + # TODO: implement later + end diff --git a/config/contents/lint/empty_conditional_body.md b/config/contents/lint/empty_conditional_body.md new file mode 100644 index 00000000..b3fc9e90 --- /dev/null +++ b/config/contents/lint/empty_conditional_body.md @@ -0,0 +1,48 @@ +This cop checks for the presence of `if`, `elsif` and `unless` branches without a body. +### Example: + # bad + if condition + end + + # bad + unless condition + end + + # bad + if condition + do_something + elsif other_condition + end + + # good + if condition + do_something + end + + # good + unless condition + do_something + end + + # good + if condition + do_something + elsif other_condition + do_something_else + end + +### Example: AllowComments: true (default) + # good + if condition + do_something + elsif other_condition + # noop + end + +### Example: AllowComments: false + # bad + if condition + do_something + elsif other_condition + # noop + end diff --git a/config/contents/lint/empty_file.md b/config/contents/lint/empty_file.md new file mode 100644 index 00000000..166d266d --- /dev/null +++ b/config/contents/lint/empty_file.md @@ -0,0 +1,16 @@ +This cop enforces that Ruby source files are not empty. + +### Example: + # bad + # Empty file + + # good + # File containing non commented source lines + +### Example: AllowComments: true (default) + # good + # File consisting only of comments + +### Example: AllowComments: false + # bad + # File consisting only of comments diff --git a/config/contents/lint/empty_in_pattern.md b/config/contents/lint/empty_in_pattern.md new file mode 100644 index 00000000..c6e97ddc --- /dev/null +++ b/config/contents/lint/empty_in_pattern.md @@ -0,0 +1,38 @@ +This cop checks for the presence of `in` pattern branches without a body. + +### Example: + + # bad + case condition + in [a] + do_something + in [a, b] + end + + # good + case condition + in [a] + do_something + in [a, b] + nil + end + +### Example: AllowComments: true (default) + + # good + case condition + in [a] + do_something + in [a, b] + # noop + end + +### Example: AllowComments: false + + # bad + case condition + in [a] + do_something + in [a, b] + # noop + end diff --git a/config/contents/lint/empty_when.md b/config/contents/lint/empty_when.md index 1d345d6e..d4f104bb 100644 --- a/config/contents/lint/empty_when.md +++ b/config/contents/lint/empty_when.md @@ -3,17 +3,38 @@ This cop checks for the presence of `when` branches without a body. ### Example: # bad - case foo - when bar then 1 - when baz then # nothing + when bar + do_something + when baz end ### Example: # good + case condition + when foo + do_something + when bar + nil + end - case foo - when bar then 1 - when baz then 2 - end \ No newline at end of file +### Example: AllowComments: true (default) + + # good + case condition + when foo + do_something + when bar + # noop + end + +### Example: AllowComments: false + + # bad + case condition + when foo + do_something + when bar + # do nothing + end diff --git a/config/contents/lint/ensure_return.md b/config/contents/lint/ensure_return.md index c155ed2e..7295a5d7 100644 --- a/config/contents/lint/ensure_return.md +++ b/config/contents/lint/ensure_return.md @@ -1,22 +1,41 @@ -This cop checks for *return* from an *ensure* block. +This cop checks for `return` from an `ensure` block. +`return` from an ensure block is a dangerous code smell as it +will take precedence over any exception being raised, +and the exception will be silently thrown away as if it were rescued. + +If you want to rescue some (or all) exceptions, best to do it explicitly ### Example: # bad - begin + def foo do_something ensure - do_something_else - return + cleanup + return self end ### Example: # good - begin + def foo do_something + self + ensure + cleanup + end + + # also good + + def foo + begin + do_something + rescue SomeException + # Let's ignore this exception + end + self ensure - do_something_else + cleanup end \ No newline at end of file diff --git a/config/contents/lint/erb_new_arguments.md b/config/contents/lint/erb_new_arguments.md new file mode 100644 index 00000000..12653336 --- /dev/null +++ b/config/contents/lint/erb_new_arguments.md @@ -0,0 +1,55 @@ + +This cop emulates the following Ruby warnings in Ruby 2.6. + +% cat example.rb +ERB.new('hi', nil, '-', '@output_buffer') +% ruby -rerb example.rb +example.rb:1: warning: Passing safe_level with the 2nd argument of +ERB.new is deprecated. Do not use it, and specify other arguments as +keyword arguments. +example.rb:1: warning: Passing trim_mode with the 3rd argument of +ERB.new is deprecated. Use keyword argument like +ERB.new(str, trim_mode:...) instead. +example.rb:1: warning: Passing eoutvar with the 4th argument of ERB.new +is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) +instead. + +Now non-keyword arguments other than first one are softly deprecated +and will be removed when Ruby 2.5 becomes EOL. +`ERB.new` with non-keyword arguments is deprecated since ERB 2.2.0. +Use `:trim_mode` and `:eoutvar` keyword arguments to `ERB.new`. +This cop identifies places where `ERB.new(str, trim_mode, eoutvar)` can +be replaced by `ERB.new(str, :trim_mode: trim_mode, eoutvar: eoutvar)`. + +### Example: + # Target codes supports Ruby 2.6 and higher only + # bad + ERB.new(str, nil, '-', '@output_buffer') + + # good + ERB.new(str, trim_mode: '-', eoutvar: '@output_buffer') + + # Target codes supports Ruby 2.5 and lower only + # good + ERB.new(str, nil, '-', '@output_buffer') + + # Target codes supports Ruby 2.6, 2.5 and lower + # bad + ERB.new(str, nil, '-', '@output_buffer') + + # good + # Ruby standard library style + # https://github.com/ruby/ruby/commit/3406c5d + if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ + ERB.new(str, trim_mode: '-', eoutvar: '@output_buffer') + else + ERB.new(str, nil, '-', '@output_buffer') + end + + # good + # Use `RUBY_VERSION` style + if RUBY_VERSION >= '2.6' + ERB.new(str, trim_mode: '-', eoutvar: '@output_buffer') + else + ERB.new(str, nil, '-', '@output_buffer') + end diff --git a/config/contents/lint/flip_flop.md b/config/contents/lint/flip_flop.md new file mode 100644 index 00000000..15b3174a --- /dev/null +++ b/config/contents/lint/flip_flop.md @@ -0,0 +1,19 @@ +This cop looks for uses of flip-flop operator +based on the Ruby Style Guide. + +Here is the history of flip-flops in Ruby. +flip-flop operator is deprecated in Ruby 2.6.0 and +the deprecation has been reverted by Ruby 2.7.0 and +backported to Ruby 2.6. +See: https://bugs.ruby-lang.org/issues/5400 + +### Example: + # bad + (1..20).each do |x| + puts x if (x == 5) .. (x == 10) + end + + # good + (1..20).each do |x| + puts x if (x >= 5) && (x <= 10) + end \ No newline at end of file diff --git a/config/contents/lint/float_comparison.md b/config/contents/lint/float_comparison.md new file mode 100644 index 00000000..b9d7bc7f --- /dev/null +++ b/config/contents/lint/float_comparison.md @@ -0,0 +1,24 @@ +This cop checks for the presence of precise comparison of floating point numbers. + +Floating point values are inherently inaccurate, and comparing them for exact equality +is almost never the desired semantics. Comparison via the `==/!=` operators checks +floating-point value representation to be exactly the same, which is very unlikely +if you perform any arithmetic operations involving precision loss. + +### Example: + # bad + x == 0.1 + x != 0.1 + + # good - using BigDecimal + x.to_d == 0.1.to_d + + # good + (x - 0.1).abs < Float::EPSILON + + # good + tolerance = 0.0001 + (x - 0.1).abs < tolerance + + # Or some other epsilon based type of comparison: + # https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/ diff --git a/config/contents/lint/format_parameter_mismatch.md b/config/contents/lint/format_parameter_mismatch.md index 8945029e..a42335cb 100644 --- a/config/contents/lint/format_parameter_mismatch.md +++ b/config/contents/lint/format_parameter_mismatch.md @@ -2,6 +2,10 @@ This lint sees if there is a mismatch between the number of expected fields for format/sprintf/#% and what is actually passed as arguments. +In addition it checks whether different formats are used in the same +format string. Do not mix numbered, unnumbered, and named formats in +the same format string. + ### Example: # bad @@ -12,4 +16,16 @@ passed as arguments. # good - format('A value: %s and another: %i', a_value, another) \ No newline at end of file + format('A value: %s and another: %i', a_value, another) + +### Example: + + # bad + + format('Unnumbered format: %s and numbered: %2$s', a_value, another) + +### Example: + + # good + + format('Numbered format: %1$s and numbered %2$s', a_value, another) \ No newline at end of file diff --git a/config/contents/lint/handle_exceptions.md b/config/contents/lint/handle_exceptions.md index 18f4c96a..6966f9af 100644 --- a/config/contents/lint/handle_exceptions.md +++ b/config/contents/lint/handle_exceptions.md @@ -1,41 +1,71 @@ This cop checks for *rescue* blocks with no body. -### Example: +### Example: AllowComments: false (default) # bad + def some_method + do_something + rescue + end + # bad def some_method do_something rescue # do nothing end -### Example: - # bad + begin + do_something + rescue + end + # bad begin do_something rescue # do nothing end -### Example: + # good + def some_method + do_something + rescue + handle_exception + end # good + begin + do_something + rescue + handle_exception + end + +### Example: AllowComments: true + # bad def some_method do_something rescue - handle_exception end -### Example: + # bad + begin + do_something + rescue + end # good + def some_method + do_something + rescue + # do nothing but comment + end + # good begin do_something rescue - handle_exception + # do nothing but comment end \ No newline at end of file diff --git a/config/contents/lint/hash_compare_by_identity.md b/config/contents/lint/hash_compare_by_identity.md new file mode 100644 index 00000000..99f6e2bd --- /dev/null +++ b/config/contents/lint/hash_compare_by_identity.md @@ -0,0 +1,15 @@ +Prefer using `Hash#compare_by_identity` than using `object_id` for hash keys. + +This cop is marked as unsafe as a hash possibly can contain other keys +besides `object_id`s. + +### Example: + # bad + hash = {} + hash[foo.object_id] = :bar + hash.key?(baz.object_id) + + # good + hash = {}.compare_by_identity + hash[foo] = :bar + hash.key?(baz) diff --git a/config/contents/lint/heredoc_method_call_position.md b/config/contents/lint/heredoc_method_call_position.md new file mode 100644 index 00000000..2ad209ea --- /dev/null +++ b/config/contents/lint/heredoc_method_call_position.md @@ -0,0 +1,26 @@ +This cop checks for the ordering of a method call where +the receiver of the call is a HEREDOC. + +### Example: + # bad + + <<-SQL + bar + SQL + .strip_indent + + <<-SQL + bar + SQL + .strip_indent + .trim + + # good + + <<~SQL + bar + SQL + + <<~SQL.trim + bar + SQL diff --git a/config/contents/lint/identity_comparison.md b/config/contents/lint/identity_comparison.md new file mode 100644 index 00000000..c52449e0 --- /dev/null +++ b/config/contents/lint/identity_comparison.md @@ -0,0 +1,12 @@ + +Prefer `equal?` over `==` when comparing `object_id`. + +`Object#equal?` is provided to compare objects for identity, and in contrast +`Object#==` is provided for the purpose of doing value comparison. + +### Example: + # bad + foo.object_id == bar.object_id + + # good + foo.equal?(bar) diff --git a/config/contents/lint/inherit_exception.md b/config/contents/lint/inherit_exception.md index 5acf90c0..cb7837bc 100644 --- a/config/contents/lint/inherit_exception.md +++ b/config/contents/lint/inherit_exception.md @@ -8,15 +8,23 @@ and its standard library subclasses, excluding subclasses of class C < Exception; end + C = Class.new(Exception) + # good class C < RuntimeError; end + C = Class.new(RuntimeError) + ### Example: EnforcedStyle: standard_error # bad class C < Exception; end + C = Class.new(Exception) + # good - class C < StandardError; end \ No newline at end of file + class C < StandardError; end + + C = Class.new(StandardError) \ No newline at end of file diff --git a/config/contents/lint/lambda_without_literal_block.md b/config/contents/lint/lambda_without_literal_block.md new file mode 100644 index 00000000..0dd6abbc --- /dev/null +++ b/config/contents/lint/lambda_without_literal_block.md @@ -0,0 +1,21 @@ +This cop checks uses of lambda without a literal block. +It emulates the following warning in Ruby 3.0: + + % ruby -vwe 'lambda(&proc {})' + ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19] + -e:1: warning: lambda without a literal block is deprecated; use the proc without + lambda instead + +This way, proc object is never converted to lambda. +Auto-correction replaces with compatible proc argument. + +### Example: + + # bad + lambda(&proc { do_something }) + lambda(&Proc.new { do_something }) + + # good + proc { do_something } + Proc.new { do_something } + lambda { do_something } # If you use lambda. diff --git a/config/contents/lint/literal_as_condition.md b/config/contents/lint/literal_as_condition.md index 8a7d79be..5c78d984 100644 --- a/config/contents/lint/literal_as_condition.md +++ b/config/contents/lint/literal_as_condition.md @@ -1,27 +1,26 @@ This cop checks for literals used as the conditions or as operands in and/or expressions serving as the conditions of -if/while/until. +if/while/until/case-when/case-in. ### Example: # bad - if 20 do_something end -### Example: - # bad - if some_var && true do_something end -### Example: - # good - if some_var && some_condition do_something + end + + # good + # When using a boolean value for an infinite loop. + while true + break if condition end \ No newline at end of file diff --git a/config/contents/lint/loop.md b/config/contents/lint/loop.md index 63d115a0..dfea642e 100644 --- a/config/contents/lint/loop.md +++ b/config/contents/lint/loop.md @@ -1,4 +1,8 @@ -This cop checks for uses of *begin...end while/until something*. +This cop checks for uses of `begin...end while/until something`. + +The cop is marked as unsafe because behaviour can change in some cases, including +if a local variable inside the loop body is accessed outside of it, or if the +loop body raises a `StopIteration` exception (which `Kernel#loop` rescues). ### Example: @@ -22,16 +26,18 @@ This cop checks for uses of *begin...end while/until something*. # good - # using while - while some_condition + # while replacement + loop do do_something + break unless some_condition end ### Example: # good - # using until - until some_condition + # until replacement + loop do do_something + break if some_condition end \ No newline at end of file diff --git a/config/contents/lint/missing_cop_enable_directive.md b/config/contents/lint/missing_cop_enable_directive.md new file mode 100644 index 00000000..c7e9c656 --- /dev/null +++ b/config/contents/lint/missing_cop_enable_directive.md @@ -0,0 +1,37 @@ +This cop checks that there is an `# rubocop:enable ...` statement +after a `# rubocop:disable ...` statement. This will prevent leaving +cop disables on wide ranges of code, that latter contributors to +a file wouldn't be aware of. + +### Example: + # Lint/MissingCopEnableDirective: + # MaximumRangeSize: .inf + + # good + # rubocop:disable Layout/SpaceAroundOperators + x= 0 + # rubocop:enable Layout/SpaceAroundOperators + # y = 1 + # EOF + + # bad + # rubocop:disable Layout/SpaceAroundOperators + x= 0 + # EOF + +### Example: + # Lint/MissingCopEnableDirective: + # MaximumRangeSize: 2 + + # good + # rubocop:disable Layout/SpaceAroundOperators + x= 0 + # With the previous, there are 2 lines on which cop is disabled. + # rubocop:enable Layout/SpaceAroundOperators + + # bad + # rubocop:disable Layout/SpaceAroundOperators + x= 0 + x += 1 + # Including this, that's 3 lines on which the cop is disabled. + # rubocop:enable Layout/SpaceAroundOperators diff --git a/config/contents/lint/missing_super.md b/config/contents/lint/missing_super.md new file mode 100644 index 00000000..d305e8c3 --- /dev/null +++ b/config/contents/lint/missing_super.md @@ -0,0 +1,38 @@ +This cop checks for the presence of constructors and lifecycle callbacks +without calls to `super`. + +This cop does not consider `method_missing` (and `respond_to_missing?`) +because in some cases it makes sense to overtake what is considered a +missing method. In other cases, the theoretical ideal handling could be +challenging or verbose for no actual gain. + +### Example: + # bad + class Employee < Person + def initialize(name, salary) + @salary = salary + end + end + + # good + class Employee < Person + def initialize(name, salary) + super(name) + @salary = salary + end + end + + # bad + class Parent + def self.inherited(base) + do_something + end + end + + # good + class Parent + def self.inherited(base) + super + do_something + end + end diff --git a/config/contents/lint/mixed_regexp_capture_types.md b/config/contents/lint/mixed_regexp_capture_types.md new file mode 100644 index 00000000..f3e3653f --- /dev/null +++ b/config/contents/lint/mixed_regexp_capture_types.md @@ -0,0 +1,16 @@ +Do not mix named captures and numbered captures in a Regexp literal +because numbered capture is ignored if they're mixed. +Replace numbered captures with non-capturing groupings or +named captures. + + # bad + /(?<foo>FOO)(BAR)/ + + # good + /(?<foo>FOO)(?<bar>BAR)/ + + # good + /(?<foo>FOO)(?:BAR)/ + + # good + /(FOO)(BAR)/ diff --git a/config/contents/lint/multiple_comparison.md b/config/contents/lint/multiple_comparison.md new file mode 100644 index 00000000..38dcc1f9 --- /dev/null +++ b/config/contents/lint/multiple_comparison.md @@ -0,0 +1,14 @@ +In math and Python, we can use `x < y < z` style comparison to compare +multiple value. However, we can't use the comparison in Ruby. However, +the comparison is not syntax error. This cop checks the bad usage of +comparison operators. + +### Example: + + # bad + x < y < z + 10 <= x <= 20 + + # good + x < y && y < z + 10 <= x && x <= 20 \ No newline at end of file diff --git a/config/contents/lint/nested_method_definition.md b/config/contents/lint/nested_method_definition.md index 32e0b308..f60941d1 100644 --- a/config/contents/lint/nested_method_definition.md +++ b/config/contents/lint/nested_method_definition.md @@ -26,14 +26,14 @@ This cop checks for nested method definitions. # good def foo - self.class_eval do + self.class.class_eval do def bar end end end def foo - self.module_exec do + self.class.module_exec do def bar end end diff --git a/config/contents/lint/nested_percent_literal.md b/config/contents/lint/nested_percent_literal.md index 3aad373f..b42a58de 100644 --- a/config/contents/lint/nested_percent_literal.md +++ b/config/contents/lint/nested_percent_literal.md @@ -9,4 +9,17 @@ This cop checks for nested percent literals. attributes = { valid_attributes: %i[name content], nested_attributes: %i[name content %i[incorrectly nested]] - } \ No newline at end of file + } + + # good + + # Neither is incompatible with the bad case, but probably the intended code. + attributes = { + valid_attributes: %i[name content], + nested_attributes: [:name, :content, %i[incorrectly nested]] + } + + attributes = { + valid_attributes: %i[name content], + nested_attributes: [:name, :content, [:incorrectly, :nested]] + } diff --git a/config/contents/lint/no_return_in_begin_end_blocks.md b/config/contents/lint/no_return_in_begin_end_blocks.md new file mode 100644 index 00000000..40bc70cc --- /dev/null +++ b/config/contents/lint/no_return_in_begin_end_blocks.md @@ -0,0 +1,36 @@ +Checks for the presence of a `return` inside a `begin..end` block +in assignment contexts. +In this situation, the `return` will result in an exit from the current +method, possibly leading to unexpected behavior. + +### Example: + + # bad + + @some_variable ||= begin + return some_value if some_condition_is_met + + do_something + end + +### Example: + + # good + + @some_variable ||= begin + if some_condition_is_met + some_value + else + do_something + end + end + + # good + + some_variable = if some_condition_is_met + return if another_condition_is_met + + some_value + else + do_something + end diff --git a/config/contents/lint/non_deterministic_require_order.md b/config/contents/lint/non_deterministic_require_order.md new file mode 100644 index 00000000..fbe37bbd --- /dev/null +++ b/config/contents/lint/non_deterministic_require_order.md @@ -0,0 +1,49 @@ +`Dir[...]` and `Dir.glob(...)` do not make any guarantees about +the order in which files are returned. The final order is +determined by the operating system and file system. +This means that using them in cases where the order matters, +such as requiring files, can lead to intermittent failures +that are hard to debug. To ensure this doesn't happen, +always sort the list. + +`Dir.glob` and `Dir[]` sort globbed results by default in Ruby 3.0. +So all bad cases are acceptable when Ruby 3.0 or higher are used. + +This cop will be deprecated and removed when supporting only Ruby 3.0 and higher. + +### Example: + + # bad + Dir["./lib/**/*.rb"].each do |file| + require file + end + + # good + Dir["./lib/**/*.rb"].sort.each do |file| + require file + end + + # bad + Dir.glob(Rails.root.join(__dir__, 'test', '*.rb')) do |file| + require file + end + + # good + Dir.glob(Rails.root.join(__dir__, 'test', '*.rb')).sort.each do |file| + require file + end + + # bad + Dir['./lib/**/*.rb'].each(&method(:require)) + + # good + Dir['./lib/**/*.rb'].sort.each(&method(:require)) + + # bad + Dir.glob(Rails.root.join('test', '*.rb'), &method(:require)) + + # good + Dir.glob(Rails.root.join('test', '*.rb')).sort.each(&method(:require)) + + # good - Respect intent if `sort` keyword option is specified in Ruby 3.0 or higher. + Dir.glob(Rails.root.join(__dir__, 'test', '*.rb'), sort: false).each(&method(:require)) diff --git a/config/contents/lint/non_local_exit_from_iterator.md b/config/contents/lint/non_local_exit_from_iterator.md index a486e2e1..0f61b5ee 100644 --- a/config/contents/lint/non_local_exit_from_iterator.md +++ b/config/contents/lint/non_local_exit_from_iterator.md @@ -1,13 +1,13 @@ This cop checks for non-local exits from iterators without a return value. It registers an offense under these conditions: - - No value is returned, - - the block is preceded by a method chain, - - the block has arguments, - - the method which receives the block is not `define_method` - or `define_singleton_method`, - - the return is not contained in an inner scope, e.g. a lambda or a - method definition. +* No value is returned, +* the block is preceded by a method chain, +* the block has arguments, +* the method which receives the block is not `define_method` +or `define_singleton_method`, +* the return is not contained in an inner scope, e.g. a lambda or a +method definition. ### Example: diff --git a/config/contents/lint/number_conversion.md b/config/contents/lint/number_conversion.md new file mode 100644 index 00000000..83a81a00 --- /dev/null +++ b/config/contents/lint/number_conversion.md @@ -0,0 +1,44 @@ +This cop warns the usage of unsafe number conversions. Unsafe +number conversion can cause unexpected error if auto type conversion +fails. Cop prefer parsing with number class instead. + +Conversion with `Integer`, `Float`, etc. will raise an `ArgumentError` +if given input that is not numeric (eg. an empty string), whereas +`to_i`, etc. will try to convert regardless of input (`''.to_i => 0`). +As such, this cop is disabled by default because it's not necessarily +always correct to raise if a value is not numeric. + +NOTE: Some values cannot be converted properly using one of the `Kernel` +method (for instance, `Time` and `DateTime` values are allowed by this +cop by default). Similarly, Rails' duration methods do not work well +with `Integer()` and can be ignored with `IgnoredMethods`. + +### Example: + + # bad + + '10'.to_i + '10.2'.to_f + '10'.to_c + ['1', '2', '3'].map(&:to_i) + foo.try(:to_f) + bar.send(:to_c) + + # good + + Integer('10', 10) + Float('10.2') + Complex('10') + ['1', '2', '3'].map { |i| Integer(i, 10) } + foo.try { |i| Float(i) } + bar.send { |i| Complex(i) } + +### Example: IgnoredMethods: [minutes] + + # good + 10.minutes.to_i + +### Example: IgnoredClasses: [Time, DateTime] (default) + + # good + Time.now.to_datetime.to_i \ No newline at end of file diff --git a/config/contents/lint/numbered_parameter_assignment.md b/config/contents/lint/numbered_parameter_assignment.md new file mode 100644 index 00000000..c0de98b9 --- /dev/null +++ b/config/contents/lint/numbered_parameter_assignment.md @@ -0,0 +1,23 @@ +This cop checks for uses of numbered parameter assignment. +It emulates the following warning in Ruby 2.7: + + % ruby -ve '_1 = :value' + ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19] + -e:1: warning: `_1' is reserved for numbered parameter; consider another name + +Assiging to numbered parameter (from `_1` to `_9`) cause an error in Ruby 3.0. + + % ruby -ve '_1 = :value' + ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19] + -e:1: _1 is reserved for numbered parameter + +NOTE: The parametered parameters are from `_1` to `_9`. This cop checks `_0`, and over `_10` +as well to prevent confusion. + +### Example: + + # bad + _1 = :value + + # good + non_numbered_parameter_name = :value diff --git a/config/contents/lint/or_assignment_to_constant.md b/config/contents/lint/or_assignment_to_constant.md new file mode 100644 index 00000000..72473956 --- /dev/null +++ b/config/contents/lint/or_assignment_to_constant.md @@ -0,0 +1,16 @@ +This cop checks for unintended or-assignment to a constant. + +Constants should always be assigned in the same location. And its value +should always be the same. If constants are assigned in multiple +locations, the result may vary depending on the order of `require`. + +Also, if you already have such an implementation, auto-correction may +change the result. + +### Example: + + # bad + CONST ||= 1 + + # good + CONST = 1 diff --git a/config/contents/lint/ordered_magic_comments.md b/config/contents/lint/ordered_magic_comments.md new file mode 100644 index 00000000..c857fd41 --- /dev/null +++ b/config/contents/lint/ordered_magic_comments.md @@ -0,0 +1,23 @@ + +Checks the proper ordering of magic comments and whether +a magic comment is not placed before a shebang. + +### Example: + # bad + + # frozen_string_literal: true + # encoding: ascii + p [''.frozen?, ''.encoding] #=> [true, #<Encoding:UTF-8>] + + # good + + # encoding: ascii + # frozen_string_literal: true + p [''.frozen?, ''.encoding] #=> [true, #<Encoding:US-ASCII>] + + # good + + #!/usr/bin/env ruby + # encoding: ascii + # frozen_string_literal: true + p [''.frozen?, ''.encoding] #=> [true, #<Encoding:US-ASCII>] diff --git a/config/contents/lint/out_of_range_regexp_ref.md b/config/contents/lint/out_of_range_regexp_ref.md new file mode 100644 index 00000000..83a647d9 --- /dev/null +++ b/config/contents/lint/out_of_range_regexp_ref.md @@ -0,0 +1,14 @@ +This cops looks for references of Regexp captures that are out of range +and thus always returns nil. + +### Example: + + /(foo)bar/ =~ 'foobar' + + # bad - always returns nil + + puts $2 # => nil + + # good + + puts $1 # => foo diff --git a/config/contents/lint/parentheses_as_grouped_expression.md b/config/contents/lint/parentheses_as_grouped_expression.md index 38c4e6b7..be9a1d3a 100644 --- a/config/contents/lint/parentheses_as_grouped_expression.md +++ b/config/contents/lint/parentheses_as_grouped_expression.md @@ -4,11 +4,9 @@ parenthesis. ### Example: # bad - - puts (x + y) - -### Example: + do_something (foo) # good - - puts(x + y) \ No newline at end of file + do_something(foo) + do_something (2 + 3) * 4 + do_something (foo * bar).baz \ No newline at end of file diff --git a/config/contents/lint/raise_exception.md b/config/contents/lint/raise_exception.md new file mode 100644 index 00000000..59fc3833 --- /dev/null +++ b/config/contents/lint/raise_exception.md @@ -0,0 +1,24 @@ +This cop checks for `raise` or `fail` statements which are +raising `Exception` class. + +You can specify a module name that will be an implicit namespace +using `AllowedImplicitNamespaces` option. The cop cause a false positive +for namespaced `Exception` when a namespace is omitted. This option can +prevent the false positive by specifying a namespace to be omitted for +`Exception`. Alternatively, make `Exception` a fully qualified class +name with an explicit namespace. + +### Example: + # bad + raise Exception, 'Error message here' + + # good + raise StandardError, 'Error message here' + +### Example: AllowedImplicitNamespaces: ['Gem'] + # good + module Gem + def self.foo + raise Exception # This exception means `Gem::Exception`. + end + end \ No newline at end of file diff --git a/config/contents/lint/redundant_cop_disable_directive.md b/config/contents/lint/redundant_cop_disable_directive.md new file mode 100644 index 00000000..0180a1b3 --- /dev/null +++ b/config/contents/lint/redundant_cop_disable_directive.md @@ -0,0 +1,19 @@ +This cop detects instances of rubocop:disable comments that can be +removed without causing any offenses to be reported. It's implemented +as a cop in that it inherits from the Cop base class and calls +add_offense. The unusual part of its implementation is that it doesn't +have any on_* methods or an investigate method. This means that it +doesn't take part in the investigation phase when the other cops do +their work. Instead, it waits until it's called in a later stage of the +execution. The reason it can't be implemented as a normal cop is that +it depends on the results of all other cops to do its work. + + +### Example: + # bad + # rubocop:disable Layout/LineLength + x += 1 + # rubocop:enable Layout/LineLength + + # good + x += 1 \ No newline at end of file diff --git a/config/contents/lint/redundant_cop_enable_directive.md b/config/contents/lint/redundant_cop_enable_directive.md new file mode 100644 index 00000000..de1fcf3c --- /dev/null +++ b/config/contents/lint/redundant_cop_enable_directive.md @@ -0,0 +1,25 @@ +This cop detects instances of rubocop:enable comments that can be +removed. + +When comment enables all cops at once `rubocop:enable all` +that cop checks whether any cop was actually enabled. +### Example: + # bad + foo = 1 + # rubocop:enable Layout/LineLength + + # good + foo = 1 +### Example: + # bad + # rubocop:disable Style/StringLiterals + foo = "1" + # rubocop:enable Style/StringLiterals + baz + # rubocop:enable all + + # good + # rubocop:disable Style/StringLiterals + foo = "1" + # rubocop:enable all + baz \ No newline at end of file diff --git a/config/contents/lint/redundant_dir_glob_sort.md b/config/contents/lint/redundant_dir_glob_sort.md new file mode 100644 index 00000000..bacd0c85 --- /dev/null +++ b/config/contents/lint/redundant_dir_glob_sort.md @@ -0,0 +1,18 @@ +Sort globbed results by default in Ruby 3.0. +This cop checks for redundant `sort` method to `Dir.glob` and `Dir[]`. + +### Example: + + # bad + Dir.glob('./lib/**/*.rb').sort.each do |file| + end + + Dir['./lib/**/*.rb'].sort.each do |file| + end + + # good + Dir.glob('./lib/**/*.rb').each do |file| + end + + Dir['./lib/**/*.rb'].each do |file| + end diff --git a/config/contents/lint/redundant_require_statement.md b/config/contents/lint/redundant_require_statement.md new file mode 100644 index 00000000..46ab7f0f --- /dev/null +++ b/config/contents/lint/redundant_require_statement.md @@ -0,0 +1,18 @@ +Checks for unnecessary `require` statement. + +The following features are unnecessary `require` statement because +they are already loaded. + + ruby -ve 'p $LOADED_FEATURES.reject { |feature| %r|/| =~ feature }' + ruby 2.2.8p477 (2017-09-14 revision 59906) [x86_64-darwin13] + ["enumerator.so", "rational.so", "complex.so", "thread.rb"] + +This cop targets Ruby 2.2 or higher containing these 4 features. + +### Example: + # bad + require 'unloaded_feature' + require 'thread' + + # good + require 'unloaded_feature' \ No newline at end of file diff --git a/config/contents/lint/redundant_safe_navigation.md b/config/contents/lint/redundant_safe_navigation.md new file mode 100644 index 00000000..f23497af --- /dev/null +++ b/config/contents/lint/redundant_safe_navigation.md @@ -0,0 +1,30 @@ +This cop checks for redundant safe navigation calls. +`instance_of?`, `kind_of?`, `is_a?`, `eql?`, `respond_to?`, and `equal?` methods +are checked by default. These are customizable with `AllowedMethods` option. + +This cop is marked as unsafe, because auto-correction can change the +return type of the expression. An offending expression that previously +could return `nil` will be auto-corrected to never return `nil`. + +In the example below, the safe navigation operator (`&.`) is unnecessary +because `NilClass` has methods like `respond_to?` and `is_a?`. + +### Example: + # bad + do_something if attrs&.respond_to?(:[]) + + # good + do_something if attrs.respond_to?(:[]) + + # bad + while node&.is_a?(BeginNode) + node = node.parent + end + + # good + while node.is_a?(BeginNode) + node = node.parent + end + + # good - without `&.` this will always return `true` + foo&.respond_to?(:to_a) diff --git a/config/contents/lint/redundant_splat_expansion.md b/config/contents/lint/redundant_splat_expansion.md new file mode 100644 index 00000000..f4b447f9 --- /dev/null +++ b/config/contents/lint/redundant_splat_expansion.md @@ -0,0 +1,64 @@ +This cop checks for unneeded usages of splat expansion + +### Example: + + # bad + a = *[1, 2, 3] + a = *'a' + a = *1 + ['a', 'b', *%w(c d e), 'f', 'g'] + + # good + c = [1, 2, 3] + a = *c + a, b = *c + a, *b = *c + a = *1..10 + a = ['a'] + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + # bad + do_something(*['foo', 'bar', 'baz']) + + # good + do_something('foo', 'bar', 'baz') + + # bad + begin + foo + rescue *[StandardError, ApplicationError] + bar + end + + # good + begin + foo + rescue StandardError, ApplicationError + bar + end + + # bad + case foo + when *[1, 2, 3] + bar + else + baz + end + + # good + case foo + when 1, 2, 3 + bar + else + baz + end + +### Example: AllowPercentLiteralArrayArgument: true (default) + + # good + do_something(*%w[foo bar baz]) + +### Example: AllowPercentLiteralArrayArgument: false + + # bad + do_something(*%w[foo bar baz]) diff --git a/config/contents/lint/redundant_string_coercion.md b/config/contents/lint/redundant_string_coercion.md new file mode 100644 index 00000000..49db53fa --- /dev/null +++ b/config/contents/lint/redundant_string_coercion.md @@ -0,0 +1,14 @@ +This cop checks for string conversion in string interpolation, +which is redundant. + +### Example: + + # bad + + "result is #{something.to_s}" + +### Example: + + # good + + "result is #{something}" \ No newline at end of file diff --git a/config/contents/lint/rescue_exception.md b/config/contents/lint/rescue_exception.md index ebd8411f..aa5ef0bb 100644 --- a/config/contents/lint/rescue_exception.md +++ b/config/contents/lint/rescue_exception.md @@ -1,4 +1,4 @@ -This cop checks for *rescue* blocks targeting the Exception class. +This cop checks for `rescue` blocks targeting the Exception class. ### Example: diff --git a/config/contents/lint/safe_navigation_chain.md b/config/contents/lint/safe_navigation_chain.md index d4ba8403..6feb81d2 100644 --- a/config/contents/lint/safe_navigation_chain.md +++ b/config/contents/lint/safe_navigation_chain.md @@ -1,6 +1,6 @@ The safe navigation operator returns nil if the receiver is -nil. If you chain an ordinary method call after a safe -navigation operator, it raises NoMethodError. We should use a +nil. If you chain an ordinary method call after a safe +navigation operator, it raises NoMethodError. We should use a safe navigation operator after a safe navigation operator. This cop checks for the problem outlined above. diff --git a/config/contents/lint/safe_navigation_consistency.md b/config/contents/lint/safe_navigation_consistency.md new file mode 100644 index 00000000..754ef793 --- /dev/null +++ b/config/contents/lint/safe_navigation_consistency.md @@ -0,0 +1,22 @@ +This cop check to make sure that if safe navigation is used for a method +call in an `&&` or `||` condition that safe navigation is used for all +method calls on that same object. + +### Example: + # bad + foo&.bar && foo.baz + + # bad + foo.bar || foo&.baz + + # bad + foo&.bar && (foobar.baz || foo.baz) + + # good + foo.bar && foo.baz + + # good + foo&.bar || foo&.baz + + # good + foo&.bar && (foobar.baz || foo&.baz) diff --git a/config/contents/lint/safe_navigation_with_empty.md b/config/contents/lint/safe_navigation_with_empty.md new file mode 100644 index 00000000..9f53375f --- /dev/null +++ b/config/contents/lint/safe_navigation_with_empty.md @@ -0,0 +1,15 @@ +This cop checks to make sure safe navigation isn't used with `empty?` in +a conditional. + +While the safe navigation operator is generally a good idea, when +checking `foo&.empty?` in a conditional, `foo` being `nil` will actually +do the opposite of what the author intends. + +### Example: + # bad + return if foo&.empty? + return unless foo&.empty? + + # good + return if foo && foo.empty? + return unless foo && foo.empty? diff --git a/config/contents/lint/script_permission.md b/config/contents/lint/script_permission.md new file mode 100644 index 00000000..afb90245 --- /dev/null +++ b/config/contents/lint/script_permission.md @@ -0,0 +1,26 @@ +This cop checks if a file which has a shebang line as +its first line is granted execute permission. + +### Example: + # bad + + # A file which has a shebang line as its first line is not + # granted execute permission. + + #!/usr/bin/env ruby + puts 'hello, world' + + # good + + # A file which has a shebang line as its first line is + # granted execute permission. + + #!/usr/bin/env ruby + puts 'hello, world' + + # good + + # A file which has not a shebang line as its first line is not + # granted execute permission. + + puts 'hello, world' diff --git a/config/contents/lint/self_assignment.md b/config/contents/lint/self_assignment.md new file mode 100644 index 00000000..00ac3b6d --- /dev/null +++ b/config/contents/lint/self_assignment.md @@ -0,0 +1,12 @@ +This cop checks for self-assignments. + +### Example: + # bad + foo = foo + foo, bar = foo, bar + Foo = Foo + + # good + foo = bar + foo, bar = bar, foo + Foo = Bar diff --git a/config/contents/lint/send_with_mixin_argument.md b/config/contents/lint/send_with_mixin_argument.md new file mode 100644 index 00000000..47547f7d --- /dev/null +++ b/config/contents/lint/send_with_mixin_argument.md @@ -0,0 +1,30 @@ + +This cop checks for `send`, `public_send`, and `__send__` methods +when using mix-in. + +`include` and `prepend` methods were private methods until Ruby 2.0, +they were mixed-in via `send` method. This cop uses Ruby 2.1 or +higher style that can be called by public methods. +And `extend` method that was originally a public method is also targeted +for style unification. + +### Example: + # bad + Foo.send(:include, Bar) + Foo.send(:prepend, Bar) + Foo.send(:extend, Bar) + + # bad + Foo.public_send(:include, Bar) + Foo.public_send(:prepend, Bar) + Foo.public_send(:extend, Bar) + + # bad + Foo.__send__(:include, Bar) + Foo.__send__(:prepend, Bar) + Foo.__send__(:extend, Bar) + + # good + Foo.include Bar + Foo.prepend Bar + Foo.extend Bar diff --git a/config/contents/lint/shadowed_argument.md b/config/contents/lint/shadowed_argument.md index 1c6db3df..58e43a5d 100644 --- a/config/contents/lint/shadowed_argument.md +++ b/config/contents/lint/shadowed_argument.md @@ -1,9 +1,12 @@ This cop checks for shadowed arguments. +This cop has `IgnoreImplicitReferences` configuration option. +It means argument shadowing is used in order to pass parameters +to zero arity `super` when `IgnoreImplicitReferences` is `true`. + ### Example: # bad - do_something do |foo| foo = 42 puts foo @@ -14,10 +17,7 @@ This cop checks for shadowed arguments. puts foo end -### Example: - # good - do_something do |foo| foo = foo + 42 puts foo @@ -30,4 +30,30 @@ This cop checks for shadowed arguments. def do_something(foo) puts foo - end \ No newline at end of file + end + +### Example: IgnoreImplicitReferences: false (default) + + # bad + def do_something(foo) + foo = 42 + super + end + + def do_something(foo) + foo = super + bar + end + +### Example: IgnoreImplicitReferences: true + + # good + def do_something(foo) + foo = 42 + super + end + + def do_something(foo) + foo = super + bar + end diff --git a/config/contents/lint/shadowing_outer_local_variable.md b/config/contents/lint/shadowing_outer_local_variable.md index e203a051..6581f7f8 100644 --- a/config/contents/lint/shadowing_outer_local_variable.md +++ b/config/contents/lint/shadowing_outer_local_variable.md @@ -1,7 +1,15 @@ -This cop looks for use of the same name as outer local variables -for block arguments or block local variables. -This is a mimic of the warning -"shadowing outer local variable - foo" from `ruby -cw`. +This cop checks for the use of local variable names from an outer scope +in block arguments or block-local variables. This mirrors the warning +given by `ruby -cw` prior to Ruby 2.6: +"shadowing outer local variable - foo". + +NOTE: Shadowing of variables in block passed to `Ractor.new` is allowed +because `Ractor` should not access outer variables. +eg. following syle is encouraged: + + worker_id, pipe = env + Ractor.new(worker_id, pipe) do |worker_id, pipe| + end ### Example: diff --git a/config/contents/lint/struct_new_override.md b/config/contents/lint/struct_new_override.md new file mode 100644 index 00000000..ea464c1b --- /dev/null +++ b/config/contents/lint/struct_new_override.md @@ -0,0 +1,17 @@ +This cop checks unexpected overrides of the `Struct` built-in methods +via `Struct.new`. + +### Example: + # bad + Bad = Struct.new(:members, :clone, :count) + b = Bad.new([], true, 1) + b.members #=> [] (overriding `Struct#members`) + b.clone #=> true (overriding `Object#clone`) + b.count #=> 1 (overriding `Enumerable#count`) + + # good + Good = Struct.new(:id, :name) + g = Good.new(1, "foo") + g.members #=> [:id, :name] + g.clone #=> #<struct Good id=1, name="foo"> + g.count #=> 2 diff --git a/config/contents/lint/suppressed_exception.md b/config/contents/lint/suppressed_exception.md new file mode 100644 index 00000000..8ea6e003 --- /dev/null +++ b/config/contents/lint/suppressed_exception.md @@ -0,0 +1,99 @@ +This cop checks for `rescue` blocks with no body. + +### Example: + + # bad + def some_method + do_something + rescue + end + + # bad + begin + do_something + rescue + end + + # good + def some_method + do_something + rescue + handle_exception + end + + # good + begin + do_something + rescue + handle_exception + end + +### Example: AllowComments: true (default) + + # good + def some_method + do_something + rescue + # do nothing + end + + # good + begin + do_something + rescue + # do nothing + end + +### Example: AllowComments: false + + # bad + def some_method + do_something + rescue + # do nothing + end + + # bad + begin + do_something + rescue + # do nothing + end + +### Example: AllowNil: true (default) + + # good + def some_method + do_something + rescue + nil + end + + # good + begin + do_something + rescue + # do nothing + end + + # good + do_something rescue nil + +### Example: AllowNil: false + + # bad + def some_method + do_something + rescue + nil + end + + # bad + begin + do_something + rescue + nil + end + + # bad + do_something rescue nil \ No newline at end of file diff --git a/config/contents/lint/symbol_conversion.md b/config/contents/lint/symbol_conversion.md new file mode 100644 index 00000000..60877e3c --- /dev/null +++ b/config/contents/lint/symbol_conversion.md @@ -0,0 +1,59 @@ +This cop checks for uses of literal strings converted to +a symbol where a literal symbol could be used instead. + +There are two possible styles for this cop. +`strict` (default) will register an offense for any incorrect usage. +`consistent` additionally requires hashes to use the same style for +every symbol key (ie. if any symbol key needs to be quoted it requires +all keys to be quoted). + +### Example: + # bad + 'string'.to_sym + :symbol.to_sym + 'underscored_string'.to_sym + :'underscored_symbol' + 'hyphenated-string'.to_sym + + # good + :string + :symbol + :underscored_string + :underscored_symbol + :'hyphenated-string' + +### Example: EnforcedStyle: strict (default) + + # bad + { + 'a': 1, + "b": 2, + 'c-d': 3 + } + + # good (don't quote keys that don't require quoting) + { + a: 1, + b: 2, + 'c-d': 3 + } + +### Example: EnforcedStyle: consistent + + # bad + { + a: 1, + 'b-c': 2 + } + + # good (quote all keys if any need quoting) + { + 'a': 1, + 'b-c': 2 + } + + # good (no quoting required) + { + a: 1, + b: 2 + } diff --git a/config/contents/lint/to_enum_arguments.md b/config/contents/lint/to_enum_arguments.md new file mode 100644 index 00000000..d647a9dc --- /dev/null +++ b/config/contents/lint/to_enum_arguments.md @@ -0,0 +1,14 @@ +This cop ensures that `to_enum`/`enum_for`, called for the current method, +has correct arguments. + +### Example: + # bad + def foo(x, y = 1) + return to_enum(__callee__, x) # `y` is missing + end + + # good + def foo(x, y = 1) + return to_enum(__callee__, x, y) + # alternatives to `__callee__` are `__method__` and `:foo` + end diff --git a/config/contents/lint/to_json.md b/config/contents/lint/to_json.md new file mode 100644 index 00000000..5e5648d9 --- /dev/null +++ b/config/contents/lint/to_json.md @@ -0,0 +1,24 @@ +This cop checks to make sure `#to_json` includes an optional argument. +When overriding `#to_json`, callers may invoke JSON +generation via `JSON.generate(your_obj)`. Since `JSON#generate` allows +for an optional argument, your method should too. + +### Example: + class Point + attr_reader :x, :y + + # bad, incorrect arity + def to_json + JSON.generate([x, y]) + end + + # good, preserving args + def to_json(*args) + JSON.generate([x, y], *args) + end + + # good, discarding args + def to_json(*_args) + JSON.generate([x, y]) + end + end diff --git a/config/contents/lint/top_level_return_with_argument.md b/config/contents/lint/top_level_return_with_argument.md new file mode 100644 index 00000000..4e34576b --- /dev/null +++ b/config/contents/lint/top_level_return_with_argument.md @@ -0,0 +1,8 @@ +This cop checks for top level return with arguments. If there is a +top-level return statement with an argument, then the argument is +always ignored. This is detected automatically since Ruby 2.7. + +### Example: + + # Detected since Ruby 2.7 + return 1 # 1 is always ignored. \ No newline at end of file diff --git a/config/contents/lint/trailing_comma_in_attribute_declaration.md b/config/contents/lint/trailing_comma_in_attribute_declaration.md new file mode 100644 index 00000000..fef3f861 --- /dev/null +++ b/config/contents/lint/trailing_comma_in_attribute_declaration.md @@ -0,0 +1,23 @@ +This cop checks for trailing commas in attribute declarations, such as +`#attr_reader`. Leaving a trailing comma will nullify the next method +definition by overriding it with a getter method. + +### Example: + + # bad + class Foo + attr_reader :foo, + + def bar + puts "Unreachable." + end + end + + # good + class Foo + attr_reader :foo + + def bar + puts "No problem!" + end + end diff --git a/config/contents/lint/triple_quotes.md b/config/contents/lint/triple_quotes.md new file mode 100644 index 00000000..239fbe17 --- /dev/null +++ b/config/contents/lint/triple_quotes.md @@ -0,0 +1,36 @@ +This cop checks for "triple quotes" (strings delimted by any odd number +of quotes greater than 1). + +Ruby allows multiple strings to be implicitly concatenated by just +being adjacent in a statement (ie. `"foo""bar" == "foobar"`). This sometimes +gives the impression that there is something special about triple quotes, but +in fact it is just extra unnecessary quotes and produces the same string. Each +pair of quotes produces an additional concatenated empty string, so the result +is still only the "actual" string within the delimiters. + +NOTE: Although this cop is called triple quotes, the same behavior is present +for strings delimited by 5, 7, etc. quotation marks. + +### Example: + # bad + """ + A string + """ + + # bad + ''' + A string + ''' + + # good + " + A string + " + + # good + <<STRING + A string + STRING + + # good (but not the same spacing as the bad case) + 'A string' \ No newline at end of file diff --git a/config/contents/lint/underscore_prefixed_variable_name.md b/config/contents/lint/underscore_prefixed_variable_name.md index a5a77a76..f01785da 100644 --- a/config/contents/lint/underscore_prefixed_variable_name.md +++ b/config/contents/lint/underscore_prefixed_variable_name.md @@ -1,7 +1,11 @@ This cop checks for underscore-prefixed variables that are actually used. -### Example: +Since block keyword arguments cannot be arbitrarily named at call +sites, the `AllowKeywordBlockArguments` will allow use of underscore- +prefixed block keyword arguments. + +### Example: AllowKeywordBlockArguments: false (default) # bad @@ -9,7 +13,9 @@ used. do_something(_num) end -### Example: + query(:sales) do |_id:, revenue:, cost:| + {_id: _id, profit: revenue - cost} + end # good @@ -17,10 +23,14 @@ used. do_something(num) end -### Example: + [1, 2, 3].each do |_num| + do_something # not using `_num` + end + +### Example: AllowKeywordBlockArguments: true # good - [1, 2, 3].each do |_num| - do_something # not using `_num` - end \ No newline at end of file + query(:sales) do |_id:, revenue:, cost:| + {_id: _id, profit: revenue - cost} + end diff --git a/config/contents/lint/unexpected_block_arity.md b/config/contents/lint/unexpected_block_arity.md new file mode 100644 index 00000000..54552b7f --- /dev/null +++ b/config/contents/lint/unexpected_block_arity.md @@ -0,0 +1,29 @@ +This cop checks for a block that is known to need more positional +block arguments than are given (by default this is configured for +`Enumerable` methods needing 2 arguments). Optional arguments are allowed, +although they don't generally make sense as the default value will +be used. Blocks that have no receiver, or take splatted arguments +(ie. `*args`) are always accepted. + +Keyword arguments (including `**kwargs`) do not get counted towards +this, as they are not used by the methods in question. + +NOTE: This cop matches for method names only and hence cannot tell apart +methods with same name in different classes. + +Method names and their expected arity can be configured like this: + +Methods: + inject: 2 + reduce: 2 + +### Example: + # bad + values.reduce {} + values.min { |a| a } + values.sort { |a; b| a + b } + + # good + values.reduce { |memo, obj| memo << obj } + values.min { |a, b| a <=> b } + values.sort { |*x| x[0] <=> x[1] } diff --git a/config/contents/lint/unmodified_reduce_accumulator.md b/config/contents/lint/unmodified_reduce_accumulator.md new file mode 100644 index 00000000..8999d664 --- /dev/null +++ b/config/contents/lint/unmodified_reduce_accumulator.md @@ -0,0 +1,59 @@ +Looks for `reduce` or `inject` blocks where the value returned (implicitly or +explicitly) does not include the accumulator. A block is considered valid as +long as at least one return value includes the accumulator. + +If the accumulator is not included in the return value, then the entire +block will just return a transformation of the last element value, and +could be rewritten as such without a loop. + +Also catches instances where an index of the accumulator is returned, as +this may change the type of object being retained. + +NOTE: For the purpose of reducing false positives, this cop only flags +returns in `reduce` blocks where the element is the only variable in +the expression (since we will not be able to tell what other variables +relate to via static analysis). + +### Example: + + # bad + (1..4).reduce(0) do |acc, el| + el * 2 + end + + # bad, may raise a NoMethodError after the first iteration + %w(a b c).reduce({}) do |acc, letter| + acc[letter] = true + end + + # good + (1..4).reduce(0) do |acc, el| + acc + el * 2 + end + + # good, element is returned but modified using the accumulator + values.reduce do |acc, el| + el << acc + el + end + + # good, returns the accumulator instead of the index + %w(a b c).reduce({}) do |acc, letter| + acc[letter] = true + acc + end + + # good, at least one branch returns the accumulator + values.reduce(nil) do |result, value| + break result if something? + value + end + + # good, recursive + keys.reduce(self) { |result, key| result[key] } + + # ignored as the return value cannot be determined + enum.reduce do |acc, el| + x = foo(acc, el) + bar(x) + end \ No newline at end of file diff --git a/config/contents/lint/unneeded_cop_disable_directive.md b/config/contents/lint/unneeded_cop_disable_directive.md new file mode 100644 index 00000000..4fc6aeb5 --- /dev/null +++ b/config/contents/lint/unneeded_cop_disable_directive.md @@ -0,0 +1,3 @@ +# The Lint/UnneededCopDisableDirective cop needs to be disabled so as +# to be able to provide a (bad) example of an unneeded disable. +# rubocop:disable Lint/UnneededCopDisableDirective \ No newline at end of file diff --git a/config/contents/lint/unneeded_cop_enable_directive.md b/config/contents/lint/unneeded_cop_enable_directive.md new file mode 100644 index 00000000..ae3bc8a4 --- /dev/null +++ b/config/contents/lint/unneeded_cop_enable_directive.md @@ -0,0 +1,3 @@ +# The Lint/UnneededCopEnableDirective cop needs to be disabled so as +# to be able to provide a (bad) example of an unneeded enable. +# rubocop:disable Lint/UnneededCopEnableDirective \ No newline at end of file diff --git a/config/contents/lint/unneeded_splat_expansion.md b/config/contents/lint/unneeded_splat_expansion.md index af4953f0..cc192824 100644 --- a/config/contents/lint/unneeded_splat_expansion.md +++ b/config/contents/lint/unneeded_splat_expansion.md @@ -39,7 +39,7 @@ This cop checks for unneeded usages of splat expansion end case foo - when *[1, 2, 3] + when 1, 2, 3 bar else baz diff --git a/config/contents/lint/unreachable_code.md b/config/contents/lint/unreachable_code.md index e00b649f..b212c62f 100644 --- a/config/contents/lint/unreachable_code.md +++ b/config/contents/lint/unreachable_code.md @@ -1,6 +1,6 @@ This cop checks for unreachable code. The check are based on the presence of flow of control -statement in non-final position in *begin*(implicit) blocks. +statement in non-final position in `begin` (implicit) blocks. ### Example: diff --git a/config/contents/lint/unreachable_loop.md b/config/contents/lint/unreachable_loop.md new file mode 100644 index 00000000..5545d009 --- /dev/null +++ b/config/contents/lint/unreachable_loop.md @@ -0,0 +1,80 @@ +This cop checks for loops that will have at most one iteration. + +A loop that can never reach the second iteration is a possible error in the code. +In rare cases where only one iteration (or at most one iteration) is intended behavior, +the code should be refactored to use `if` conditionals. + +NOTE: Block methods that are used with `Enumerable`s are considered to be loops. + +`IgnoredPatterns` can be used to match against the block receiver in order to allow +code that would otherwise be registered as an offense (eg. `times` used not in an +`Enumerable` context). + +### Example: + # bad + while node + do_something(node) + node = node.parent + break + end + + # good + while node + do_something(node) + node = node.parent + end + + # bad + def verify_list(head) + item = head + begin + if verify(item) + return true + else + return false + end + end while(item) + end + + # good + def verify_list(head) + item = head + begin + if verify(item) + item = item.next + else + return false + end + end while(item) + + true + end + + # bad + def find_something(items) + items.each do |item| + if something?(item) + return item + else + raise NotFoundError + end + end + end + + # good + def find_something(items) + items.each do |item| + if something?(item) + return item + end + end + raise NotFoundError + end + + # bad + 2.times { raise ArgumentError } + +### Example: IgnoredPatterns: [/(exactly|at_least|at_most)\(\d+\)\.times/] (default) + + # good + exactly(2).times { raise StandardError } \ No newline at end of file diff --git a/config/contents/lint/unused_block_argument.md b/config/contents/lint/unused_block_argument.md index d4263b69..d01856a9 100644 --- a/config/contents/lint/unused_block_argument.md +++ b/config/contents/lint/unused_block_argument.md @@ -1,9 +1,7 @@ This cop checks for unused block arguments. ### Example: - # bad - do_something do |used, unused| puts used end @@ -16,10 +14,7 @@ This cop checks for unused block arguments. puts :baz end -### Example: - - #good - + # good do_something do |used, _unused| puts used end @@ -30,4 +25,24 @@ This cop checks for unused block arguments. define_method(:foo) do |_bar| puts :baz - end \ No newline at end of file + end + +### Example: IgnoreEmptyBlocks: true (default) + # good + do_something { |unused| } + +### Example: IgnoreEmptyBlocks: false + # bad + do_something { |unused| } + +### Example: AllowUnusedKeywordArguments: false (default) + # bad + do_something do |unused: 42| + foo + end + +### Example: AllowUnusedKeywordArguments: true + # good + do_something do |unused: 42| + foo + end diff --git a/config/contents/lint/unused_method_argument.md b/config/contents/lint/unused_method_argument.md index 05105013..25fdba55 100644 --- a/config/contents/lint/unused_method_argument.md +++ b/config/contents/lint/unused_method_argument.md @@ -1,17 +1,54 @@ This cop checks for unused method arguments. ### Example: - # bad - def some_method(used, unused, _unused_but_allowed) puts used end -### Example: - # good - def some_method(used, _unused, _unused_but_allowed) puts used - end \ No newline at end of file + end + +### Example: AllowUnusedKeywordArguments: false (default) + # bad + def do_something(used, unused: 42) + used + end + +### Example: AllowUnusedKeywordArguments: true + # good + def do_something(used, unused: 42) + used + end + +### Example: IgnoreEmptyMethods: true (default) + # good + def do_something(unused) + end + +### Example: IgnoreEmptyMethods: false + # bad + def do_something(unused) + end + +### Example: IgnoreNotImplementedMethods: true (default) + # good + def do_something(unused) + raise NotImplementedError + end + + def do_something_else(unused) + fail "TODO" + end + +### Example: IgnoreNotImplementedMethods: false + # bad + def do_something(unused) + raise NotImplementedError + end + + def do_something_else(unused) + fail "TODO" + end diff --git a/config/contents/lint/uri_escape_unescape.md b/config/contents/lint/uri_escape_unescape.md index c8e6eb2d..a9550b51 100644 --- a/config/contents/lint/uri_escape_unescape.md +++ b/config/contents/lint/uri_escape_unescape.md @@ -1,9 +1,9 @@ This cop identifies places where `URI.escape` can be replaced by -`CGI.escape`, `URI.encode_www_form` or `URI.encode_www_form_component` +`CGI.escape`, `URI.encode_www_form`, or `URI.encode_www_form_component` depending on your specific use case. Also this cop identifies places where `URI.unescape` can be replaced by -`CGI.unescape`, `URI.decode_www_form` or `URI.decode_www_form_component` -depending on your specific use case. +`CGI.unescape`, `URI.decode_www_form`, +or `URI.decode_www_form_component` depending on your specific use case. ### Example: # bad diff --git a/config/contents/lint/useless_access_modifier.md b/config/contents/lint/useless_access_modifier.md index 244449bd..0ffadaed 100644 --- a/config/contents/lint/useless_access_modifier.md +++ b/config/contents/lint/useless_access_modifier.md @@ -4,23 +4,64 @@ class or module body. Conditionally-defined methods are considered as always being defined, and thus access modifiers guarding such methods are not redundant. -### Example: +This cop has `ContextCreatingMethods` option. The default setting value +is an empty array that means no method is specified. +This setting is an array of methods which, when called, are known to +create its own context in the module's current access context. + +It also has `MethodCreatingMethods` option. The default setting value +is an empty array that means no method is specified. +This setting is an array of methods which, when called, are known to +create other methods in the module's current access context. +### Example: + # bad class Foo public # this is redundant (default access is public) def method end + end - private # this is not redundant (a method is defined) - def method2 + # bad + class Foo + # The following is redundant (methods defined on the class' + # singleton class are not affected by the public modifier) + public + + def self.method3 end + end + # bad + class Foo + protected + + define_method(:method2) do + end + + protected # this is redundant (repeated from previous modifier) + + [1,2,3].each do |i| + define_method("foo#{i}") do + end + end + end + + # bad + class Foo private # this is redundant (no following methods are defined) end -### Example: + # good + class Foo + private # this is not redundant (a method is defined) + + def method2 + end + end + # good class Foo # The following is not redundant (conditionally defined methods are # considered as always defining a method) @@ -30,31 +71,22 @@ are not redundant. def method end end + end - protected # this is not redundant (method is defined) + # good + class Foo + protected # this is not redundant (a method is defined) define_method(:method2) do end - - protected # this is redundant (repeated from previous modifier) - - [1,2,3].each do |i| - define_method("foo#{i}") do - end - end - - # The following is redundant (methods defined on the class' - # singleton class are not affected by the public modifier) - public - - def self.method3 - end end -### Example: +### Example: ContextCreatingMethods: concerning # Lint/UselessAccessModifier: # ContextCreatingMethods: # - concerning + + # good require 'active_support/concern' class Foo concerning :Bar do @@ -74,10 +106,12 @@ are not redundant. end end -### Example: +### Example: MethodCreatingMethods: delegate # Lint/UselessAccessModifier: # MethodCreatingMethods: # - delegate + + # good require 'active_support/core_ext/module/delegation' class Foo # this is not redundant because `delegate` creates methods diff --git a/config/contents/lint/useless_else_without_rescue.md b/config/contents/lint/useless_else_without_rescue.md index 0aae7485..3601d103 100644 --- a/config/contents/lint/useless_else_without_rescue.md +++ b/config/contents/lint/useless_else_without_rescue.md @@ -1,5 +1,8 @@ This cop checks for useless `else` in `begin..end` without `rescue`. +NOTE: This syntax is no longer valid on Ruby 2.6 or higher and +this cop is going to be removed at some point the future. + ### Example: # bad diff --git a/config/contents/lint/useless_method_definition.md b/config/contents/lint/useless_method_definition.md new file mode 100644 index 00000000..581023b6 --- /dev/null +++ b/config/contents/lint/useless_method_definition.md @@ -0,0 +1,30 @@ +This cop checks for useless method definitions, specifically: empty constructors +and methods just delegating to `super`. + +This cop is marked as unsafe as it can trigger false positives for cases when +an empty constructor just overrides the parent constructor, which is bad anyway. + +### Example: + # bad + def initialize + super + end + + def method + super + end + + # good - with default arguments + def initialize(x = Object.new) + super + end + + # good + def initialize + super + initialize_internals + end + + def method(*args) + super(:extra_arg, *args) + end diff --git a/config/contents/lint/useless_setter_call.md b/config/contents/lint/useless_setter_call.md index fa5ca9e6..e508a164 100644 --- a/config/contents/lint/useless_setter_call.md +++ b/config/contents/lint/useless_setter_call.md @@ -1,5 +1,10 @@ This cop checks for setter call to local variable as the final expression of a function definition. +Its auto-correction is marked as unsafe because return value will be changed. + +NOTE: There are edge cases in which the local variable references a +value that is also accessible outside the local scope. This is not +detected by the cop, and it can yield a false positive. ### Example: diff --git a/config/contents/lint/useless_times.md b/config/contents/lint/useless_times.md new file mode 100644 index 00000000..85504660 --- /dev/null +++ b/config/contents/lint/useless_times.md @@ -0,0 +1,17 @@ +This cop checks for uses of `Integer#times` that will never yield +(when the integer <= 0) or that will only ever yield once +(`1.times`). + +This cop is marked as unsafe as `times` returns its receiver, which +is *usually* OK, but might change behavior. + +### Example: + # bad + -5.times { do_something } + 0.times { do_something } + 1.times { do_something } + 1.times { |i| do_something(i) } + + # good + do_something + do_something(1) \ No newline at end of file diff --git a/config/contents/lint/void.md b/config/contents/lint/void.md index 816a07d7..26d17736 100644 --- a/config/contents/lint/void.md +++ b/config/contents/lint/void.md @@ -1,38 +1,37 @@ -This cop checks for operators, variables and literals used -in void context. - -### Example: +This cop checks for operators, variables, literals, and nonmutating +methods used in void context. +### Example: CheckForMethodsWithNoSideEffects: false (default) # bad - def some_method some_num * 10 do_something end -### Example: - - # bad - def some_method(some_var) some_var do_something end -### Example: +### Example: CheckForMethodsWithNoSideEffects: true + # bad + def some_method(some_array) + some_array.sort + do_something(some_array) + end # good - def some_method do_something some_num * 10 end -### Example: - - # good - def some_method(some_var) do_something some_var + end + + def some_method(some_array) + some_array.sort! + do_something(some_array) end \ No newline at end of file diff --git a/config/contents/metrics/abc_size.md b/config/contents/metrics/abc_size.md index 5c7ed79b..8aec6e0f 100644 --- a/config/contents/metrics/abc_size.md +++ b/config/contents/metrics/abc_size.md @@ -1,3 +1,25 @@ This cop checks that the ABC size of methods is not higher than the configured maximum. The ABC size is based on assignments, branches -(method calls), and conditions. See http://c2.com/cgi/wiki?AbcMetric \ No newline at end of file +(method calls), and conditions. See http://c2.com/cgi/wiki?AbcMetric +and https://en.wikipedia.org/wiki/ABC_Software_Metric. + +You can have repeated "attributes" calls count as a single "branch". +For this purpose, attributes are any method with no argument; no attempt +is meant to distinguish actual `attr_reader` from other methods. + +### Example: CountRepeatedAttributes: false (default is true) + + # `model` and `current_user`, refenced 3 times each, + # are each counted as only 1 branch each if + # `CountRepeatedAttributes` is set to 'false' + + def search + @posts = model.active.visible_by(current_user) + .search(params[:q]) + @posts = model.some_process(@posts, current_user) + @posts = model.another_process(@posts, current_user) + + render 'pages/search/page' + end + +This cop also takes into account `IgnoredMethods` (defaults to `[]`) \ No newline at end of file diff --git a/config/contents/metrics/block_length.md b/config/contents/metrics/block_length.md index 585f1e86..79cf3e4b 100644 --- a/config/contents/metrics/block_length.md +++ b/config/contents/metrics/block_length.md @@ -1,4 +1,32 @@ This cop checks if the length of a block exceeds some maximum value. Comment lines can optionally be ignored. The maximum allowed length is configurable. -The cop can be configured to ignore blocks passed to certain methods. \ No newline at end of file +The cop can be configured to ignore blocks passed to certain methods. + +You can set literals you want to fold with `CountAsOne`. +Available are: 'array', 'hash', and 'heredoc'. Each literal +will be counted as one line regardless of its actual size. + + +NOTE: The `ExcludedMethods` configuration is deprecated and only kept +for backwards compatibility. Please use `IgnoredMethods` instead. + +### Example: CountAsOne: ['array', 'heredoc'] + + something do + array = [ # +1 + 1, + 2 + ] + + hash = { # +3 + key: 'value' + } + + msg = <<~HEREDOC # +1 + Heredoc + content. + HEREDOC + end # 5 points + +NOTE: This cop does not apply for `Struct` definitions. \ No newline at end of file diff --git a/config/contents/metrics/class_length.md b/config/contents/metrics/class_length.md index 8b0da319..7aa17c2c 100644 --- a/config/contents/metrics/class_length.md +++ b/config/contents/metrics/class_length.md @@ -1,3 +1,28 @@ This cop checks if the length a class exceeds some maximum value. Comment lines can optionally be ignored. -The maximum allowed length is configurable. \ No newline at end of file +The maximum allowed length is configurable. + +You can set literals you want to fold with `CountAsOne`. +Available are: 'array', 'hash', and 'heredoc'. Each literal +will be counted as one line regardless of its actual size. + +### Example: CountAsOne: ['array', 'heredoc'] + + class Foo + ARRAY = [ # +1 + 1, + 2 + ] + + HASH = { # +3 + key: 'value' + } + + MSG = <<~HEREDOC # +1 + Heredoc + content. + HEREDOC + end # 5 points + + +NOTE: This cop also applies for `Struct` definitions. \ No newline at end of file diff --git a/config/contents/metrics/cyclomatic_complexity.md b/config/contents/metrics/cyclomatic_complexity.md index 7b977729..34c0b56a 100644 --- a/config/contents/metrics/cyclomatic_complexity.md +++ b/config/contents/metrics/cyclomatic_complexity.md @@ -7,4 +7,20 @@ An if statement (or unless or ?:) increases the complexity by one. An else branch does not, since it doesn't add a decision point. The && operator (or keyword and) can be converted to a nested if statement, and ||/or is shorthand for a sequence of ifs, so they also add one. -Loops can be said to have an exit condition, so they add one. \ No newline at end of file +Loops can be said to have an exit condition, so they add one. +Blocks that are calls to builtin iteration methods +(e.g. `ary.map{...}) also add one, others are ignored. + + def each_child_node(*types) # count begins: 1 + unless block_given? # unless: +1 + return to_enum(__method__, *types) + + children.each do |child| # each{}: +1 + next unless child.is_a?(Node) # unless: +1 + + yield child if types.empty? || # if: +1, ||: +1 + types.include?(child.type) + end + + self + end # total: 6 \ No newline at end of file diff --git a/config/contents/metrics/method_length.md b/config/contents/metrics/method_length.md index a26d2980..74c86cde 100644 --- a/config/contents/metrics/method_length.md +++ b/config/contents/metrics/method_length.md @@ -1,3 +1,28 @@ This cop checks if the length of a method exceeds some maximum value. Comment lines can optionally be ignored. -The maximum allowed length is configurable. \ No newline at end of file +The maximum allowed length is configurable. + +You can set literals you want to fold with `CountAsOne`. +Available are: 'array', 'hash', and 'heredoc'. Each literal +will be counted as one line regardless of its actual size. + +NOTE: The `ExcludedMethods` configuration is deprecated and only kept +for backwards compatibility. Please use `IgnoredMethods` instead. + +### Example: CountAsOne: ['array', 'heredoc'] + + def m + array = [ # +1 + 1, + 2 + ] + + hash = { # +3 + key: 'value' + } + + <<~HEREDOC # +1 + Heredoc + content. + HEREDOC + end # 5 points diff --git a/config/contents/metrics/module_length.md b/config/contents/metrics/module_length.md index b36e05a0..dcfdd5d3 100644 --- a/config/contents/metrics/module_length.md +++ b/config/contents/metrics/module_length.md @@ -1,3 +1,25 @@ This cop checks if the length a module exceeds some maximum value. Comment lines can optionally be ignored. -The maximum allowed length is configurable. \ No newline at end of file +The maximum allowed length is configurable. + +You can set literals you want to fold with `CountAsOne`. +Available are: 'array', 'hash', and 'heredoc'. Each literal +will be counted as one line regardless of its actual size. + +### Example: CountAsOne: ['array', 'heredoc'] + + module M + ARRAY = [ # +1 + 1, + 2 + ] + + HASH = { # +3 + key: 'value' + } + + MSG = <<~HEREDOC # +1 + Heredoc + content. + HEREDOC + end # 5 points diff --git a/config/contents/metrics/parameter_lists.md b/config/contents/metrics/parameter_lists.md index 6fad6d95..e004efba 100644 --- a/config/contents/metrics/parameter_lists.md +++ b/config/contents/metrics/parameter_lists.md @@ -1,3 +1,46 @@ This cop checks for methods with too many parameters. + The maximum number of parameters is configurable. -Keyword arguments can optionally be excluded from the total count. \ No newline at end of file +Keyword arguments can optionally be excluded from the total count, +as they add less complexity than positional or optional parameters. + +### Example: Max: 3 + # good + def foo(a, b, c = 1) + end + +### Example: Max: 2 + # bad + def foo(a, b, c = 1) + end + +### Example: CountKeywordArgs: true (default) + # counts keyword args towards the maximum + + # bad (assuming Max is 3) + def foo(a, b, c, d: 1) + end + + # good (assuming Max is 3) + def foo(a, b, c: 1) + end + +### Example: CountKeywordArgs: false + # don't count keyword args towards the maximum + + # good (assuming Max is 3) + def foo(a, b, c, d: 1) + end + +This cop also checks for the maximum number of optional parameters. +This can be configured using the `MaxOptionalParameters` config option. + +### Example: MaxOptionalParameters: 3 (default) + # good + def foo(a = 1, b = 2, c = 3) + end + +### Example: MaxOptionalParameters: 2 + # bad + def foo(a = 1, b = 2, c = 3) + end diff --git a/config/contents/naming/accessor_method_name.md b/config/contents/naming/accessor_method_name.md index 82707dfc..855ba74e 100644 --- a/config/contents/naming/accessor_method_name.md +++ b/config/contents/naming/accessor_method_name.md @@ -1,4 +1,10 @@ -This cop makes sure that accessor methods are named properly. +This cop makes sure that accessor methods are named properly. Applies +to both instance and class methods. + +NOTE: Offenses are only registered for methods with the expected +arity. Getters (`get_attribute`) must have no arguments to be +registered, and setters (`set_attribute(value)`) must have exactly +one. ### Example: # bad @@ -15,4 +21,12 @@ This cop makes sure that accessor methods are named properly. # good def attribute + end + + # accepted, incorrect arity for getter + def get_value(attr) + end + + # accepted, incorrect arity for setter + def set_value end \ No newline at end of file diff --git a/config/contents/naming/ascii_identifiers.md b/config/contents/naming/ascii_identifiers.md index a2528c6d..882a6e67 100644 --- a/config/contents/naming/ascii_identifiers.md +++ b/config/contents/naming/ascii_identifiers.md @@ -1,4 +1,6 @@ -This cop checks for non-ascii characters in identifier names. +This cop checks for non-ascii characters in identifier and constant names. +Identifiers are always checked and whether constants are checked +can be controlled using AsciiConstants config. ### Example: # bad @@ -28,3 +30,17 @@ This cop checks for non-ascii characters in identifier names. # good params[:width_gteq] + +### Example: AsciiConstants: true (default) + # bad + class Foö + end + + FOÖ = "foo" + +### Example: AsciiConstants: false + # good + class Foö + end + + FOÖ = "foo" diff --git a/config/contents/naming/block_parameter_name.md b/config/contents/naming/block_parameter_name.md new file mode 100644 index 00000000..83fdea44 --- /dev/null +++ b/config/contents/naming/block_parameter_name.md @@ -0,0 +1,32 @@ +This cop checks block parameter names for how descriptive they +are. It is highly configurable. + +The `MinNameLength` config option takes an integer. It represents +the minimum amount of characters the name must be. Its default is 1. +The `AllowNamesEndingInNumbers` config option takes a boolean. When +set to false, this cop will register offenses for names ending with +numbers. Its default is false. The `AllowedNames` config option +takes an array of permitted names that will never register an +offense. The `ForbiddenNames` config option takes an array of +restricted names that will always register an offense. + +### Example: + # bad + bar do |varOne, varTwo| + varOne + varTwo + end + + # With `AllowNamesEndingInNumbers` set to false + foo { |num1, num2| num1 * num2 } + + # With `MinParamNameLength` set to number greater than 1 + baz { |a, b, c| do_stuff(a, b, c) } + + # good + bar do |thud, fred| + thud + fred + end + + foo { |speed, distance| speed * distance } + + baz { |age, height, gender| do_stuff(age, height, gender) } \ No newline at end of file diff --git a/config/contents/naming/class_and_module_camel_case.md b/config/contents/naming/class_and_module_camel_case.md index 4c7a0045..049451b0 100644 --- a/config/contents/naming/class_and_module_camel_case.md +++ b/config/contents/naming/class_and_module_camel_case.md @@ -1,6 +1,12 @@ -This cops checks for class and module names with +This cop checks for class and module names with an underscore in them. +`AllowedNames` config takes an array of permitted names. +Its default value is `['module_parent']`. +These names can be full class/module names or part of the name. +eg. Adding `my_class` to the `AllowedNames` config will allow names like +`my_class`, `my_class::User`, `App::my_class`, `App::my_class::User`, etc. + ### Example: # bad class My_Class @@ -12,4 +18,6 @@ an underscore in them. class MyClass end module MyModule + end + class module_parent::MyModule end \ No newline at end of file diff --git a/config/contents/naming/file_name.md b/config/contents/naming/file_name.md index ef41a0cb..b6b9e260 100644 --- a/config/contents/naming/file_name.md +++ b/config/contents/naming/file_name.md @@ -2,6 +2,11 @@ This cop makes sure that Ruby source files have snake_case names. Ruby scripts (i.e. source files with a shebang in the first line) are ignored. +The cop also ignores `.gemspec` files, because Bundler +recommends using dashes to separate namespaces in nested gems +(i.e. `bundler-console` becomes `Bundler::Console`). As such, the +gemspec is supposed to be named `bundler-console.gemspec`. + ### Example: # bad lib/layoutManager.rb diff --git a/config/contents/naming/heredoc_delimiter_naming.md b/config/contents/naming/heredoc_delimiter_naming.md index de0f768c..1f4ae125 100644 --- a/config/contents/naming/heredoc_delimiter_naming.md +++ b/config/contents/naming/heredoc_delimiter_naming.md @@ -1,6 +1,6 @@ This cop checks that your heredocs are using meaningful delimiters. By default it disallows `END` and `EO*`, and can be configured through -blacklisting additional delimiters. +forbidden listing additional delimiters. ### Example: diff --git a/config/contents/naming/inclusive_language.md b/config/contents/naming/inclusive_language.md new file mode 100644 index 00000000..e6fdf827 --- /dev/null +++ b/config/contents/naming/inclusive_language.md @@ -0,0 +1,52 @@ +This cops recommends the use of inclusive language instead of problematic terms. +The cop can check the following locations for offenses: +- identifiers +- constants +- variables +- strings +- symbols +- comments +- file paths +Each of these locations can be individually enabled/disabled via configuration, +for example CheckIdentifiers = true/false. + +Flagged terms are configurable for the cop. For each flagged term an optional +Regex can be specified to identify offenses. Suggestions for replacing a flagged term can +be configured and will be displayed as part of the offense message. +An AllowedRegex can be specified for a flagged term to exempt allowed uses of the term. + +### Example: FlaggedTerms: { whitelist: { Suggestions: ['allowlist'] } } + # Suggest replacing identifier whitelist with allowlist + + # bad + whitelist_users = %w(user1 user1) + + # good + allowlist_users = %w(user1 user2) + +### Example: FlaggedTerms: { master: { Suggestions: ['main', 'primary', 'leader'] } } + # Suggest replacing master in an instance variable name with main, primary, or leader + + # bad + @master_node = 'node1.example.com' + + # good + @primary_node = 'node1.example.com' + +### Example: FlaggedTerms: { whitelist: { Regex: !ruby/regexp '/white[-_\s]?list' } } + # Identify problematic terms using a Regexp + + # bad + white_list = %w(user1 user2) + + # good + allow_list = %w(user1 user2) + +### Example: FlaggedTerms: { master: { AllowedRegex: 'master\'?s degree' } } + # Specify allowed uses of the flagged term as a string or regexp. + + # bad + # They had a masters + + # good + # They had a master's degree diff --git a/config/contents/naming/memoized_instance_variable_name.md b/config/contents/naming/memoized_instance_variable_name.md new file mode 100644 index 00000000..33aeda87 --- /dev/null +++ b/config/contents/naming/memoized_instance_variable_name.md @@ -0,0 +1,140 @@ +This cop checks for memoized methods whose instance variable name +does not match the method name. Applies to both regular methods +(defined with `def`) and dynamic methods (defined with +`define_method` or `define_singleton_method`). + +This cop can be configured with the EnforcedStyleForLeadingUnderscores +directive. It can be configured to allow for memoized instance variables +prefixed with an underscore. Prefixing ivars with an underscore is a +convention that is used to implicitly indicate that an ivar should not +be set or referenced outside of the memoization method. + +### Example: EnforcedStyleForLeadingUnderscores: disallowed (default) + # bad + # Method foo is memoized using an instance variable that is + # not `@foo`. This can cause confusion and bugs. + def foo + @something ||= calculate_expensive_thing + end + + def foo + return @something if defined?(@something) + @something = calculate_expensive_thing + end + + # good + def _foo + @foo ||= calculate_expensive_thing + end + + # good + def foo + @foo ||= calculate_expensive_thing + end + + # good + def foo + @foo ||= begin + calculate_expensive_thing + end + end + + # good + def foo + helper_variable = something_we_need_to_calculate_foo + @foo ||= calculate_expensive_thing(helper_variable) + end + + # good + define_method(:foo) do + @foo ||= calculate_expensive_thing + end + + # good + define_method(:foo) do + return @foo if defined?(@foo) + @foo = calculate_expensive_thing + end + +### Example: EnforcedStyleForLeadingUnderscores: required + # bad + def foo + @something ||= calculate_expensive_thing + end + + # bad + def foo + @foo ||= calculate_expensive_thing + end + + def foo + return @foo if defined?(@foo) + @foo = calculate_expensive_thing + end + + # good + def foo + @_foo ||= calculate_expensive_thing + end + + # good + def _foo + @_foo ||= calculate_expensive_thing + end + + def foo + return @_foo if defined?(@_foo) + @_foo = calculate_expensive_thing + end + + # good + define_method(:foo) do + @_foo ||= calculate_expensive_thing + end + + # good + define_method(:foo) do + return @_foo if defined?(@_foo) + @_foo = calculate_expensive_thing + end + +### Example: EnforcedStyleForLeadingUnderscores :optional + # bad + def foo + @something ||= calculate_expensive_thing + end + + # good + def foo + @foo ||= calculate_expensive_thing + end + + # good + def foo + @_foo ||= calculate_expensive_thing + end + + # good + def _foo + @_foo ||= calculate_expensive_thing + end + + # good + def foo + return @_foo if defined?(@_foo) + @_foo = calculate_expensive_thing + end + + # good + define_method(:foo) do + @foo ||= calculate_expensive_thing + end + + # good + define_method(:foo) do + @_foo ||= calculate_expensive_thing + end + +This cop relies on the pattern `@instance_var ||= ...`, +but this is sometimes used for other purposes than memoization +so this cop is considered unsafe. \ No newline at end of file diff --git a/config/contents/naming/method_name.md b/config/contents/naming/method_name.md index 9ef8928e..6180eff0 100644 --- a/config/contents/naming/method_name.md +++ b/config/contents/naming/method_name.md @@ -1,6 +1,15 @@ This cop makes sure that all methods use the configured style, snake_case or camelCase, for their names. +This cop has `IgnoredPatterns` configuration option. + + Naming/MethodName: + IgnoredPatterns: + - '\A\s*onSelectionBulkChange\s*' + - '\A\s*onSelectionCleared\s*' + +Method names matching patterns are always allowed. + ### Example: EnforcedStyle: snake_case (default) # bad def fooBar; end diff --git a/config/contents/naming/method_parameter_name.md b/config/contents/naming/method_parameter_name.md new file mode 100644 index 00000000..7200c3c5 --- /dev/null +++ b/config/contents/naming/method_parameter_name.md @@ -0,0 +1,40 @@ +This cop checks method parameter names for how descriptive they +are. It is highly configurable. + +The `MinNameLength` config option takes an integer. It represents +the minimum amount of characters the name must be. Its default is 3. +The `AllowNamesEndingInNumbers` config option takes a boolean. When +set to false, this cop will register offenses for names ending with +numbers. Its default is false. The `AllowedNames` config option +takes an array of permitted names that will never register an +offense. The `ForbiddenNames` config option takes an array of +restricted names that will always register an offense. + +### Example: + # bad + def bar(varOne, varTwo) + varOne + varTwo + end + + # With `AllowNamesEndingInNumbers` set to false + def foo(num1, num2) + num1 * num2 + end + + # With `MinArgNameLength` set to number greater than 1 + def baz(a, b, c) + do_stuff(a, b, c) + end + + # good + def bar(thud, fred) + thud + fred + end + + def foo(speed, distance) + speed * distance + end + + def baz(age_a, height_b, gender_c) + do_stuff(age_a, height_b, gender_c) + end \ No newline at end of file diff --git a/config/contents/naming/predicate_name.md b/config/contents/naming/predicate_name.md index ca482bbd..07a2d0cc 100644 --- a/config/contents/naming/predicate_name.md +++ b/config/contents/naming/predicate_name.md @@ -2,6 +2,9 @@ This cop makes sure that predicates are named properly. ### Example: # bad + def is_even(value) + end + def is_even?(value) end @@ -10,6 +13,9 @@ This cop makes sure that predicates are named properly. end # bad + def has_value + end + def has_value? end diff --git a/config/contents/naming/rescued_exceptions_variable_name.md b/config/contents/naming/rescued_exceptions_variable_name.md new file mode 100644 index 00000000..843b24d0 --- /dev/null +++ b/config/contents/naming/rescued_exceptions_variable_name.md @@ -0,0 +1,54 @@ +This cop makes sure that rescued exceptions variables are named as +expected. + +The `PreferredName` config option takes a `String`. It represents +the required name of the variable. Its default is `e`. + +NOTE: This cop does not consider nested rescues because it cannot +guarantee that the variable from the outer rescue is not used within +the inner rescue (in which case, changing the inner variable would +shadow the outer variable). + +### Example: PreferredName: e (default) + # bad + begin + # do something + rescue MyException => exception + # do something + end + + # good + begin + # do something + rescue MyException => e + # do something + end + + # good + begin + # do something + rescue MyException => _e + # do something + end + +### Example: PreferredName: exception + # bad + begin + # do something + rescue MyException => e + # do something + end + + # good + begin + # do something + rescue MyException => exception + # do something + end + + # good + begin + # do something + rescue MyException => _exception + # do something + end diff --git a/config/contents/naming/uncommunicative_block_param_name.md b/config/contents/naming/uncommunicative_block_param_name.md new file mode 100644 index 00000000..f7ea8503 --- /dev/null +++ b/config/contents/naming/uncommunicative_block_param_name.md @@ -0,0 +1,32 @@ +This cop checks block parameter names for how descriptive they +are. It is highly configurable. + +The `MinNameLength` config option takes an integer. It represents +the minimum amount of characters the name must be. Its default is 1. +The `AllowNamesEndingInNumbers` config option takes a boolean. When +set to false, this cop will register offenses for names ending with +numbers. Its default is false. The `AllowedNames` config option +takes an array of whitelisted names that will never register an +offense. The `ForbiddenNames` config option takes an array of +blacklisted names that will always register an offense. + +### Example: + # bad + bar do |varOne, varTwo| + varOne + varTwo + end + + # With `AllowNamesEndingInNumbers` set to false + foo { |num1, num2| num1 * num2 } + + # With `MinParamNameLength` set to number greater than 1 + baz { |a, b, c| do_stuff(a, b, c) } + + # good + bar do |thud, fred| + thud + fred + end + + foo { |speed, distance| speed * distance } + + baz { |age, height, gender| do_stuff(age, height, gender) } \ No newline at end of file diff --git a/config/contents/naming/uncommunicative_method_param_name.md b/config/contents/naming/uncommunicative_method_param_name.md new file mode 100644 index 00000000..cca06e48 --- /dev/null +++ b/config/contents/naming/uncommunicative_method_param_name.md @@ -0,0 +1,40 @@ +This cop checks method parameter names for how descriptive they +are. It is highly configurable. + +The `MinNameLength` config option takes an integer. It represents +the minimum amount of characters the name must be. Its default is 3. +The `AllowNamesEndingInNumbers` config option takes a boolean. When +set to false, this cop will register offenses for names ending with +numbers. Its default is false. The `AllowedNames` config option +takes an array of whitelisted names that will never register an +offense. The `ForbiddenNames` config option takes an array of +blacklisted names that will always register an offense. + +### Example: + # bad + def bar(varOne, varTwo) + varOne + varTwo + end + + # With `AllowNamesEndingInNumbers` set to false + def foo(num1, num2) + num1 * num2 + end + + # With `MinArgNameLength` set to number greater than 1 + def baz(a, b, c) + do_stuff(a, b, c) + end + + # good + def bar(thud, fred) + thud + fred + end + + def foo(speed, distance) + speed * distance + end + + def baz(age_a, height_b, gender_c) + do_stuff(age_a, height_b, gender_c) + end \ No newline at end of file diff --git a/config/contents/naming/variable_number.md b/config/contents/naming/variable_number.md index a3b0e735..f2ef0525 100644 --- a/config/contents/naming/variable_number.md +++ b/config/contents/naming/variable_number.md @@ -1,34 +1,92 @@ This cop makes sure that all numbered variables use the -configured style, snake_case, normalcase or non_integer, +configured style, snake_case, normalcase, or non_integer, for their numbering. +Additionally, `CheckMethodNames` and `CheckSymbols` configuration options +can be used to specify whether method names and symbols should be checked. +Both are enabled by default. + ### Example: EnforcedStyle: snake_case # bad - + :some_sym1 variable1 = 1 - # good + def some_method1; end + + def some_method_1(arg1); end + # good + :some_sym_1 variable_1 = 1 + def some_method_1; end + + def some_method_1(arg_1); end + ### Example: EnforcedStyle: normalcase (default) # bad - + :some_sym_1 variable_1 = 1 - # good + def some_method_1; end + + def some_method1(arg_1); end + # good + :some_sym1 variable1 = 1 + def some_method1; end + + def some_method1(arg1); end + ### Example: EnforcedStyle: non_integer # bad + :some_sym1 + :some_sym_1 variable1 = 1 - variable_1 = 1 + def some_method1; end + + def some_method_1; end + + def some_methodone(arg1); end + def some_methodone(arg_1); end + # good + :some_symone + :some_sym_one variableone = 1 + variable_one = 1 + + def some_methodone; end + + def some_method_one; end + + def some_methodone(argone); end + def some_methodone(arg_one); end + + # In the following examples, we assume `EnforcedStyle: normalcase` (default). + +### Example: CheckMethodNames: true (default) + # bad + def some_method_1; end - variable_one = 1 \ No newline at end of file +### Example: CheckMethodNames: false + # good + def some_method_1; end + +### Example: CheckSymbols: true (default) + # bad + :some_sym_1 + +### Example: CheckSymbols: false + # good + :some_sym_1 + +### Example: AllowedIdentifiers: [capture3] + # good + expect(Open3).to receive(:capture3) diff --git a/config/contents/rails/active_record_aliases.md b/config/contents/rails/active_record_aliases.md new file mode 100644 index 00000000..dcbd40ba --- /dev/null +++ b/config/contents/rails/active_record_aliases.md @@ -0,0 +1,9 @@ +Checks that ActiveRecord aliases are not used. The direct method names +are more clear and easier to read. + +### Example: + #bad + Book.update_attributes!(author: 'Alice') + + #good + Book.update!(author: 'Alice') \ No newline at end of file diff --git a/config/contents/rails/active_record_override.md b/config/contents/rails/active_record_override.md new file mode 100644 index 00000000..df768a71 --- /dev/null +++ b/config/contents/rails/active_record_override.md @@ -0,0 +1,20 @@ +Checks for overriding built-in Active Record methods instead of using +callbacks. + +### Example: + # bad + class Book < ApplicationRecord + def save + self.title = title.upcase! + super + end + end + + # good + class Book < ApplicationRecord + before_save :upcase_title + + def upcase_title + self.title = title.upcase! + end + end diff --git a/config/contents/rails/assert_not.md b/config/contents/rails/assert_not.md new file mode 100644 index 00000000..a3ec5a9a --- /dev/null +++ b/config/contents/rails/assert_not.md @@ -0,0 +1,9 @@ + +Use `assert_not` instead of `assert !`. + +### Example: + # bad + assert !x + + # good + assert_not x diff --git a/config/contents/rails/belongs_to.md b/config/contents/rails/belongs_to.md new file mode 100644 index 00000000..71cbcf74 --- /dev/null +++ b/config/contents/rails/belongs_to.md @@ -0,0 +1,47 @@ +This cop looks for belongs_to associations where we control whether the +association is required via the deprecated `required` option instead. + +Since Rails 5, belongs_to associations are required by default and this +can be controlled through the use of `optional: true`. + +From the release notes: + + belongs_to will now trigger a validation error by default if the + association is not present. You can turn this off on a + per-association basis with optional: true. Also deprecate required + option in favor of optional for belongs_to. (Pull Request) + +In the case that the developer is doing `required: false`, we +definitely want to autocorrect to `optional: true`. + +However, without knowing whether they've set overridden the default +value of `config.active_record.belongs_to_required_by_default`, we +can't say whether it's safe to remove `required: true` or whether we +should replace it with `optional: false` (or, similarly, remove a +superfluous `optional: false`). Therefore, in the cases we're using +`required: true`, we'll simply invert it to `optional: false` and the +user can remove depending on their defaults. + +### Example: + # bad + class Post < ApplicationRecord + belongs_to :blog, required: false + end + + # good + class Post < ApplicationRecord + belongs_to :blog, optional: true + end + + # bad + class Post < ApplicationRecord + belongs_to :blog, required: true + end + + # good + class Post < ApplicationRecord + belongs_to :blog, optional: false + end + +@see https://guides.rubyonrails.org/5_0_release_notes.html +@see https://github.com/rails/rails/pull/18937 \ No newline at end of file diff --git a/config/contents/rails/blank.md b/config/contents/rails/blank.md index 96dc9c0a..1938164e 100644 --- a/config/contents/rails/blank.md +++ b/config/contents/rails/blank.md @@ -1,34 +1,50 @@ -This cops checks for code that can be changed to `blank?`. -Settings: - NilOrEmpty: Convert checks for `nil` or `empty?` to `blank?` - NotPresent: Convert usages of not `present?` to `blank?` - UnlessPresent: Convert usages of `unless` `present?` to `blank?` - -### Example: - # NilOrEmpty: true - # bad - foo.nil? || foo.empty? - foo == nil || foo.empty? - - # good - foo.blank? - - # NotPresent: true - # bad - !foo.present? - - # good - foo.blank? - - # UnlessPresent: true - # bad - something unless foo.present? - unless foo.present? - something - end - - # good - something if foo.blank? - if foo.blank? - something - end \ No newline at end of file +This cop checks for code that can be written with simpler conditionals +using `Object#blank?` defined by Active Support. + +Interaction with `Style/UnlessElse`: +The configuration of `NotPresent` will not produce an offense in the +context of `unless else` if `Style/UnlessElse` is inabled. This is +to prevent interference between the auto-correction of the two cops. + +### Example: NilOrEmpty: true (default) + # Converts usages of `nil? || empty?` to `blank?` + + # bad + foo.nil? || foo.empty? + foo == nil || foo.empty? + + # good + foo.blank? + +### Example: NotPresent: true (default) + # Converts usages of `!present?` to `blank?` + + # bad + !foo.present? + + # good + foo.blank? + +### Example: UnlessPresent: true (default) + # Converts usages of `unless present?` to `if blank?` + + # bad + something unless foo.present? + + # good + something if foo.blank? + + # bad + unless foo.present? + something + end + + # good + if foo.blank? + something + end + + # good + def blank? + !present? + end \ No newline at end of file diff --git a/config/contents/rails/bulk_change_table.md b/config/contents/rails/bulk_change_table.md new file mode 100644 index 00000000..715f18ff --- /dev/null +++ b/config/contents/rails/bulk_change_table.md @@ -0,0 +1,62 @@ +This Cop checks whether alter queries are combinable. +If combinable queries are detected, it suggests to you +to use `change_table` with `bulk: true` instead. +This option causes the migration to generate a single +ALTER TABLE statement combining multiple column alterations. + +The `bulk` option is only supported on the MySQL and +the PostgreSQL (5.2 later) adapter; thus it will +automatically detect an adapter from `development` environment +in `config/database.yml` when the `Database` option is not set. +If the adapter is not `mysql2` or `postgresql`, +this Cop ignores offenses. + +### Example: + # bad + def change + add_column :users, :name, :string, null: false + add_column :users, :nickname, :string + + # ALTER TABLE `users` ADD `name` varchar(255) NOT NULL + # ALTER TABLE `users` ADD `nickname` varchar(255) + end + + # good + def change + change_table :users, bulk: true do |t| + t.string :name, null: false + t.string :nickname + end + + # ALTER TABLE `users` ADD `name` varchar(255) NOT NULL, + # ADD `nickname` varchar(255) + end + +### Example: + # bad + def change + change_table :users do |t| + t.string :name, null: false + t.string :nickname + end + end + + # good + def change + change_table :users, bulk: true do |t| + t.string :name, null: false + t.string :nickname + end + end + + # good + # When you don't want to combine alter queries. + def change + change_table :users, bulk: false do |t| + t.string :name, null: false + t.string :nickname + end + end + +@see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table +@see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html \ No newline at end of file diff --git a/config/contents/rails/date.md b/config/contents/rails/date.md index d75472d2..0aba40e3 100644 --- a/config/contents/rails/date.md +++ b/config/contents/rails/date.md @@ -1,19 +1,19 @@ This cop checks for the correct use of Date methods, such as Date.today, Date.current etc. -Using Date.today is dangerous, because it doesn't know anything about -Rails time zone. You must use Time.zone.today instead. +Using `Date.today` is dangerous, because it doesn't know anything about +Rails time zone. You must use `Time.zone.today` instead. -The cop also reports warnings when you are using 'to_time' method, +The cop also reports warnings when you are using `to_time` method, because it doesn't know about Rails time zone either. Two styles are supported for this cop. When EnforcedStyle is 'strict' -then the Date methods (today, current, yesterday, tomorrow) -are prohibited and the usage of both 'to_time' -and 'to_time_in_current_zone' is reported as warning. +then the Date methods `today`, `current`, `yesterday`, and `tomorrow` +are prohibited and the usage of both `to_time` +and 'to_time_in_current_zone' are reported as warning. -When EnforcedStyle is 'flexible' then only 'Date.today' is prohibited -and only 'to_time' is reported as warning. +When EnforcedStyle is 'flexible' then only `Date.today` is prohibited +and only `to_time` is reported as warning. ### Example: EnforcedStyle: strict # bad @@ -21,7 +21,6 @@ and only 'to_time' is reported as warning. Date.yesterday Date.today date.to_time - date.to_time_in_current_zone # good Time.zone.today @@ -37,4 +36,4 @@ and only 'to_time' is reported as warning. Time.zone.today - 1.day Date.current Date.yesterday - date.to_time_in_current_zone + date.in_time_zone diff --git a/config/contents/rails/delegate.md b/config/contents/rails/delegate.md index 3fdb58c3..5b6e85dd 100644 --- a/config/contents/rails/delegate.md +++ b/config/contents/rails/delegate.md @@ -30,7 +30,7 @@ When set to `false`, this case is legal. foo.bar end - # EnforceForPrefixed: true +### Example: EnforceForPrefixed: true (default) # bad def foo_bar foo.bar @@ -39,7 +39,7 @@ When set to `false`, this case is legal. # good delegate :bar, to: :foo, prefix: true - # EnforceForPrefixed: false +### Example: EnforceForPrefixed: false # good def foo_bar foo.bar diff --git a/config/contents/rails/dynamic_find_by.md b/config/contents/rails/dynamic_find_by.md index 73d9621c..366f77dd 100644 --- a/config/contents/rails/dynamic_find_by.md +++ b/config/contents/rails/dynamic_find_by.md @@ -1,6 +1,6 @@ This cop checks dynamic `find_by_*` methods. Use `find_by` instead of dynamic method. -See. https://github.com/bbatsov/rails-style-guide#find_by +See. https://github.com/rubocop-hq/rails-style-guide#find_by ### Example: # bad diff --git a/config/contents/rails/exit.md b/config/contents/rails/exit.md index 46c4d73c..524901c3 100644 --- a/config/contents/rails/exit.md +++ b/config/contents/rails/exit.md @@ -1,12 +1,12 @@ -This cop enforces that 'exit' calls are not used within a rails app. -Valid options are instead to raise an error, break, return or some +This cop enforces that `exit` calls are not used within a rails app. +Valid options are instead to raise an error, break, return, or some other form of stopping execution of current request. -There are two obvious cases where 'exit' is particularly harmful: +There are two obvious cases where `exit` is particularly harmful: -- Usage in library code for your application. Even though rails will -rescue from a SystemExit and continue on, unit testing that library -code will result in specs exiting (potentially silently if exit(0) +- Usage in library code for your application. Even though Rails will +rescue from a `SystemExit` and continue on, unit testing that library +code will result in specs exiting (potentially silently if `exit(0)` is used.) - Usage in application code outside of the web process could result in the program exiting, which could result in the code failing to run and diff --git a/config/contents/rails/file_path.md b/config/contents/rails/file_path.md index 399cc86a..7427765f 100644 --- a/config/contents/rails/file_path.md +++ b/config/contents/rails/file_path.md @@ -1,11 +1,21 @@ This cop is used to identify usages of file path joining process -to use `Rails.root.join` clause. +to use `Rails.root.join` clause. It is used to add uniformity when +joining paths. -### Example: - # bad - Rails.root.join('app/models/goober') - File.join(Rails.root, 'app/models/goober') - "#{Rails.root}/app/models/goober" +### Example: EnforcedStyle: arguments (default) + # bad + Rails.root.join('app/models/goober') + File.join(Rails.root, 'app/models/goober') + "#{Rails.root}/app/models/goober" - # good - Rails.root.join('app', 'models', 'goober') \ No newline at end of file + # good + Rails.root.join('app', 'models', 'goober') + +### Example: EnforcedStyle: slashes + # bad + Rails.root.join('app', 'models', 'goober') + File.join(Rails.root, 'app/models/goober') + "#{Rails.root}/app/models/goober" + + # good + Rails.root.join('app/models/goober') diff --git a/config/contents/rails/http_positional_arguments.md b/config/contents/rails/http_positional_arguments.md index c3676a5f..55feff47 100644 --- a/config/contents/rails/http_positional_arguments.md +++ b/config/contents/rails/http_positional_arguments.md @@ -1,6 +1,6 @@ This cop is used to identify usages of http methods like `get`, `post`, `put`, `patch` without the usage of keyword arguments in your tests and -change them to use keyword args. This cop only applies to Rails >= 5 . +change them to use keyword args. This cop only applies to Rails >= 5. If you are running Rails < 5 you should disable the Rails/HttpPositionalArguments cop or set your TargetRailsVersion in your .rubocop.yml file to 4.0, etc. diff --git a/config/contents/rails/http_status.md b/config/contents/rails/http_status.md new file mode 100644 index 00000000..1503e282 --- /dev/null +++ b/config/contents/rails/http_status.md @@ -0,0 +1,27 @@ +Enforces use of symbolic or numeric value to define HTTP status. + +### Example: EnforcedStyle: symbolic (default) + # bad + render :foo, status: 200 + render json: { foo: 'bar' }, status: 200 + render plain: 'foo/bar', status: 304 + redirect_to root_url, status: 301 + + # good + render :foo, status: :ok + render json: { foo: 'bar' }, status: :ok + render plain: 'foo/bar', status: :not_modified + redirect_to root_url, status: :moved_permanently + +### Example: EnforcedStyle: numeric + # bad + render :foo, status: :ok + render json: { foo: 'bar' }, status: :not_found + render plain: 'foo/bar', status: :not_modified + redirect_to root_url, status: :moved_permanently + + # good + render :foo, status: 200 + render json: { foo: 'bar' }, status: 404 + render plain: 'foo/bar', status: 304 + redirect_to root_url, status: 301 diff --git a/config/contents/rails/ignored_skip_action_filter_option.md b/config/contents/rails/ignored_skip_action_filter_option.md new file mode 100644 index 00000000..bac1b369 --- /dev/null +++ b/config/contents/rails/ignored_skip_action_filter_option.md @@ -0,0 +1,34 @@ +This cop checks that `if` and `only` (or `except`) are not used together +as options of `skip_*` action filter. + +The `if` option will be ignored when `if` and `only` are used together. +Similarly, the `except` option will be ignored when `if` and `except` +are used together. + +### Example: + # bad + class MyPageController < ApplicationController + skip_before_action :login_required, + only: :show, if: :trusted_origin? + end + + # good + class MyPageController < ApplicationController + skip_before_action :login_required, + if: -> { trusted_origin? && action_name == "show" } + end + +### Example: + # bad + class MyPageController < ApplicationController + skip_before_action :login_required, + except: :admin, if: :trusted_origin? + end + + # good + class MyPageController < ApplicationController + skip_before_action :login_required, + if: -> { trusted_origin? && action_name != "admin" } + end + +@see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options \ No newline at end of file diff --git a/config/contents/rails/inverse_of.md b/config/contents/rails/inverse_of.md index dc64ae6b..0ca5669a 100644 --- a/config/contents/rails/inverse_of.md +++ b/config/contents/rails/inverse_of.md @@ -1,8 +1,14 @@ This cop looks for has_(one|many) and belongs_to associations where -ActiveRecord can't automatically determine the inverse association -because of a scope or the options used. This can result in unnecessary -queries in some circumstances. `:inverse_of` must be manually specified -for associations to work in both ways, or set to `false` to opt-out. +Active Record can't automatically determine the inverse association +because of a scope or the options used. Using the blog with order scope +example below, traversing the a Blog's association in both directions +with `blog.posts.first.blog` would cause the `blog` to be loaded from +the database twice. + +`:inverse_of` must be manually specified for Active Record to use the +associated object in memory, or set to `false` to opt-out. Note that +setting `nil` does not stop Active Record from trying to determine the +inverse automatically, and is not considered a valid value for this. ### Example: # good @@ -27,9 +33,8 @@ for associations to work in both ways, or set to `false` to opt-out. # good class Blog < ApplicationRecord has_many(:posts, - -> { order(published_at: :desc) }, - inverse_of: :blog - ) + -> { order(published_at: :desc) }, + inverse_of: :blog) end class Post < ApplicationRecord @@ -47,6 +52,14 @@ for associations to work in both ways, or set to `false` to opt-out. belongs_to :blog end + # good + # When you don't want to use the inverse association. + class Blog < ApplicationRecord + has_many(:posts, + -> { order(published_at: :desc) }, + inverse_of: false) + end + ### Example: # bad class Picture < ApplicationRecord @@ -108,5 +121,5 @@ for associations to work in both ways, or set to `false` to opt-out. has_many :physicians, through: :appointments end -@see http://guides.rubyonrails.org/association_basics.html#bi-directional-associations -@see http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses \ No newline at end of file +@see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations +@see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses \ No newline at end of file diff --git a/config/contents/rails/lexically_scoped_action_filter.md b/config/contents/rails/lexically_scoped_action_filter.md index d24185f7..27a35143 100644 --- a/config/contents/rails/lexically_scoped_action_filter.md +++ b/config/contents/rails/lexically_scoped_action_filter.md @@ -1,10 +1,13 @@ -This cop checks that methods specified in the filter's `only` -or `except` options are explicitly defined in the controller. +This cop checks that methods specified in the filter's `only` or +`except` options are defined within the same class or module. -You can specify methods of superclass or methods added by mixins -on the filter, but these confuse developers. If you specify methods -where are defined on another controller, you should define the filter -in that controller. +You can technically specify methods of superclass or methods added by +mixins on the filter, but these can confuse developers. If you specify +methods that are defined in other classes or modules, you should +define the filter in that class or module. + +If you rely on behaviour defined in the superclass actions, you must +remember to invoke `super` in the subclass actions. ### Example: # bad @@ -27,4 +30,50 @@ in that controller. def logout end + end + +### Example: + # bad + module FooMixin + extend ActiveSupport::Concern + + included do + before_action proc { authenticate }, only: :foo + end + end + + # good + module FooMixin + extend ActiveSupport::Concern + + included do + before_action proc { authenticate }, only: :foo + end + + def foo + # something + end + end + +### Example: + class ContentController < ApplicationController + def update + @content.update(content_attributes) + end + end + + class ArticlesController < ContentController + before_action :load_article, only: [:update] + + # the cop requires this method, but it relies on behaviour defined + # in the superclass, so needs to invoke `super` + def update + super + end + + private + + def load_article + @content = Article.find(params[:article_id]) + end end \ No newline at end of file diff --git a/config/contents/rails/link_to_blank.md b/config/contents/rails/link_to_blank.md new file mode 100644 index 00000000..abdeddae --- /dev/null +++ b/config/contents/rails/link_to_blank.md @@ -0,0 +1,17 @@ +This cop checks for calls to `link_to` that contain a +`target: '_blank'` but no `rel: 'noopener'`. This can be a security +risk as the loaded page will have control over the previous page +and could change its location for phishing purposes. + +The option `rel: 'noreferrer'` also blocks this behavior +and removes the http-referrer header. + +### Example: + # bad + link_to 'Click here', url, target: '_blank' + + # good + link_to 'Click here', url, target: '_blank', rel: 'noopener' + + # good + link_to 'Click here', url, target: '_blank', rel: 'noreferrer' \ No newline at end of file diff --git a/config/contents/rails/output_safety.md b/config/contents/rails/output_safety.md index bc4da1bb..653bdbd9 100644 --- a/config/contents/rails/output_safety.md +++ b/config/contents/rails/output_safety.md @@ -1,7 +1,7 @@ -This cop checks for the use of output safety calls like html_safe, -raw, and safe_concat. These methods do not escape content. They +This cop checks for the use of output safety calls like `html_safe`, +`raw`, and `safe_concat`. These methods do not escape content. They simply return a SafeBuffer containing the content as is. Instead, -use safe_join to join content and escape it and concat to +use `safe_join` to join content and escape it and concat to concatenate content and escape it, ensuring its safety. ### Example: diff --git a/config/contents/rails/present.md b/config/contents/rails/present.md index efd552ae..73cc031f 100644 --- a/config/contents/rails/present.md +++ b/config/contents/rails/present.md @@ -1,31 +1,40 @@ -This cops checks for code that can be changed to `blank?`. -Settings: - NotNilAndNotEmpty: Convert checks for not `nil` and `not empty?` - to `present?` - NotBlank: Convert usages of not `blank?` to `present?` - UnlessBlank: Convert usages of `unless` `blank?` to `if` `present?` - -### Example: - # NotNilAndNotEmpty: true - # bad - !foo.nil? && !foo.empty? - foo != nil && !foo.empty? - !foo.blank? - - # good - foo.present? - - # NotBlank: true - # bad - !foo.blank? - not foo.blank? - - # good - foo.present? - - # UnlessBlank: true - # bad - something unless foo.blank? - - # good - something if foo.present? \ No newline at end of file +This cop checks for code that can be written with simpler conditionals +using `Object#present?` defined by Active Support. + +Interaction with `Style/UnlessElse`: +The configuration of `NotBlank` will not produce an offense in the +context of `unless else` if `Style/UnlessElse` is inabled. This is +to prevent interference between the auto-correction of the two cops. + +### Example: NotNilAndNotEmpty: true (default) + # Converts usages of `!nil? && !empty?` to `present?` + + # bad + !foo.nil? && !foo.empty? + + # bad + foo != nil && !foo.empty? + + # good + foo.present? + +### Example: NotBlank: true (default) + # Converts usages of `!blank?` to `present?` + + # bad + !foo.blank? + + # bad + not foo.blank? + + # good + foo.present? + +### Example: UnlessBlank: true (default) + # Converts usages of `unless blank?` to `if present?` + + # bad + something unless foo.blank? + + # good + something if foo.present? \ No newline at end of file diff --git a/config/contents/rails/read_write_attribute.md b/config/contents/rails/read_write_attribute.md index bd13c372..b4fb15c7 100644 --- a/config/contents/rails/read_write_attribute.md +++ b/config/contents/rails/read_write_attribute.md @@ -1,5 +1,13 @@ -This cop checks for the use of the read_attribute or -write_attribute methods. +This cop checks for the use of the `read_attribute` or `write_attribute` +methods and recommends square brackets instead. + +If an attribute is missing from the instance (for example, when +initialized by a partial `select`) then `read_attribute` +will return nil, but square brackets will raise +an `ActiveModel::MissingAttributeError`. + +Explicitly raising an error in this situation is preferable, and that +is why rubocop recommends using square brackets. ### Example: diff --git a/config/contents/rails/redundant_allow_nil.md b/config/contents/rails/redundant_allow_nil.md new file mode 100644 index 00000000..1d688363 --- /dev/null +++ b/config/contents/rails/redundant_allow_nil.md @@ -0,0 +1,22 @@ +Checks Rails model validations for a redundant `allow_nil` when +`allow_blank` is present. + +### Example: + # bad + validates :x, length: { is: 5 }, allow_nil: true, allow_blank: true + + # bad + validates :x, length: { is: 5 }, allow_nil: false, allow_blank: true + + # bad + validates :x, length: { is: 5 }, allow_nil: false, allow_blank: false + + # good + validates :x, length: { is: 5 }, allow_blank: true + + # good + validates :x, length: { is: 5 }, allow_blank: false + + # good + # Here, `nil` is valid but `''` is not + validates :x, length: { is: 5 }, allow_nil: true, allow_blank: false diff --git a/config/contents/rails/reflection_class_name.md b/config/contents/rails/reflection_class_name.md new file mode 100644 index 00000000..d9d091c5 --- /dev/null +++ b/config/contents/rails/reflection_class_name.md @@ -0,0 +1,10 @@ +This cop checks if the value of the option `class_name`, in +the definition of a reflection is a string. + +### Example: + # bad + has_many :accounts, class_name: Account + has_many :accounts, class_name: Account.name + + # good + has_many :accounts, class_name: 'Account' \ No newline at end of file diff --git a/config/contents/rails/refute_methods.md b/config/contents/rails/refute_methods.md new file mode 100644 index 00000000..68dd79a8 --- /dev/null +++ b/config/contents/rails/refute_methods.md @@ -0,0 +1,13 @@ + +Use `assert_not` methods instead of `refute` methods. + +### Example: + # bad + refute false + refute_empty [1, 2, 3] + refute_equal true, false + + # good + assert_not false + assert_not_empty [1, 2, 3] + assert_not_equal true, false diff --git a/config/contents/rails/reversible_migration.md b/config/contents/rails/reversible_migration.md index 37f97475..3c35ba96 100644 --- a/config/contents/rails/reversible_migration.md +++ b/config/contents/rails/reversible_migration.md @@ -119,4 +119,4 @@ reversible. end end -@see http://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html \ No newline at end of file +@see https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html \ No newline at end of file diff --git a/config/contents/rails/safe_navigation.md b/config/contents/rails/safe_navigation.md index fb2d3411..4663dc7e 100644 --- a/config/contents/rails/safe_navigation.md +++ b/config/contents/rails/safe_navigation.md @@ -1,6 +1,5 @@ This cop converts usages of `try!` to `&.`. It can also be configured -to convert `try`. It will convert code to use safe navigation if the -target Ruby version is set to 2.3+ +to convert `try`. It will convert code to use safe navigation. ### Example: # ConvertTry: false diff --git a/config/contents/rails/save_bang.md b/config/contents/rails/save_bang.md index aaf533f5..623bd0eb 100644 --- a/config/contents/rails/save_bang.md +++ b/config/contents/rails/save_bang.md @@ -2,12 +2,21 @@ This cop identifies possible cases where Active Record save! or related should be used instead of save because the model might have failed to save and an exception is better than unhandled failure. -This will ignore calls that return a boolean for success if the result -is assigned to a variable or used as the condition in an if/unless -statement. It will also ignore calls that return a model assigned to a -variable that has a call to `persisted?`. Finally, it will ignore any -call with more than 2 arguments as that is likely not an Active Record -call or a Model.update(id, attributes) call. +This will allow: +- update or save calls, assigned to a variable, + or used as a condition in an if/unless/case statement. +- create calls, assigned to a variable that then has a + call to `persisted?`. +- calls if the result is explicitly returned from methods and blocks, + or provided as arguments. +- calls whose signature doesn't look like an ActiveRecord + persistence method. + +By default it will also allow implicit returns from methods and blocks. +that behavior can be turned off with `AllowImplicitReturn: false`. + +You can permit receivers that are giving false positives with +`AllowedReceivers: []` ### Example: @@ -29,4 +38,55 @@ call or a Model.update(id, attributes) call. user = User.find_or_create_by(name: 'Joe') unless user.persisted? # ... - end \ No newline at end of file + end + + def save_user + return user.save + end + +### Example: AllowImplicitReturn: true (default) + + # good + users.each { |u| u.save } + + def save_user + user.save + end + +### Example: AllowImplicitReturn: false + + # bad + users.each { |u| u.save } + def save_user + user.save + end + + # good + users.each { |u| u.save! } + + def save_user + user.save! + end + + def save_user + return user.save + end + +### Example: AllowedReceivers: ['merchant.customers', 'Service::Mailer'] + + # bad + merchant.create + customers.builder.save + Mailer.create + + module Service::Mailer + self.create + end + + # good + merchant.customers.create + MerchantService.merchant.customers.destroy + Service::Mailer.update(message: 'Message') + ::Service::Mailer.update + Services::Service::Mailer.update(message: 'Message') + Service::Mailer::update diff --git a/config/contents/rails/skips_model_validations.md b/config/contents/rails/skips_model_validations.md index 88c62222..af12ceb7 100644 --- a/config/contents/rails/skips_model_validations.md +++ b/config/contents/rails/skips_model_validations.md @@ -1,6 +1,8 @@ This cop checks for the use of methods which skip validations which are listed in -http://guides.rubyonrails.org/active_record_validations.html#skipping-validations +https://guides.rubyonrails.org/active_record_validations.html#skipping-validations + +Methods may be ignored from this rule by configuring a `Whitelist`. ### Example: # bad @@ -11,10 +13,19 @@ http://guides.rubyonrails.org/active_record_validations.html#skipping-validation person.toggle :active product.touch Billing.update_all("category = 'authorized', author = 'David'") - user.update_attribute(website: 'example.com') + user.update_attribute(:website, 'example.com') user.update_columns(last_request_at: Time.current) Post.update_counters 5, comment_count: -1, action_count: 1 # good - user.update_attributes(website: 'example.com') - FileUtils.touch('file') \ No newline at end of file + user.update(website: 'example.com') + FileUtils.touch('file') + +### Example: Whitelist: ["touch"] + # bad + DiscussionBoard.decrement_counter(:post_count, 5) + DiscussionBoard.increment_counter(:post_count, 5) + person.toggle :active + + # good + user.touch diff --git a/config/contents/rails/time_zone.md b/config/contents/rails/time_zone.md index 1e60c2c3..b2f9d3f0 100644 --- a/config/contents/rails/time_zone.md +++ b/config/contents/rails/time_zone.md @@ -1,7 +1,7 @@ This cop checks for the use of Time methods without zone. -Built on top of Ruby on Rails style guide (https://github.com/bbatsov/rails-style-guide#time) -and the article http://danilenko.org/2012/7/6/rails_timezones/ . +Built on top of Ruby on Rails style guide (https://github.com/rubocop-hq/rails-style-guide#time) +and the article http://danilenko.org/2012/7/6/rails_timezones/ Two styles are supported for this cop. When EnforcedStyle is 'strict' then only use of Time.zone is allowed. @@ -9,16 +9,32 @@ then only use of Time.zone is allowed. When EnforcedStyle is 'flexible' then it's also allowed to use Time.in_time_zone. -### Example: - # always offense +### Example: EnforcedStyle: strict + # `strict` means that `Time` should be used with `zone`. + + # bad + Time.now + Time.parse('2015-03-02 19:05:37') + + # bad + Time.current + Time.at(timestamp).in_time_zone + + # good + Time.zone.now + Time.zone.parse('2015-03-02 19:05:37') + +### Example: EnforcedStyle: flexible (default) + # `flexible` allows usage of `in_time_zone` instead of `zone`. + + # bad Time.now Time.parse('2015-03-02 19:05:37') - # no offense + # good Time.zone.now Time.zone.parse('2015-03-02 19:05:37') - # no offense only if style is 'flexible' + # good Time.current - DateTime.strptime(str, "%Y-%m-%d %H:%M %Z").in_time_zone Time.at(timestamp).in_time_zone \ No newline at end of file diff --git a/config/contents/rails/uniq_before_pluck.md b/config/contents/rails/uniq_before_pluck.md index 403b3f16..5116d63a 100644 --- a/config/contents/rails/uniq_before_pluck.md +++ b/config/contents/rails/uniq_before_pluck.md @@ -3,13 +3,6 @@ Prefer the use of uniq (or distinct), before pluck instead of after. The use of uniq before pluck is preferred because it executes within the database. -### Example: - # bad - Model.pluck(:id).uniq - - # good - Model.uniq.pluck(:id) - This cop has two different enforcement modes. When the EnforcedStyle is conservative (the default) then only calls to pluck on a constant (i.e. a model class) before uniq are added as offenses. @@ -19,12 +12,27 @@ uniq are added as offenses. This may lead to false positives as the cop cannot distinguish between calls to pluck on an ActiveRecord::Relation vs a call to pluck on an ActiveRecord::Associations::CollectionProxy. -### Example: +Autocorrect is disabled by default for this cop since it may generate +false positives. + +### Example: EnforcedStyle: conservative (default) + # bad + Model.pluck(:id).uniq + + # good + Model.uniq.pluck(:id) + +### Example: EnforcedStyle: aggressive + # bad # this will return a Relation that pluck is called on Model.where(cond: true).pluck(:id).uniq + # bad # an association on an instance will return a CollectionProxy instance.assoc.pluck(:id).uniq -Autocorrect is disabled by default for this cop since it may generate -false positives. + # bad + Model.pluck(:id).uniq + + # good + Model.uniq.pluck(:id) diff --git a/config/contents/rails/validation.md b/config/contents/rails/validation.md new file mode 100644 index 00000000..0f84e47c --- /dev/null +++ b/config/contents/rails/validation.md @@ -0,0 +1,28 @@ +This cop checks for the use of old-style attribute validation macros. + +### Example: + # bad + validates_acceptance_of :foo + validates_confirmation_of :foo + validates_exclusion_of :foo + validates_format_of :foo + validates_inclusion_of :foo + validates_length_of :foo + validates_numericality_of :foo + validates_presence_of :foo + validates_absence_of :foo + validates_size_of :foo + validates_uniqueness_of :foo + + # good + validates :foo, acceptance: true + validates :foo, confirmation: true + validates :foo, exclusion: true + validates :foo, format: true + validates :foo, inclusion: true + validates :foo, length: true + validates :foo, numericality: true + validates :foo, presence: true + validates :foo, absence: true + validates :foo, size: true + validates :foo, uniqueness: true diff --git a/config/contents/security/json_load.md b/config/contents/security/json_load.md index 357a0ebc..a6cb8904 100644 --- a/config/contents/security/json_load.md +++ b/config/contents/security/json_load.md @@ -10,9 +10,9 @@ option, like `JSON.parse('false', quirks_mode: true)`. Other similar issues may apply. ### Example: - # always offense + # bad JSON.load("{}") JSON.restore("{}") - # no offense + # good JSON.parse("{}") diff --git a/config/contents/security/open.md b/config/contents/security/open.md new file mode 100644 index 00000000..b5f23f2b --- /dev/null +++ b/config/contents/security/open.md @@ -0,0 +1,17 @@ +This cop checks for the use of `Kernel#open` and `URI.open`. + +`Kernel#open` and `URI.open` enable not only file access but also process +invocation by prefixing a pipe symbol (e.g., `open("| ls")`). +So, it may lead to a serious security risk by using variable input to +the argument of `Kernel#open` and `URI.open`. It would be better to use +`File.open`, `IO.popen` or `URI.parse#open` explicitly. + +### Example: + # bad + open(something) + URI.open(something) + + # good + File.open(something) + IO.popen(something) + URI.parse(something).open \ No newline at end of file diff --git a/config/contents/style/access_modifier_declarations.md b/config/contents/style/access_modifier_declarations.md new file mode 100644 index 00000000..1ceb727c --- /dev/null +++ b/config/contents/style/access_modifier_declarations.md @@ -0,0 +1,59 @@ +Access modifiers should be declared to apply to a group of methods +or inline before each method, depending on configuration. +EnforcedStyle config covers only method definitions. +Applications of visibility methods to symbols can be controlled +using AllowModifiersOnSymbols config. + +### Example: EnforcedStyle: group (default) + # bad + class Foo + + private def bar; end + private def baz; end + + end + + # good + class Foo + + private + + def bar; end + def baz; end + + end + +### Example: EnforcedStyle: inline + # bad + class Foo + + private + + def bar; end + def baz; end + + end + + # good + class Foo + + private def bar; end + private def baz; end + + end + +### Example: AllowModifiersOnSymbols: true (default) + # good + class Foo + + private :bar, :baz + + end + +### Example: AllowModifiersOnSymbols: false + # bad + class Foo + + private :bar, :baz + + end \ No newline at end of file diff --git a/config/contents/style/accessor_grouping.md b/config/contents/style/accessor_grouping.md new file mode 100644 index 00000000..7649752d --- /dev/null +++ b/config/contents/style/accessor_grouping.md @@ -0,0 +1,30 @@ +This cop checks for grouping of accessors in `class` and `module` bodies. +By default it enforces accessors to be placed in grouped declarations, +but it can be configured to enforce separating them in multiple declarations. + +NOTE: `Sorbet` is not compatible with "grouped" style. Consider "separated" style +or disabling this cop. + +### Example: EnforcedStyle: grouped (default) + # bad + class Foo + attr_reader :bar + attr_reader :baz + end + + # good + class Foo + attr_reader :bar, :baz + end + +### Example: EnforcedStyle: separated + # bad + class Foo + attr_reader :bar, :baz + end + + # good + class Foo + attr_reader :bar + attr_reader :baz + end diff --git a/config/contents/style/and_or.md b/config/contents/style/and_or.md index 5fe53dfd..b8cdfac7 100644 --- a/config/contents/style/and_or.md +++ b/config/contents/style/and_or.md @@ -1,8 +1,8 @@ This cop checks for uses of `and` and `or`, and suggests using `&&` and -`|| instead`. It can be configured to check only in conditions, or in +`||` instead. It can be configured to check only in conditions or in all contexts. -### Example: EnforcedStyle: always (default) +### Example: EnforcedStyle: always # bad foo.save and return @@ -17,7 +17,7 @@ all contexts. if foo && bar end -### Example: EnforcedStyle: conditionals +### Example: EnforcedStyle: conditionals (default) # bad if foo and bar end diff --git a/config/contents/style/arguments_forwarding.md b/config/contents/style/arguments_forwarding.md new file mode 100644 index 00000000..42481859 --- /dev/null +++ b/config/contents/style/arguments_forwarding.md @@ -0,0 +1,34 @@ +In Ruby 2.7, arguments forwarding has been added. + +This cop identifies places where `do_something(*args, &block)` +can be replaced by `do_something(...)`. + +### Example: + # bad + def foo(*args, &block) + bar(*args, &block) + end + + # bad + def foo(*args, **kwargs, &block) + bar(*args, **kwargs, &block) + end + + # good + def foo(...) + bar(...) + end + +### Example: AllowOnlyRestArgument: true (default) + # good + def foo(*args) + bar(*args) + end + +### Example: AllowOnlyRestArgument: false + # bad + # The following code can replace the arguments with `...`, + # but it will change the behavior. Because `...` forwards block also. + def foo(*args) + bar(*args) + end diff --git a/config/contents/style/array_coercion.md b/config/contents/style/array_coercion.md new file mode 100644 index 00000000..56629f05 --- /dev/null +++ b/config/contents/style/array_coercion.md @@ -0,0 +1,16 @@ +This cop enforces the use of `Array()` instead of explicit `Array` check or `[*var]`. + +This cop is disabled by default because false positive will occur if +the argument of `Array()` is not an array (e.g. Hash, Set), +an array will be returned as an incompatibility result. + +### Example: + # bad + paths = [paths] unless paths.is_a?(Array) + paths.each { |path| do_something(path) } + + # bad (always creates a new Array instance) + [*paths].each { |path| do_something(path) } + + # good (and a bit more readable) + Array(paths).each { |path| do_something(path) } diff --git a/config/contents/style/array_join.md b/config/contents/style/array_join.md index 43f4f5b5..1bb2c769 100644 --- a/config/contents/style/array_join.md +++ b/config/contents/style/array_join.md @@ -1,4 +1,4 @@ -This cop checks for uses of "*" as a substitute for *join*. +This cop checks for uses of "*" as a substitute for _join_. Not all cases can reliably checked, due to Ruby's dynamic types, so we consider only cases when the first argument is an @@ -10,4 +10,4 @@ array literal or the second is a string literal. %w(foo bar baz) * "," # good - %w(foo bar bax).join(",") + %w(foo bar baz).join(",") diff --git a/config/contents/style/ascii_comments.md b/config/contents/style/ascii_comments.md index ce01eb0b..d1c72abc 100644 --- a/config/contents/style/ascii_comments.md +++ b/config/contents/style/ascii_comments.md @@ -1,6 +1,6 @@ This cop checks for non-ascii (non-English) characters in comments. You could set an array of allowed non-ascii chars in -AllowedChars attribute (empty by default). +`AllowedChars` attribute (copyright notice "©" by default). ### Example: # bad diff --git a/config/contents/style/begin_block.md b/config/contents/style/begin_block.md new file mode 100644 index 00000000..e37a9b77 --- /dev/null +++ b/config/contents/style/begin_block.md @@ -0,0 +1,6 @@ + +This cop checks for BEGIN blocks. + +### Example: + # bad + BEGIN { test } diff --git a/config/contents/style/bisected_attr_accessor.md b/config/contents/style/bisected_attr_accessor.md new file mode 100644 index 00000000..84ac5321 --- /dev/null +++ b/config/contents/style/bisected_attr_accessor.md @@ -0,0 +1,14 @@ +This cop checks for places where `attr_reader` and `attr_writer` +for the same method can be combined into single `attr_accessor`. + +### Example: + # bad + class Foo + attr_reader :bar + attr_writer :bar + end + + # good + class Foo + attr_accessor :bar + end diff --git a/config/contents/style/block_delimiters.md b/config/contents/style/block_delimiters.md index ea82f350..f0ed2b8b 100644 --- a/config/contents/style/block_delimiters.md +++ b/config/contents/style/block_delimiters.md @@ -55,6 +55,30 @@ multi-line blocks. x }.inspect + # The AllowBracesOnProceduralOneLiners option is ignored unless the + # EnforcedStyle is set to `semantic`. If so: + + # If the AllowBracesOnProceduralOneLiners option is unspecified, or + # set to `false` or any other falsey value, then semantic purity is + # maintained, so one-line procedural blocks must use do-end, not + # braces. + + # bad + collection.each { |element| puts element } + + # good + collection.each do |element| puts element end + + # If the AllowBracesOnProceduralOneLiners option is set to `true`, or + # any other truthy value, then one-line procedural blocks may use + # either style. (There is no setting for requiring braces on them.) + + # good + collection.each { |element| puts element } + + # also good + collection.each do |element| puts element end + ### Example: EnforcedStyle: braces_for_chaining # bad words.each do |word| @@ -65,3 +89,40 @@ multi-line blocks. words.each { |word| word.flip.flop }.join("-") + +### Example: EnforcedStyle: always_braces + # bad + words.each do |word| + word.flip.flop + end + + # good + words.each { |word| + word.flip.flop + } + +### Example: BracesRequiredMethods: ['sig'] + + # Methods listed in the BracesRequiredMethods list, such as 'sig' + # in this example, will require `{...}` braces. This option takes + # precedence over all other configurations except IgnoredMethods. + + # bad + sig do + params( + foo: string, + ).void + end + def bar(foo) + puts foo + end + + # good + sig { + params( + foo: string, + ).void + } + def bar(foo) + puts foo + end diff --git a/config/contents/style/case_equality.md b/config/contents/style/case_equality.md index deaf6028..ed491d0f 100644 --- a/config/contents/style/case_equality.md +++ b/config/contents/style/case_equality.md @@ -9,4 +9,17 @@ This cop checks for uses of the case equality operator(===). # good something.is_a?(Array) (1..100).include?(7) - some_string =~ /something/ + /something/.match?(some_string) + +### Example: AllowOnConstant + # Style/CaseEquality: + # AllowOnConstant: true + + # bad + (1..100) === 7 + /something/ === some_string + + # good + Array === something + (1..100).include?(7) + /something/.match?(some_string) diff --git a/config/contents/style/case_like_if.md b/config/contents/style/case_like_if.md new file mode 100644 index 00000000..ec1f1fcc --- /dev/null +++ b/config/contents/style/case_like_if.md @@ -0,0 +1,22 @@ +This cop identifies places where `if-elsif` constructions +can be replaced with `case-when`. + +### Example: + # bad + if status == :active + perform_action + elsif status == :inactive || status == :hibernating + check_timeout + else + final_action + end + + # good + case status + when :active + perform_action + when :inactive, :hibernating + check_timeout + else + final_action + end diff --git a/config/contents/style/class_equality_comparison.md b/config/contents/style/class_equality_comparison.md new file mode 100644 index 00000000..0b236722 --- /dev/null +++ b/config/contents/style/class_equality_comparison.md @@ -0,0 +1,12 @@ +This cop enforces the use of `Object#instance_of?` instead of class comparison +for equality. + +### Example: + # bad + var.class == Date + var.class.equal?(Date) + var.class.eql?(Date) + var.class.name == 'Date' + + # good + var.instance_of?(Date) diff --git a/config/contents/style/class_methods_definitions.md b/config/contents/style/class_methods_definitions.md new file mode 100644 index 00000000..1894d744 --- /dev/null +++ b/config/contents/style/class_methods_definitions.md @@ -0,0 +1,54 @@ +This cop enforces using `def self.method_name` or `class << self` to define class methods. + +### Example: EnforcedStyle: def_self (default) + # bad + class SomeClass + class << self + attr_accessor :class_accessor + + def class_method + # ... + end + end + end + + # good + class SomeClass + def self.class_method + # ... + end + + class << self + attr_accessor :class_accessor + end + end + + # good - contains private method + class SomeClass + class << self + attr_accessor :class_accessor + + private + + def private_class_method + # ... + end + end + end + +### Example: EnforcedStyle: self_class + # bad + class SomeClass + def self.class_method + # ... + end + end + + # good + class SomeClass + class << self + def class_method + # ... + end + end + end diff --git a/config/contents/style/class_vars.md b/config/contents/style/class_vars.md index 677514d3..23af0929 100644 --- a/config/contents/style/class_vars.md +++ b/config/contents/style/class_vars.md @@ -1,3 +1,41 @@ This cop checks for uses of class variables. Offenses are signaled only on assignment to class variables to -reduce the number of offenses that would be reported. \ No newline at end of file +reduce the number of offenses that would be reported. + +You have to be careful when setting a value for a class +variable; if a class has been inherited, changing the +value of a class variable also affects the inheriting +classes. This means that it's almost always better to +use a class instance variable instead. + +### Example: + # bad + class A + @@test = 10 + end + + class A + def self.test(name, value) + class_variable_set("@@#{name}", value) + end + end + + class A; end + A.class_variable_set(:@@test, 10) + + # good + class A + @test = 10 + end + + class A + def test + @@test # you can access class variable without offense + end + end + + class A + def self.test(name) + class_variable_get("@@#{name}") # you can access without offense + end + end diff --git a/config/contents/style/collection_compact.md b/config/contents/style/collection_compact.md new file mode 100644 index 00000000..72b33d74 --- /dev/null +++ b/config/contents/style/collection_compact.md @@ -0,0 +1,23 @@ +This cop checks for places where custom logic on rejection nils from arrays +and hashes can be replaced with `{Array,Hash}#{compact,compact!}`. + +It is marked as unsafe by default because false positives may occur in the +nil check of block arguments to the receiver object. +For example, `[[1, 2], [3, nil]].reject { |first, second| second.nil? }` +and `[[1, 2], [3, nil]].compact` are not compatible. This will work fine +when the receiver is a hash object. + +### Example: + # bad + array.reject { |e| e.nil? } + array.select { |e| !e.nil? } + + # good + array.compact + + # bad + hash.reject! { |k, v| v.nil? } + hash.select! { |k, v| !v.nil? } + + # good + hash.compact! diff --git a/config/contents/style/collection_methods.md b/config/contents/style/collection_methods.md index c086a72a..d37826dd 100644 --- a/config/contents/style/collection_methods.md +++ b/config/contents/style/collection_methods.md @@ -3,4 +3,31 @@ from the Enumerable module. Unfortunately we cannot actually know if a method is from Enumerable or not (static analysis limitation), so this cop -can yield some false positives. \ No newline at end of file +can yield some false positives. + +You can customize the mapping from undesired method to desired method. + +e.g. to use `detect` over `find`: + + Style/CollectionMethods: + PreferredMethods: + find: detect + +The default mapping for `PreferredMethods` behaves as follows. + +### Example: + # bad + items.collect + items.collect! + items.inject + items.detect + items.find_all + items.member? + + # good + items.map + items.map! + items.reduce + items.find + items.select + items.include? diff --git a/config/contents/style/combinable_loops.md b/config/contents/style/combinable_loops.md new file mode 100644 index 00000000..2b326b8b --- /dev/null +++ b/config/contents/style/combinable_loops.md @@ -0,0 +1,51 @@ +This cop checks for places where multiple consecutive loops over the same data +can be combined into a single loop. It is very likely that combining them +will make the code more efficient and more concise. + +It is marked as unsafe, because the first loop might modify +a state that the second loop depends on; these two aren't combinable. + +### Example: + # bad + def method + items.each do |item| + do_something(item) + end + + items.each do |item| + do_something_else(item) + end + end + + # good + def method + items.each do |item| + do_something(item) + do_something_else(item) + end + end + + # bad + def method + for item in items do + do_something(item) + end + + for item in items do + do_something_else(item) + end + end + + # good + def method + for item in items do + do_something(item) + do_something_else(item) + end + end + + # good + def method + each_slice(2) { |slice| do_something(slice) } + each_slice(3) { |slice| do_something(slice) } + end diff --git a/config/contents/style/comment_annotation.md b/config/contents/style/comment_annotation.md index aa8ee0c0..14e20edb 100644 --- a/config/contents/style/comment_annotation.md +++ b/config/contents/style/comment_annotation.md @@ -1,7 +1,13 @@ This cop checks that comment annotation keywords are written according to guidelines. -### Example: +NOTE: With a multiline comment block (where each line is only a +comment), only the first line will be able to register an offense, even +if an annotation keyword starts another line. This is done to prevent +incorrect registering of keywords (eg. `review`) inside a paragraph as an +annotation. + +### Example: RequireColon: true (default) # bad # TODO make better @@ -24,4 +30,23 @@ to guidelines. # Optimize does not work # good - # OPTIMIZE: does not work \ No newline at end of file + # OPTIMIZE: does not work + +### Example: RequireColon: false + # bad + # TODO: make better + + # good + # TODO make better + + # bad + # fixme does not work + + # good + # FIXME does not work + + # bad + # Optimize does not work + + # good + # OPTIMIZE does not work \ No newline at end of file diff --git a/config/contents/style/commented_keyword.md b/config/contents/style/commented_keyword.md index cafebf5e..e98f96b6 100644 --- a/config/contents/style/commented_keyword.md +++ b/config/contents/style/commented_keyword.md @@ -1,8 +1,12 @@ This cop checks for comments put on the same line as some keywords. -These keywords are: `begin`, `class`, `def`, `end`, `module`. +These keywords are: `class`, `module`, `def`, `begin`, `end`. -Note that some comments (such as `:nodoc:` and `rubocop:disable`) are -allowed. +Note that some comments +(`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`) +are allowed. + +Auto-correction removes comments from `end` keyword and keeps comments +for `class`, `module`, `def` and `begin` above the keyword. ### Example: # bad diff --git a/config/contents/style/constant_visibility.md b/config/contents/style/constant_visibility.md new file mode 100644 index 00000000..29506bbd --- /dev/null +++ b/config/contents/style/constant_visibility.md @@ -0,0 +1,40 @@ +This cop checks that constants defined in classes and modules have +an explicit visibility declaration. By default, Ruby makes all class- +and module constants public, which litters the public API of the +class or module. Explicitly declaring a visibility makes intent more +clear, and prevents outside actors from touching private state. + +### Example: + + # bad + class Foo + BAR = 42 + BAZ = 43 + end + + # good + class Foo + BAR = 42 + private_constant :BAR + + BAZ = 43 + public_constant :BAZ + end + +### Example: IgnoreModules: false (default) + # bad + class Foo + MyClass = Struct.new() + end + + # good + class Foo + MyClass = Struct.new() + public_constant :MyClass + end + +### Example: IgnoreModules: true + # good + class Foo + MyClass = Struct.new() + end diff --git a/config/contents/style/copyright.md b/config/contents/style/copyright.md index 39d59b74..1e00d3cf 100644 --- a/config/contents/style/copyright.md +++ b/config/contents/style/copyright.md @@ -1,11 +1,11 @@ Check that a copyright notice was given in each source file. The default regexp for an acceptable copyright notice can be found in -config/default.yml. The default can be changed as follows: +config/default.yml. The default can be changed as follows: - Style/Copyright: - Notice: '^Copyright (\(c\) )?2\d{3} Acme Inc' + Style/Copyright: + Notice: '^Copyright (\(c\) )?2\d{3} Acme Inc' -This regex string is treated as an unanchored regex. For each file +This regex string is treated as an unanchored regex. For each file that RuboCop scans, a comment that matches this regex must be found or an offense is reported. diff --git a/config/contents/style/date_time.md b/config/contents/style/date_time.md index 0ce72d97..6c16e202 100644 --- a/config/contents/style/date_time.md +++ b/config/contents/style/date_time.md @@ -1,5 +1,8 @@ -This cop checks for uses of `DateTime` that should be replaced by -`Date` or `Time`. +This cop checks for consistent usage of the `DateTime` class over the +`Time` class. This cop is disabled by default since these classes, +although highly overlapping, have particularities that make them not +replaceable in certain situations when dealing with multiple timezones +and/or DST. ### Example: @@ -12,8 +15,24 @@ This cop checks for uses of `DateTime` that should be replaced by # bad - uses `DateTime` for modern date DateTime.iso8601('2016-06-29') - # good - uses `Date` for modern date - Date.iso8601('2016-06-29') + # good - uses `Time` for modern date + Time.iso8601('2016-06-29') # good - uses `DateTime` with start argument for historical date - DateTime.iso8601('1751-04-23', Date::ENGLAND) \ No newline at end of file + DateTime.iso8601('1751-04-23', Date::ENGLAND) + +### Example: AllowCoercion: false (default) + + # bad - coerces to `DateTime` + something.to_datetime + + # good - coerces to `Time` + something.to_time + +### Example: AllowCoercion: true + + # good + something.to_datetime + + # good + something.to_time \ No newline at end of file diff --git a/config/contents/style/disable_cops_within_source_code_directive.md b/config/contents/style/disable_cops_within_source_code_directive.md new file mode 100644 index 00000000..9f7b9a42 --- /dev/null +++ b/config/contents/style/disable_cops_within_source_code_directive.md @@ -0,0 +1,24 @@ +Detects comments to enable/disable RuboCop. +This is useful if want to make sure that every RuboCop error gets fixed +and not quickly disabled with a comment. + +Specific cops can be allowed with the `AllowedCops` configuration. Note that +if this configuration is set, `rubocop:disable all` is still disallowed. + +### Example: + # bad + # rubocop:disable Metrics/AbcSize + def foo + end + # rubocop:enable Metrics/AbcSize + + # good + def foo + end + +### Example: AllowedCops: [Metrics/AbcSize] + # good + # rubocop:disable Metrics/AbcSize + def foo + end + # rubocop:enable Metrics/AbcSize diff --git a/config/contents/style/document_dynamic_eval_definition.md b/config/contents/style/document_dynamic_eval_definition.md new file mode 100644 index 00000000..90e8ec2d --- /dev/null +++ b/config/contents/style/document_dynamic_eval_definition.md @@ -0,0 +1,71 @@ +When using `class_eval` (or other `eval`) with string interpolation, +add a comment block showing its appearance if interpolated (a practice used in Rails code). + +### Example: + # from activesupport/lib/active_support/core_ext/string/output_safety.rb + + # bad + UNSAFE_STRING_METHODS.each do |unsafe_method| + if 'String'.respond_to?(unsafe_method) + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*params, &block) + to_str.#{unsafe_method}(*params, &block) + end + + def #{unsafe_method}!(*params) + @dirty = true + super + end + EOT + end + end + + # good, inline comments in heredoc + UNSAFE_STRING_METHODS.each do |unsafe_method| + if 'String'.respond_to?(unsafe_method) + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*params, &block) # def capitalize(*params, &block) + to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block) + end # end + + def #{unsafe_method}!(*params) # def capitalize!(*params) + @dirty = true # @dirty = true + super # super + end # end + EOT + end + end + + # good, block comments in heredoc + class_eval <<-EOT, __FILE__, __LINE__ + 1 + # def capitalize!(*params) + # @dirty = true + # super + # end + + def #{unsafe_method}!(*params) + @dirty = true + super + end + EOT + + # good, block comments before heredoc + class_eval( + # def capitalize!(*params) + # @dirty = true + # super + # end + + <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}!(*params) + @dirty = true + super + end + EOT + ) + + # bad - interpolated string without comment + class_eval("def #{unsafe_method}!(*params); end") + + # good - with inline comment or replace it with block comment using heredoc + class_eval("def #{unsafe_method}!(*params); end # def capitalize!(*params); end") \ No newline at end of file diff --git a/config/contents/style/documentation.md b/config/contents/style/documentation.md index dc468719..c9e7e88a 100644 --- a/config/contents/style/documentation.md +++ b/config/contents/style/documentation.md @@ -1,7 +1,8 @@ -This cop checks for missing top-level documentation of -classes and modules. Classes with no body are exempt from the -check and so are namespace modules - modules that have nothing in -their bodies except classes, other modules, or constant definitions. +This cop checks for missing top-level documentation of classes and +modules. Classes with no body are exempt from the check and so are +namespace modules - modules that have nothing in their bodies except +classes, other modules, constant definitions or constant visibility +declarations. The documentation requirement is annulled if the class or module has a "#:nodoc:" comment next to it. Likewise, "#:nodoc: all" does the @@ -13,8 +14,52 @@ same for all its children. # ... end + module Math + end + # good # Description/Explanation of Person class class Person # ... end + + # allowed + # Class without body + class Person + end + + # Namespace - A namespace can be a class or a module + # Containing a class + module Namespace + # Description/Explanation of Person class + class Person + # ... + end + end + + # Containing constant visibility declaration + module Namespace + class Private + end + + private_constant :Private + end + + # Containing constant definition + module Namespace + Public = Class.new + end + + # Macro calls + module Namespace + extend Foo + end + +### Example: AllowedConstants: ['ClassMethods'] + + # good + module A + module ClassMethods + # ... + end + end diff --git a/config/contents/style/documentation_method.md b/config/contents/style/documentation_method.md index e76d4fac..09a8cdec 100644 --- a/config/contents/style/documentation_method.md +++ b/config/contents/style/documentation_method.md @@ -41,4 +41,47 @@ non-public methods. # Documentation def foo.bar puts baz - end \ No newline at end of file + end + +### Example: RequireForNonPublicMethods: false (default) + # good + class Foo + protected + def do_something + end + end + + class Foo + private + def do_something + end + end + +### Example: RequireForNonPublicMethods: true + # bad + class Foo + protected + def do_something + end + end + + class Foo + private + def do_something + end + end + + # good + class Foo + protected + # Documentation + def do_something + end + end + + class Foo + private + # Documentation + def do_something + end + end diff --git a/config/contents/style/double_cop_disable_directive.md b/config/contents/style/double_cop_disable_directive.md new file mode 100644 index 00000000..f8bcb218 --- /dev/null +++ b/config/contents/style/double_cop_disable_directive.md @@ -0,0 +1,17 @@ +Detects double disable comments on one line. This is mostly to catch +automatically generated comments that need to be regenerated. + +### Example: + # bad + def f # rubocop:disable Style/For # rubocop:disable Metrics/AbcSize + end + + # good + # rubocop:disable Metrics/AbcSize + def f # rubocop:disable Style/For + end + # rubocop:enable Metrics/AbcSize + + # if both fit on one line + def f # rubocop:disable Style/For, Metrics/AbcSize + end diff --git a/config/contents/style/double_negation.md b/config/contents/style/double_negation.md index 0d84a037..d197ba2c 100644 --- a/config/contents/style/double_negation.md +++ b/config/contents/style/double_negation.md @@ -1,15 +1,28 @@ -This cop checks for uses of double negation (!!) to convert something -to a boolean value. As this is both cryptic and usually redundant, it -should be avoided. +This cop checks for uses of double negation (`!!`) to convert something to a boolean value. -### Example: +When using `EnforcedStyle: allowed_in_returns`, allow double negation in contexts +that use boolean as a return value. When using `EnforcedStyle: forbidden`, double negation +should be forbidden always. +### Example: # bad !!something # good !something.nil? +### Example: EnforcedStyle: allowed_in_returns (default) + # good + def foo? + !!return_value + end + +### Example: EnforcedStyle: forbidden + # bad + def foo? + !!return_value + end + Please, note that when something is a boolean value !!something and !something.nil? are not the same thing. As you're unlikely to write code that can accept values of any type diff --git a/config/contents/style/empty_literal.md b/config/contents/style/empty_literal.md index 6f2006f9..64ccfaaf 100644 --- a/config/contents/style/empty_literal.md +++ b/config/contents/style/empty_literal.md @@ -1,5 +1,5 @@ This cop checks for the use of a method, the result of which -would be a literal, like an empty array, hash or string. +would be a literal, like an empty array, hash, or string. ### Example: # bad diff --git a/config/contents/style/empty_method.md b/config/contents/style/empty_method.md index 67a8dd82..48673baa 100644 --- a/config/contents/style/empty_method.md +++ b/config/contents/style/empty_method.md @@ -3,7 +3,7 @@ By default it enforces empty method definitions to go on a single line (compact style), but it can be configured to enforce the `end` to go on its own line (expanded style). -Note: A method definition is not considered empty if it contains +NOTE: A method definition is not considered empty if it contains comments. ### Example: EnforcedStyle: compact (default) diff --git a/config/contents/style/encoding.md b/config/contents/style/encoding.md new file mode 100644 index 00000000..c85a0b99 --- /dev/null +++ b/config/contents/style/encoding.md @@ -0,0 +1,6 @@ +This cop checks ensures source files have no utf-8 encoding comments. +### Example: + # bad + # encoding: UTF-8 + # coding: UTF-8 + # -*- coding: UTF-8 -*- \ No newline at end of file diff --git a/config/contents/style/end_block.md b/config/contents/style/end_block.md new file mode 100644 index 00000000..8f05ddfe --- /dev/null +++ b/config/contents/style/end_block.md @@ -0,0 +1,8 @@ +This cop checks for END blocks. + +### Example: + # bad + END { puts 'Goodbye!' } + + # good + at_exit { puts 'Goodbye!' } diff --git a/config/contents/style/endless_method.md b/config/contents/style/endless_method.md new file mode 100644 index 00000000..d2ebc196 --- /dev/null +++ b/config/contents/style/endless_method.md @@ -0,0 +1,42 @@ +This cop checks for endless methods. + +It can enforce either the use of endless methods definitions +for single-lined method bodies, or disallow endless methods. + +Other method definition types are not considered by this cop. + +The supported styles are: + +* allow_single_line (default) - only single line endless method definitions are allowed. +* allow_always - all endless method definitions are allowed. +* disallow - all endless method definitions are disallowed. + +NOTE: Incorrect endless method definitions will always be +corrected to a multi-line definition. + +### Example: EnforcedStyle: allow_single_line (default) + # good + def my_method() = x + + # bad, multi-line endless method + def my_method() = x.foo + .bar + .baz + +### Example: EnforcedStyle: allow_always + # good + def my_method() = x + + # good + def my_method() = x.foo + .bar + .baz + +### Example: EnforcedStyle: disallow + # bad + def my_method; x end + + # bad + def my_method() = x.foo + .bar + .baz diff --git a/config/contents/style/eval_with_location.md b/config/contents/style/eval_with_location.md index 550f4ae6..9ec075c4 100644 --- a/config/contents/style/eval_with_location.md +++ b/config/contents/style/eval_with_location.md @@ -1,6 +1,16 @@ -This cop checks `eval` method usage. `eval` can receive source location -metadata, that are filename and line number. The metadata is used by -backtraces. This cop recommends to pass the metadata to `eval` method. +This cop ensures that eval methods (`eval`, `instance_eval`, `class_eval` +and `module_eval`) are given filename and line number values (`__FILE__` +and `__LINE__`). This data is used to ensure that any errors raised +within the evaluated code will be given the correct identification +in a backtrace. + +The cop also checks that the line number given relative to `__LINE__` is +correct. + +This cop will autocorrect incorrect or missing filename and line number +values. However, if `eval` is called without a binding argument, the cop +will not attempt to automatically add a binding, or add filename and +line values. ### Example: # bad @@ -25,4 +35,15 @@ backtraces. This cop recommends to pass the metadata to `eval` method. C.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def do_something end - RUBY \ No newline at end of file + RUBY + +This cop works only when a string literal is given as a code string. +No offence is reported if a string variable is given as below: + +### Example: + # not checked + code = <<-RUBY + def do_something + end + RUBY + eval code diff --git a/config/contents/style/even_odd.md b/config/contents/style/even_odd.md index 83ed6897..c79711b9 100644 --- a/config/contents/style/even_odd.md +++ b/config/contents/style/even_odd.md @@ -1,5 +1,5 @@ -This cop checks for places where Integer#even? or Integer#odd? -should have been used. +This cop checks for places where `Integer#even?` or `Integer#odd?` +can be used. ### Example: diff --git a/config/contents/style/expand_path_arguments.md b/config/contents/style/expand_path_arguments.md new file mode 100644 index 00000000..baea3e0b --- /dev/null +++ b/config/contents/style/expand_path_arguments.md @@ -0,0 +1,36 @@ +This cop checks for use of the `File.expand_path` arguments. +Likewise, it also checks for the `Pathname.new` argument. + +Contrastive bad case and good case are alternately shown in +the following examples. + +### Example: + # bad + File.expand_path('..', __FILE__) + + # good + File.expand_path(__dir__) + + # bad + File.expand_path('../..', __FILE__) + + # good + File.expand_path('..', __dir__) + + # bad + File.expand_path('.', __FILE__) + + # good + File.expand_path(__FILE__) + + # bad + Pathname(__FILE__).parent.expand_path + + # good + Pathname(__dir__).expand_path + + # bad + Pathname.new(__FILE__).parent.expand_path + + # good + Pathname.new(__dir__).expand_path diff --git a/config/contents/style/explicit_block_argument.md b/config/contents/style/explicit_block_argument.md new file mode 100644 index 00000000..55757c61 --- /dev/null +++ b/config/contents/style/explicit_block_argument.md @@ -0,0 +1,34 @@ +This cop enforces the use of explicit block argument to avoid writing +block literal that just passes its arguments to another block. + +NOTE: This cop only registers an offense if the block args match the +yield args exactly. + +### Example: + # bad + def with_tmp_dir + Dir.mktmpdir do |tmp_dir| + Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments + end + end + + # bad + def nine_times + 9.times { yield } + end + + # good + def with_tmp_dir(&block) + Dir.mktmpdir do |tmp_dir| + Dir.chdir(tmp_dir, &block) + end + end + + with_tmp_dir do |dir| + puts "dir is accessible as a parameter and pwd is set: #{dir}" + end + + # good + def nine_times(&block) + 9.times(&block) + end diff --git a/config/contents/style/exponential_notation.md b/config/contents/style/exponential_notation.md new file mode 100644 index 00000000..da12646c --- /dev/null +++ b/config/contents/style/exponential_notation.md @@ -0,0 +1,53 @@ +This cop enforces consistency when using exponential notation +for numbers in the code (eg 1.2e4). Different styles are supported: + +* `scientific` which enforces a mantissa between 1 (inclusive) and 10 (exclusive). +* `engineering` which enforces the exponent to be a multiple of 3 and the mantissa + to be between 0.1 (inclusive) and 10 (exclusive). +* `integral` which enforces the mantissa to always be a whole number without + trailing zeroes. + +### Example: EnforcedStyle: scientific (default) + # Enforces a mantissa between 1 (inclusive) and 10 (exclusive). + + # bad + 10e6 + 0.3e4 + 11.7e5 + 3.14e0 + + # good + 1e7 + 3e3 + 1.17e6 + 3.14 + +### Example: EnforcedStyle: engineering + # Enforces using multiple of 3 exponents, + # mantissa should be between 0.1 (inclusive) and 1000 (exclusive) + + # bad + 3.2e7 + 0.1e5 + 12e5 + 1232e6 + + # good + 32e6 + 10e3 + 1.2e6 + 1.232e9 + +### Example: EnforcedStyle: integral + # Enforces the mantissa to have no decimal part and no + # trailing zeroes. + + # bad + 3.2e7 + 0.1e5 + 120e4 + + # good + 32e6 + 1e4 + 12e5 diff --git a/config/contents/style/float_division.md b/config/contents/style/float_division.md new file mode 100644 index 00000000..98a8fd63 --- /dev/null +++ b/config/contents/style/float_division.md @@ -0,0 +1,39 @@ +This cop checks for division with integers coerced to floats. +It is recommended to either always use `fdiv` or coerce one side only. +This cop also provides other options for code consistency. + +This cop is marked as unsafe, because if operand variable is a string object +then `.to_f` will be removed and an error will occur. + +### Example: EnforcedStyle: single_coerce (default) + # bad + a.to_f / b.to_f + + # good + a.to_f / b + a / b.to_f + +### Example: EnforcedStyle: left_coerce + # bad + a / b.to_f + a.to_f / b.to_f + + # good + a.to_f / b + +### Example: EnforcedStyle: right_coerce + # bad + a.to_f / b + a.to_f / b.to_f + + # good + a / b.to_f + +### Example: EnforcedStyle: fdiv + # bad + a / b.to_f + a.to_f / b + a.to_f / b.to_f + + # good + a.fdiv(b) \ No newline at end of file diff --git a/config/contents/style/for.md b/config/contents/style/for.md index 70f68e5f..96edb4cb 100644 --- a/config/contents/style/for.md +++ b/config/contents/style/for.md @@ -1,4 +1,34 @@ -This cop looks for uses of the *for* keyword, or *each* method. The +This cop looks for uses of the `for` keyword or `each` method. The preferred alternative is set in the EnforcedStyle configuration -parameter. An *each* call with a block on a single line is always -allowed, however. \ No newline at end of file +parameter. An `each` call with a block on a single line is always +allowed. + +### Example: EnforcedStyle: each (default) + # bad + def foo + for n in [1, 2, 3] do + puts n + end + end + + # good + def foo + [1, 2, 3].each do |n| + puts n + end + end + +### Example: EnforcedStyle: for + # bad + def foo + [1, 2, 3].each do |n| + puts n + end + end + + # good + def foo + for n in [1, 2, 3] do + puts n + end + end diff --git a/config/contents/style/format_string.md b/config/contents/style/format_string.md index c3720ff0..9dce92ce 100644 --- a/config/contents/style/format_string.md +++ b/config/contents/style/format_string.md @@ -6,7 +6,7 @@ manner for all cases, so only two scenarios are considered - if the first argument is a string literal and if the second argument is an array literal. -### Example: EnforcedStyle: format(default) +### Example: EnforcedStyle: format (default) # bad puts sprintf('%10s', 'hoge') puts '%10s' % 'hoge' diff --git a/config/contents/style/format_string_token.md b/config/contents/style/format_string_token.md index c74463b7..c52a4cfe 100644 --- a/config/contents/style/format_string_token.md +++ b/config/contents/style/format_string_token.md @@ -1,12 +1,13 @@ Use a consistent style for named format string tokens. -**Note:** -`unannotated` style cop only works for strings +NOTE: `unannotated` style cop only works for strings which are passed as arguments to those methods: -`sprintf`, `format`, `%`. -The reason is that *unannotated* format is very similar +`printf`, `sprintf`, `format`, `%`. +The reason is that _unannotated_ format is very similar to encoded URLs or Date/Time formatting strings. +This cop can be customized ignored methods with `IgnoredMethods`. + ### Example: EnforcedStyle: annotated (default) # bad @@ -29,7 +30,33 @@ to encoded URLs or Date/Time formatting strings. # bad format('%<greeting>s', greeting: 'Hello') - format('%{greeting}', 'Hello') + format('%{greeting}', greeting: 'Hello') + + # good + format('%s', 'Hello') + +It is allowed to contain unannotated token +if the number of them is less than or equals to +`MaxUnannotatedPlaceholdersAllowed`. + +### Example: MaxUnannotatedPlaceholdersAllowed: 0 + + # bad + format('%06d', 10) + format('%s %s.', 'Hello', 'world') + + # good + format('%<number>06d', number: 10) + +### Example: MaxUnannotatedPlaceholdersAllowed: 1 (default) + + # bad + format('%s %s.', 'Hello', 'world') + + # good + format('%06d', 10) + +### Example: IgnoredMethods: [redirect] # good - format('%s', 'Hello') \ No newline at end of file + redirect('foo/%{bar_id}') diff --git a/config/contents/style/frozen_string_literal_comment.md b/config/contents/style/frozen_string_literal_comment.md index 43bf7513..03e0ee65 100644 --- a/config/contents/style/frozen_string_literal_comment.md +++ b/config/contents/style/frozen_string_literal_comment.md @@ -1,25 +1,17 @@ -This cop is designed to help upgrade to Ruby 3.0. It will add the -comment `# frozen_string_literal: true` to the top of files to -enable frozen string literals. Frozen string literals may be default -in Ruby 3.0. The comment will be added below a shebang and encoding -comment. The frozen string literal comment is only valid in Ruby 2.3+. - -### Example: EnforcedStyle: when_needed (default) - # The `when_needed` style will add the frozen string literal comment - # to files only when the `TargetRubyVersion` is set to 2.3+. - # bad - module Foo - # ... - end +This cop is designed to help you transition from mutable string literals +to frozen string literals. +It will add the `# frozen_string_literal: true` magic comment to the top +of files to enable frozen string literals. Frozen string literals may be +default in future Ruby. The comment will be added below a shebang and +encoding comment. - # good - # frozen_string_literal: true +Note that the cop will ignore files where the comment exists but is set +to `false` instead of `true`. - module Foo - # ... - end +To require a blank line after this comment, please see +`Layout/EmptyLineAfterMagicComment` cop. -### Example: EnforcedStyle: always +### Example: EnforcedStyle: always (default) # The `always` style will always add the frozen string literal comment # to a file, regardless of the Ruby version or if `freeze` or `<<` are # called on a string literal. @@ -35,6 +27,13 @@ comment. The frozen string literal comment is only valid in Ruby 2.3+. # ... end + # good + # frozen_string_literal: false + + module Bar + # ... + end + ### Example: EnforcedStyle: never # The `never` will enforce that the frozen string literal comment does # not exist in a file. @@ -48,4 +47,27 @@ comment. The frozen string literal comment is only valid in Ruby 2.3+. # good module Baz # ... + end + +### Example: EnforcedStyle: always_true + # The `always_true` style enforces that the frozen string literal + # comment is set to `true`. This is a stricter option than `always` + # and forces projects to use frozen string literals. + # bad + # frozen_string_literal: false + + module Baz + # ... + end + + # bad + module Baz + # ... + end + + # good + # frozen_string_literal: true + + module Bar + # ... end \ No newline at end of file diff --git a/config/contents/style/global_std_stream.md b/config/contents/style/global_std_stream.md new file mode 100644 index 00000000..b2c5ec14 --- /dev/null +++ b/config/contents/style/global_std_stream.md @@ -0,0 +1,23 @@ +This cop enforces the use of `$stdout/$stderr/$stdin` instead of `STDOUT/STDERR/STDIN`. +`STDOUT/STDERR/STDIN` are constants, and while you can actually +reassign (possibly to redirect some stream) constants in Ruby, you'll get +an interpreter warning if you do so. + +### Example: + # bad + STDOUT.puts('hello') + + hash = { out: STDOUT, key: value } + + def m(out = STDOUT) + out.puts('hello') + end + + # good + $stdout.puts('hello') + + hash = { out: $stdout, key: value } + + def m(out = $stdout) + out.puts('hello') + end diff --git a/config/contents/style/global_vars.md b/config/contents/style/global_vars.md index 3e388b84..73832516 100644 --- a/config/contents/style/global_vars.md +++ b/config/contents/style/global_vars.md @@ -1,4 +1,4 @@ -This cops looks for uses of global variables. +This cop looks for uses of global variables. It does not report offenses for built-in global variables. Built-in global variables are allowed by default. Additionally users can allow additional variables via the AllowedVariables option. diff --git a/config/contents/style/guard_clause.md b/config/contents/style/guard_clause.md index 00725de9..e201de3b 100644 --- a/config/contents/style/guard_clause.md +++ b/config/contents/style/guard_clause.md @@ -12,6 +12,7 @@ expression # good def test return unless something + work end @@ -29,4 +30,15 @@ expression # good raise 'exception' if something + ok + + # bad + if something + foo || raise('exception') + else + ok + end + + # good + foo || raise('exception') if something ok \ No newline at end of file diff --git a/config/contents/style/hash_as_last_array_item.md b/config/contents/style/hash_as_last_array_item.md new file mode 100644 index 00000000..65e73b8c --- /dev/null +++ b/config/contents/style/hash_as_last_array_item.md @@ -0,0 +1,25 @@ +Checks for presence or absence of braces around hash literal as a last +array item depending on configuration. + +NOTE: This cop will ignore arrays where all items are hashes, regardless of +EnforcedStyle. + +### Example: EnforcedStyle: braces (default) + # bad + [1, 2, one: 1, two: 2] + + # good + [1, 2, { one: 1, two: 2 }] + + # good + [{ one: 1 }, { two: 2 }] + +### Example: EnforcedStyle: no_braces + # bad + [1, 2, { one: 1, two: 2 }] + + # good + [1, 2, one: 1, two: 2] + + # good + [{ one: 1 }, { two: 2 }] \ No newline at end of file diff --git a/config/contents/style/hash_conversion.md b/config/contents/style/hash_conversion.md new file mode 100644 index 00000000..69c7a286 --- /dev/null +++ b/config/contents/style/hash_conversion.md @@ -0,0 +1,27 @@ +This cop checks the usage of pre-2.1 `Hash[args]` method of converting enumerables and +sequences of values to hashes. + +Correction code from splat argument (`Hash[*ary]`) is not simply determined. For example, +`Hash[*ary]` can be replaced with `ary.each_slice(2).to_h` but it will be complicated. +So, `AllowSplatArgument` option is true by default to allow splat argument for simple code. + +### Example: + # bad + Hash[ary] + + # good + ary.to_h + + # bad + Hash[key1, value1, key2, value2] + + # good + {key1 => value1, key2 => value2} + +### Example: AllowSplatArgument: true (default) + # good + Hash[*ary] + +### Example: AllowSplatArgument: false + # bad + Hash[*ary] diff --git a/config/contents/style/hash_each_methods.md b/config/contents/style/hash_each_methods.md new file mode 100644 index 00000000..11953aeb --- /dev/null +++ b/config/contents/style/hash_each_methods.md @@ -0,0 +1,19 @@ +This cop checks for uses of `each_key` and `each_value` Hash methods. + +NOTE: If you have an array of two-element arrays, you can put + parentheses around the block arguments to indicate that you're not + working with a hash, and suppress RuboCop offenses. + +### Example: + # bad + hash.keys.each { |k| p k } + hash.values.each { |v| p v } + + # good + hash.each_key { |k| p k } + hash.each_value { |v| p v } + +### Example: AllowedReceivers: ['execute'] + # good + execute(sql).keys.each { |v| p v } + execute(sql).values.each { |v| p v } \ No newline at end of file diff --git a/config/contents/style/hash_except.md b/config/contents/style/hash_except.md new file mode 100644 index 00000000..b8300c13 --- /dev/null +++ b/config/contents/style/hash_except.md @@ -0,0 +1,19 @@ +This cop checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods +that can be replaced with `Hash#except` method. + +This cop should only be enabled on Ruby version 3.0 or higher. +(`Hash#except` was added in Ruby 3.0.) + +For safe detection, it is limited to commonly used string and symbol comparisons +when used `==`. +And do not check `Hash#delete_if` and `Hash#keep_if` to change receiver object. + +### Example: + + # bad + {foo: 1, bar: 2, baz: 3}.reject {|k, v| k == :bar } + {foo: 1, bar: 2, baz: 3}.select {|k, v| k != :bar } + {foo: 1, bar: 2, baz: 3}.filter {|k, v| k != :bar } + + # good + {foo: 1, bar: 2, baz: 3}.except(:bar) diff --git a/config/contents/style/hash_like_case.md b/config/contents/style/hash_like_case.md new file mode 100644 index 00000000..5f57c142 --- /dev/null +++ b/config/contents/style/hash_like_case.md @@ -0,0 +1,32 @@ +This cop checks for places where `case-when` represents a simple 1:1 +mapping and can be replaced with a hash lookup. + +### Example: MinBranchesCount: 3 (default) + # bad + case country + when 'europe' + 'http://eu.example.com' + when 'america' + 'http://us.example.com' + when 'australia' + 'http://au.example.com' + end + + # good + SITES = { + 'europe' => 'http://eu.example.com', + 'america' => 'http://us.example.com', + 'australia' => 'http://au.example.com' + } + SITES[country] + +### Example: MinBranchesCount: 4 + # good + case country + when 'europe' + 'http://eu.example.com' + when 'america' + 'http://us.example.com' + when 'australia' + 'http://au.example.com' + end diff --git a/config/contents/style/hash_syntax.md b/config/contents/style/hash_syntax.md index 77e6a05d..a973bb5a 100644 --- a/config/contents/style/hash_syntax.md +++ b/config/contents/style/hash_syntax.md @@ -8,11 +8,11 @@ A separate offense is registered for each problematic pair. The supported styles are: * ruby19 - forces use of the 1.9 syntax (e.g. `{a: 1}`) when hashes have - all symbols for keys +all symbols for keys * hash_rockets - forces use of hash rockets for all hashes * no_mixed_keys - simply checks for hashes with mixed syntaxes * ruby19_no_mixed_keys - forces use of ruby 1.9 syntax and forbids mixed - syntax hashes +syntax hashes ### Example: EnforcedStyle: ruby19 (default) # bad diff --git a/config/contents/style/hash_transform_keys.md b/config/contents/style/hash_transform_keys.md new file mode 100644 index 00000000..9517280b --- /dev/null +++ b/config/contents/style/hash_transform_keys.md @@ -0,0 +1,22 @@ +This cop looks for uses of `_.each_with_object({}) {...}`, +`_.map {...}.to_h`, and `Hash[_.map {...}]` that are actually just +transforming the keys of a hash, and tries to use a simpler & faster +call to `transform_keys` instead. + +This can produce false positives if we are transforming an enumerable +of key-value-like pairs that isn't actually a hash, e.g.: +`[[k1, v1], [k2, v2], ...]` + +This cop should only be enabled on Ruby version 2.5 or newer +(`transform_keys` was added in Ruby 2.5.) + +### Example: + # bad + {a: 1, b: 2}.each_with_object({}) { |(k, v), h| h[foo(k)] = v } + Hash[{a: 1, b: 2}.collect { |k, v| [foo(k), v] }] + {a: 1, b: 2}.map { |k, v| [k.to_s, v] }.to_h + {a: 1, b: 2}.to_h { |k, v| [k.to_s, v] } + + # good + {a: 1, b: 2}.transform_keys { |k| foo(k) } + {a: 1, b: 2}.transform_keys { |k| k.to_s } \ No newline at end of file diff --git a/config/contents/style/hash_transform_values.md b/config/contents/style/hash_transform_values.md new file mode 100644 index 00000000..4bb33426 --- /dev/null +++ b/config/contents/style/hash_transform_values.md @@ -0,0 +1,22 @@ +This cop looks for uses of `_.each_with_object({}) {...}`, +`_.map {...}.to_h`, and `Hash[_.map {...}]` that are actually just +transforming the values of a hash, and tries to use a simpler & faster +call to `transform_values` instead. + +This can produce false positives if we are transforming an enumerable +of key-value-like pairs that isn't actually a hash, e.g.: +`[[k1, v1], [k2, v2], ...]` + +This cop should only be enabled on Ruby version 2.4 or newer +(`transform_values` was added in Ruby 2.4.) + +### Example: + # bad + {a: 1, b: 2}.each_with_object({}) { |(k, v), h| h[k] = foo(v) } + Hash[{a: 1, b: 2}.collect { |k, v| [k, foo(v)] }] + {a: 1, b: 2}.map { |k, v| [k, v * v] }.to_h + {a: 1, b: 2}.to_h { |k, v| [k, v * v] } + + # good + {a: 1, b: 2}.transform_values { |v| foo(v) } + {a: 1, b: 2}.transform_values { |v| v * v } \ No newline at end of file diff --git a/config/contents/style/identical_conditional_branches.md b/config/contents/style/identical_conditional_branches.md index fd9e7907..ae543f47 100644 --- a/config/contents/style/identical_conditional_branches.md +++ b/config/contents/style/identical_conditional_branches.md @@ -1,5 +1,10 @@ -This cop checks for identical lines at the beginning or end of -each branch of a conditional statement. +This cop checks for identical expressions at the beginning or end of +each branch of a conditional expression. Such expressions should normally +be placed outside the conditional expression - before or after it. + +NOTE: The cop is poorly named and some people might think that it actually +checks for duplicated conditional branches. The name will probably be changed +in a future major RuboCop release. ### Example: # bad @@ -56,4 +61,26 @@ each branch of a conditional statement. else do_x do_z + end + + # bad + case foo + in 1 + do_x + in 2 + do_x + else + do_x + end + + # good + case foo + in 1 + do_x + do_y + in 2 + # nothing + else + do_x + do_z end \ No newline at end of file diff --git a/config/contents/style/if_inside_else.md b/config/contents/style/if_inside_else.md index 74e18227..b5431dc6 100644 --- a/config/contents/style/if_inside_else.md +++ b/config/contents/style/if_inside_else.md @@ -21,4 +21,34 @@ This helps to keep the nesting level from getting too deep. action_b else action_c - end \ No newline at end of file + end + +### Example: AllowIfModifier: false (default) + # bad + if condition_a + action_a + else + action_b if condition_b + end + + # good + if condition_a + action_a + elsif condition_b + action_b + end + +### Example: AllowIfModifier: true + # good + if condition_a + action_a + else + action_b if condition_b + end + + # good + if condition_a + action_a + elsif condition_b + action_b + end diff --git a/config/contents/style/if_unless_modifier.md b/config/contents/style/if_unless_modifier.md index ed38881d..cfb720ac 100644 --- a/config/contents/style/if_unless_modifier.md +++ b/config/contents/style/if_unless_modifier.md @@ -1,6 +1,10 @@ -Checks for if and unless statements that would fit on one line -if written as a modifier if/unless. The maximum line length is -configured in the `Metrics/LineLength` cop. +Checks for `if` and `unless` statements that would fit on one line if +written as modifier `if`/`unless`. The cop also checks for modifier +`if`/`unless` lines that exceed the maximum line length. + +The maximum line length is configured in the `Layout/LineLength` +cop. The tab size is configured in the `IndentationWidth` of the +`Layout/IndentationStyle` cop. ### Example: # bad @@ -12,6 +16,16 @@ configured in the `Metrics/LineLength` cop. Foo.do_something end + do_something_with_a_long_name(arg) if long_condition_that_prevents_code_fit_on_single_line + # good do_stuff(bar) if condition - Foo.do_something unless qux.empty? \ No newline at end of file + Foo.do_something unless qux.empty? + + if long_condition_that_prevents_code_fit_on_single_line + do_something_with_a_long_name(arg) + end + + if short_condition # a long comment that makes it too long if it were just a single line + do_something + end \ No newline at end of file diff --git a/config/contents/style/if_with_boolean_literal_branches.md b/config/contents/style/if_with_boolean_literal_branches.md new file mode 100644 index 00000000..f2663135 --- /dev/null +++ b/config/contents/style/if_with_boolean_literal_branches.md @@ -0,0 +1,23 @@ +This cop checks for redundant `if` with boolean literal branches. +It checks only conditions to return boolean value (`true` or `false`) for safe detection. +The conditions to be checked are comparison methods, predicate methods, and double negative. +However, auto-correction is unsafe because there is no guarantee that all predicate methods +will return boolean value. Those methods can be allowed with `AllowedMethods` config. + +### Example: + # bad + if foo == bar + true + else + false + end + + # bad + foo == bar ? true : false + + # good + foo == bar + +### Example: AllowedMethods: ['nonzero?'] + # good + num.nonzero? ? true : false diff --git a/config/contents/style/in_pattern_then.md b/config/contents/style/in_pattern_then.md new file mode 100644 index 00000000..ce440444 --- /dev/null +++ b/config/contents/style/in_pattern_then.md @@ -0,0 +1,14 @@ +This cop checks for `in;` uses in `case` expressions. + +### Example: + # bad + case expression + in pattern_a; foo + in pattern_b; bar + end + + # good + case expression + in pattern_a then foo + in pattern_b then bar + end diff --git a/config/contents/style/infinite_loop.md b/config/contents/style/infinite_loop.md index 22357ac2..94e870c4 100644 --- a/config/contents/style/infinite_loop.md +++ b/config/contents/style/infinite_loop.md @@ -1,5 +1,9 @@ Use `Kernel#loop` for infinite loops. +This cop is marked as unsafe as the rule does not necessarily +apply if the body might raise a `StopIteration` exception; contrary to +other infinite loops, `Kernel#loop` silently rescues that and returns `nil`. + ### Example: # bad while true diff --git a/config/contents/style/inverse_methods.md b/config/contents/style/inverse_methods.md index db8fa0bc..26fe29b3 100644 --- a/config/contents/style/inverse_methods.md +++ b/config/contents/style/inverse_methods.md @@ -21,4 +21,10 @@ of the block that is passed to the method should be defined in foo.any? { |f| f.even? } foo != bar foo == bar - !!('foo' =~ /^\w+$/) \ No newline at end of file + !!('foo' =~ /^\w+$/) + !(foo.class < Numeric) # Checking class hierarchy is allowed + # Blocks with guard clauses are ignored: + foo.select do |f| + next if f.zero? + f != 1 + end \ No newline at end of file diff --git a/config/contents/style/ip_addresses.md b/config/contents/style/ip_addresses.md new file mode 100644 index 00000000..721fddb2 --- /dev/null +++ b/config/contents/style/ip_addresses.md @@ -0,0 +1,13 @@ +This cop checks for hardcoded IP addresses, which can make code +brittle. IP addresses are likely to need to be changed when code +is deployed to a different server or environment, which may break +a deployment if forgotten. Prefer setting IP addresses in ENV or +other configuration. + +### Example: + + # bad + ip_address = '127.59.241.29' + + # good + ip_address = ENV['DEPLOYMENT_IP_ADDRESS'] \ No newline at end of file diff --git a/config/contents/style/keyword_parameters_order.md b/config/contents/style/keyword_parameters_order.md new file mode 100644 index 00000000..c3bf6524 --- /dev/null +++ b/config/contents/style/keyword_parameters_order.md @@ -0,0 +1,27 @@ +This cop enforces that optional keyword parameters are placed at the +end of the parameters list. + +This improves readability, because when looking through the source, +it is expected to find required parameters at the beginning of parameters list +and optional parameters at the end. + +### Example: + # bad + def some_method(first: false, second:, third: 10) + # body omitted + end + + # good + def some_method(second:, first: false, third: 10) + # body omitted + end + + # bad + do_something do |first: false, second:, third: 10| + # body omitted + end + + # good + do_something do |second:, first: false, third: 10| + # body omitted + end diff --git a/config/contents/style/method_call_with_args_parentheses.md b/config/contents/style/method_call_with_args_parentheses.md index c156ac6f..88e95944 100644 --- a/config/contents/style/method_call_with_args_parentheses.md +++ b/config/contents/style/method_call_with_args_parentheses.md @@ -1,8 +1,48 @@ -This cop checks presence of parentheses in method calls containing -parameters. By default, macro methods are ignored. Additional methods -can be added to the `IgnoredMethods` list. +This cop enforces the presence (default) or absence of parentheses in +method calls containing parameters. -### Example: +In the default style (require_parentheses), macro methods are ignored. +Additional methods can be added to the `IgnoredMethods` +or `IgnoredPatterns` list. These options are +valid only in the default style. Macros can be included by +either setting `IgnoreMacros` to false or adding specific macros to +the `IncludedMacros` list. + +Precedence of options is all follows: + +1. `IgnoredMethods` +2. `IgnoredPatterns` +3. `IncludedMacros` + +eg. If a method is listed in both +`IncludedMacros` and `IgnoredMethods`, then the latter takes +precedence (that is, the method is ignored). + +In the alternative style (omit_parentheses), there are three additional +options. + +1. `AllowParenthesesInChaining` is `false` by default. Setting it to + `true` allows the presence of parentheses in the last call during + method chaining. + +2. `AllowParenthesesInMultilineCall` is `false` by default. Setting it + to `true` allows the presence of parentheses in multi-line method + calls. + +3. `AllowParenthesesInCamelCaseMethod` is `false` by default. This + allows the presence of parentheses when calling a method whose name + begins with a capital letter and which has no arguments. Setting it + to `true` allows the presence of parentheses in such a method call + even with arguments. + +NOTE: Parentheses are still allowed in cases where omitting them +results in ambiguous or syntactically incorrect code. For example, +parentheses are required around a method with arguments when inside an +endless method definition introduced in Ruby 3.0. Parentheses are also +allowed when forwarding arguments with the triple-dot syntax introduced +in Ruby 2.7 as omitting them starts an endless range. + +### Example: EnforcedStyle: require_parentheses (default) # bad array.delete e @@ -21,16 +61,127 @@ can be added to the `IgnoredMethods` list. # okay with `puts` listed in `IgnoredMethods` puts 'test' - # IgnoreMacros: true (default) + # okay with `^assert` listed in `IgnoredPatterns` + assert_equal 'test', x + +### Example: EnforcedStyle: omit_parentheses + + # bad + array.delete(e) + + # good + array.delete e + + # bad + foo.enforce(strict: true) + + # good + foo.enforce strict: true + + # good + # Allows parens for calls that won't produce valid Ruby or be ambiguous. + model.validate strict(true) + + # good + # Allows parens for calls that won't produce valid Ruby or be ambiguous. + yield path, File.basename(path) + + # good + # Operators methods calls with parens + array&.[](index) + + # good + # Operators methods without parens, if you prefer + array.[] index + + # good + # Operators methods calls with parens + array&.[](index) + + # good + # Operators methods without parens, if you prefer + array.[] index + +### Example: IgnoreMacros: true (default) # good class Foo bar :baz end - # IgnoreMacros: false +### Example: IgnoreMacros: false # bad class Foo bar :baz - end \ No newline at end of file + end + +### Example: AllowParenthesesInMultilineCall: false (default) + + # bad + foo.enforce( + strict: true + ) + + # good + foo.enforce \ + strict: true + +### Example: AllowParenthesesInMultilineCall: true + + # good + foo.enforce( + strict: true + ) + + # good + foo.enforce \ + strict: true + +### Example: AllowParenthesesInChaining: false (default) + + # bad + foo().bar(1) + + # good + foo().bar 1 + +### Example: AllowParenthesesInChaining: true + + # good + foo().bar(1) + + # good + foo().bar 1 + +### Example: AllowParenthesesInCamelCaseMethod: false (default) + + # bad + Array(1) + + # good + Array 1 + +### Example: AllowParenthesesInCamelCaseMethod: true + + # good + Array(1) + + # good + Array 1 + +### Example: AllowParenthesesInStringInterpolation: false (default) + + # bad + "#{t('this.is.bad')}" + + # good + "#{t 'this.is.better'}" + +### Example: AllowParenthesesInStringInterpolation: true + + # good + "#{t('this.is.good')}" + + # good + "#{t 'this.is.also.good'}" \ No newline at end of file diff --git a/config/contents/style/method_called_on_do_end_block.md b/config/contents/style/method_called_on_do_end_block.md index 6a254906..378b2436 100644 --- a/config/contents/style/method_called_on_do_end_block.md +++ b/config/contents/style/method_called_on_do_end_block.md @@ -3,7 +3,16 @@ this check is that it's easy to miss the call tacked on to the block when reading code. ### Example: - + # bad a do b - end.c \ No newline at end of file + end.c + + # good + a { b }.c + + # good + foo = a do + b + end + foo.c \ No newline at end of file diff --git a/config/contents/style/method_def_parentheses.md b/config/contents/style/method_def_parentheses.md index 28f34928..3e6b99ad 100644 --- a/config/contents/style/method_def_parentheses.md +++ b/config/contents/style/method_def_parentheses.md @@ -1,6 +1,9 @@ -This cops checks for parentheses around the arguments in method +This cop checks for parentheses around the arguments in method definitions. Both instance and class/singleton methods are checked. +This cop does not consider endless methods, since parentheses are +always required for them. + ### Example: EnforcedStyle: require_parentheses (default) # The `require_parentheses` style requires method definitions # to always use parentheses @@ -55,8 +58,8 @@ definitions. Both instance and class/singleton methods are checked. ### Example: EnforcedStyle: require_no_parentheses_except_multiline # The `require_no_parentheses_except_multiline` style prefers no - # parantheses when method definition arguments fit on single line, - # but prefers parantheses when arguments span multiple lines. + # parentheses when method definition arguments fit on single line, + # but prefers parentheses when arguments span multiple lines. # bad def bar(num1, num2) diff --git a/config/contents/style/method_missing_super.md b/config/contents/style/method_missing_super.md new file mode 100644 index 00000000..4117e35d --- /dev/null +++ b/config/contents/style/method_missing_super.md @@ -0,0 +1,15 @@ +This cop checks for the presence of `method_missing` without +falling back on `super`. + +### Example: + #bad + def method_missing(name, *args) + # ... + end + + #good + + def method_missing(name, *args) + # ... + super + end \ No newline at end of file diff --git a/config/contents/style/missing_else.md b/config/contents/style/missing_else.md index addee86b..eed12751 100644 --- a/config/contents/style/missing_else.md +++ b/config/contents/style/missing_else.md @@ -1,25 +1,90 @@ Checks for `if` expressions that do not have an `else` branch. -SupportedStyles -if -### Example: +Supported styles are: if, case, both. + +### Example: EnforcedStyle: if + # warn when an `if` expression is missing an `else` branch. + # bad if condition statement end -case -### Example: + # good + if condition + statement + else + # the content of `else` branch will be determined by Style/EmptyElse + end + + # good + case var + when condition + statement + end + + # good + case var + when condition + statement + else + # the content of `else` branch will be determined by Style/EmptyElse + end + +### Example: EnforcedStyle: case + # warn when a `case` expression is missing an `else` branch. + # bad case var when condition statement end -### Example: + # good + case var + when condition + statement + else + # the content of `else` branch will be determined by Style/EmptyElse + end + # good if condition statement + end + + # good + if condition + statement + else + # the content of `else` branch will be determined by Style/EmptyElse + end + +### Example: EnforcedStyle: both (default) + # warn when an `if` or `case` expression is missing an `else` branch. + + # bad + if condition + statement + end + + # bad + case var + when condition + statement + end + + # good + if condition + statement + else + # the content of `else` branch will be determined by Style/EmptyElse + end + + # good + case var + when condition + statement else - # the content of the else branch will be determined by Style/EmptyElse + # the content of `else` branch will be determined by Style/EmptyElse end \ No newline at end of file diff --git a/config/contents/style/missing_respond_to_missing.md b/config/contents/style/missing_respond_to_missing.md new file mode 100644 index 00000000..4c6a83fa --- /dev/null +++ b/config/contents/style/missing_respond_to_missing.md @@ -0,0 +1,17 @@ +This cop checks for the presence of `method_missing` without also +defining `respond_to_missing?`. + +### Example: + #bad + def method_missing(name, *args) + # ... + end + + #good + def respond_to_missing?(name, include_private) + # ... + end + + def method_missing(name, *args) + # ... + end diff --git a/config/contents/style/mixin_usage.md b/config/contents/style/mixin_usage.md index 06c9d126..dc106427 100644 --- a/config/contents/style/mixin_usage.md +++ b/config/contents/style/mixin_usage.md @@ -1,8 +1,6 @@ -This cop checks that `include`, `extend` and `prepend` exists at -the top level. -Using these at the top level affects the behavior of `Object`. -There will not be using `include`, `extend` and `prepend` at -the top level. Let's use it inside `class` or `module`. +This cop checks that `include`, `extend` and `prepend` statements appear +inside classes and modules, not at the top level, so as to not affect +the behavior of `Object`. ### Example: # bad diff --git a/config/contents/style/module_function.md b/config/contents/style/module_function.md index dc05a05c..998c60b8 100644 --- a/config/contents/style/module_function.md +++ b/config/contents/style/module_function.md @@ -1,7 +1,7 @@ -This cops checks for use of `extend self` or `module_function` in a +This cop checks for use of `extend self` or `module_function` in a module. -Supported styles are: module_function, extend_self. +Supported styles are: module_function, extend_self, forbidden. ### Example: EnforcedStyle: module_function (default) # bad @@ -16,6 +16,18 @@ Supported styles are: module_function, extend_self. # ... end +In case there are private methods, the cop won't be activated. +Otherwise, it forces to change the flow of the default code. + +### Example: EnforcedStyle: module_function (default) + # good + module Test + extend self + # ... + private + # ... + end + ### Example: EnforcedStyle: extend_self # bad module Test @@ -29,5 +41,28 @@ Supported styles are: module_function, extend_self. # ... end -These offenses are not auto-corrected since there are different +The option `forbidden` prohibits the usage of both styles. + +### Example: EnforcedStyle: forbidden + # bad + module Test + module_function + # ... + end + + # bad + module Test + extend self + # ... + end + + # bad + module Test + extend self + # ... + private + # ... + end + +These offenses are not safe to auto-correct since there are different implications to each approach. \ No newline at end of file diff --git a/config/contents/style/multiline_block_chain.md b/config/contents/style/multiline_block_chain.md index d0eb6576..a84faa57 100644 --- a/config/contents/style/multiline_block_chain.md +++ b/config/contents/style/multiline_block_chain.md @@ -3,8 +3,17 @@ multiple lines. ### Example: - Thread.list.find_all do |t| + # bad + Thread.list.select do |t| t.alive? end.map do |t| t.object_id + end + + # good + alive_threads = Thread.list.select do |t| + t.alive? + end + alive_threads.map do |t| + t.object_id end \ No newline at end of file diff --git a/config/contents/style/multiline_in_pattern_then.md b/config/contents/style/multiline_in_pattern_then.md new file mode 100644 index 00000000..ecdd3e26 --- /dev/null +++ b/config/contents/style/multiline_in_pattern_then.md @@ -0,0 +1,23 @@ +This cop checks uses of the `then` keyword in multi-line `in` statement. + +### Example: + # bad + case expression + in pattern then + end + + # good + case expression + in pattern + end + + # good + case expression + in pattern then do_something + end + + # good + case expression + in pattern then do_something(arg1, + arg2) + end diff --git a/config/contents/style/multiline_method_signature.md b/config/contents/style/multiline_method_signature.md new file mode 100644 index 00000000..3ed7ccfc --- /dev/null +++ b/config/contents/style/multiline_method_signature.md @@ -0,0 +1,14 @@ +This cop checks for method signatures that span multiple lines. + +### Example: + + # good + + def foo(bar, baz) + end + + # bad + + def foo(bar, + baz) + end diff --git a/config/contents/style/multiline_ternary_operator.md b/config/contents/style/multiline_ternary_operator.md index a7873300..e29f7e2d 100644 --- a/config/contents/style/multiline_ternary_operator.md +++ b/config/contents/style/multiline_ternary_operator.md @@ -1,5 +1,8 @@ This cop checks for multi-line ternary op expressions. +NOTE: `return if ... else ... end` is syntax error. If `return` is used before +multiline ternary operator expression, it cannot be auto-corrected. + ### Example: # bad a = cond ? @@ -12,9 +15,8 @@ This cop checks for multi-line ternary op expressions. # good a = cond ? b : c - a = - if cond - b - else - c - end \ No newline at end of file + a = if cond + b + else + c + end \ No newline at end of file diff --git a/config/contents/style/multiline_when_then.md b/config/contents/style/multiline_when_then.md new file mode 100644 index 00000000..e1cd9e35 --- /dev/null +++ b/config/contents/style/multiline_when_then.md @@ -0,0 +1,24 @@ +This cop checks uses of the `then` keyword +in multi-line when statements. + +### Example: + # bad + case foo + when bar then + end + + # good + case foo + when bar + end + + # good + case foo + when bar then do_something + end + + # good + case foo + when bar then do_something(arg1, + arg2) + end diff --git a/config/contents/style/multiple_comparison.md b/config/contents/style/multiple_comparison.md index d8ee73d5..09617c2c 100644 --- a/config/contents/style/multiple_comparison.md +++ b/config/contents/style/multiple_comparison.md @@ -1,5 +1,8 @@ This cop checks against comparing a variable with multiple items, where -`Array#include?` could be used instead to avoid code repetition. +`Array#include?`, `Set#include?` or a `case` could be used instead +to avoid code repetition. +It accepts comparisons of multiple method calls to avoid unnecessary method calls +by default. It can be configured by `AllowMethodComparison` option. ### Example: # bad @@ -8,4 +11,27 @@ This cop checks against comparing a variable with multiple items, where # good a = 'a' - foo if ['a', 'b', 'c'].include?(a) \ No newline at end of file + foo if ['a', 'b', 'c'].include?(a) + + VALUES = Set['a', 'b', 'c'].freeze + # elsewhere... + foo if VALUES.include?(a) + + case foo + when 'a', 'b', 'c' then foo + # ... + end + + # accepted (but consider `case` as above) + foo if a == b.lightweight || a == b.heavyweight + +### Example: AllowMethodComparison: true (default) + # good + foo if a == b.lightweight || a == b.heavyweight + +### Example: AllowMethodComparison: false + # bad + foo if a == b.lightweight || a == b.heavyweight + + # good + foo if [b.lightweight, b.heavyweight].include?(a) \ No newline at end of file diff --git a/config/contents/style/mutable_constant.md b/config/contents/style/mutable_constant.md index 074bc292..bb4e0b88 100644 --- a/config/contents/style/mutable_constant.md +++ b/config/contents/style/mutable_constant.md @@ -1,9 +1,49 @@ This cop checks whether some constant value isn't a mutable literal (e.g. array or hash). -### Example: +Strict mode can be used to freeze all constants, rather than +just literals. +Strict mode is considered an experimental feature. It has not been +updated with an exhaustive list of all methods that will produce +frozen objects so there is a decent chance of getting some false +positives. Luckily, there is no harm in freezing an already +frozen object. + +NOTE: Regexp and Range literals are frozen objects since Ruby 3.0. + +### Example: EnforcedStyle: literals (default) # bad CONST = [1, 2, 3] # good - CONST = [1, 2, 3].freeze \ No newline at end of file + CONST = [1, 2, 3].freeze + + # good + CONST = <<~TESTING.freeze + This is a heredoc + TESTING + + # good + CONST = Something.new + + +### Example: EnforcedStyle: strict + # bad + CONST = Something.new + + # bad + CONST = Struct.new do + def foo + puts 1 + end + end + + # good + CONST = Something.new.freeze + + # good + CONST = Struct.new do + def foo + puts 1 + end + end.freeze \ No newline at end of file diff --git a/config/contents/style/negated_if.md b/config/contents/style/negated_if.md index 11afb6b1..94ba0693 100644 --- a/config/contents/style/negated_if.md +++ b/config/contents/style/negated_if.md @@ -1,9 +1,9 @@ Checks for uses of if with a negated condition. Only ifs without else are considered. There are three different styles: - - both - - prefix - - postfix +* both +* prefix +* postfix ### Example: EnforcedStyle: both (default) # enforces `unless` for `prefix` and `postfix` conditionals diff --git a/config/contents/style/negated_if_else_condition.md b/config/contents/style/negated_if_else_condition.md new file mode 100644 index 00000000..0a9acbf9 --- /dev/null +++ b/config/contents/style/negated_if_else_condition.md @@ -0,0 +1,23 @@ +This cop checks for uses of `if-else` and ternary operators with a negated condition +which can be simplified by inverting condition and swapping branches. + +### Example: + # bad + if !x + do_something + else + do_something_else + end + + # good + if x + do_something_else + else + do_something + end + + # bad + !x ? do_something : do_something_else + + # good + x ? do_something_else : do_something diff --git a/config/contents/style/negated_unless.md b/config/contents/style/negated_unless.md new file mode 100644 index 00000000..4fb61d95 --- /dev/null +++ b/config/contents/style/negated_unless.md @@ -0,0 +1,55 @@ +Checks for uses of unless with a negated condition. Only unless +without else are considered. There are three different styles: + +* both +* prefix +* postfix + +### Example: EnforcedStyle: both (default) + # enforces `if` for `prefix` and `postfix` conditionals + + # bad + unless !foo + bar + end + + # good + if foo + bar + end + + # bad + bar unless !foo + + # good + bar if foo + +### Example: EnforcedStyle: prefix + # enforces `if` for just `prefix` conditionals + + # bad + unless !foo + bar + end + + # good + if foo + bar + end + + # good + bar unless !foo + +### Example: EnforcedStyle: postfix + # enforces `if` for just `postfix` conditionals + + # bad + bar unless !foo + + # good + bar if foo + + # good + unless !foo + bar + end \ No newline at end of file diff --git a/config/contents/style/negated_while.md b/config/contents/style/negated_while.md new file mode 100644 index 00000000..a4e96768 --- /dev/null +++ b/config/contents/style/negated_while.md @@ -0,0 +1,19 @@ +Checks for uses of while with a negated condition. + +### Example: + # bad + while !foo + bar + end + + # good + until foo + bar + end + + # bad + bar until !foo + + # good + bar while foo + bar while !foo && baz \ No newline at end of file diff --git a/config/contents/style/nested_parenthesized_calls.md b/config/contents/style/nested_parenthesized_calls.md index 1d7daff4..7ceee98c 100644 --- a/config/contents/style/nested_parenthesized_calls.md +++ b/config/contents/style/nested_parenthesized_calls.md @@ -3,7 +3,7 @@ of a parenthesized method call. ### Example: # good - method1(method2(arg), method3(arg)) + method1(method2(arg)) # bad - method1(method2 arg, method3, arg) \ No newline at end of file + method1(method2 arg) \ No newline at end of file diff --git a/config/contents/style/nested_ternary_operator.md b/config/contents/style/nested_ternary_operator.md new file mode 100644 index 00000000..18dd12cb --- /dev/null +++ b/config/contents/style/nested_ternary_operator.md @@ -0,0 +1,12 @@ +This cop checks for nested ternary op expressions. + +### Example: + # bad + a ? (b ? b1 : b2) : a2 + + # good + if a + b ? b1 : b2 + else + a2 + end \ No newline at end of file diff --git a/config/contents/style/next.md b/config/contents/style/next.md index add4e1d1..c2437bf0 100644 --- a/config/contents/style/next.md +++ b/config/contents/style/next.md @@ -15,18 +15,18 @@ Use `next` to skip iteration instead of a condition at the end. end # good - [1, 2].each do |o| - puts o unless o == 1 + [1, 2].each do |a| + puts a if a == 1 end ### Example: EnforcedStyle: always # With `always` all conditions at the end of an iteration needs to be # replaced by next - with `skip_modifier_ifs` the modifier if like - # this one are ignored: `[1, 2].each { |a| return 'yes' if a == 1 }` + # this one are ignored: `[1, 2].each { |a| puts a if a == 1 }` # bad - [1, 2].each do |o| - puts o unless o == 1 + [1, 2].each do |a| + puts a if a == 1 end # bad diff --git a/config/contents/style/nil_comparison.md b/config/contents/style/nil_comparison.md index 7527dbba..1c25ffe3 100644 --- a/config/contents/style/nil_comparison.md +++ b/config/contents/style/nil_comparison.md @@ -1,6 +1,9 @@ -This cop checks for comparison of something with nil using ==. +This cop checks for comparison of something with nil using `==` and +`nil?`. -### Example: +Supported styles are: predicate, comparison. + +### Example: EnforcedStyle: predicate (default) # bad if x == nil @@ -8,4 +11,14 @@ This cop checks for comparison of something with nil using ==. # good if x.nil? - end \ No newline at end of file + end + +### Example: EnforcedStyle: comparison + + # bad + if x.nil? + end + + # good + if x == nil + end diff --git a/config/contents/style/nil_lambda.md b/config/contents/style/nil_lambda.md new file mode 100644 index 00000000..3441d980 --- /dev/null +++ b/config/contents/style/nil_lambda.md @@ -0,0 +1,28 @@ +This cop checks for lambdas and procs that always return nil, +which can be replaced with an empty lambda or proc instead. + +### Example: + # bad + -> { nil } + + lambda do + next nil + end + + proc { nil } + + Proc.new do + break nil + end + + # good + -> {} + + lambda do + end + + -> (x) { nil if x } + + proc {} + + Proc.new { nil if x } diff --git a/config/contents/style/non_nil_check.md b/config/contents/style/non_nil_check.md index 0df90100..1d6fc710 100644 --- a/config/contents/style/non_nil_check.md +++ b/config/contents/style/non_nil_check.md @@ -1,23 +1,37 @@ This cop checks for non-nil checks, which are usually redundant. -### Example: +With `IncludeSemanticChanges` set to `false` by default, this cop +does not report offenses for `!x.nil?` and does no changes that might +change behavior. +Also `IncludeSemanticChanges` set to `false` with `EnforcedStyle: comparison` of +`Style/NilComparison` cop, this cop does not report offenses for `x != nil` and +does no changes to `!x.nil?` style. + +With `IncludeSemanticChanges` set to `true`, this cop reports offenses +for `!x.nil?` and autocorrects that and `x != nil` to solely `x`, which +is *usually* OK, but might change behavior. +### Example: # bad if x != nil end - # good (when not allowing semantic changes) - # bad (when allowing semantic changes) - if !x.nil? - end - - # good (when allowing semantic changes) + # good if x end -Non-nil checks are allowed if they are the final nodes of predicate. - + # Non-nil checks are allowed if they are the final nodes of predicate. # good def signed_in? !current_user.nil? - end \ No newline at end of file + end + +### Example: IncludeSemanticChanges: false (default) + # good + if !x.nil? + end + +### Example: IncludeSemanticChanges: true + # bad + if !x.nil? + end diff --git a/config/contents/style/numeric_literal_prefix.md b/config/contents/style/numeric_literal_prefix.md index 27d6a694..3834a971 100644 --- a/config/contents/style/numeric_literal_prefix.md +++ b/config/contents/style/numeric_literal_prefix.md @@ -1,7 +1,30 @@ -This cop checks for octal, hex, binary and decimal literals using +This cop checks for octal, hex, binary, and decimal literals using uppercase prefixes and corrects them to lowercase prefix or no prefix (in case of decimals). -eg. for octal use `0o` instead of `0` or `0O`. -Can be configured to use `0` only for octal literals using -`EnforcedOctalStyle` => `zero_only` \ No newline at end of file +### Example: EnforcedOctalStyle: zero_with_o (default) + # bad - missing octal prefix + num = 01234 + + # bad - uppercase prefix + num = 0O1234 + num = 0X12AB + num = 0B10101 + + # bad - redundant decimal prefix + num = 0D1234 + num = 0d1234 + + # good + num = 0o1234 + num = 0x12AB + num = 0b10101 + num = 1234 + +### Example: EnforcedOctalStyle: zero_only + # bad + num = 0o1234 + num = 0O1234 + + # good + num = 01234 \ No newline at end of file diff --git a/config/contents/style/numeric_literals.md b/config/contents/style/numeric_literals.md index 09edbaa9..26140263 100644 --- a/config/contents/style/numeric_literals.md +++ b/config/contents/style/numeric_literals.md @@ -4,16 +4,20 @@ of digits in them. ### Example: # bad - 1000000 1_00_000 1_0000 # good - 1_000_000 1000 - # good unless Strict is set +### Example: Strict: false (default) + # good + 10_000_00 # typical representation of $10,000 in cents + +### Example: Strict: true + + # bad 10_000_00 # typical representation of $10,000 in cents diff --git a/config/contents/style/numeric_predicate.md b/config/contents/style/numeric_predicate.md index 506c012c..5559e095 100644 --- a/config/contents/style/numeric_predicate.md +++ b/config/contents/style/numeric_predicate.md @@ -3,13 +3,13 @@ This cop checks for usage of comparison operators (`==`, These can be replaced by their respective predicate methods. The cop can also be configured to do the reverse. -The cop disregards `#nonzero?` as it its value is truthy or falsey, +The cop disregards `#nonzero?` as its value is truthy or falsey, but not `true` and `false`, and thus not always interchangeable with `!= 0`. The cop ignores comparisons to global variables, since they are often populated with objects which can be compared with integers, but are -not themselves `Interger` polymorphic. +not themselves `Integer` polymorphic. ### Example: EnforcedStyle: predicate (default) # bad diff --git a/config/contents/style/one_line_conditional.md b/config/contents/style/one_line_conditional.md new file mode 100644 index 00000000..ebe52f1a --- /dev/null +++ b/config/contents/style/one_line_conditional.md @@ -0,0 +1,27 @@ +Checks for uses of if/then/else/end constructs on a single line. +AlwaysCorrectToMultiline config option can be set to true to auto-convert all offenses to +multi-line constructs. When AlwaysCorrectToMultiline is false (default case) the +auto-correct will first try converting them to ternary operators. + +### Example: + # bad + if foo then bar else baz end + + # bad + unless foo then baz else bar end + + # good + foo ? bar : baz + + # good + bar if foo + + # good + if foo then bar end + + # good + if foo + bar + else + baz + end \ No newline at end of file diff --git a/config/contents/style/optional_arguments.md b/config/contents/style/optional_arguments.md index 66c48bec..c54754b3 100644 --- a/config/contents/style/optional_arguments.md +++ b/config/contents/style/optional_arguments.md @@ -1,5 +1,5 @@ This cop checks for optional arguments to methods -that do not come at the end of the argument list +that do not come at the end of the argument list. ### Example: # bad diff --git a/config/contents/style/optional_boolean_parameter.md b/config/contents/style/optional_boolean_parameter.md new file mode 100644 index 00000000..cd3be011 --- /dev/null +++ b/config/contents/style/optional_boolean_parameter.md @@ -0,0 +1,26 @@ +This cop checks for places where keyword arguments can be used instead of +boolean arguments when defining methods. `respond_to_missing?` method is allowed by default. +These are customizable with `AllowedMethods` option. + +### Example: + # bad + def some_method(bar = false) + puts bar + end + + # bad - common hack before keyword args were introduced + def some_method(options = {}) + bar = options.fetch(:bar, false) + puts bar + end + + # good + def some_method(bar: false) + puts bar + end + +### Example: AllowedMethods: ['some_method'] + # good + def some_method(bar = false) + puts bar + end diff --git a/config/contents/style/parentheses_around_condition.md b/config/contents/style/parentheses_around_condition.md index e8fa7e83..93fda0db 100644 --- a/config/contents/style/parentheses_around_condition.md +++ b/config/contents/style/parentheses_around_condition.md @@ -1,6 +1,11 @@ This cop checks for the presence of superfluous parentheses around the condition of if/unless/while/until. +`AllowSafeAssignment` option for safe assignment. +By safe assignment we mean putting parentheses around +an assignment to indicate "I know I'm using an assignment +as a condition. It's not a mistake." + ### Example: # bad x += 1 while (x < 10) @@ -16,4 +21,29 @@ condition of if/unless/while/until. if x > 10 elsif x < 3 - end \ No newline at end of file + end + +### Example: AllowSafeAssignment: true (default) + # good + foo unless (bar = baz) + +### Example: AllowSafeAssignment: false + # bad + foo unless (bar = baz) + +### Example: AllowInMultilineConditions: false (default) + # bad + if (x > 10 && + y > 10) + end + + # good + if x > 10 && + y > 10 + end + +### Example: AllowInMultilineConditions: true + # good + if (x > 10 && + y > 10) + end diff --git a/config/contents/style/perl_backrefs.md b/config/contents/style/perl_backrefs.md index df7da493..3bde9bd6 100644 --- a/config/contents/style/perl_backrefs.md +++ b/config/contents/style/perl_backrefs.md @@ -1,5 +1,6 @@ This cop looks for uses of Perl-style regexp match -backreferences like $1, $2, etc. +backreferences and their English versions like +$1, $2, $&, &+, $MATCH, $PREMATCH, etc. ### Example: # bad diff --git a/config/contents/style/proc.md b/config/contents/style/proc.md index cfe47225..82300e34 100644 --- a/config/contents/style/proc.md +++ b/config/contents/style/proc.md @@ -1,4 +1,4 @@ -This cops checks for uses of Proc.new where Kernel#proc +This cop checks for uses of Proc.new where Kernel#proc would be more appropriate. ### Example: diff --git a/config/contents/style/quoted_symbols.md b/config/contents/style/quoted_symbols.md new file mode 100644 index 00000000..d4e428c4 --- /dev/null +++ b/config/contents/style/quoted_symbols.md @@ -0,0 +1,26 @@ +Checks if the quotes used for quoted symbols match the configured defaults. +By default uses the same configuration as `Style/StringLiterals`. + +String interpolation is always kept in double quotes. + +Note: `Lint/SymbolConversion` can be used in parallel to ensure that symbols +are not quoted that don't need to be. This cop is for configuring the quoting +style to use for symbols that require quotes. + +### Example: EnforcedStyle: same_as_string_literals (default) / single_quotes + # bad + :"abc-def" + + # good + :'abc-def' + :"#{str}" + :"a\'b" + +### Example: EnforcedStyle: double_quotes + # bad + :'abc-def' + + # good + :"abc-def" + :"#{str}" + :"a\'b" \ No newline at end of file diff --git a/config/contents/style/raise_args.md b/config/contents/style/raise_args.md index ba5e95dc..ae2a2d04 100644 --- a/config/contents/style/raise_args.md +++ b/config/contents/style/raise_args.md @@ -8,22 +8,31 @@ The exploded style works identically, but with the addition that it will also suggest constructing error objects when the exception is passed multiple arguments. +The exploded style has an `AllowedCompactTypes` configuration +option that takes an Array of exception name Strings. + ### Example: EnforcedStyle: exploded (default) # bad - raise StandardError.new("message") + raise StandardError.new('message') # good - raise StandardError, "message" - fail "message" + raise StandardError, 'message' + fail 'message' + raise MyCustomError raise MyCustomError.new(arg1, arg2, arg3) raise MyKwArgError.new(key1: val1, key2: val2) + # With `AllowedCompactTypes` set to ['MyWrappedError'] + raise MyWrappedError.new(obj) + raise MyWrappedError.new(obj), 'message' + ### Example: EnforcedStyle: compact # bad - raise StandardError, "message" + raise StandardError, 'message' raise RuntimeError, arg1, arg2, arg3 # good - raise StandardError.new("message") + raise StandardError.new('message') + raise MyCustomError raise MyCustomError.new(arg1, arg2, arg3) - fail "message" \ No newline at end of file + fail 'message' \ No newline at end of file diff --git a/config/contents/style/redundant_argument.md b/config/contents/style/redundant_argument.md new file mode 100644 index 00000000..2b6ea011 --- /dev/null +++ b/config/contents/style/redundant_argument.md @@ -0,0 +1,37 @@ +This cop checks for a redundant argument passed to certain methods. + +Limitations: + +1. This cop matches for method names only and hence cannot tell apart + methods with same name in different classes. +2. This cop is limited to methods with single parameter. +3. This cop is unsafe if certain special global variables (e.g. `$;`, `$/`) are set. + That depends on the nature of the target methods, of course. + +Method names and their redundant arguments can be configured like this: + +Methods: + join: '' + split: ' ' + chomp: "\n" + chomp!: "\n" + foo: 2 + +### Example: + # bad + array.join('') + [1, 2, 3].join("") + string.split(" ") + "first\nsecond".split(" ") + string.chomp("\n") + string.chomp!("\n") + A.foo(2) + + # good + array.join + [1, 2, 3].join + string.split + "first second".split + string.chomp + string.chomp! + A.foo \ No newline at end of file diff --git a/config/contents/style/redundant_assignment.md b/config/contents/style/redundant_assignment.md new file mode 100644 index 00000000..dcda6ace --- /dev/null +++ b/config/contents/style/redundant_assignment.md @@ -0,0 +1,33 @@ +This cop checks for redundant assignment before returning. + +### Example: + # bad + def test + x = foo + x + end + + # bad + def test + if x + z = foo + z + elsif y + z = bar + z + end + end + + # good + def test + foo + end + + # good + def test + if x + foo + elsif y + bar + end + end diff --git a/config/contents/style/redundant_begin.md b/config/contents/style/redundant_begin.md index 587c62bd..e0b49290 100644 --- a/config/contents/style/redundant_begin.md +++ b/config/contents/style/redundant_begin.md @@ -4,6 +4,7 @@ Currently it checks for code like this: ### Example: + # bad def redundant begin ala @@ -13,9 +14,45 @@ Currently it checks for code like this: end end + # good def preferred ala bala rescue StandardError => e something + end + + # bad + begin + do_something + end + + # good + do_something + + # bad + do_something do + begin + something + rescue => ex + anything + end + end + + # good + # In Ruby 2.5 or later, you can omit `begin` in `do-end` block. + do_something do + something + rescue => ex + anything + end + + # good + # Stabby lambdas don't support implicit `begin` in `do-end` blocks. + -> do + begin + foo + rescue Bar + baz + end end \ No newline at end of file diff --git a/config/contents/style/redundant_capital_w.md b/config/contents/style/redundant_capital_w.md new file mode 100644 index 00000000..d3e3b89b --- /dev/null +++ b/config/contents/style/redundant_capital_w.md @@ -0,0 +1,11 @@ +This cop checks for usage of the %W() syntax when %w() would do. + +### Example: + # bad + %W(cat dog pig) + %W[door wall floor] + + # good + %w/swim run bike/ + %w[shirt pants shoes] + %W(apple #{fruit} grape) \ No newline at end of file diff --git a/config/contents/style/redundant_condition.md b/config/contents/style/redundant_condition.md new file mode 100644 index 00000000..ad752bfa --- /dev/null +++ b/config/contents/style/redundant_condition.md @@ -0,0 +1,26 @@ +This cop checks for unnecessary conditional expressions. + +### Example: + # bad + a = b ? b : c + + # good + a = b || c + +### Example: + # bad + if b + b + else + c + end + + # good + b || c + + # good + if b + b + elsif cond + c + end diff --git a/config/contents/style/redundant_fetch_block.md b/config/contents/style/redundant_fetch_block.md new file mode 100644 index 00000000..8cad7a29 --- /dev/null +++ b/config/contents/style/redundant_fetch_block.md @@ -0,0 +1,27 @@ +This cop identifies places where `fetch(key) { value }` +can be replaced by `fetch(key, value)`. + +In such cases `fetch(key, value)` method is faster +than `fetch(key) { value }`. + +### Example: SafeForConstants: false (default) + # bad + hash.fetch(:key) { 5 } + hash.fetch(:key) { true } + hash.fetch(:key) { nil } + array.fetch(5) { :value } + ENV.fetch(:key) { 'value' } + + # good + hash.fetch(:key, 5) + hash.fetch(:key, true) + hash.fetch(:key, nil) + array.fetch(5, :value) + ENV.fetch(:key, 'value') + +### Example: SafeForConstants: true + # bad + ENV.fetch(:key) { VALUE } + + # good + ENV.fetch(:key, VALUE) diff --git a/config/contents/style/redundant_file_extension_in_require.md b/config/contents/style/redundant_file_extension_in_require.md new file mode 100644 index 00000000..06b55eeb --- /dev/null +++ b/config/contents/style/redundant_file_extension_in_require.md @@ -0,0 +1,20 @@ +This cop checks for the presence of superfluous `.rb` extension in +the filename provided to `require` and `require_relative`. + +Note: If the extension is omitted, Ruby tries adding '.rb', '.so', + and so on to the name until found. If the file named cannot be found, + a `LoadError` will be raised. + There is an edge case where `foo.so` file is loaded instead of a `LoadError` + if `foo.so` file exists when `require 'foo.rb'` will be changed to `require 'foo'`, + but that seems harmless. + +### Example: + # bad + require 'foo.rb' + require_relative '../foo.rb' + + # good + require 'foo' + require 'foo.so' + require_relative '../foo' + require_relative '../foo.so' diff --git a/config/contents/style/redundant_freeze.md b/config/contents/style/redundant_freeze.md index e9b446c0..88425518 100644 --- a/config/contents/style/redundant_freeze.md +++ b/config/contents/style/redundant_freeze.md @@ -1,4 +1,6 @@ -This cop check for uses of Object#freeze on immutable objects. +This cop check for uses of `Object#freeze` on immutable objects. + +NOTE: Regexp and Range literals are frozen objects since Ruby 3.0. ### Example: # bad diff --git a/config/contents/style/redundant_interpolation.md b/config/contents/style/redundant_interpolation.md new file mode 100644 index 00000000..34d5078d --- /dev/null +++ b/config/contents/style/redundant_interpolation.md @@ -0,0 +1,12 @@ +This cop checks for strings that are just an interpolated expression. + +### Example: + + # bad + "#{@var}" + + # good + @var.to_s + + # good if @var is already a String + @var \ No newline at end of file diff --git a/config/contents/style/redundant_percent_q.md b/config/contents/style/redundant_percent_q.md new file mode 100644 index 00000000..ff90e3dd --- /dev/null +++ b/config/contents/style/redundant_percent_q.md @@ -0,0 +1,13 @@ +This cop checks for usage of the %q/%Q syntax when '' or "" would do. + +### Example: + + # bad + name = %q(Bruce Wayne) + time = %q(8 o'clock) + question = %q("What did you say?") + + # good + name = 'Bruce Wayne' + time = "8 o'clock" + question = '"What did you say?"' diff --git a/config/contents/style/redundant_regexp_character_class.md b/config/contents/style/redundant_regexp_character_class.md new file mode 100644 index 00000000..c29395f4 --- /dev/null +++ b/config/contents/style/redundant_regexp_character_class.md @@ -0,0 +1,24 @@ +This cop checks for unnecessary single-element Regexp character classes. + +### Example: + + # bad + r = /[x]/ + + # good + r = /x/ + + # bad + r = /[\s]/ + + # good + r = /\s/ + + # bad + r = %r{/[b]} + + # good + r = %r{/b} + + # good + r = /[ab]/ \ No newline at end of file diff --git a/config/contents/style/redundant_regexp_escape.md b/config/contents/style/redundant_regexp_escape.md new file mode 100644 index 00000000..9eb79b87 --- /dev/null +++ b/config/contents/style/redundant_regexp_escape.md @@ -0,0 +1,29 @@ +This cop checks for redundant escapes inside Regexp literals. + +### Example: + # bad + %r{foo\/bar} + + # good + %r{foo/bar} + + # good + /foo\/bar/ + + # good + %r/foo\/bar/ + + # good + %r!foo\!bar! + + # bad + /a\-b/ + + # good + /a-b/ + + # bad + /[\+\-]\d/ + + # good + /[+\-]\d/ \ No newline at end of file diff --git a/config/contents/style/redundant_return.md b/config/contents/style/redundant_return.md index 44e0ac76..ebf4660a 100644 --- a/config/contents/style/redundant_return.md +++ b/config/contents/style/redundant_return.md @@ -1,11 +1,15 @@ This cop checks for redundant `return` expressions. ### Example: + # These bad cases should be extended to handle methods whose body is + # if/else or a case expression with a default branch. + # bad def test return something end + # bad def test one two @@ -13,5 +17,27 @@ This cop checks for redundant `return` expressions. return something end -It should be extended to handle methods whose body is if/else -or a case expression with a default branch. \ No newline at end of file + # good + def test + return something if something_else + end + + # good + def test + if x + elsif y + else + end + end + +### Example: AllowMultipleReturnValues: false (default) + # bad + def test + return x, y + end + +### Example: AllowMultipleReturnValues: true + # good + def test + return x, y + end diff --git a/config/contents/style/redundant_self.md b/config/contents/style/redundant_self.md index 31363caa..d8eb4b83 100644 --- a/config/contents/style/redundant_self.md +++ b/config/contents/style/redundant_self.md @@ -6,7 +6,7 @@ The usage of `self` is only needed when: presence of a method name clash with an argument or a local variable. -* Calling an attribute writer to prevent an local variable assignment. +* Calling an attribute writer to prevent a local variable assignment. Note, with using explicit self you can only send messages with public or protected scope, you cannot send private messages this way. diff --git a/config/contents/style/redundant_self_assignment.md b/config/contents/style/redundant_self_assignment.md new file mode 100644 index 00000000..83afcb41 --- /dev/null +++ b/config/contents/style/redundant_self_assignment.md @@ -0,0 +1,23 @@ +This cop checks for places where redundant assignments are made for in place +modification methods. + +This cop is marked as unsafe, because it can produce false positives for +user defined methods having one of the expected names, but not modifying +its receiver in place. + +### Example: + # bad + args = args.concat(ary) + hash = hash.merge!(other) + + # good + args.concat(foo) + args += foo + hash.merge!(other) + + # bad + self.foo = foo.concat(ary) + + # good + foo.concat(ary) + self.foo += ary diff --git a/config/contents/style/redundant_sort.md b/config/contents/style/redundant_sort.md new file mode 100644 index 00000000..06dc33b1 --- /dev/null +++ b/config/contents/style/redundant_sort.md @@ -0,0 +1,45 @@ +This cop is used to identify instances of sorting and then +taking only the first or last element. The same behavior can +be accomplished without a relatively expensive sort by using +`Enumerable#min` instead of sorting and taking the first +element and `Enumerable#max` instead of sorting and taking the +last element. Similarly, `Enumerable#min_by` and +`Enumerable#max_by` can replace `Enumerable#sort_by` calls +after which only the first or last element is used. + +### Example: + # bad + [2, 1, 3].sort.first + [2, 1, 3].sort[0] + [2, 1, 3].sort.at(0) + [2, 1, 3].sort.slice(0) + + # good + [2, 1, 3].min + + # bad + [2, 1, 3].sort.last + [2, 1, 3].sort[-1] + [2, 1, 3].sort.at(-1) + [2, 1, 3].sort.slice(-1) + + # good + [2, 1, 3].max + + # bad + arr.sort_by(&:foo).first + arr.sort_by(&:foo)[0] + arr.sort_by(&:foo).at(0) + arr.sort_by(&:foo).slice(0) + + # good + arr.min_by(&:foo) + + # bad + arr.sort_by(&:foo).last + arr.sort_by(&:foo)[-1] + arr.sort_by(&:foo).at(-1) + arr.sort_by(&:foo).slice(-1) + + # good + arr.max_by(&:foo) diff --git a/config/contents/style/redundant_sort_by.md b/config/contents/style/redundant_sort_by.md new file mode 100644 index 00000000..a03586dc --- /dev/null +++ b/config/contents/style/redundant_sort_by.md @@ -0,0 +1,12 @@ +This cop identifies places where `sort_by { ... }` can be replaced by +`sort`. + +### Example: + # bad + array.sort_by { |x| x } + array.sort_by do |var| + var + end + + # good + array.sort \ No newline at end of file diff --git a/config/contents/style/rescue_modifier.md b/config/contents/style/rescue_modifier.md index 95b4c978..ecf82258 100644 --- a/config/contents/style/rescue_modifier.md +++ b/config/contents/style/rescue_modifier.md @@ -1,12 +1,36 @@ This cop checks for uses of rescue in its modifier form. +The cop to check `rescue` in its modifier form is added for following +reasons: + +* The syntax of modifier form `rescue` can be misleading because it + might led us to believe that `rescue` handles the given exception + but it actually rescue all exceptions to return the given rescue + block. In this case, value returned by handle_error or + SomeException. + +* Modifier form `rescue` would rescue all the exceptions. It would + silently skip all exception or errors and handle the error. + Example: If `NoMethodError` is raised, modifier form rescue would + handle the exception. + ### Example: # bad some_method rescue handle_error + # bad + some_method rescue SomeException + # good begin some_method rescue handle_error + end + + # good + begin + some_method + rescue SomeException + handle_error end \ No newline at end of file diff --git a/config/contents/style/safe_navigation.md b/config/contents/style/safe_navigation.md index c52b6458..f559f158 100644 --- a/config/contents/style/safe_navigation.md +++ b/config/contents/style/safe_navigation.md @@ -1,6 +1,9 @@ This cop transforms usages of a method call safeguarded by a non `nil` check for the variable whose method is being called to -safe navigation (`&.`). +safe navigation (`&.`). If there is a method chain, all of the methods +in the chain need to be checked for safety, and all of the methods will +need to be changed to use safe navigation. We have limited the cop to +not register an offense for method chains that exceed 2 methods. Configuration option: ConvertCodeThatCanStartToReturnNil The default for this is `false`. When configured to `true`, this will @@ -13,6 +16,7 @@ returns. ### Example: # bad foo.bar if foo + foo.bar.baz if foo foo.bar(param1, param2) if foo foo.bar { |e| e.something } if foo foo.bar(param) { |e| e.something } if foo @@ -22,19 +26,34 @@ returns. foo.bar unless foo.nil? foo && foo.bar + foo && foo.bar.baz foo && foo.bar(param1, param2) foo && foo.bar { |e| e.something } foo && foo.bar(param) { |e| e.something } # good foo&.bar + foo&.bar&.baz foo&.bar(param1, param2) foo&.bar { |e| e.something } foo&.bar(param) { |e| e.something } + foo && foo.bar.baz.qux # method chain with more than 2 methods + foo && foo.nil? # method that `nil` responds to + # Method calls that do not use `.` + foo && foo < bar + foo < bar if foo + + # When checking `foo&.empty?` in a conditional, `foo` being `nil` will actually + # do the opposite of what the author intends. + foo && foo.empty? + + # This could start returning `nil` as well as the return of the method foo.nil? || foo.bar !foo || foo.bar - # Methods that `nil` will `respond_to?` should not be converted to - # use safe navigation - foo.to_i if foo \ No newline at end of file + # Methods that are used on assignment, arithmetic operation or + # comparison should not be converted to use safe navigation + foo.baz = bar if foo + foo.baz + bar if foo + foo.bar > 2 if foo \ No newline at end of file diff --git a/config/contents/style/sample.md b/config/contents/style/sample.md new file mode 100644 index 00000000..104eaec2 --- /dev/null +++ b/config/contents/style/sample.md @@ -0,0 +1,24 @@ +This cop is used to identify usages of `shuffle.first`, +`shuffle.last`, and `shuffle[]` and change them to use +`sample` instead. + +### Example: + # bad + [1, 2, 3].shuffle.first + [1, 2, 3].shuffle.first(2) + [1, 2, 3].shuffle.last + [2, 1, 3].shuffle.at(0) + [2, 1, 3].shuffle.slice(0) + [1, 2, 3].shuffle[2] + [1, 2, 3].shuffle[0, 2] # sample(2) will do the same + [1, 2, 3].shuffle[0..2] # sample(3) will do the same + [1, 2, 3].shuffle(random: Random.new).first + + # good + [1, 2, 3].shuffle + [1, 2, 3].sample + [1, 2, 3].sample(3) + [1, 2, 3].shuffle[1, 3] # sample(3) might return a longer Array + [1, 2, 3].shuffle[1..3] # sample(3) might return a longer Array + [1, 2, 3].shuffle[foo, bar] + [1, 2, 3].shuffle(random: Random.new) \ No newline at end of file diff --git a/config/contents/style/semicolon.md b/config/contents/style/semicolon.md index 5cc5e1f7..f58cd2d7 100644 --- a/config/contents/style/semicolon.md +++ b/config/contents/style/semicolon.md @@ -1,6 +1,9 @@ This cop checks for multiple expressions placed on the same line. It also checks for lines terminated with a semicolon. +This cop has `AllowAsExpressionSeparator` configuration option. +It allows `;` to separate several expressions on the same line. + ### Example: # bad foo = 1; bar = 2; @@ -9,4 +12,12 @@ It also checks for lines terminated with a semicolon. # good foo = 1 bar = 2 - baz = 3 \ No newline at end of file + baz = 3 + +### Example: AllowAsExpressionSeparator: false (default) + # bad + foo = 1; bar = 2 + +### Example: AllowAsExpressionSeparator: true + # good + foo = 1; bar = 2 \ No newline at end of file diff --git a/config/contents/style/single_argument_dig.md b/config/contents/style/single_argument_dig.md new file mode 100644 index 00000000..11424cef --- /dev/null +++ b/config/contents/style/single_argument_dig.md @@ -0,0 +1,19 @@ +Sometimes using dig method ends up with just a single +argument. In such cases, dig should be replaced with []. + +### Example: + # bad + { key: 'value' }.dig(:key) + [1, 2, 3].dig(0) + + # good + { key: 'value' }[:key] + [1, 2, 3][0] + + # good + { key1: { key2: 'value' } }.dig(:key1, :key2) + [1, [2, [3]]].dig(1, 1) + + # good + keys = %i[key1 key2] + { key1: { key2: 'value' } }.dig(*keys) diff --git a/config/contents/style/single_line_block_params.md b/config/contents/style/single_line_block_params.md index 5436ec1b..8721bd0e 100644 --- a/config/contents/style/single_line_block_params.md +++ b/config/contents/style/single_line_block_params.md @@ -2,4 +2,24 @@ This cop checks whether the block parameters of a single-line method accepting a block match the names specified via configuration. For instance one can configure `reduce`(`inject`) to use |a, e| as -parameters. \ No newline at end of file +parameters. + +Configuration option: Methods +Should be set to use this cop. Array of hashes, where each key is the +method name and value - array of argument names. + +### Example: Methods: [{reduce: %w[a b]}] + # bad + foo.reduce { |c, d| c + d } + foo.reduce { |_, _d| 1 } + + # good + foo.reduce { |a, b| a + b } + foo.reduce { |a, _b| a } + foo.reduce { |a, (id, _)| a + id } + foo.reduce { true } + + # good + foo.reduce do |c, d| + c + d + end \ No newline at end of file diff --git a/config/contents/style/single_line_methods.md b/config/contents/style/single_line_methods.md index af731aae..8a394971 100644 --- a/config/contents/style/single_line_methods.md +++ b/config/contents/style/single_line_methods.md @@ -1,6 +1,12 @@ This cop checks for single-line method definitions that contain a body. It will accept single-line methods with no body. +Endless methods added in Ruby 3.0 are also accepted by this cop. + +If `Style/EndlessMethod` is enabled with `EnforcedStyle: allow_single_line` or +`allow_always`, single-line methods will be auto-corrected to endless +methods if there is only one statement in the body. + ### Example: # bad def some_method; body end @@ -8,6 +14,14 @@ It will accept single-line methods with no body. def @table.columns; super; end # good - def no_op; end def self.resource_class=(klass); end def @table.columns; end + def some_method() = body + +### Example: AllowIfMethodIsEmpty: true (default) + # good + def no_op; end + +### Example: AllowIfMethodIsEmpty: false + # bad + def no_op; end diff --git a/config/contents/style/slicing_with_range.md b/config/contents/style/slicing_with_range.md new file mode 100644 index 00000000..35400c3c --- /dev/null +++ b/config/contents/style/slicing_with_range.md @@ -0,0 +1,9 @@ +This cop checks that arrays are sliced with endless ranges instead of +`ary[start..-1]` on Ruby 2.6+. + +### Example: + # bad + items[1..-1] + + # good + items[1..] \ No newline at end of file diff --git a/config/contents/style/sole_nested_conditional.md b/config/contents/style/sole_nested_conditional.md new file mode 100644 index 00000000..beb30eb2 --- /dev/null +++ b/config/contents/style/sole_nested_conditional.md @@ -0,0 +1,28 @@ +If the branch of a conditional consists solely of a conditional node, +its conditions can be combined with the conditions of the outer branch. +This helps to keep the nesting level from getting too deep. + +### Example: + # bad + if condition_a + if condition_b + do_something + end + end + + # good + if condition_a && condition_b + do_something + end + +### Example: AllowModifier: false (default) + # bad + if condition_a + do_something if condition_b + end + +### Example: AllowModifier: true + # good + if condition_a + do_something if condition_b + end diff --git a/config/contents/style/special_global_vars.md b/config/contents/style/special_global_vars.md new file mode 100644 index 00000000..a75cbdbb --- /dev/null +++ b/config/contents/style/special_global_vars.md @@ -0,0 +1,44 @@ + +This cop looks for uses of Perl-style global variables. + +### Example: EnforcedStyle: use_english_names (default) + # good + puts $LOAD_PATH + puts $LOADED_FEATURES + puts $PROGRAM_NAME + puts $ERROR_INFO + puts $ERROR_POSITION + puts $FIELD_SEPARATOR # or $FS + puts $OUTPUT_FIELD_SEPARATOR # or $OFS + puts $INPUT_RECORD_SEPARATOR # or $RS + puts $OUTPUT_RECORD_SEPARATOR # or $ORS + puts $INPUT_LINE_NUMBER # or $NR + puts $LAST_READ_LINE + puts $DEFAULT_OUTPUT + puts $DEFAULT_INPUT + puts $PROCESS_ID # or $PID + puts $CHILD_STATUS + puts $LAST_MATCH_INFO + puts $IGNORECASE + puts $ARGV # or ARGV + +### Example: EnforcedStyle: use_perl_names + # good + puts $: + puts $" + puts $0 + puts $! + puts $@ + puts $; + puts $, + puts $/ + puts $\ + puts $. + puts $_ + puts $> + puts $< + puts $$ + puts $? + puts $~ + puts $= + puts $* diff --git a/config/contents/style/static_class.md b/config/contents/style/static_class.md new file mode 100644 index 00000000..eae4d0af --- /dev/null +++ b/config/contents/style/static_class.md @@ -0,0 +1,38 @@ +This cop checks for places where classes with only class methods can be +replaced with a module. Classes should be used only when it makes sense to create +instances out of them. + +This cop is marked as unsafe, because it is possible that this class is a parent +for some other subclass, monkey-patched with instance methods or +a dummy instance is instantiated from it somewhere. + +### Example: + # bad + class SomeClass + def self.some_method + # body omitted + end + + def self.some_other_method + # body omitted + end + end + + # good + module SomeModule + module_function + + def some_method + # body omitted + end + + def some_other_method + # body omitted + end + end + + # good - has instance method + class SomeClass + def instance_method; end + def self.class_method; end + end diff --git a/config/contents/style/stderr_puts.md b/config/contents/style/stderr_puts.md index b9e39a04..5210f6da 100644 --- a/config/contents/style/stderr_puts.md +++ b/config/contents/style/stderr_puts.md @@ -1,6 +1,6 @@ This cop identifies places where `$stderr.puts` can be replaced by `warn`. The latter has the advantage of easily being disabled by, -e.g. the -W0 interpreter flag, or setting $VERBOSE to nil. +the `-W0` interpreter flag or setting `$VERBOSE` to `nil`. ### Example: # bad diff --git a/config/contents/style/string_chars.md b/config/contents/style/string_chars.md new file mode 100644 index 00000000..c07d0edc --- /dev/null +++ b/config/contents/style/string_chars.md @@ -0,0 +1,12 @@ +Checks for uses of `String#split` with empty string or regexp literal argument. + +This cop is marked as unsafe. But probably it's quite unlikely that some other class would +define a `split` method that takes exactly the same arguments. + +### Example: + # bad + string.split(//) + string.split('') + + # good + string.chars diff --git a/config/contents/style/string_concatenation.md b/config/contents/style/string_concatenation.md new file mode 100644 index 00000000..f72084eb --- /dev/null +++ b/config/contents/style/string_concatenation.md @@ -0,0 +1,42 @@ +This cop checks for places where string concatenation +can be replaced with string interpolation. + +The cop can autocorrect simple cases but will skip autocorrecting +more complex cases where the resulting code would be harder to read. +In those cases, it might be useful to extract statements to local +variables or methods which you can then interpolate in a string. + +NOTE: When concatenation between two strings is broken over multiple +lines, this cop does not register an offense; instead, +`Style/LineEndConcatenation` will pick up the offense if enabled. + +Two modes are supported: +1. `aggressive` style checks and corrects all occurrences of `+` where +either the left or right side of `+` is a string literal. +2. `conservative` style on the other hand, checks and corrects only if +left side (receiver of `+` method call) is a string literal. +This is useful when the receiver is some expression that returns string like `Pathname` +instead of a string literal. + +### Example: Mode: aggressive (default) + # bad + email_with_name = user.name + ' <' + user.email + '>' + Pathname.new('/') + 'test' + + # good + email_with_name = "#{user.name} <#{user.email}>" + email_with_name = format('%s <%s>', user.name, user.email) + "#{Pathname.new('/')}test" + + # accepted, line-end concatenation + name = 'First' + + 'Last' + +### Example: Mode: conservative + # bad + 'Hello' + user.name + + # good + "Hello #{user.name}" + user.name + '!!' + Pathname.new('/') + 'test' diff --git a/config/contents/style/strip.md b/config/contents/style/strip.md new file mode 100644 index 00000000..20f263b1 --- /dev/null +++ b/config/contents/style/strip.md @@ -0,0 +1,10 @@ +This cop identifies places where `lstrip.rstrip` can be replaced by +`strip`. + +### Example: + # bad + 'abc'.lstrip.rstrip + 'abc'.rstrip.lstrip + + # good + 'abc'.strip \ No newline at end of file diff --git a/config/contents/style/struct_inheritance.md b/config/contents/style/struct_inheritance.md index 4eb00d93..9723ff57 100644 --- a/config/contents/style/struct_inheritance.md +++ b/config/contents/style/struct_inheritance.md @@ -3,7 +3,14 @@ This cop checks for inheritance from Struct.new. ### Example: # bad class Person < Struct.new(:first_name, :last_name) + def age + 42 + end end # good - Person = Struct.new(:first_name, :last_name) \ No newline at end of file + Person = Struct.new(:first_name, :last_name) do + def age + 42 + end + end \ No newline at end of file diff --git a/config/contents/style/swap_values.md b/config/contents/style/swap_values.md new file mode 100644 index 00000000..47941a6b --- /dev/null +++ b/config/contents/style/swap_values.md @@ -0,0 +1,12 @@ +This cop enforces the use of shorthand-style swapping of 2 variables. +Its autocorrection is marked as unsafe, because it can erroneously remove +the temporary variable which is used later. + +### Example: + # bad + tmp = x + x = y + y = tmp + + # good + x, y = y, x diff --git a/config/contents/style/symbol_array.md b/config/contents/style/symbol_array.md index ed453b16..cf7fe5fc 100644 --- a/config/contents/style/symbol_array.md +++ b/config/contents/style/symbol_array.md @@ -6,8 +6,8 @@ projects which do not want to use that syntax. Configuration option: MinSize If set, arrays with fewer elements than this value will not trigger the -cop. For example, a `MinSize of `3` will not enforce a style on an array -of 2 or fewer elements. +cop. For example, a `MinSize` of `3` will not enforce a style on an +array of 2 or fewer elements. ### Example: EnforcedStyle: percent (default) # good diff --git a/config/contents/style/symbol_proc.md b/config/contents/style/symbol_proc.md index 6bafa728..e9ea6ae6 100644 --- a/config/contents/style/symbol_proc.md +++ b/config/contents/style/symbol_proc.md @@ -1,8 +1,23 @@ Use symbols as procs when possible. +If you prefer a style that allows block for method with arguments, +please set `true` to `AllowMethodsWithArguments`. + ### Example: # bad something.map { |s| s.upcase } + something.map { _1.upcase } + + # good + something.map(&:upcase) + +### Example: AllowMethodsWithArguments: false (default) + # bad + something.do_something(foo) { |o| o.bar } + + # good + something.do_something(foo, &:bar) +### Example: AllowMethodsWithArguments: true # good - something.map(&:upcase) \ No newline at end of file + something.do_something(foo) { |o| o.bar } \ No newline at end of file diff --git a/config/contents/style/ternary_parentheses.md b/config/contents/style/ternary_parentheses.md index c30d2e11..6eb6445c 100644 --- a/config/contents/style/ternary_parentheses.md +++ b/config/contents/style/ternary_parentheses.md @@ -3,6 +3,11 @@ conditions. It is configurable to enforce inclusion or omission of parentheses using `EnforcedStyle`. Omission is only enforced when removing the parentheses won't cause a different behavior. +`AllowSafeAssignment` option for safe assignment. +By safe assignment we mean putting parentheses around +an assignment to indicate "I know I'm using an assignment +as a condition. It's not a mistake." + ### Example: EnforcedStyle: require_no_parentheses (default) # bad foo = (bar?) ? a : b @@ -34,4 +39,12 @@ removing the parentheses won't cause a different behavior. # good foo = bar? ? a : b foo = bar.baz? ? a : b - foo = (bar && baz) ? a : b \ No newline at end of file + foo = (bar && baz) ? a : b + +### Example: AllowSafeAssignment: true (default) + # good + foo = (bar = baz) ? a : b + +### Example: AllowSafeAssignment: false + # bad + foo = (bar = baz) ? a : b diff --git a/config/contents/style/top_level_method_definition.md b/config/contents/style/top_level_method_definition.md new file mode 100644 index 00000000..d6226783 --- /dev/null +++ b/config/contents/style/top_level_method_definition.md @@ -0,0 +1,41 @@ +Newcomers to ruby applications may write top-level methods, +when ideally they should be organized in appropriate classes or modules. +This cop looks for definitions of top-level methods and warns about them. + +However for ruby scripts it is perfectly fine to use top-level methods. +Hence this cop is disabled by default. + +### Example: + # bad + def some_method + end + + # bad + def self.some_method + end + + # bad + define_method(:foo) { puts 1 } + + # good + module Foo + def some_method + end + end + + # good + class Foo + def self.some_method + end + end + + # good + Struct.new do + def some_method + end + end + + # good + class Foo + define_method(:foo) { puts 1 } + end \ No newline at end of file diff --git a/config/contents/style/trailing_body_on_class.md b/config/contents/style/trailing_body_on_class.md new file mode 100644 index 00000000..4e8d0098 --- /dev/null +++ b/config/contents/style/trailing_body_on_class.md @@ -0,0 +1,11 @@ +This cop checks for trailing code after the class definition. + +### Example: + # bad + class Foo; def foo; end + end + + # good + class Foo + def foo; end + end diff --git a/config/contents/style/trailing_body_on_method_definition.md b/config/contents/style/trailing_body_on_method_definition.md index 655e61ab..eaa3019e 100644 --- a/config/contents/style/trailing_body_on_method_definition.md +++ b/config/contents/style/trailing_body_on_method_definition.md @@ -1,5 +1,7 @@ This cop checks for trailing code after the method definition. +NOTE: It always accepts endless method definitions that are basically on the same line. + ### Example: # bad def some_method; do_stuff @@ -18,3 +20,5 @@ This cop checks for trailing code after the method definition. b = foo b[c: x] end + + def endless_method = do_stuff diff --git a/config/contents/style/trailing_body_on_module.md b/config/contents/style/trailing_body_on_module.md new file mode 100644 index 00000000..3e42155f --- /dev/null +++ b/config/contents/style/trailing_body_on_module.md @@ -0,0 +1,11 @@ +This cop checks for trailing code after the module definition. + +### Example: + # bad + module Foo extend self + end + + # good + module Foo + extend self + end diff --git a/config/contents/style/trailing_comma_in_arguments.md b/config/contents/style/trailing_comma_in_arguments.md index 2a8bf88b..442c28f3 100644 --- a/config/contents/style/trailing_comma_in_arguments.md +++ b/config/contents/style/trailing_comma_in_arguments.md @@ -1,15 +1,31 @@ This cop checks for trailing comma in argument lists. +The supported styles are: + +* `consistent_comma`: Requires a comma after the last argument, +for all parenthesized method calls with arguments. +* `comma`: Requires a comma after the last argument, but only for +parenthesized method calls where each argument is on its own line. +* `no_comma`: Requires that there is no comma after the last +argument. ### Example: EnforcedStyleForMultiline: consistent_comma # bad method(1, 2,) + # good + method(1, 2) + # good method( 1, 2, 3, ) + # good + method( + 1, 2, 3, + ) + # good method( 1, @@ -20,6 +36,31 @@ This cop checks for trailing comma in argument lists. # bad method(1, 2,) + # good + method(1, 2) + + # bad + method( + 1, 2, + 3, + ) + + # good + method( + 1, 2, + 3 + ) + + # bad + method( + 1, 2, 3, + ) + + # good + method( + 1, 2, 3 + ) + # good method( 1, @@ -30,6 +71,9 @@ This cop checks for trailing comma in argument lists. # bad method(1, 2,) + # good + method(1, 2) + # good method( 1, diff --git a/config/contents/style/trailing_comma_in_array_literal.md b/config/contents/style/trailing_comma_in_array_literal.md new file mode 100644 index 00000000..97c6b388 --- /dev/null +++ b/config/contents/style/trailing_comma_in_array_literal.md @@ -0,0 +1,78 @@ +This cop checks for trailing comma in array literals. +The configuration options are: + +* `consistent_comma`: Requires a comma after the +last item of all non-empty, multiline array literals. +* `comma`: Requires a comma after last item in an array, +but only when each item is on its own line. +* `no_comma`: Does not requires a comma after the +last item in an array + +### Example: EnforcedStyleForMultiline: consistent_comma + # bad + a = [1, 2,] + + # good + a = [1, 2] + + # good + a = [ + 1, 2, + 3, + ] + + # good + a = [ + 1, 2, 3, + ] + + # good + a = [ + 1, + 2, + ] + +### Example: EnforcedStyleForMultiline: comma + # bad + a = [1, 2,] + + # good + a = [1, 2] + + # bad + a = [ + 1, 2, + 3, + ] + + # good + a = [ + 1, 2, + 3 + ] + + # bad + a = [ + 1, 2, 3, + ] + + # good + a = [ + 1, 2, 3 + ] + + # good + a = [ + 1, + 2, + ] + +### Example: EnforcedStyleForMultiline: no_comma (default) + # bad + a = [1, 2,] + + # good + a = [ + 1, + 2 + ] \ No newline at end of file diff --git a/config/contents/style/trailing_comma_in_block_args.md b/config/contents/style/trailing_comma_in_block_args.md new file mode 100644 index 00000000..452110da --- /dev/null +++ b/config/contents/style/trailing_comma_in_block_args.md @@ -0,0 +1,37 @@ +This cop checks whether trailing commas in block arguments are +required. Blocks with only one argument and a trailing comma require +that comma to be present. Blocks with more than one argument never +require a trailing comma. + +### Example: + # bad + add { |foo, bar,| foo + bar } + + # good + add { |foo, bar| foo + bar } + + # good + add { |foo,| foo } + + # good + add { foo } + + # bad + add do |foo, bar,| + foo + bar + end + + # good + add do |foo, bar| + foo + bar + end + + # good + add do |foo,| + foo + end + + # good + add do + foo + bar + end \ No newline at end of file diff --git a/config/contents/style/trailing_comma_in_hash_literal.md b/config/contents/style/trailing_comma_in_hash_literal.md new file mode 100644 index 00000000..8153f236 --- /dev/null +++ b/config/contents/style/trailing_comma_in_hash_literal.md @@ -0,0 +1,81 @@ +This cop checks for trailing comma in hash literals. +The configuration options are: + +* `consistent_comma`: Requires a comma after the +last item of all non-empty, multiline hash literals. +* `comma`: Requires a comma after the last item in a hash, +but only when each item is on its own line. +* `no_comma`: Does not requires a comma after the +last item in a hash + +### Example: EnforcedStyleForMultiline: consistent_comma + + # bad + a = { foo: 1, bar: 2, } + + # good + a = { foo: 1, bar: 2 } + + # good + a = { + foo: 1, bar: 2, + qux: 3, + } + + # good + a = { + foo: 1, bar: 2, qux: 3, + } + + # good + a = { + foo: 1, + bar: 2, + } + +### Example: EnforcedStyleForMultiline: comma + + # bad + a = { foo: 1, bar: 2, } + + # good + a = { foo: 1, bar: 2 } + + # bad + a = { + foo: 1, bar: 2, + qux: 3, + } + + # good + a = { + foo: 1, bar: 2, + qux: 3 + } + + # bad + a = { + foo: 1, bar: 2, qux: 3, + } + + # good + a = { + foo: 1, bar: 2, qux: 3 + } + + # good + a = { + foo: 1, + bar: 2, + } + +### Example: EnforcedStyleForMultiline: no_comma (default) + + # bad + a = { foo: 1, bar: 2, } + + # good + a = { + foo: 1, + bar: 2 + } \ No newline at end of file diff --git a/config/contents/style/trailing_underscore_variable.md b/config/contents/style/trailing_underscore_variable.md index 155b625b..e5432030 100644 --- a/config/contents/style/trailing_underscore_variable.md +++ b/config/contents/style/trailing_underscore_variable.md @@ -15,5 +15,10 @@ This cop checks for extra underscores in variable assignment. a, *b, _ = foo() # => The correction `a, *b, = foo()` is a syntax error - # good if AllowNamedUnderscoreVariables is true - a, b, _something = foo() \ No newline at end of file +### Example: AllowNamedUnderscoreVariables: true (default) + # good + a, b, _something = foo() + +### Example: AllowNamedUnderscoreVariables: false + # bad + a, b, _something = foo() diff --git a/config/contents/style/trivial_accessors.md b/config/contents/style/trivial_accessors.md index 25aac76a..eec34d0e 100644 --- a/config/contents/style/trivial_accessors.md +++ b/config/contents/style/trivial_accessors.md @@ -21,4 +21,69 @@ have been created with the attr_* family of functions automatically. class << self attr_reader :baz + end + +### Example: ExactNameMatch: true (default) + # good + def name + @other_name + end + +### Example: ExactNameMatch: false + # bad + def name + @other_name + end + +### Example: AllowPredicates: true (default) + # good + def foo? + @foo + end + +### Example: AllowPredicates: false + # bad + def foo? + @foo + end + + # good + attr_reader :foo + +### Example: AllowDSLWriters: true (default) + # good + def on_exception(action) + @on_exception=action + end + +### Example: AllowDSLWriters: false + # bad + def on_exception(action) + @on_exception=action + end + + # good + attr_writer :on_exception + +### Example: IgnoreClassMethods: false (default) + # bad + def self.foo + @foo + end + + # good + class << self + attr_reader :foo + end + +### Example: IgnoreClassMethods: true + # good + def self.foo + @foo + end + +### Example: AllowedMethods: ['allowed_method'] + # good + def allowed_method + @foo end \ No newline at end of file diff --git a/config/contents/style/unless_else.md b/config/contents/style/unless_else.md index 3a0da0ea..d109a1fd 100644 --- a/config/contents/style/unless_else.md +++ b/config/contents/style/unless_else.md @@ -1,4 +1,4 @@ -This cop looks for *unless* expressions with *else* clauses. +This cop looks for `unless` expressions with `else` clauses. ### Example: # bad diff --git a/config/contents/style/unless_logical_operators.md b/config/contents/style/unless_logical_operators.md new file mode 100644 index 00000000..cc230f58 --- /dev/null +++ b/config/contents/style/unless_logical_operators.md @@ -0,0 +1,43 @@ +This cop checks for the use of logical operators in an `unless` condition. +It discourages such code, as the condition becomes more difficult +to read and understand. + +This cop supports two styles: +- `forbid_mixed_logical_operators` (default) +- `forbid_logical_operators` + +`forbid_mixed_logical_operators` style forbids the use of more than one type +of logical operators. This makes the `unless` condition easier to read +because either all conditions need to be met or any condition need to be met +in order for the expression to be truthy or falsey. + +`forbid_logical_operators` style forbids any use of logical operator. +This makes it even more easy to read the `unless` condition as +there is only one condition in the expression. + +### Example: EnforcedStyle: forbid_mixed_logical_operators (default) + # bad + return unless a || b && c + return unless a && b || c + return unless a && b and c + return unless a || b or c + return unless a && b or c + return unless a || b and c + + # good + return unless a && b && c + return unless a || b || c + return unless a and b and c + return unless a or b or c + return unless a? + +### Example: EnforcedStyle: forbid_logical_operators + # bad + return unless a || b + return unless a && b + return unless a or b + return unless a and b + + # good + return unless a + return unless a? \ No newline at end of file diff --git a/config/contents/style/unneeded_condition.md b/config/contents/style/unneeded_condition.md new file mode 100644 index 00000000..ad752bfa --- /dev/null +++ b/config/contents/style/unneeded_condition.md @@ -0,0 +1,26 @@ +This cop checks for unnecessary conditional expressions. + +### Example: + # bad + a = b ? b : c + + # good + a = b || c + +### Example: + # bad + if b + b + else + c + end + + # good + b || c + + # good + if b + b + elsif cond + c + end diff --git a/config/contents/style/unneeded_percent_q.md b/config/contents/style/unneeded_percent_q.md new file mode 100644 index 00000000..ff90e3dd --- /dev/null +++ b/config/contents/style/unneeded_percent_q.md @@ -0,0 +1,13 @@ +This cop checks for usage of the %q/%Q syntax when '' or "" would do. + +### Example: + + # bad + name = %q(Bruce Wayne) + time = %q(8 o'clock) + question = %q("What did you say?") + + # good + name = 'Bruce Wayne' + time = "8 o'clock" + question = '"What did you say?"' diff --git a/config/contents/style/unneeded_sort.md b/config/contents/style/unneeded_sort.md new file mode 100644 index 00000000..06dc33b1 --- /dev/null +++ b/config/contents/style/unneeded_sort.md @@ -0,0 +1,45 @@ +This cop is used to identify instances of sorting and then +taking only the first or last element. The same behavior can +be accomplished without a relatively expensive sort by using +`Enumerable#min` instead of sorting and taking the first +element and `Enumerable#max` instead of sorting and taking the +last element. Similarly, `Enumerable#min_by` and +`Enumerable#max_by` can replace `Enumerable#sort_by` calls +after which only the first or last element is used. + +### Example: + # bad + [2, 1, 3].sort.first + [2, 1, 3].sort[0] + [2, 1, 3].sort.at(0) + [2, 1, 3].sort.slice(0) + + # good + [2, 1, 3].min + + # bad + [2, 1, 3].sort.last + [2, 1, 3].sort[-1] + [2, 1, 3].sort.at(-1) + [2, 1, 3].sort.slice(-1) + + # good + [2, 1, 3].max + + # bad + arr.sort_by(&:foo).first + arr.sort_by(&:foo)[0] + arr.sort_by(&:foo).at(0) + arr.sort_by(&:foo).slice(0) + + # good + arr.min_by(&:foo) + + # bad + arr.sort_by(&:foo).last + arr.sort_by(&:foo)[-1] + arr.sort_by(&:foo).at(-1) + arr.sort_by(&:foo).slice(-1) + + # good + arr.max_by(&:foo) diff --git a/config/contents/style/unpack_first.md b/config/contents/style/unpack_first.md new file mode 100644 index 00000000..86188e19 --- /dev/null +++ b/config/contents/style/unpack_first.md @@ -0,0 +1,13 @@ +This cop checks for accessing the first element of `String#unpack` +which can be replaced with the shorter method `unpack1`. + +### Example: + + # bad + 'foo'.unpack('h*').first + 'foo'.unpack('h*')[0] + 'foo'.unpack('h*').slice(0) + 'foo'.unpack('h*').at(0) + + # good + 'foo'.unpack1('h*') diff --git a/config/contents/style/when_then.md b/config/contents/style/when_then.md index fb380f7d..8122165b 100644 --- a/config/contents/style/when_then.md +++ b/config/contents/style/when_then.md @@ -1,4 +1,4 @@ -This cop checks for *when;* uses in *case* expressions. +This cop checks for `when;` uses in `case` expressions. ### Example: # bad diff --git a/config/contents/style/while_until_modifier.md b/config/contents/style/while_until_modifier.md index ed23ff3e..bc2f6524 100644 --- a/config/contents/style/while_until_modifier.md +++ b/config/contents/style/while_until_modifier.md @@ -1,6 +1,6 @@ Checks for while and until statements that would fit on one line if written as a modifier while/until. The maximum line length is -configured in the `Metrics/LineLength` cop. +configured in the `Layout/LineLength` cop. ### Example: # bad @@ -18,4 +18,13 @@ configured in the `Metrics/LineLength` cop. end # good - x += 1 until x > 10 \ No newline at end of file + x += 1 until x > 10 + +### Example: + # bad + x += 100 while x < 500 # a long comment that makes code too long if it were a single line + + # good + while x < 500 # a long comment that makes code too long if it were a single line + x += 100 + end \ No newline at end of file diff --git a/config/contents/style/yoda_condition.md b/config/contents/style/yoda_condition.md index 458b7e60..271b1566 100644 --- a/config/contents/style/yoda_condition.md +++ b/config/contents/style/yoda_condition.md @@ -1,8 +1,8 @@ -This cop checks for Yoda conditions, i.e. comparison operations where -readability is reduced because the operands are not ordered the same -way as they would be ordered in spoken English. +This cop can either enforce or forbid Yoda conditions, +i.e. comparison operations where the order of expression is reversed. +eg. `5 == x` -### Example: EnforcedStyle: all_comparison_operators (default) +### Example: EnforcedStyle: forbid_for_all_comparison_operators (default) # bad 99 == foo "bar" != foo @@ -14,12 +14,36 @@ way as they would be ordered in spoken English. foo == "bar" foo <= 42 bar > 10 + "#{interpolation}" == foo + /#{interpolation}/ == foo -### Example: EnforcedStyle: equality_operators_only +### Example: EnforcedStyle: forbid_for_equality_operators_only # bad 99 == foo "bar" != foo # good 99 >= foo - 3 < a && a < 5 \ No newline at end of file + 3 < a && a < 5 + +### Example: EnforcedStyle: require_for_all_comparison_operators + # bad + foo == 99 + foo == "bar" + foo <= 42 + bar > 10 + + # good + 99 == foo + "bar" != foo + 42 >= foo + 10 < bar + +### Example: EnforcedStyle: require_for_equality_operators_only + # bad + 99 >= foo + 3 < a && a < 5 + + # good + 99 == foo + "bar" != foo \ No newline at end of file diff --git a/config/contents/style/zero_length_predicate.md b/config/contents/style/zero_length_predicate.md index c49927d3..9d03f49b 100644 --- a/config/contents/style/zero_length_predicate.md +++ b/config/contents/style/zero_length_predicate.md @@ -2,7 +2,7 @@ This cop checks for numeric comparisons that can be replaced by a predicate method, such as receiver.length == 0, receiver.length > 0, receiver.length != 0, receiver.length < 1 and receiver.size == 0 that can be -replaced by receiver.empty? and !receiver.empty. +replaced by receiver.empty? and !receiver.empty?. ### Example: # bad diff --git a/lib/cc/engine/category_parser.rb b/lib/cc/engine/category_parser.rb index 3139ccab..533259d6 100644 --- a/lib/cc/engine/category_parser.rb +++ b/lib/cc/engine/category_parser.rb @@ -26,8 +26,7 @@ def category "Rails/HasAndBelongsToMany" => "Style", "Rails/TimeZone" => "Style", "Rails/Validation" => "Style", - "Style" => "Style", - "Migrations/RemoveIndex" => "Performance", + "Style" => "Style" }.freeze attr_reader :cop_name diff --git a/lib/cc/engine/rubocop.rb b/lib/cc/engine/rubocop.rb index f0643590..1ba39a93 100644 --- a/lib/cc/engine/rubocop.rb +++ b/lib/cc/engine/rubocop.rb @@ -5,7 +5,6 @@ require "pathname" require "rubocop" require "rubocop/config_patch" -require "rubocop/cop_patches" require "cc/engine/config_upgrader" require "cc/engine/source_file" require "cc/engine/category_parser" diff --git a/lib/rubocop/config_patch.rb b/lib/rubocop/config_patch.rb index 8bd033f6..2a25c9cc 100644 --- a/lib/rubocop/config_patch.rb +++ b/lib/rubocop/config_patch.rb @@ -3,11 +3,18 @@ require "rubocop/config" module RuboCopConfigRescue - def reject_obsolete_cops_and_parameters + def reject_obsolete! + super + rescue RuboCop::ValidationError => e + warn e.message + end + + def alert_about_unrecognized_cops(_) super rescue RuboCop::ValidationError => e warn e.message end end -RuboCop::Config.prepend RuboCopConfigRescue +RuboCop::ConfigObsoletion.prepend RuboCopConfigRescue +RuboCop::ConfigValidator.prepend RuboCopConfigRescue diff --git a/lib/rubocop/cop_patches.rb b/lib/rubocop/cop_patches.rb deleted file mode 100644 index ea025280..00000000 --- a/lib/rubocop/cop_patches.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module RuboCopCopNodeLoc - # RuboCop's default implementation of `add_offense` in `Cop` only gets the - # location of the keyword associated with the problem which, for things - # like complexity checkers, is just the method def line. This isn't very - # useful for checkers where the entire method body is relevant. Fetching - # this information from an `Offense` instance is difficult, since the - # original AST is no longer available. So it's easier to monkey-path - # this method on complexity checkers to send the location of the entire - # method to the created `Offense`. - def add_offense(node, location: :expression, message: nil, severity: nil, &block) - super(node, location: node.loc, message: message, severity: severity, &block) - end -end - -[ - RuboCop::Cop::Metrics::AbcSize, - RuboCop::Cop::Metrics::BlockLength, - RuboCop::Cop::Metrics::ClassLength, - RuboCop::Cop::Metrics::CyclomaticComplexity, - RuboCop::Cop::Metrics::MethodLength, - RuboCop::Cop::Metrics::ModuleLength, - RuboCop::Cop::Metrics::PerceivedComplexity, -].each do |cop_class| - cop_class.prepend RuboCopCopNodeLoc -end diff --git a/lib/tasks/docs.rake b/lib/tasks/docs.rake index c8ed19bc..dd6f9131 100644 --- a/lib/tasks/docs.rake +++ b/lib/tasks/docs.rake @@ -9,14 +9,14 @@ namespace :docs do MIN_LINES = 3 COP_FOLDERS = %w[bundler gemspec layout lint metrics naming performance rails security style].freeze - `git clone https://github.com/bbatsov/rubocop.git rubocop-git` + `git clone https://github.com/rubocop-hq/rubocop rubocop-git` `cd rubocop-git && git checkout tags/v#{RuboCop::Version.version}` files = Dir.glob("./rubocop-git/lib/rubocop/cop/{#{COP_FOLDERS.join(",")}}/**.rb") documentation_files = files.each_with_object({}) do |file, hash| content = File.read(file) - content = content.gsub(/.*\n\s+(?=module RuboCop)/, "") + content = content.gsub(/.*\n.+(?=module RuboCop)/m, "") class_doc = content.match(/(\s+#.*)+/).to_s doc_lines = class_doc. diff --git a/spec/cc/engine/config_upgrader_spec.rb b/spec/cc/engine/config_upgrader_spec.rb index af4f848c..6ad5aa4c 100644 --- a/spec/cc/engine/config_upgrader_spec.rb +++ b/spec/cc/engine/config_upgrader_spec.rb @@ -12,10 +12,9 @@ def get_true true end CODE - create_source_file(".rubocop.yml", <<~CONFIG) - Style/AccessorMethodName: - Enabled: false - CONFIG + + create_source_file(".rubocop.yml", + File.read("spec/support/config_upgrader_rubocop.yml")) # No warnings about obsolete cop name expect do diff --git a/spec/cc/engine/rubocop_spec.rb b/spec/cc/engine/rubocop_spec.rb index 411eac76..022464e3 100644 --- a/spec/cc/engine/rubocop_spec.rb +++ b/spec/cc/engine/rubocop_spec.rb @@ -24,7 +24,7 @@ def method it "reads the configured ruby_style file" do create_source_file("foo.rb", <<~EORUBY) def method - unused = "x" and "y" + unused = "x" if "y" and "z" return false end @@ -61,7 +61,7 @@ def method(a, b, c, d) it "respects the default .rubocop.yml file" do create_source_file("foo.rb", <<~EORUBY) def method - unused = "x" and "y" + unused = "x" if "y" and "z" return false end @@ -149,10 +149,12 @@ def method end it "handles different locations properly" do + pseudo_source_range_klass = RuboCop::Cop::Offense::const_get(:PseudoSourceRange) + allow_any_instance_of(RuboCop::Cop::Team).to receive(:inspect_file).and_return( [ OpenStruct.new( - location: RuboCop::Cop::Lint::Syntax::PseudoSourceRange.new( + location: pseudo_source_range_klass.new( 1, 0, "" ), cop_name: "fake", @@ -247,7 +249,7 @@ def method it "skips local disables" do create_source_file("test.rb", <<~EORUBY) def method - # rubocop:disable UselessAssignment + # rubocop:disable Lint/UselessAssignment unused = "x" return false diff --git a/spec/rubocop/config_patch_spec.rb b/spec/rubocop/config_patch_spec.rb index b76fe5d8..1e01cab5 100644 --- a/spec/rubocop/config_patch_spec.rb +++ b/spec/rubocop/config_patch_spec.rb @@ -5,7 +5,7 @@ module CC::Engine describe "Rubocop config patch" do - it "prevents config from raisong on obsolete cops" do + it "prevents config from raising on obsolete cops" do config = RuboCop::Config.new( { "Style/TrailingComma" => { @@ -19,8 +19,27 @@ module CC::Engine expect { config.validate }.to_not raise_error end.to output(//).to_stderr end - it "warns about obsolete cops" do + config = RuboCop::Config.new( + { + "Layout/AlignArguments" => { + "Enabled" => true + } + }, + ".rubocop.yml" + ) + + expected = <<~EOM + The `Layout/AlignArguments` cop has been renamed to `Layout/ArgumentAlignment`. + (obsolete configuration found in .rubocop.yml, please update it) + unrecognized cop Layout/AlignArguments found in .rubocop.yml + Did you mean `Layout/HashAlignment`? + EOM + + expect { config.validate }.to output(expected).to_stderr + end + + it "warns about removed cops" do config = RuboCop::Config.new( { "Style/TrailingComma" => { @@ -30,9 +49,15 @@ module CC::Engine ".rubocop.yml" ) - expect { config.validate }.to output( - %r{Warning: unrecognized cop Style/TrailingComma found in .rubocop.yml} - ).to_stderr + + expected = <<~EOM + The `Style/TrailingComma` cop has been removed. Please use `Style/TrailingCommaInArguments`, `Style/TrailingCommaInArrayLiteral` and/or `Style/TrailingCommaInHashLiteral` instead. + (obsolete configuration found in .rubocop.yml, please update it) + unrecognized cop Style/TrailingComma found in .rubocop.yml + Did you mean `Style/TrailingCommaInArguments`? + EOM + + expect { config.validate }.to output(expected).to_stderr end end end diff --git a/spec/rubocop/cop_patches_spec.rb b/spec/rubocop/cop_patches_spec.rb index f4573fd0..021ef38e 100644 --- a/spec/rubocop/cop_patches_spec.rb +++ b/spec/rubocop/cop_patches_spec.rb @@ -4,22 +4,6 @@ module CC::Engine describe "Rubocop cops patch" do include RubocopRunner - it "patches all relevant cops" do - patchable_cops = - RuboCop::Cop::Cop.registry.select do |cop| - cop.ancestors.include?(RuboCop::Cop::MethodComplexity) || - cop.ancestors.include?(RuboCop::Cop::ClassishLength) || - cop.ancestors.include?(RuboCop::Cop::TooManyLines) - end - - patched_cops = - RuboCop::Cop::Cop.registry.select do |cop| - cop.ancestors.include?(RuboCopCopNodeLoc) - end - - expect(patched_cops).to match_array patchable_cops - end - describe "Metrics::AbcSize patch" do it "includes complete method body for cyclomatic complexity issue" do create_source_file("my_script", <<~EORUBY) diff --git a/spec/support/config_upgrader_rubocop.yml b/spec/support/config_upgrader_rubocop.yml new file mode 100644 index 00000000..b4ade617 --- /dev/null +++ b/spec/support/config_upgrader_rubocop.yml @@ -0,0 +1,194 @@ +Style/AccessorMethodName: + Enabled: false +Style/FrozenStringLiteralComment: + Enabled: false + +# 1.18 +Layout/LineEndStringConcatenationIndentation: # (new in 1.18) + Enabled: true +Naming/InclusiveLanguage: # (new in 1.18) + Enabled: true + +# 1.16 +Lint/EmptyInPattern: # (new in 1.16) + Enabled: true +Style/InPatternThen: # (new in 1.16) + Enabled: true +Style/MultilineInPatternThen: # (new in 1.16) + Enabled: true +Style/QuotedSymbols: # (new in 1.16) + Enabled: true + +# 1.12 +Style/StringChars: # (new in 1.12) + Enabled: true + +# 1.10 +Gemspec/DateAssignment: # (new in 1.10) + Enabled: true +Style/HashConversion: # (new in 1.10) + Enabled: true + +# 1.9 +Lint/NumberedParameterAssignment: # (new in 1.9) + Enabled: true +Lint/OrAssignmentToConstant: # (new in 1.9) + Enabled: true +Lint/SymbolConversion: # (new in 1.9) + Enabled: true +Lint/TripleQuotes: # (new in 1.9) + Enabled: true +Style/IfWithBooleanLiteralBranches: # (new in 1.9) + Enabled: true + +# 1.8 +Lint/DeprecatedConstants: # (new in 1.8) + Enabled: true +Lint/LambdaWithoutLiteralBlock: # (new in 1.8) + Enabled: true +Lint/RedundantDirGlobSort: # (new in 1.8) + Enabled: true +Style/EndlessMethod: # (new in 1.8) + Enabled: true + +# 1.5 through 1.7 +Layout/SpaceBeforeBrackets: # (new in 1.7) + Enabled: true +Lint/AmbiguousAssignment: # (new in 1.7) + Enabled: true +Lint/UnexpectedBlockArity: # (new in 1.5) + Enabled: true +Style/HashExcept: # (new in 1.7) + Enabled: true + + # 1.1 through 1.4 +Lint/DuplicateBranch: # (new in 1.3) + Enabled: true +Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1) + Enabled: true +Lint/EmptyBlock: # (new in 1.1) + Enabled: true +Lint/EmptyClass: # (new in 1.3) + Enabled: true +Lint/NoReturnInBeginEndBlocks: # (new in 1.2) + Enabled: true +Lint/ToEnumArguments: # (new in 1.1) + Enabled: true +Lint/UnmodifiedReduceAccumulator: # (new in 1.1) + Enabled: true +Style/ArgumentsForwarding: # (new in 1.1) + Enabled: true +Style/CollectionCompact: # (new in 1.2) + Enabled: true +Style/DocumentDynamicEvalDefinition: # (new in 1.1) + Enabled: true +Style/NegatedIfElseCondition: # (new in 1.2) + Enabled: true +Style/NilLambda: # (new in 1.3) + Enabled: true +Style/RedundantArgument: # (new in 1.4) + Enabled: true +Style/SwapValues: # (new in 1.1) + Enabled: true + +# 0.91 +Layout/BeginEndAlignment: # (new in 0.91) + Enabled: true +Lint/ConstantDefinitionInBlock: # (new in 0.91) + Enabled: true +Lint/IdentityComparison: # (new in 0.91) + Enabled: true +Lint/UselessTimes: # (new in 0.91) + Enabled: true + +# New Cops +Layout/EmptyLinesAroundAttributeAccessor: + Enabled: false +Layout/SpaceAroundMethodCallOperator: + Enabled: false +Lint/BinaryOperatorWithIdenticalOperands: + Enabled: false +Lint/DeprecatedOpenSSLConstant: + Enabled: false +Lint/DuplicateElsifCondition: + Enabled: false +Lint/DuplicateRequire: + Enabled: false +Lint/DuplicateRescueException: + Enabled: false +Lint/EmptyConditionalBody: + Enabled: false +Lint/EmptyFile: + Enabled: false +Lint/FloatComparison: + Enabled: false +Lint/MissingSuper: + Enabled: false +Lint/MixedRegexpCaptureTypes: + Enabled: false +Lint/OutOfRangeRegexpRef: + Enabled: false +Lint/RaiseException: + Enabled: false +Lint/SelfAssignment: + Enabled: false +Lint/StructNewOverride: + Enabled: false +Lint/TopLevelReturnWithArgument: + Enabled: false +Lint/TrailingCommaInAttributeDeclaration: + Enabled: false +Lint/UnreachableLoop: + Enabled: false +Lint/UselessMethodDefinition: + Enabled: false +Style/AccessorGrouping: + Enabled: false +Style/ArrayCoercion: + Enabled: false +Style/BisectedAttrAccessor: + Enabled: false +Style/CaseLikeIf: + Enabled: false +Style/CombinableLoops: + Enabled: false +Style/ExplicitBlockArgument: + Enabled: false +Style/ExponentialNotation: + Enabled: false +Style/GlobalStdStream: + Enabled: false +Style/HashAsLastArrayItem: + Enabled: false +Style/HashEachMethods: + Enabled: false +Style/HashLikeCase: + Enabled: false +Style/HashTransformKeys: + Enabled: false +Style/HashTransformValues: + Enabled: false +Style/KeywordParametersOrder: + Enabled: false +Style/OptionalBooleanParameter: + Enabled: false +Style/RedundantAssignment: + Enabled: false +Style/RedundantFetchBlock: + Enabled: false +Style/RedundantFileExtensionInRequire: + Enabled: false +Style/RedundantRegexpCharacterClass: + Enabled: false +Style/RedundantRegexpEscape: + Enabled: false +Style/RedundantSelfAssignment: + Enabled: false +Style/SingleArgumentDig: + Enabled: false +Style/SlicingWithRange: + Enabled: false +Style/SoleNestedConditional: + Enabled: false +Style/StringConcatenation: + Enabled: false diff --git a/spec/support/currently_undocumented_cops.txt b/spec/support/currently_undocumented_cops.txt index 1a7c1b05..4d4ff1a7 100644 --- a/spec/support/currently_undocumented_cops.txt +++ b/spec/support/currently_undocumented_cops.txt @@ -1,21 +1,3 @@ -RuboCop::Cop::Layout::EndOfLine -RuboCop::Cop::Layout::InitialIndentation -RuboCop::Cop::Layout::SpaceInsideBrackets -RuboCop::Cop::Layout::Tab -RuboCop::Cop::Layout::TrailingBlankLines -RuboCop::Cop::Layout::TrailingWhitespace -RuboCop::Cop::Lint::MissingCopEnableDirective -RuboCop::Cop::Lint::ScriptPermission RuboCop::Cop::Lint::Syntax -RuboCop::Cop::Metrics::LineLength -RuboCop::Cop::Performance::FixedSize -RuboCop::Cop::Rails::Validation -RuboCop::Cop::Style::BeginBlock RuboCop::Cop::Style::ConditionalAssignment -RuboCop::Cop::Style::Encoding -RuboCop::Cop::Style::EndBlock -RuboCop::Cop::Style::NegatedWhile -RuboCop::Cop::Style::NestedTernaryOperator -RuboCop::Cop::Style::OneLineConditional -RuboCop::Cop::Style::SpecialGlobalVars -RuboCop::Cop::Style::UnneededPercentQ +RuboCop::Cop::Migration::DepartmentName