diff --git a/.circleci/config.yml b/.circleci/config.yml index 8bff89a8fbf3..2b501c24034a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -132,6 +132,29 @@ jobs: steps: *rubocop_steps + # Ruby 3.3 + ruby-3.3-spec: + docker: + - image: cimg/ruby:3.3 + environment: + <<: *common_env + steps: + *spec_steps + ruby-3.3-ascii_spec: + docker: + - image: cimg/ruby:3.3 + environment: + <<: *common_env + steps: + *ascii_spec_steps + ruby-3.3-rubocop: + docker: + - image: cimg/ruby:3.3 + environment: + <<: *common_env + steps: + *rubocop_steps + # ruby-head (nightly snapshot build) ruby-head-spec: docker: @@ -159,7 +182,7 @@ jobs: cc-setup: docker: # Specify the latest version to prevent "cimg/ruby:latest not found: manifest unknown: manifest unknown" error. - - image: cimg/ruby:3.2 + - image: cimg/ruby:3.3 environment: <<: *common_env steps: @@ -178,7 +201,7 @@ jobs: cc-upload-coverage: docker: # Specify the latest version to prevent "cimg/ruby:latest not found: manifest unknown: manifest unknown" error. - - image: cimg/ruby:3.2 + - image: cimg/ruby:3.3 environment: CC_TEST_REPORTER_ID: a11b66bfbb1acdf220d5cb317b2e945a986fd85adebe29a76d411ad6d74ec31f environment: @@ -189,14 +212,14 @@ jobs: - run: name: Upload coverage results to Code Climate command: | - ./tmp/cc-test-reporter sum-coverage tmp/codeclimate.*.json --parts 4 --output tmp/codeclimate.total.json + ./tmp/cc-test-reporter sum-coverage tmp/codeclimate.*.json --parts 5 --output tmp/codeclimate.total.json ./tmp/cc-test-reporter upload-coverage --input tmp/codeclimate.total.json # Miscellaneous tasks documentation-checks: docker: # Specify the latest version to prevent "cimg/ruby:latest not found: manifest unknown: manifest unknown" error. - - image: cimg/ruby:3.2 + - image: cimg/ruby:3.3 environment: <<: *common_env steps: @@ -232,6 +255,11 @@ workflows: - cc-setup - ruby-3.2-ascii_spec - ruby-3.2-rubocop + - ruby-3.3-spec: + requires: + - cc-setup + - ruby-3.3-ascii_spec + - ruby-3.3-rubocop - ruby-head-spec: requires: - cc-setup @@ -244,3 +272,4 @@ workflows: - ruby-3.0-spec - ruby-3.1-spec - ruby-3.2-spec + - ruby-3.3-spec diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e8acecbdbed9..21810f1c9c4a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -38,7 +38,7 @@ output by `rubocop -V`, include them as well. Here's an example: ``` $ [bundle exec] rubocop -V -1.52.1 (using Parser 2.7.2.0, rubocop-ast 1.1.1, running on ruby 2.7.2) [x86_64-linux] - - rubocop-performance 1.9.1 - - rubocop-rspec 2.0.0 +1.60.2 (using Parser 3.2.2.3, rubocop-ast 1.29.0, running on ruby 3.2.2) [x86_64-linux] + - rubocop-performance 1.18.0 + - rubocop-rspec 2.23.2 ``` diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index cccbb432bf3e..f3f2f11d877e 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -16,7 +16,7 @@ jobs: name: Yamllint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Yamllint uses: karancode/yamllint-github-action@v2.1.1 with: diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 047037ea6b4d..f21ae36e9a8c 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -29,7 +29,7 @@ jobs: matrix: # [ubuntu, macos, windows] os: [windows] - ruby: ['2.7', '3.0', '3.1', '3.2', 'head'] + ruby: ['2.7', '3.0', '3.1', '3.2', '3.3', 'head'] include: - os: windows ruby: mingw @@ -45,7 +45,7 @@ jobs: echo "TMPDIR=$env:RUNNER_TEMP" >> $GITHUB_ENV git config --system core.autocrlf false - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: set up Ruby uses: ruby/setup-ruby@v1 with: @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: set up Ruby uses: ruby/setup-ruby@v1 with: @@ -80,7 +80,7 @@ jobs: runs-on: ubuntu-latest name: RSpec 4 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use latest RSpec 4 from `4-0-dev` branch run: | sed -e "/'rspec', '~> 3/d" -i Gemfile diff --git a/.github/workflows/spell_checking.yml b/.github/workflows/spell_checking.yml index 502e564ba017..cbfbc09d60b3 100644 --- a/.github/workflows/spell_checking.yml +++ b/.github/workflows/spell_checking.yml @@ -14,7 +14,7 @@ jobs: name: Check spelling of all files with codespell runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: codespell-project/actions-codespell@v2 with: check_filenames: true @@ -24,7 +24,7 @@ jobs: name: Check spelling of all files in commit with misspell runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install run: wget -O - -q https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | sh -s -- -b . - name: Misspell diff --git a/.rubocop.yml b/.rubocop.yml index e18416ca0e94..bdae9cc7ebd5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -139,14 +139,20 @@ RSpec: - expect_no_offenses - expect_offense -RSpec/FilePath: - Exclude: - - spec/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new_spec.rb - - spec/rubocop/formatter/junit_formatter_spec.rb - RSpec/PredicateMatcher: EnforcedStyle: explicit +RSpec/FilePath: + Enabled: false + +RSpec/SpecFilePathFormat: + CustomTransform: + GitHubActionsFormatter: github_actions_formatter + JUnitFormatter: junit_formatter + RedundantLetRuboCopConfigNew: redundant_let_rubocop_config_new + Exclude: + - spec/rubocop/cop/mixin/enforce_superclass_spec.rb + RSpec/MessageSpies: EnforcedStyle: receive diff --git a/CHANGELOG.md b/CHANGELOG.md index 093ff95085a6..ec46fadbc63c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,431 @@ ## master (unreleased) +## 1.60.2 (2024-01-24) + +### Bug fixes + +* [#12627](https://github.com/rubocop/rubocop/issues/12627): Fix a false positive for `Layout/RedundantLineBreak` when using index access call chained on multiple lines with backslash. ([@koic][]) +* [#12626](https://github.com/rubocop/rubocop/pull/12626): Fix a false positive for `Style/ArgumentsForwarding` when naming a block argument `&`. ([@koic][]) +* [#12635](https://github.com/rubocop/rubocop/pull/12635): Fix a false positive for `Style/HashEachMethods` when both arguments are unused. ([@earlopain][]) +* [#12636](https://github.com/rubocop/rubocop/pull/12636): Fix an error for `Style/HashEachMethods` when a block with both parameters has no body. ([@earlopain][]) +* [#12638](https://github.com/rubocop/rubocop/issues/12638): Fix an `Errno::ENOENT` error when using server mode. ([@koic][]) +* [#12628](https://github.com/rubocop/rubocop/pull/12628): Fix a false positive for `Style/ArgumentsForwarding` when using block arg forwarding with positional arguments forwarding to within block. ([@koic][]) +* [#12642](https://github.com/rubocop/rubocop/pull/12642): Fix false positives for `Style/HashEachMethods` when using array converter method. ([@koic][]) +* [#12632](https://github.com/rubocop/rubocop/issues/12632): Fix an infinite loop error when `EnforcedStyle: explicit` of `Naming/BlockForwarding` with `Style/ArgumentsForwarding`. ([@koic][]) + +## 1.60.1 (2024-01-17) + +### Bug fixes + +* [#12625](https://github.com/rubocop/rubocop/pull/12625): Fix an error when server cache dir has read-only file system. ([@Strzesia][]) +* [#12618](https://github.com/rubocop/rubocop/issues/12618): Fix false positives for `Style/ArgumentsForwarding` when using block argument forwarding with other arguments. ([@koic][]) +* [#12614](https://github.com/rubocop/rubocop/issues/12614): Fix false positiveis for `Style/RedundantParentheses` when parentheses in control flow keyword with multiline style argument. ([@koic][]) + +### Changes + +* [#12617](https://github.com/rubocop/rubocop/issues/12617): Make `Style/CollectionCompact` aware of `grep_v` with nil. ([@koic][]) + +## 1.60.0 (2024-01-15) + +### Bug fixes + +* [#12603](https://github.com/rubocop/rubocop/issues/12603): Fix an infinite loop error for `Style/MultilineTernaryOperator` when using a method call as a ternary operator condition with a line break between receiver and method. ([@koic][]) +* [#12549](https://github.com/rubocop/rubocop/issues/12549): Fix a false positive for `Style/RedundantLineContinuation` when line continuations for multiline leading dot method chain with a blank line. ([@koic][]) +* [#12610](https://github.com/rubocop/rubocop/pull/12610): Accept parentheses in argument calls with blocks for `Style/MethodCallWithArgsParentheses` `omit_parentheses` style. ([@gsamokovarov][]) +* [#12580](https://github.com/rubocop/rubocop/pull/12580): Fix an infinite loop error for `Layout/EndAlignment` when misaligned in singleton class assignments with `EnforcedStyleAlignWith: variable`. ([@koic][]) +* [#12548](https://github.com/rubocop/rubocop/issues/12548): Fix an infinite loop error for `Layout/FirstArgumentIndentation` when specifying `EnforcedStyle: with_fixed_indentation` of `Layout/ArrayAlignment`. ([@koic][]) +* [#12236](https://github.com/rubocop/rubocop/issues/12236): Fix an error for `Lint/ShadowedArgument` when self assigning to a block argument in `for`. ([@koic][]) +* [#12569](https://github.com/rubocop/rubocop/issues/12569): Fix an error for `Style/IdenticalConditionalBranches` when using `if`...`else` with identical leading lines that assign to `self.foo`. ([@koic][]) +* [#12437](https://github.com/rubocop/rubocop/issues/12437): Fix an infinite loop error for `EnforcedStyle: omit_parentheses` of `Style/MethodCallWithArgsParentheses` with `Style/SuperWithArgsParentheses`. ([@koic][]) +* [#12558](https://github.com/rubocop/rubocop/issues/12558): Fix an incorrect autocorrect for `Style/MapToHash` when using `map.to_h` without receiver. ([@koic][]) +* [#12179](https://github.com/rubocop/rubocop/issues/12179): Let `--auto-gen-config` generate `Exclude` when `Max` is overridden. ([@jonas054][]) +* [#12574](https://github.com/rubocop/rubocop/issues/12574): Fix bug for unrecognized style in --auto-gen-config. ([@jonas054][]) +* [#12542](https://github.com/rubocop/rubocop/issues/12542): Fix false positive for `Lint/MixedRegexpCaptureTypes` when using look-ahead matcher. ([@marocchino][]) +* [#12607](https://github.com/rubocop/rubocop/pull/12607): Fix a false positive for `Style/RedundantParentheses` when regexp literal attempts to match against a parenthesized condition. ([@koic][]) +* [#12539](https://github.com/rubocop/rubocop/pull/12539): Fix false positives for `Lint/LiteralAssignmentInCondition` when a collection literal contains non-literal elements. ([@koic][]) +* [#12571](https://github.com/rubocop/rubocop/issues/12571): Fix false positives for `Naming/BlockForwarding` when using explicit block forwarding in block method. ([@koic][]) +* [#12537](https://github.com/rubocop/rubocop/issues/12537): Fix false positives for `Style/RedundantParentheses` when `AllowInMultilineConditions: true` of `Style/ParenthesesAroundCondition`. ([@koic][]) +* [#12578](https://github.com/rubocop/rubocop/pull/12578): Fix false positives for `Style/ArgumentsForwarding` when rest arguments forwarding to a method in block. ([@koic][]) +* [#12540](https://github.com/rubocop/rubocop/issues/12540): Fix false positives for `Style/HashEachMethods` when rest block argument of `Enumerable#each` method is used. ([@koic][]) +* [#12529](https://github.com/rubocop/rubocop/issues/12529): Fix false positives for `Style/ParenthesesAroundCondition`. ([@koic][]) +* [#12556](https://github.com/rubocop/rubocop/issues/12556): Fix false positives for `Style/RedundantParentheses` when parentheses are used around a semantic operator in expressions within assignments. ([@koic][]) +* [#12541](https://github.com/rubocop/rubocop/pull/12541): Fix false negative in `Style/ArgumentsForwarding` when a block is forwarded but other args aren't. ([@dvandersluis][]) +* [#12581](https://github.com/rubocop/rubocop/pull/12581): Handle trailing line continuation in `Layout/LineContinuationLeadingSpace`. ([@eugeneius][]) +* [#12601](https://github.com/rubocop/rubocop/issues/12601): Make `Style/EachForSimpleLoop` accept block with no parameters. ([@koic][]) + +### Changes + +* [#12535](https://github.com/rubocop/rubocop/pull/12535): Allow --autocorrect with --display-only-fail-level-offenses. ([@naveg][]) +* [#12572](https://github.com/rubocop/rubocop/pull/12572): Follow a Ruby 3.3 warning for `Security/Open` when `open` with a literal string starting with a pipe. ([@koic][]) +* [#12453](https://github.com/rubocop/rubocop/issues/12453): Make `Style/RedundantEach` aware of safe navigation operator. ([@koic][]) +* [#12233](https://github.com/rubocop/rubocop/issues/12233): Make `Style/SlicingWithRange` aware of redundant and beginless range. ([@koic][]) +* [#12388](https://github.com/rubocop/rubocop/pull/12388): Reject additional 'expanded' `EnforcedStyle` options when `--no-auto-gen-enforced-style` is given. ([@kpost][]) +* [#12593](https://github.com/rubocop/rubocop/pull/12593): Require Parser 3.3.0.2 or higher. ([@koic][]) + +## 1.59.0 (2023-12-11) + +### New features + +* [#12518](https://github.com/rubocop/rubocop/pull/12518): Add new `Lint/ItWithoutArgumentsInBlock` cop. ([@koic][]) + +### Bug fixes + +* [#12434](https://github.com/rubocop/rubocop/issues/12434): Fix a false positive for `Lint/LiteralAssignmentInCondition` when using interpolated string or xstring literals. ([@koic][]) +* [#12435](https://github.com/rubocop/rubocop/issues/12435): Fix a false positive for `Lint/SelfAssignment` when using attribute assignment with method call with arguments. ([@koic][]) +* [#12444](https://github.com/rubocop/rubocop/issues/12444): Fix false positive for `Style/HashEachMethods` when receiver literal is not a hash literal. ([@koic][]) +* [#12524](https://github.com/rubocop/rubocop/issues/12524): Fix a false positive for `Style/MethodCallWithArgsParentheses` when `EnforcedStyle: omit_parentheses` and parens in `when` clause is used to pass an argument. ([@koic][]) +* [#12505](https://github.com/rubocop/rubocop/pull/12505): Fix a false positive for `Style/RedundantParentheses` when using parenthesized `lambda` or `proc` with `do`...`end` block. ([@koic][]) +* [#12442](https://github.com/rubocop/rubocop/issues/12442): Fix an incorrect autocorrect for `Style/CombinableLoops` when looping over the same data as previous loop in `do`...`end` and `{`...`}` blocks. ([@koic][]) +* [#12432](https://github.com/rubocop/rubocop/pull/12432): Fix a false positive for `Lint/LiteralAssignmentInCondition` when using parallel assignment with splat operator in block of guard condition. ([@koic][]) +* [#12441](https://github.com/rubocop/rubocop/issues/12441): Fix false positives for `Style/HashEachMethods` when using destructed block arguments. ([@koic][]) +* [#12436](https://github.com/rubocop/rubocop/issues/12436): Fix false positives for `Style/RedundantParentheses` when a part of range is a parenthesized condition. ([@koic][]) +* [#12429](https://github.com/rubocop/rubocop/issues/12429): Fix incorrect autocorrect for `Style/MapToHash` when using dot method calls for `to_h`. ([@koic][]) +* [#12488](https://github.com/rubocop/rubocop/issues/12488): Make `Lint/HashCompareByIdentity` aware of safe navigation operator. ([@koic][]) +* [#12489](https://github.com/rubocop/rubocop/issues/12489): Make `Lint/NextWithoutAccumulator` aware of safe navigation operator. ([@koic][]) +* [#12490](https://github.com/rubocop/rubocop/issues/12490): Make `Lint/NumberConversion` aware of safe navigation operator. ([@koic][]) +* [#12491](https://github.com/rubocop/rubocop/issues/12491): Make `Lint/RedundantWithIndex` aware of safe navigation operator. ([@koic][]) +* [#12492](https://github.com/rubocop/rubocop/issues/12492): Make `Lint/RedundantWithObject` aware of safe navigation operator. ([@koic][]) +* [#12493](https://github.com/rubocop/rubocop/issues/12493): Make `Lint/UnmodifiedReduceAccumulator` aware of safe navigation operator. ([@koic][]) +* [#12473](https://github.com/rubocop/rubocop/issues/12473): Make `Style/ClassCheck` aware of safe navigation operator. ([@koic][]) +* [#12445](https://github.com/rubocop/rubocop/issues/12445): Make `Style/CollectionCompact` aware of safe navigation operator. ([@koic][]) +* [#12474](https://github.com/rubocop/rubocop/issues/12474): Make `Style/ConcatArrayLiterals` aware of safe navigation operator. ([@koic][]) +* [#12476](https://github.com/rubocop/rubocop/issues/12476): Make `Style/DateTime` aware of safe navigation operator. ([@koic][]) +* [#12479](https://github.com/rubocop/rubocop/issues/12479): Make `Style/EachWithObject` aware of safe navigation operator. ([@koic][]) +* [#12446](https://github.com/rubocop/rubocop/issues/12446): Make `Style/HashExcept` aware of safe navigation operator. ([@koic][]) +* [#12447](https://github.com/rubocop/rubocop/issues/12447): Make `Style/MapCompactWithConditionalBlock` aware of safe navigation operator. ([@koic][]) +* [#12484](https://github.com/rubocop/rubocop/issues/12484): Make `Style/Next` aware of safe navigation operator. ([@koic][]) +* [#12486](https://github.com/rubocop/rubocop/issues/12486): Make `Style/RedundantArgument` aware of safe navigation operator. ([@koic][]) +* [#12454](https://github.com/rubocop/rubocop/issues/12454): Make `Style/RedundantFetchBlock` aware of safe navigation operator. ([@koic][]) +* [#12495](https://github.com/rubocop/rubocop/issues/12495): Make `Layout/RedundantLineBreak` aware of safe navigation operator. ([@koic][]) +* [#12455](https://github.com/rubocop/rubocop/issues/12455): Make `Style/RedundantSortBy` aware of safe navigation operator. ([@koic][]) +* [#12456](https://github.com/rubocop/rubocop/issues/12456): Make `Style/RedundantSortBy` aware of safe navigation operator. ([@koic][]) +* [#12480](https://github.com/rubocop/rubocop/issues/12480): Make `Style/ExactRegexpMatch` aware of safe navigation operator. ([@koic][]) +* [#12457](https://github.com/rubocop/rubocop/issues/12457): Make `Style/Sample` aware of safe navigation operator. ([@koic][]) +* [#12458](https://github.com/rubocop/rubocop/issues/12458): Make `Style/SelectByRegexp` cops aware of safe navigation operator. ([@koic][]) +* [#12494](https://github.com/rubocop/rubocop/issues/12494): Make `Layout/SingleLineBlockChain` aware of safe navigation operator. ([@koic][]) +* [#12461](https://github.com/rubocop/rubocop/issues/12461): Make `Style/StringChars` aware of safe navigation operator. ([@koic][]) +* [#12468](https://github.com/rubocop/rubocop/issues/12468): Make `Style/Strip` aware of safe navigation operator. ([@koic][]) +* [#12469](https://github.com/rubocop/rubocop/issues/12469): Make `Style/UnpackFirst` aware of safe navigation operator. ([@koic][]) + +### Changes + +* [#12522](https://github.com/rubocop/rubocop/pull/12522): Make `Style/MethodCallWithoutArgsParentheses` allow the parenthesized `it` method in a block. ([@koic][]) +* [#12523](https://github.com/rubocop/rubocop/pull/12523): Make `Style/RedundantSelf` allow the `self.it` method in a block. ([@koic][]) + +## 1.58.0 (2023-12-01) + +### New features + +* [#12420](https://github.com/rubocop/rubocop/pull/12420): Add new `Lint/LiteralAssignmentInCondition` cop. ([@koic][]) +* [#12353](https://github.com/rubocop/rubocop/issues/12353): Add new `Style/SuperWithArgsParentheses` cop. ([@koic][]) +* [#12406](https://github.com/rubocop/rubocop/issues/12406): Add new `Style/ArrayFirstLast` cop. ([@fatkodima][]) + +### Bug fixes + +* [#12372](https://github.com/rubocop/rubocop/issues/12372): Fix a false negative for `Lint/Debugger` when used within method arguments a `begin`...`end` block. ([@koic][]) +* [#12378](https://github.com/rubocop/rubocop/pull/12378): Fix a false negative for `Style/Semicolon` when a semicolon at the beginning of a lambda block. ([@koic][]) +* [#12146](https://github.com/rubocop/rubocop/issues/12146): Fix a false positive for `Lint/FloatComparison` when comparing against zero. ([@earlopain][]) +* [#12404](https://github.com/rubocop/rubocop/issues/12404): Fix a false positive for `Layout/RescueEnsureAlignment` when aligned `rescue` in `do`-`end` numbered block in a method. ([@koic][]) +* [#12374](https://github.com/rubocop/rubocop/issues/12374): Fix a false positive for `Layout/SpaceBeforeSemicolon` when a space between an opening lambda brace and a semicolon. ([@koic][]) +* [#12326](https://github.com/rubocop/rubocop/pull/12326): Fix an error for `Style/RedundantDoubleSplatHashBraces` when method call for parenthesized no hash double double splat. ([@koic][]) +* [#12361](https://github.com/rubocop/rubocop/issues/12361): Fix an incorrect autocorrect for `Naming/BlockForwarding` and `Style/ArgumentsForwarding` when autocorrection conflicts for anonymous arguments. ([@koic][]) +* [#12324](https://github.com/rubocop/rubocop/issues/12324): Fix an error for `Layout/RescueEnsureAlignment` when using `rescue` in `do`...`end` block assigned to object attribute. ([@koic][]) +* [#12322](https://github.com/rubocop/rubocop/issues/12322): Fix an error for `Style/CombinableLoops` when looping over the same data for the third consecutive time or more. ([@koic][]) +* [#12366](https://github.com/rubocop/rubocop/pull/12366): Fix a false negative for `Layout/ExtraSpacing` when a file has exactly two comments. ([@eugeneius][]) +* [#12373](https://github.com/rubocop/rubocop/issues/12373): Fix a false negative for `Lint/SymbolConversion` when using string interpolation. ([@earlopain][]) +* [#12402](https://github.com/rubocop/rubocop/issues/12402): Fix false negatives for `Style/RedundantLineContinuation` when redundant line continuations for a block are used, especially without parentheses around first argument. ([@koic][]) +* [#12311](https://github.com/rubocop/rubocop/issues/12311): Fix false negatives for `Style/RedundantParentheses` when parentheses around logical operator keywords in method definition. ([@koic][]) +* [#12394](https://github.com/rubocop/rubocop/issues/12394): Fix false negatives for `Style/RedundantReturn` when `lambda` (`->`) ending with `return`. ([@koic][]) +* [#12377](https://github.com/rubocop/rubocop/issues/12377): Fix false positives for `Lint/Void` when a collection literal that includes non-literal elements in a method definition. ([@koic][]) +* [#12407](https://github.com/rubocop/rubocop/pull/12407): Fix an incorrect autocorrect for `Style/MapToHash` with `Layout/SingleLineBlockChain`. ([@koic][]) +* [#12409](https://github.com/rubocop/rubocop/issues/12409): Fix an incorrect autocorrect for `Lint/SafeNavigationChain` when ordinary method chain exists after safe navigation leading dot method call. ([@koic][]) +* [#12363](https://github.com/rubocop/rubocop/issues/12363): Fix incorrect rendering of HTML character entities in `HTMLFormatter` formatter. ([@koic][]) +* [#12424](https://github.com/rubocop/rubocop/issues/12424): Make `Style/HashEachMethods` aware of safe navigation operator. ([@koic][]) +* [#12413](https://github.com/rubocop/rubocop/issues/12413): Make `Style/InverseMethods` aware of safe navigation operator. ([@koic][]) +* [#12408](https://github.com/rubocop/rubocop/pull/12408): Make `Style/MapToHash` aware of safe navigation operator. ([@koic][]) + +### Changes + +* [#12328](https://github.com/rubocop/rubocop/issues/12328): Make `Style/AutoResourceCleanup` aware of `Tempfile.open`. ([@koic][]) +* [#12412](https://github.com/rubocop/rubocop/issues/12412): Enhance `Lint/RedundantSafeNavigation` to handle conversion methods with defaults. ([@fatkodima][]) +* [#12410](https://github.com/rubocop/rubocop/issues/12410): Enhance `Lint/SelfAssignment` to check attribute assignment and key assignment. ([@fatkodima][]) +* [#12370](https://github.com/rubocop/rubocop/issues/12370): Make `Style/HashEachMethods` aware of unused block value. ([@koic][]) +* [#12380](https://github.com/rubocop/rubocop/issues/12380): Make `Style/RedundantParentheses` aware of lambda or proc. ([@koic][]) +* [#12421](https://github.com/rubocop/rubocop/pull/12421): Make `Style/SelfAssignment` aware of `%`, `^`, `<<`, and `>>` operators. ([@koic][]) +* [#12305](https://github.com/rubocop/rubocop/pull/12305): Require `rubocop-ast` version 1.30 or greater. ([@sambostock][]) +* [#12337](https://github.com/rubocop/rubocop/issues/12337): Supports `EnforcedStyleForRationalLiterals` option for `Layout/SpaceAroundOperators`. ([@koic][]) +* [#12296](https://github.com/rubocop/rubocop/issues/12296): Support `RedundantRestArgumentNames`, `RedundantKeywordRestArgumentNames`, and `RedundantBlockArgumentNames` options for `Style/ArgumentsForwarding`. ([@koic][]) + +## 1.57.2 (2023-10-26) + +### Bug fixes + +* [#12274](https://github.com/rubocop/rubocop/issues/12274): Fix a false positive for `Lint/Void` when `each`'s receiver is an object of `Enumerator` to which `filter` has been applied. ([@koic][]) +* [#12291](https://github.com/rubocop/rubocop/issues/12291): Fix a false positive for `Metrics/ClassLength` when a class with a singleton class definition. ([@koic][]) +* [#12293](https://github.com/rubocop/rubocop/issues/12293): Fix a false positive for `Style/RedundantDoubleSplatHashBraces` when using double splat hash braces with `merge` and method chain. ([@koic][]) +* [#12298](https://github.com/rubocop/rubocop/issues/12298): Fix a false positive for `Style/RedundantParentheses` when using a parenthesized hash literal as the first argument in a method call without parentheses. ([@koic][]) +* [#12283](https://github.com/rubocop/rubocop/pull/12283): Fix an error for `Style/SingleLineDoEndBlock` when using single line `do`...`end` with no body. ([@koic][]) +* [#12312](https://github.com/rubocop/rubocop/issues/12312): Fix an incorrect autocorrect for `Style/HashSyntax` when braced hash key and value are the same and it is used in `if`...`else`. ([@koic][]) +* [#12307](https://github.com/rubocop/rubocop/issues/12307): Fix an infinite loop error for `Layout/EndAlignment` when `EnforcedStyleAlignWith: variable` and using a conditional statement in a method argument on the same line and `end` with method call is not aligned. ([@koic][]) +* [#11652](https://github.com/rubocop/rubocop/issues/11652): Make `--auto-gen-config` generate `inherit_from` correctly inside ERB `if`. ([@jonas054][]) +* [#12310](https://github.com/rubocop/rubocop/issues/12310): Drop `base64` gem from runtime dependency. ([@koic][]) +* [#12300](https://github.com/rubocop/rubocop/issues/12300): Fix an error for `Style/IdenticalConditionalBranches` when `if`...`else` with identical leading lines and using index assign. ([@koic][]) +* [#12286](https://github.com/rubocop/rubocop/issues/12286): Fix false positives for `Style/RedundantDoubleSplatHashBraces` when using double splat with a hash literal enclosed in parenthesized ternary operator. ([@koic][]) +* [#12279](https://github.com/rubocop/rubocop/issues/12279): Fix false positives for `Lint/EmptyConditionalBody` when missing 2nd `if` body with a comment. ([@koic][]) +* [#12275](https://github.com/rubocop/rubocop/issues/12275): Fix a false positive for `Style/RedundantDoubleSplatHashBraces` when using double splat within block argument containing a hash literal in an array literal. ([@koic][]) +* [#12284](https://github.com/rubocop/rubocop/issues/12284): Fix false positives for `Style/SingleArgumentDig` when using some anonymous argument syntax. ([@koic][]) +* [#12301](https://github.com/rubocop/rubocop/issues/12301): Make `Style/RedundantFilterChain` aware of safe navigation operator. ([@koic][]) + +## 1.57.1 (2023-10-13) + +### Bug fixes + +* [#12271](https://github.com/rubocop/rubocop/issues/12271): Fix a false positive for `Lint/RedundantSafeNavigation` when using snake case constant receiver. ([@koic][]) +* [#12265](https://github.com/rubocop/rubocop/issues/12265): Fix an error for `Layout/MultilineMethodCallIndentation` when usingarithmetic operation with block inside a grouped expression. ([@koic][]) +* [#12177](https://github.com/rubocop/rubocop/pull/12177): Fix an incorrect autocorrect for `Style/RedundantException`. ([@ydah][]) +* [#12261](https://github.com/rubocop/rubocop/issues/12261): Fix an infinite loop for `Layout/MultilineMethodCallIndentation` when multiline method chain with a block argument and method chain. ([@ydah][]) +* [#12263](https://github.com/rubocop/rubocop/issues/12263): Fix false positives for `Style/RedundantDoubleSplatHashBraces` when method call for no hash braced double splat receiver. ([@koic][]) +* [#12262](https://github.com/rubocop/rubocop/pull/12262): Fix an incorrect autocorrect for `Style/RedundantDoubleSplatHashBraces` when using double splat hash braces with `merge` method call twice. ([@koic][]) + +## 1.57.0 (2023-10-11) + +### New features + +* [#12227](https://github.com/rubocop/rubocop/pull/12227): Add new `Style/SingleLineDoEndBlock` cop. ([@koic][]) +* [#12246](https://github.com/rubocop/rubocop/pull/12246): Make `Lint/RedundantSafeNavigation` aware of constant receiver. ([@koic][]) +* [#12257](https://github.com/rubocop/rubocop/issues/12257): Make `Style/RedundantDoubleSplatHashBraces` aware of `merge` methods. ([@koic][]) + +### Bug fixes + +* [#12244](https://github.com/rubocop/rubocop/issues/12244): Fix a false negative for `Lint/Debugger` when using debugger method inside block. ([@koic][]) +* [#12231](https://github.com/rubocop/rubocop/issues/12231): Fix a false negative for `Metrics/ModuleLength` when defining a singleton class in a module. ([@koic][]) +* [#12249](https://github.com/rubocop/rubocop/issues/12249): Fix a false positive `Style/IdenticalConditionalBranches` when `if`..`else` with identical leading lines and assign to condition value. ([@koic][]) +* [#12253](https://github.com/rubocop/rubocop/pull/12253): Fix `Lint/LiteralInInterpolation` to accept an empty string literal interpolated in words literal. ([@knu][]) +* [#12198](https://github.com/rubocop/rubocop/issues/12198): Fix an error for flip-flop with beginless or endless ranges. ([@koic][]) +* [#12259](https://github.com/rubocop/rubocop/issues/12259): Fix an error for `Lint/MixedCaseRange` when using nested character class in regexp. ([@koic][]) +* [#12237](https://github.com/rubocop/rubocop/issues/12237): Fix an error for `Style/NestedTernaryOperator` when a ternary operator has a nested ternary operator within an `if`. ([@koic][]) +* [#12228](https://github.com/rubocop/rubocop/pull/12228): Fix false negatives for `Style/MultilineBlockChain` when using multiline block chain with safe navigation operator. ([@koic][]) +* [#12247](https://github.com/rubocop/rubocop/pull/12247): Fix false negatives for `Style/RedundantParentheses` when using logical or comparison expressions with redundant parentheses. ([@koic][]) +* [#12226](https://github.com/rubocop/rubocop/issues/12226): Fix false positives for `Layout/MultilineMethodCallIndentation` when aligning methods in multiline block chain. ([@koic][]) +* [#12076](https://github.com/rubocop/rubocop/issues/12076): Fixed an issue where the top-level cache folder was named differently during two consecutive rubocop runs. ([@K-S-A][]) + +### Changes + +* [#12235](https://github.com/rubocop/rubocop/pull/12235): Enable auto parallel inspection when config file is specified. ([@aboutNisblee][]) +* [#12234](https://github.com/rubocop/rubocop/pull/12234): Enhance `Style/FormatString`'s autocorrection when using known conversion methods whose return value is not an array. ([@koic][]) +* [#12128](https://github.com/rubocop/rubocop/issues/12128): Make `Style/GuardClause` aware of `define_method`. ([@koic][]) +* [#12126](https://github.com/rubocop/rubocop/pull/12126): Make `Style/RedundantFilterChain` aware of `select.present?` when `ActiveSupportExtensionsEnabled` config is `true`. ([@koic][]) +* [#12250](https://github.com/rubocop/rubocop/pull/12250): Mark `Lint/RedundantRequireStatement` as unsafe autocorrect. ([@koic][]) +* [#12097](https://github.com/rubocop/rubocop/issues/12097): Mark unsafe autocorrect for `Style/ClassEqualityComparison`. ([@koic][]) +* [#12210](https://github.com/rubocop/rubocop/issues/12210): Mark `Style/RedundantFilterChain` as unsafe autocorrect. ([@koic][]) + +## 1.56.4 (2023-09-28) + +### Bug fixes + +* [#12221](https://github.com/rubocop/rubocop/issues/12221): Fix a false positive for `Layout/EmptyLineAfterGuardClause` when using `return` before guard condition with heredoc. ([@koic][]) +* [#12213](https://github.com/rubocop/rubocop/issues/12213): Fix a false positive for `Lint/OrderedMagicComments` when comment text `# encoding: ISO-8859-1` is embedded within example code as source code comment. ([@koic][]) +* [#12205](https://github.com/rubocop/rubocop/issues/12205): Fix an error for `Style/OperatorMethodCall` when using `foo bar./ baz`. ([@koic][]) +* [#12208](https://github.com/rubocop/rubocop/issues/12208): Fix an incorrect autocorrect for the `--disable-uncorrectable` command line option when registering an offense is outside a percent array. ([@koic][]) +* [#12203](https://github.com/rubocop/rubocop/pull/12203): Fix an incorrect autocorrect for `Lint/SafeNavigationChain` when using safe navigation with comparison operator as an expression of logical operator or comparison operator's operand. ([@koic][]) +* [#12206](https://github.com/rubocop/rubocop/pull/12206): Fix an incorrect autocorrect for `Style/OperatorMethodCall` when using `foo./bar`. ([@koic][]) +* [#12202](https://github.com/rubocop/rubocop/pull/12202): Fix an incorrect autocorrect for `Style/RedundantConditional` when unless/else with boolean results. ([@ydah][]) +* [#12199](https://github.com/rubocop/rubocop/issues/12199): Fix false negatives for `Layout/MultilineMethodCallIndentation` when using safe navigation operator. ([@koic][]) + +### Changes + +* [#12197](https://github.com/rubocop/rubocop/pull/12197): Make `Style/CollectionMethods` aware of `collect_concat`. ([@koic][]) + +## 1.56.3 (2023-09-11) + +### Bug fixes + +* [#12151](https://github.com/rubocop/rubocop/issues/12151): Make `Layout/EmptyLineAfterGuardClause` allow `:nocov:` directive after guard clause. ([@koic][]) +* [#12195](https://github.com/rubocop/rubocop/issues/12195): Fix a false negative for `Layout/SpaceAfterNot` when a newline is present after `!`. ([@ymap][]) +* [#12192](https://github.com/rubocop/rubocop/issues/12192): Fix a false positive for `Layout/RedundantLineBreak` when using quoted symbols with a single newline. ([@ymap][]) +* [#12190](https://github.com/rubocop/rubocop/issues/12190): Fix a false positive for `Layout/SpaceAroundOperators` when aligning operators vertically. ([@koic][]) +* [#12171](https://github.com/rubocop/rubocop/issues/12171): Fix a false positive for `Style/ArrayIntersect` when using block argument for `Enumerable#any?`. ([@koic][]) +* [#12172](https://github.com/rubocop/rubocop/issues/12172): Fix a false positive for `Style/EmptyCaseCondition` when using `return`, `break`, `next` or method call before empty case condition. ([@koic][]) +* [#12162](https://github.com/rubocop/rubocop/issues/12162): Fix an error for `Bundler/DuplicatedGroup` when there's a duplicate set of groups and the `group` value contains a splat. ([@koic][]) +* [#12182](https://github.com/rubocop/rubocop/issues/12182): Fix an error for `Lint/UselessAssignment` when variables are assigned using chained assignment and remain unreferenced. ([@koic][]) +* [#12181](https://github.com/rubocop/rubocop/issues/12181): Fix an incorrect autocorrect for `Lint/UselessAssignment` when variables are assigned with sequential assignment using the comma operator and unreferenced. ([@koic][]) +* [#12187](https://github.com/rubocop/rubocop/issues/12187): Fix an incorrect autocorrect for `Style/SoleNestedConditional` when comment is in an empty nested `if` body. ([@ymap][]) +* [#12183](https://github.com/rubocop/rubocop/pull/12183): Fix an incorrect autocorrect for `Style/MultilineTernaryOperator` when returning a multiline ternary operator expression with safe navigation method call. ([@koic][]) +* [#12168](https://github.com/rubocop/rubocop/issues/12168): Fix bug in `Style/ArgumentsForwarding` when there are repeated send nodes. ([@owst][]) +* [#12185](https://github.com/rubocop/rubocop/pull/12185): Set target version for `Layout/HeredocIndentation`. ([@tagliala][]) + +## 1.56.2 (2023-08-29) + +### Bug fixes + +* [#12138](https://github.com/rubocop/rubocop/issues/12138): Fix a false positive for `Layout/LineContinuationLeadingSpace` when a backslash is part of a multiline string literal. ([@ymap][]) +* [#12155](https://github.com/rubocop/rubocop/pull/12155): Fix false positive for `Layout/RedundantLineBreak` when using a modified singleton method definition. ([@koic][]) +* [#12143](https://github.com/rubocop/rubocop/issues/12143): Fix a false positive for `Lint/ToEnumArguments` when using anonymous keyword arguments forwarding. ([@koic][]) +* [#12148](https://github.com/rubocop/rubocop/pull/12148): Fix an incorrect autocorrect for `Lint/NonAtomicFileOperation` when using `FileUtils.remove_dir`, `FileUtils.remove_entry`, or `FileUtils.remove_entry_secure`. ([@koic][]) +* [#12141](https://github.com/rubocop/rubocop/issues/12141): Fix false positive for `Style/ArgumentsForwarding` when method def includes additional kwargs. ([@owst][]) +* [#12154](https://github.com/rubocop/rubocop/issues/12154): Fix incorrect `diagnosticProvider` value of LSP. ([@koic][]) + +## 1.56.1 (2023-08-21) + +### Bug fixes + +* [#12136](https://github.com/rubocop/rubocop/pull/12136): Fix a false negative for `Layout/LeadingCommentSpace` when using `#+` or `#-` as they are not RDoc comments. ([@koic][]) +* [#12113](https://github.com/rubocop/rubocop/issues/12113): Fix a false positive for `Bundler/DuplicatedGroup` when groups are duplicated but `source`, `git`, `platforms`, or `path` values are different. ([@koic][]) +* [#12134](https://github.com/rubocop/rubocop/issues/12134): Fix a false positive for `Style/MethodCallWithArgsParentheses` when parentheses are used in one-line `in` pattern matching. ([@koic][]) +* [#12111](https://github.com/rubocop/rubocop/issues/12111): Fix an error for `Bundler/DuplicatedGroup` group declaration has keyword option. ([@koic][]) +* [#12109](https://github.com/rubocop/rubocop/issues/12109): Fix an error for `Style/ArgumentsForwarding` cop when forwarding kwargs/block arg and an additional arg. ([@ydah][]) +* [#12117](https://github.com/rubocop/rubocop/issues/12117): Fix a false positive for `Style/ArgumentsForwarding` cop when not always forwarding block. ([@owst][]) +* [#12115](https://github.com/rubocop/rubocop/pull/12115): Fix an error for `Style/Lambda` when using numbered parameter with a multiline `->` call. ([@koic][]) +* [#12124](https://github.com/rubocop/rubocop/issues/12124): Fix false positives for `Style/RedundantParentheses` when parentheses in `super` or `yield` call with multiline style argument. ([@koic][]) +* [#12120](https://github.com/rubocop/rubocop/pull/12120): Fix false positives for `Style/SymbolArray` when `%i` array containing unescaped `[`, `]`, `(`, or `)`. ([@koic][]) +* [#12133](https://github.com/rubocop/rubocop/pull/12133): Fix `Style/RedundantSelfAssignmentBranch` to handle heredocs. ([@r7kamura][]) +* [#12105](https://github.com/rubocop/rubocop/issues/12105): Fix target ruby `Gem::Requirement` matcher and version parsing to support multiple version constraints. ([@ItsEcholot][]) + +## 1.56.0 (2023-08-09) + +### New features + +* [#12074](https://github.com/rubocop/rubocop/pull/12074): Add new `Bundler/DuplicatedGroup` cop. ([@OwlKing][]) +* [#12078](https://github.com/rubocop/rubocop/pull/12078): Make LSP server support `rubocop.formatAutocorrectsAll` execute command. ([@koic][]) + +### Bug fixes + +* [#12106](https://github.com/rubocop/rubocop/issues/12106): Fix a false negative for `Style/RedundantReturn` when returning value with guard clause and `return` is used. ([@koic][]) +* [#12095](https://github.com/rubocop/rubocop/pull/12095): Fix a false positive for `Style/Alias` when `EncforcedStyle: prefer_alias` and using `alias` with interpolated symbol argument. ([@koic][]) +* [#12098](https://github.com/rubocop/rubocop/pull/12098): Fix a false positive for `Style/ClassEqualityComparison` when comparing interpolated string class name for equality. ([@koic][]) +* [#12102](https://github.com/rubocop/rubocop/pull/12102): Fix an error for `Style/LambdaCall` when using nested lambda call `x.().()`. ([@koic][]) +* [#12099](https://github.com/rubocop/rubocop/pull/12099): Fix an incorrect autocorrect for `Style/Alias` when `EncforcedStyle: prefer_alias_method` and using `alias` with interpolated symbol argument. ([@koic][]) +* [#12085](https://github.com/rubocop/rubocop/issues/12085): Fix an error for `Lint/SuppressedException` when `AllowNil: true` is set and endless method definition is used. ([@koic][]) +* [#12087](https://github.com/rubocop/rubocop/issues/12087): Fix false positives for `Style/ArgumentsForwarding` with additional args/kwargs in def/send nodes. ([@owst][]) +* [#12071](https://github.com/rubocop/rubocop/issues/12071): Fix `Style/SymbolArray` false positives when using square brackets or interpolation in a symbol literal in a percent style array. ([@jasondoc3][]) +* [#12061](https://github.com/rubocop/rubocop/issues/12061): Support regex in StringLiteralsInInterpolation. ([@jonas054][]) +* [#12091](https://github.com/rubocop/rubocop/pull/12091): With `--fail-level A` ignore non-correctable offenses at :info severity. ([@naveg][]) + +### Changes + +* [#12094](https://github.com/rubocop/rubocop/pull/12094): Add `base64` gem to runtime dependency to suppress Ruby 3.3's warning. ([@koic][]) + +## 1.55.1 (2023-07-31) + +### Bug fixes + +* [#12068](https://github.com/rubocop/rubocop/pull/12068): Fix a false positive for `Style/ReturnNilInPredicateMethodDefinition` when the last method argument in method definition is `nil`. ([@koic][]) +* [#12082](https://github.com/rubocop/rubocop/issues/12082): Fix an error for `Lint/UselessAssignment` when a variable is assigned and unreferenced in `for` with multiple variables. ([@koic][]) +* [#12079](https://github.com/rubocop/rubocop/issues/12079): Fix an error for `Style/MixinGrouping` when mixin method has no arguments. ([@koic][]) +* [#11637](https://github.com/rubocop/rubocop/pull/11637): Correct Rubocop for `private_class_method` method documentation. ([@bigzed][]) +* [#12070](https://github.com/rubocop/rubocop/pull/12070): Fix false positive in `Style/ArgumentsForwarding` when receiver forwards args/kwargs. ([@owst][]) + +## 1.55.0 (2023-07-25) + +### New features + +* [#11794](https://github.com/rubocop/rubocop/pull/11794): Add support to `Style/ArgumentsForwarding` for anonymous arg/kwarg forwarding in Ruby 3.2. ([@owst][]) +* [#12044](https://github.com/rubocop/rubocop/issues/12044): Make LSP server support `layoutMode` option to run layout cops. ([@koic][]) +* [#12056](https://github.com/rubocop/rubocop/pull/12056): Make LSP server support `lintMode` option to run lint cops. ([@koic][]) +* [#12046](https://github.com/rubocop/rubocop/issues/12046): Make `ReturnNilInPredicateMethodDefinition` aware of `nil` at the end of predicate method definition. ([@koic][]) + +### Bug fixes + +* [#12055](https://github.com/rubocop/rubocop/pull/12055): Allow parentheses in single-line match patterns when using the `omit_parentheses` style of `Style/MethodCallWithArgsParentheses`. ([@gsamokovarov][]) +* [#12050](https://github.com/rubocop/rubocop/pull/12050): Fix a false positive for `Layout/RedundantLineBreak` when inspecting the `%` form string `%\n\n`. ([@koic][]) +* [#12063](https://github.com/rubocop/rubocop/pull/12063): Fix `Style/CombinableLoops` when one of the loops is empty. ([@fatkodima][]) +* [#12059](https://github.com/rubocop/rubocop/issues/12059): Fix a false negative for `Style/StringLiteralsInInterpolation` for symbols with interpolation. ([@fatkodima][]) +* [#11834](https://github.com/rubocop/rubocop/issues/11834): Fix false positive for when variable in inside conditional branch in nested node. ([@alexeyschepin][]) +* [#11802](https://github.com/rubocop/rubocop/issues/11802): Improve handling of `[]` and `()` with percent symbol arrays. ([@jasondoc3][]) +* [#12052](https://github.com/rubocop/rubocop/issues/12052): Fix "Subfolders can't include glob special characters". ([@meric426][], [@loveo][]) +* [#12062](https://github.com/rubocop/rubocop/pull/12062): Fix `LoadError` when loading RuboCop from a symlinked location on Windows. ([@p0deje][]) + +### Changes + +* [#12064](https://github.com/rubocop/rubocop/pull/12064): Make `Style/RedundantArgument` aware of `exit` and `exit!`. ([@koic][]) +* [#12015](https://github.com/rubocop/rubocop/issues/12015): Mark `Style/HashConversion` as unsafe autocorrection. ([@koic][]) + +## 1.54.2 (2023-07-13) + +### Bug fixes + +* [#12043](https://github.com/rubocop/rubocop/pull/12043): Fix a false negative for `Layout/ExtraSpacing` when some characters are vertically aligned. ([@koic][]) +* [#12040](https://github.com/rubocop/rubocop/pull/12040): Fix a false positive for `Layout/TrailingEmptyLines` to prevent the following incorrect autocorrection when inspecting the `%` form string `%\n\n`. ([@koic][]) +* [#1867](https://github.com/rubocop/rubocop/issues/1867): Fix an error when `AllCops:Exclude` is empty in .rubocop.yml. ([@koic][]) +* [#12034](https://github.com/rubocop/rubocop/issues/12034): Fix invalid byte sequence in UTF-8 error when using an invalid encoding string. ([@koic][]) +* [#12038](https://github.com/rubocop/rubocop/pull/12038): Output the "server restarting" message to stderr. ([@knu][]) + +## 1.54.1 (2023-07-04) + +### Bug fixes + +* [#12024](https://github.com/rubocop/rubocop/issues/12024): Fix a false positive for `Lint/RedundantRegexpQuantifiers` when interpolation is used in a regexp literal. ([@koic][]) +* [#12020](https://github.com/rubocop/rubocop/issues/12020): This PR fixes an infinite loop error for `Layout/SpaceAfterComma` with `Layout/SpaceBeforeSemicolon` when autocorrection conflicts. ([@koic][]) +* [#12014](https://github.com/rubocop/rubocop/pull/12014): Fix an error for `Lint/UselessAssignment` when part of a multiple assignment is enclosed in parentheses. ([@koic][]) +* [#12011](https://github.com/rubocop/rubocop/pull/12011): Fix an error for `Metrics/MethodLength` when using a heredoc in a block without block arguments. ([@koic][]) +* [#12010](https://github.com/rubocop/rubocop/pull/12010): Fix false negatives for `Style/RedundantRegexpArgument` when using safe navigation operator. ([@koic][]) + +## 1.54.0 (2023-07-01) + +### New features + +* [#12000](https://github.com/rubocop/rubocop/pull/12000): Support safe or unsafe autocorrect config for LSP. ([@koic][]) + +### Bug fixes + +* [#12005](https://github.com/rubocop/rubocop/issues/12005): Fix a false negative for `Lint/Debugger` when using debugger method inside lambda. ([@koic][]) +* [#11986](https://github.com/rubocop/rubocop/issues/11986): Fix a false positive for `Lint/MixedCaseRange` when the number of characters at the start or end of range is other than 1. ([@koic][]) +* [#11992](https://github.com/rubocop/rubocop/issues/11992): Fix an unexpected `NoMethodError` for built-in language server when an internal error occurs. ([@koic][]) +* [#11994](https://github.com/rubocop/rubocop/issues/11994): Fix an error for `Layout/LineEndStringConcatenationIndentation` when inspecting the `%` from string `%\n\n`. ([@koic][]) +* [#12007](https://github.com/rubocop/rubocop/issues/12007): Fix an error for `Layout/SpaceAroundOperators` when using unary operator with double colon. ([@koic][]) +* [#11996](https://github.com/rubocop/rubocop/issues/11996): Fix an error for `Style/IfWithSemicolon` when without branch bodies. ([@koic][]) +* [#12009](https://github.com/rubocop/rubocop/pull/12009): Fix an error for `Style/YodaCondition` when equality check method is used without the first argument. ([@koic][]) +* [#11998](https://github.com/rubocop/rubocop/issues/11998): Fix an error when inspecting blank heredoc delimiter. ([@koic][]) +* [#11989](https://github.com/rubocop/rubocop/issues/11989): Fix an incorrect autocorrect for `Style/RedundantRegexpArgument` when using unicode chars. ([@koic][]) +* [#12001](https://github.com/rubocop/rubocop/issues/12001): Fix code length calculator for method calls with heredoc. ([@fatkodima][]) +* [#12002](https://github.com/rubocop/rubocop/pull/12002): Fix `Lint/Void` cop for `__ENCODING__` constant. ([@fatkodima][]) + +### Changes + +* [#11983](https://github.com/rubocop/rubocop/pull/11983): Add Ridgepole files to default `Include` list. ([@ydah][]) +* [#11738](https://github.com/rubocop/rubocop/issues/11738): Enhances empty_line_between_defs to treat configured macros like defs. ([@catwomey][]) + +## 1.53.1 (2023-06-26) + +### Bug fixes + +* [#11974](https://github.com/rubocop/rubocop/issues/11974): Fix an error for `Style/RedundantCurrentDirectoryInPath` when using string interpolation in `require_relative`. ([@koic][]) +* [#11981](https://github.com/rubocop/rubocop/issues/11981): Fix an incorrect autocorrect for `Style/RedundantRegexpArgument` when using double quote and single quote characters. ([@koic][]) +* [#11836](https://github.com/rubocop/rubocop/issues/11836): Should not offense single-quoted symbol containing double quotes in `Lint/SymbolConversion` . ([@KessaPassa][]) + +## 1.53.0 (2023-06-23) + +### New features + +* [#11561](https://github.com/rubocop/rubocop/pull/11561): Add new `Lint/MixedCaseRange` cop. ([@rwstauner][]) +* [#11565](https://github.com/rubocop/rubocop/pull/11565): Add new `Lint/RedundantRegexpQuantifiers` cop. ([@jaynetics][]) +* [#11925](https://github.com/rubocop/rubocop/issues/11925): Add new `Style/RedundantCurrentDirectoryInPath` cop. ([@koic][]) +* [#11595](https://github.com/rubocop/rubocop/pull/11595): Add new `Style/RedundantRegexpArgument` cop. ([@koic][]) +* [#11967](https://github.com/rubocop/rubocop/pull/11967): Add new `Style/ReturnNilInPredicateMethodDefinition` cop. ([@koic][]) +* [#11745](https://github.com/rubocop/rubocop/pull/11745): Add new `Style/YAMLFileRead` cop. ([@koic][]) +* [#11926](https://github.com/rubocop/rubocop/pull/11926): Support built-in LSP server. ([@koic][]) + +### Bug fixes + +* [#11953](https://github.com/rubocop/rubocop/issues/11953): Fix a false negative for `Lint/DuplicateHashKey` when there is a duplicated constant key in the hash literal. ([@koic][]) +* [#11945](https://github.com/rubocop/rubocop/issues/11945): Fix a false negative for `Style/RedundantSelfAssignmentBranch` when using method chaining or arguments in ternary branch. ([@koic][]) +* [#11949](https://github.com/rubocop/rubocop/issues/11949): Fix a false positive for `Layout/RedundantLineBreak` when using a line broken string. ([@koic][]) +* [#11931](https://github.com/rubocop/rubocop/pull/11931): Fix a false positive for `Lint/RedundantRequireStatement` when using `PP.pp`. ([@koic][]) +* [#11946](https://github.com/rubocop/rubocop/pull/11946): Fix an error for `Lint/NumberConversion` when using multiple number conversion methods. ([@koic][]) +* [#11972](https://github.com/rubocop/rubocop/issues/11972): Fix an error for `Lint/Void` when `CheckForMethodsWithNoSideEffects: true` and using a method definition. ([@koic][]) +* [#11958](https://github.com/rubocop/rubocop/pull/11958): Fix error for `Style/IdenticalConditionalBranches` when using empty parentheses in the `if` branch. ([@koic][]) +* [#11962](https://github.com/rubocop/rubocop/issues/11962): Fix an error for `Style/RedundantStringEscape` when an escaped double quote precedes interpolation in a symbol literal. ([@koic][]) +* [#11947](https://github.com/rubocop/rubocop/issues/11947): Fix an error for `Style/ConditionalAssignment` with an assignment that uses `if` branch bodies, which include a block. ([@koic][]) +* [#11959](https://github.com/rubocop/rubocop/pull/11959): Fix false negatives for `Layout/EmptyLinesAroundExceptionHandlingKeywords` when using Ruby 2.5's `rescue` inside block and Ruby 2.7's numbered block. ([@koic][]) +* [#10902](https://github.com/rubocop/rubocop/issues/10902): Fix an error for `Style/RedundantRegexpEscape` string with invalid byte sequence in UTF-8. ([@ydah][]) +* [#11562](https://github.com/rubocop/rubocop/pull/11562): Fixed escaped octal handling and detection in `Lint/DuplicateRegexpCharacterClassElement`. ([@rwstauner][]) + +### Changes + +* [#11904](https://github.com/rubocop/rubocop/pull/11904): Mark `Layout/ClassStructure` as unsafe to autocorrect. ([@nevans][]) +* [#8506](https://github.com/rubocop/rubocop/issues/8506): Add `AllowedParentClasses` config to `Lint/MissingSuper`. ([@iMacTia][]) + ## 1.52.1 (2023-06-12) ### Bug fixes @@ -89,7 +514,7 @@ ### Changes * [#11859](https://github.com/rubocop/rubocop/pull/11859): Add rubocop-factory_bot to suggested extensions. ([@ydah][]) -* [#10791](https://github.com/rubocop/rubocop/pull/10791): **(Breaking)** Drop runtime support for Ruby 2.6 and JRuby 9.3 (CRuby 2.6 compatible). ([@koic][]) +* [#11791](https://github.com/rubocop/rubocop/pull/11791): **(Breaking)** Drop runtime support for Ruby 2.6 and JRuby 9.3 (CRuby 2.6 compatible). ([@koic][]) * [#11826](https://github.com/rubocop/rubocop/pull/11826): Exclude `**/*.jb` from `Lint/TopLevelReturnWithArgument`. ([@r7kamura][]) * [#11871](https://github.com/rubocop/rubocop/pull/11871): Mark `Style/DataInheritance` as unsafe autocorrect, `Style/OpenStructUse` as unsafe, and `Security/CompoundHash` as unsafe. ([@koic][]) @@ -1474,7 +1899,7 @@ * [#9748](https://github.com/rubocop/rubocop/pull/9748): Prevent infinite loops during symlink traversal. ([@Tonkpils][]) * [#9762](https://github.com/rubocop/rubocop/issues/9762): Update `VariableForce` to be able to handle `case-match` nodes. ([@dvandersluis][]) * [#9729](https://github.com/rubocop/rubocop/issues/9729): Fix an error for `Style/IfUnlessModifier` when variable assignment is used in the branch body of if modifier. ([@koic][]) -* [#9750](https://github.com/rubocop/rubocop/issues/9750): Fix an incorrect auto-correct for `Style/SoleNestedConditional` when when using nested `if` within `unless foo == bar`. ([@koic][]) +* [#9750](https://github.com/rubocop/rubocop/issues/9750): Fix an incorrect auto-correct for `Style/SoleNestedConditional` when using nested `if` within `unless foo == bar`. ([@koic][]) * [#9751](https://github.com/rubocop/rubocop/pull/9751): `Style/StringLiterals` autocorrects `'\\'` into `"\\"`. ([@etiennebarrie][]) * [#9732](https://github.com/rubocop/rubocop/pull/9732): Support deprecated Socket.gethostbyaddr and Socket.gethostbyname. ([@AndreiEres][]) * [#9713](https://github.com/rubocop/rubocop/issues/9713): Fix autocorrection for block local variables in `Lint/UnusedBlockArgument`. ([@tejasbubane][]) @@ -2204,7 +2629,7 @@ * [#8531](https://github.com/rubocop/rubocop/issues/8531): Add new `Lint/EmptyFile` cop. ([@fatkodima][]) * Add new `Lint/TrailingCommaInAttributeDeclaration` cop. ([@drenmi][]) * [#8578](https://github.com/rubocop/rubocop/pull/8578): Add `:restore_registry` context and `stub_cop_class` helper class. ([@marcandre][]) -* [#8579](https://github.com/rubocop/rubocop/pull/8579): Add `Cop.documentation_url`. ([@marcandre][]) +* [#8579](https://github.com/rubocop/rubocop/pull/8579): Add `Cop.documentation_url`. ([@marcandre][]) * [#8510](https://github.com/rubocop/rubocop/pull/8510): Add `RegexpNode#each_capture` and `parsed_tree`. ([@marcandre][]) * [#8365](https://github.com/rubocop/rubocop/pull/8365): Cops defining `on_send` can be optimized by defining the constant `RESTRICT_ON_SEND` with a list of acceptable method names. ([@marcandre][]) @@ -2884,7 +3309,7 @@ ### New features * [#7084](https://github.com/rubocop/rubocop/pull/7084): Permit to specify TargetRubyVersion 2.7. ([@koic][]) -* [#7092](https://github.com/rubocop/rubocop/pull/7092): Node patterns can now use `*`, `+` and `?` for repetitions. ([@marcandre][]) +* [#7092](https://github.com/rubocop/rubocop/pull/7092): Node patterns can now use `*`, `+` and `?` for repetitions. ([@marcandre][]) ### Bug fixes @@ -3629,7 +4054,7 @@ * [#3394](https://github.com/rubocop/rubocop/issues/3394): Add new `Style/TrailingCommaInArrayLiteral` cop. ([@garettarrowood][]) * [#3394](https://github.com/rubocop/rubocop/issues/3394): Add new `Style/TrailingCommaInHashLiteral` cop. ([@garettarrowood][]) * [#5319](https://github.com/rubocop/rubocop/pull/5319): Add new `Security/Open` cop. ([@mame][]) -* Add `EnforcedStyleForEmptyBrackets` configuration to `Layout/SpaceInsideReferenceBrackets`.([@garettarrowood][]) +* Add `EnforcedStyleForEmptyBrackets` configuration to `Layout/SpaceInsideReferenceBrackets`. ([@garettarrowood][]) * [#5050](https://github.com/rubocop/rubocop/issues/5050): Add auto-correction to `Style/ModuleFunction`. ([@garettarrowood][]) * [#5358](https://github.com/rubocop/rubocop/pull/5358): `--no-auto-gen-timestamp` CLI option suppresses the inclusion of the date and time it was generated in auto-generated config. ([@dominicsayers][]) * [#4274](https://github.com/rubocop/rubocop/issues/4274): Add new `Layout/EmptyComment` cop. ([@koic][]) @@ -3681,7 +4106,7 @@ * [#5582](https://github.com/rubocop/rubocop/issues/5582): Fix `end` alignment for variable assignment with line break after `=` in `Layout/EndAlignment`. ([@jonas054][]) * [#5602](https://github.com/rubocop/rubocop/pull/5602): Fix false positive for `Style/ColonMethodCall` when using Java package namespace. ([@koic][]) * [#5603](https://github.com/rubocop/rubocop/pull/5603): Fix falsey offense for `Style/RedundantSelf` with pseudo variables. ([@pocke][]) -* [#5547](https://github.com/rubocop/rubocop/issues/5547): Fix auto-correction of of `Layout/BlockEndNewline` when there is top level code outside of a class. ([@rrosenblum][]) +* [#5547](https://github.com/rubocop/rubocop/issues/5547): Fix auto-correction of `Layout/BlockEndNewline` when there is top level code outside of a class. ([@rrosenblum][]) * [#5599](https://github.com/rubocop/rubocop/issues/5599): Fix the suggestion being used by `Lint/NumberConversion` to use base 10 with Integer. ([@rrosenblum][]) * [#5534](https://github.com/rubocop/rubocop/issues/5534): Fix `Style/EachWithObject` auto-correction leaves an empty line. ([@flyerhzm][]) * Fix `Layout/EmptyLinesAroundAccessModifier` false-negative when next string after access modifier started with end. ([@unkmas][]) @@ -3849,7 +4274,7 @@ ### Bug fixes -* [#3312](https://github.com/rubocop/rubocop/issues/3312): Make `Rails/Date` Correct false positive on `#to_time` for strings ending in UTC-"Z".([@erikdstock][]) +* [#3312](https://github.com/rubocop/rubocop/issues/3312): Make `Rails/Date` Correct false positive on `#to_time` for strings ending in UTC-"Z". ([@erikdstock][]) * [#4741](https://github.com/rubocop/rubocop/issues/4741): Make `Style/SafeNavigation` correctly exclude methods called without dot. ([@drenmi][]) * [#4740](https://github.com/rubocop/rubocop/issues/4740): Make `Lint/RescueWithoutErrorClass` aware of modifier form `rescue`. ([@drenmi][]) * [#4745](https://github.com/rubocop/rubocop/issues/4745): Make `Style/SafeNavigation` ignore negated continuations. ([@drenmi][]) @@ -4294,7 +4719,7 @@ ### Changes -* [#3601](https://github.com/rubocop/rubocop/pull/3601): Change default args for `Style/SingleLineBlockParams`. This cop checks that `reduce` and `inject` use the variable names `a` and `e` for block arguments. These defaults are uncommunicative variable names and thus conflict with the ["Uncommunicative Variable Name" check in Reek](https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md). Default args changed to `acc` and `elem`.([@jessieay][]) +* [#3601](https://github.com/rubocop/rubocop/pull/3601): Change default args for `Style/SingleLineBlockParams`. This cop checks that `reduce` and `inject` use the variable names `a` and `e` for block arguments. These defaults are uncommunicative variable names and thus conflict with the ["Uncommunicative Variable Name" check in Reek](https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md). Default args changed to `acc` and `elem`. ([@jessieay][]) * [#3645](https://github.com/rubocop/rubocop/pull/3645): Fix bug with empty case when nodes in `Style/RedundantReturn`. ([@tiagocasanovapt][]) * [#3263](https://github.com/rubocop/rubocop/issues/3263): Fix auto-correct of if statements inside of unless else statements in `Style/ConditionalAssignment`. ([@rrosenblum][]) * Bump default Ruby version to 2.1. ([@drenmi][]) @@ -4775,7 +5200,7 @@ * [#2721](https://github.com/rubocop/rubocop/issues/2721): Do not register an offense for constants wrapped in parentheses passed to `rescue` in `Style/RedundantParentheses`. ([@rrosenblum][]) * [#2742](https://github.com/rubocop/rubocop/issues/2742): Fix `Style/TrailingCommaInArguments` & `Style/TrailingCommaInLiteral` for inline single element arrays. ([@annih][]) * [#2768](https://github.com/rubocop/rubocop/issues/2768): Allow parentheses after keyword `not` in `Style/MethodCallParentheses`. ([@lumeet][]) -* [#2758](https://github.com/rubocop/rubocop/issues/2758): Allow leading underscores in camel case variable names.([@mmcguinn][]) +* [#2758](https://github.com/rubocop/rubocop/issues/2758): Allow leading underscores in camel case variable names. ([@mmcguinn][]) ### Changes @@ -5141,7 +5566,7 @@ * [#1973](https://github.com/rubocop/rubocop/issues/1973): Do not register an offense in `Performance/Detect` when `select` is called on `Enumerable::Lazy`. ([@palkan][]) * [#2015](https://github.com/rubocop/rubocop/issues/2015): Fix bug occurring for auto-correction of a misaligned `end` in a file with only one method. ([@jonas054][]) * Allow string interpolation segments inside single quoted string literals when double quotes are preferred. ([@segiddins][]) -* [#2026](https://github.com/rubocop/rubocop/issues/2026): Allow `Time.current` when style is "acceptable".([@palkan][]) +* [#2026](https://github.com/rubocop/rubocop/issues/2026): Allow `Time.current` when style is "acceptable". ([@palkan][]) * [#2029](https://github.com/rubocop/rubocop/issues/2029): Fix bug where `Style/RedundantReturn` auto-corrects returning implicit hashes to invalid syntax. ([@rrosenblum][]) * [#2021](https://github.com/rubocop/rubocop/issues/2021): Fix bug in `Style/BlockDelimiters` when a `semantic` expression is used in an array or a range. ([@lumeet][]) * [#1992](https://github.com/rubocop/rubocop/issues/1992): Allow parentheses in assignment to a variable with the same name as the method's in `Style/MethodCallParentheses`. ([@lumeet][]) @@ -5149,7 +5574,7 @@ * [#2006](https://github.com/rubocop/rubocop/issues/2006): Fix crash in `Style/FirstParameterIndentation` in case of nested offenses. ([@unmanbearpig][]) * [#2059](https://github.com/rubocop/rubocop/issues/2059): Don't check for trivial accessors in modules. ([@bbatsov][]) * Add proper punctuation to the end of offense messages, where it is missing. ([@lumeet][]) -* [#2071](https://github.com/rubocop/rubocop/pull/2071): Keep line breaks in place on WordArray auto-correct.([@unmanbearpig][]) +* [#2071](https://github.com/rubocop/rubocop/pull/2071): Keep line breaks in place on WordArray auto-correct. ([@unmanbearpig][]) * [#2075](https://github.com/rubocop/rubocop/pull/2075): Properly correct `Style/PercentLiteralDelimiters` with escape characters in them. ([@rrosenblum][]) * [#2023](https://github.com/rubocop/rubocop/issues/2023): Avoid auto-correction corruption in `IndentationWidth`. ([@jonas054][]) * [#2080](https://github.com/rubocop/rubocop/issues/2080): Properly parse code in `Performance/Count` when calling `select..count` in a class that extends an enumerable. ([@rrosenblum][]) @@ -5943,7 +6368,7 @@ * [#661](https://github.com/rubocop/rubocop/issues/661): `EndAlignment` cop is now configurable for alignment with `keyword` (default) or `variable`. ([@jonas054][]) * Allow to overwrite the severity of a cop with the new `Severity` param. ([@codez][]) * New cop `FlipFlop` checks for flip flops. ([@agrimm][]) -* [#577](https://github.com/rubocop/rubocop/issues/577): Introduced `MethodDefParentheses` to allow for for requiring either parentheses or no parentheses in method definitions. Replaces `DefWithoutParentheses`. ([@skanev][]) +* [#577](https://github.com/rubocop/rubocop/issues/577): Introduced `MethodDefParentheses` to allow for requiring either parentheses or no parentheses in method definitions. Replaces `DefWithoutParentheses`. ([@skanev][]) * [#693](https://github.com/rubocop/rubocop/pull/693): Generation of parameter values (i.e., not only `Enabled: false`) in `rubocop-todo.yml` by the `--auto-gen-config` option is now supported for some cops. ([@jonas054][]) * New cop `AccessorMethodName` checks accessor method names for non-idiomatic names like `get_attribute` and `set_attribute`. ([@bbatsov][]) * New cop `PredicateName` checks the names of predicate methods for non-idiomatic names like `is_something`, `has_something`, etc. ([@bbatsov][]) @@ -6000,7 +6425,7 @@ ### Changes * [#557](https://github.com/rubocop/rubocop/pull/557): Configuration files for excluded files are no longer loaded. ([@jonas054][]) -* [#571](https://github.com/rubocop/rubocop/pull/571): The default rake task now runs RuboCop over itself! ([@nevir][]) +* [#571](https://github.com/rubocop/rubocop/pull/571): The default rake task now runs RuboCop over itself!. ([@nevir][]) * Encoding errors are reported as fatal offences rather than printed with red text. ([@jonas054][]) * `AccessControl` cop is now configurable with the `EnforcedStyle` option. ([@sds][]) * Split `AccessControl` cop to `AccessModifierIndentation` and `EmptyLinesAroundAccessModifier`. ([@bbatsov][]) @@ -7136,3 +7561,21 @@ [@Bhacaz]: https://github.com/Bhacaz [@naveg]: https://github.com/naveg [@lucthev]: https://github.com/lucthev +[@nevans]: https://github.com/nevans +[@iMacTia]: https://github.com/iMacTia +[@rwstauner]: https://github.com/rwstauner +[@catwomey]: https://github.com/catwomey +[@alexeyschepin]: https://github.com/alexeyschepin +[@meric426]: https://github.com/meric426 +[@loveo]: https://github.com/loveo +[@p0deje]: https://github.com/p0deje +[@bigzed]: https://github.com/bigzed +[@OwlKing]: https://github.com/OwlKing +[@ItsEcholot]: https://github.com/ItsEcholot +[@ymap]: https://github.com/ymap +[@aboutNisblee]: https://github.com/aboutNisblee +[@K-S-A]: https://github.com/K-S-A +[@earlopain]: https://github.com/earlopain +[@kpost]: https://github.com/kpost +[@marocchino]: https://github.com/marocchino +[@Strzesia]: https://github.com/Strzesia diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0093d18b405..5bc61d3d677b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,9 +17,9 @@ do so. ```console $ rubocop -V -1.52.1 (using Parser 2.7.2.0, rubocop-ast 1.1.1, running on ruby 2.7.2) [x86_64-linux] - - rubocop-performance 1.9.1 - - rubocop-rspec 2.0.0 +1.60.2 (using Parser 3.2.2.3, rubocop-ast 1.29.0, running on ruby 3.2.2) [x86_64-linux] + - rubocop-performance 1.18.0 + - rubocop-rspec 2.23.2 ``` * Include any relevant code to the issue summary. diff --git a/Gemfile b/Gemfile index eec4f74810a5..0458044019bb 100644 --- a/Gemfile +++ b/Gemfile @@ -10,9 +10,9 @@ gem 'bundler', '>= 1.15.0', '< 3.0' gem 'memory_profiler', platform: :mri gem 'rake', '~> 13.0' gem 'rspec', '~> 3.7' -gem 'rubocop-performance', '~> 1.18.0' +gem 'rubocop-performance', '~> 1.20.0' gem 'rubocop-rake', '~> 0.6.0' -gem 'rubocop-rspec', '~> 2.22.0' +gem 'rubocop-rspec', '~> 2.26.0' # Workaround for cc-test-reporter with SimpleCov 0.18. # Stop upgrading SimpleCov until the following issue will be resolved. # https://github.com/codeclimate/test-reporter/issues/418 diff --git a/LICENSE.txt b/LICENSE.txt index 9f6b36e842e4..aee79d6c1140 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2012-23 Bozhidar Batsov +Copyright (c) 2012-24 Bozhidar Batsov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index e5937a8133b2..7b11c9ee9a5f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- RuboCop Logo + RuboCop Logo

---------- @@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi in your `Gemfile`: ```rb -gem 'rubocop', '~> 1.52', require: false +gem 'rubocop', '~> 1.60', require: false ``` See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details. @@ -67,6 +67,8 @@ $ cd my/cool/ruby/project $ rubocop ``` +You can also use this magic in your favorite editor with RuboCop's [built-in LSP server](https://docs.rubocop.org/rubocop/usage/lsp.html). + ## Documentation You can read a lot more about RuboCop in its [official docs](https://docs.rubocop.org). @@ -246,5 +248,5 @@ RuboCop's changelog is available [here](CHANGELOG.md). ## Copyright -Copyright (c) 2012-2023 Bozhidar Batsov. See [LICENSE.txt](LICENSE.txt) for +Copyright (c) 2012-2024 Bozhidar Batsov. See [LICENSE.txt](LICENSE.txt) for further details. diff --git a/Rakefile b/Rakefile index e4cd84ca400b..2b16ab783e10 100644 --- a/Rakefile +++ b/Rakefile @@ -77,7 +77,7 @@ task documentation_syntax_check: :yard_for_generate_documentation do require 'parser/ruby25' require 'parser/ruby26' require 'parser/ruby27' - require 'parser/ruby32' + require 'parser/ruby33' ok = true YARD::Registry.load! @@ -108,7 +108,7 @@ task documentation_syntax_check: :yard_for_generate_documentation do elsif cop == RuboCop::Cop::Lint::NumberedParameterAssignment Parser::Ruby27.new(RuboCop::AST::Builder.new) else - Parser::Ruby32.new(RuboCop::AST::Builder.new) + Parser::Ruby33.new(RuboCop::AST::Builder.new) end parser.diagnostics.all_errors_are_fatal = true parser.parse(buffer) diff --git a/changelog/change_error_message_on_incorrect_namespace.md b/changelog/change_error_message_on_incorrect_namespace.md new file mode 100644 index 000000000000..2a319ba5b644 --- /dev/null +++ b/changelog/change_error_message_on_incorrect_namespace.md @@ -0,0 +1 @@ +* [#12641](https://github.com/rubocop/rubocop/pull/12641): Make error message clearer when the namespace is incorrect. ([@maruth-stripe][]) diff --git a/changelog/change_mark_style_raise_args_as_unsafe.md b/changelog/change_mark_style_raise_args_as_unsafe.md new file mode 100644 index 000000000000..15ae9d9de788 --- /dev/null +++ b/changelog/change_mark_style_raise_args_as_unsafe.md @@ -0,0 +1 @@ +* [#12637](https://github.com/rubocop/rubocop/pull/12637): Mark `Style/RaiseArgs` as unsafe. ([@r7kamura][]) diff --git a/changelog/change_source_order_for_target_ruby.md b/changelog/change_source_order_for_target_ruby.md new file mode 100644 index 000000000000..ece6a74860be --- /dev/null +++ b/changelog/change_source_order_for_target_ruby.md @@ -0,0 +1 @@ +* [#12645](https://github.com/rubocop/rubocop/pull/12645): Change source order for target ruby to check gemspec after RuboCop configuration. ([@jenshenny][]) diff --git a/changelog/fix_a_false_positive_for_layout_redundant_line_break.md b/changelog/fix_a_false_positive_for_layout_redundant_line_break.md deleted file mode 100644 index a27f3f96061a..000000000000 --- a/changelog/fix_a_false_positive_for_layout_redundant_line_break.md +++ /dev/null @@ -1 +0,0 @@ -* [#11949](https://github.com/rubocop/rubocop/issues/11949): Fix a false positive for `Layout/RedundantLineBreak` when using a line broken string. ([@koic][]) diff --git a/changelog/fix_an_error_for_lint_number_conversion.md b/changelog/fix_an_error_for_lint_number_conversion.md deleted file mode 100644 index 4375c57a207b..000000000000 --- a/changelog/fix_an_error_for_lint_number_conversion.md +++ /dev/null @@ -1 +0,0 @@ -* [#11946](https://github.com/rubocop/rubocop/pull/11946): Fix an error for `Lint/NumberConversion` when using multiple number conversion methods. ([@koic][]) diff --git a/changelog/fix_an_error_for_style_case_like_if.md b/changelog/fix_an_error_for_style_case_like_if.md new file mode 100644 index 000000000000..364a285ff35a --- /dev/null +++ b/changelog/fix_an_error_for_style_case_like_if.md @@ -0,0 +1 @@ +* [#12690](https://github.com/rubocop/rubocop/issues/12690): Fix an error for `Style/CaseLikeIf` when using `==` with literal and using ternary operator. ([@koic][]) diff --git a/changelog/fix_an_incorrect_autocorrect_for_lint_empty_conditional_body.md b/changelog/fix_an_incorrect_autocorrect_for_lint_empty_conditional_body.md new file mode 100644 index 000000000000..3020445682d7 --- /dev/null +++ b/changelog/fix_an_incorrect_autocorrect_for_lint_empty_conditional_body.md @@ -0,0 +1 @@ +* [#12668](https://github.com/rubocop/rubocop/issues/12668): Fix an incorrect autocorrect for `Lint/EmptyConditionalBody` when missing `if` body with conditional `else` body. ([@koic][]) diff --git a/changelog/fix_an_incorrect_autocorrect_for_style_map_compact_with_conditional_block.md b/changelog/fix_an_incorrect_autocorrect_for_style_map_compact_with_conditional_block.md new file mode 100644 index 000000000000..95221870820d --- /dev/null +++ b/changelog/fix_an_incorrect_autocorrect_for_style_map_compact_with_conditional_block.md @@ -0,0 +1 @@ +* [#12683](https://github.com/rubocop/rubocop/issues/12683): Fix an incorrect autocorrect for `Style/MapCompactWithConditionalBlock` when using guard clause with `next` implicitly nil. ([@koic][]) diff --git a/changelog/fix_an_incorrect_for_style_object_then.md b/changelog/fix_an_incorrect_for_style_object_then.md new file mode 100644 index 000000000000..cdc931c9375f --- /dev/null +++ b/changelog/fix_an_incorrect_for_style_object_then.md @@ -0,0 +1 @@ +* [#12693](https://github.com/rubocop/rubocop/issues/12693): Fix an incorrect autocorrect for `Style/ObjectThen` when using `yield_self` without receiver. ([@koic][]) diff --git a/changelog/fix_error_for_layout_redundant_line_break.md b/changelog/fix_error_for_layout_redundant_line_break.md new file mode 100644 index 000000000000..d5bb6abc74ac --- /dev/null +++ b/changelog/fix_error_for_layout_redundant_line_break.md @@ -0,0 +1 @@ +* [#12656](https://github.com/rubocop/rubocop/pull/12656): Fix an error for `Layout/RedundantLineBreak` when using index access call chained on multiline hash literal. ([@koic][]) diff --git a/changelog/fix_error_for_style_conditional_assignment.md b/changelog/fix_error_for_style_conditional_assignment.md deleted file mode 100644 index ae2cd2e5a5df..000000000000 --- a/changelog/fix_error_for_style_conditional_assignment.md +++ /dev/null @@ -1 +0,0 @@ -* [#11947](https://github.com/rubocop/rubocop/issues/11947): Fix an error for `Style/ConditionalAssignment` with an assignment that uses `if` branch bodies, which include a block. ([@koic][]) diff --git a/changelog/fix_error_for_style_multiline_ternary_operator.md b/changelog/fix_error_for_style_multiline_ternary_operator.md new file mode 100644 index 000000000000..d95c296446c1 --- /dev/null +++ b/changelog/fix_error_for_style_multiline_ternary_operator.md @@ -0,0 +1 @@ +* [#12691](https://github.com/rubocop/rubocop/issues/12691): Fix an error for `Style/MultilineTernaryOperator` when nesting multiline ternary operators. ([@koic][]) diff --git a/changelog/fix_false_negative_for_style_redundant_return.md b/changelog/fix_false_negative_for_style_redundant_return.md new file mode 100644 index 000000000000..a4d95077cc24 --- /dev/null +++ b/changelog/fix_false_negative_for_style_redundant_return.md @@ -0,0 +1 @@ +* [#12674](https://github.com/rubocop/rubocop/pull/12674): Fix false negatives for `Style/RedundantReturn` when using pattern matching. ([@koic][]) diff --git a/changelog/fix_false_negatives_for_lint_redundant_safe_navigation.md b/changelog/fix_false_negatives_for_lint_redundant_safe_navigation.md new file mode 100644 index 000000000000..08e4f28ab123 --- /dev/null +++ b/changelog/fix_false_negatives_for_lint_redundant_safe_navigation.md @@ -0,0 +1 @@ +* [#12673](https://github.com/rubocop/rubocop/pull/12673): Fix false negatives for `Lint/RedundantSafeNavigation` when using safe navigation operator for literal receiver. ([@koic][]) diff --git a/changelog/fix_false_positive_for_lint_void.md b/changelog/fix_false_positive_for_lint_void.md new file mode 100644 index 000000000000..02713b25af8f --- /dev/null +++ b/changelog/fix_false_positive_for_lint_void.md @@ -0,0 +1 @@ +* [#12687](https://github.com/rubocop/rubocop/issues/12687): Fix a false positive for `Lint/Void` when `each` block with conditional expressions that has multiple statements. ([@koic][]) diff --git a/changelog/fix_false_positives_for_style_inverse_methods.md b/changelog/fix_false_positives_for_style_inverse_methods.md new file mode 100644 index 000000000000..67968dc866a8 --- /dev/null +++ b/changelog/fix_false_positives_for_style_inverse_methods.md @@ -0,0 +1 @@ +* [#12649](https://github.com/rubocop/rubocop/issues/12649): Fix false positives for `Style/InverseMethods` when using relational comparison operator with safe navigation. ([@koic][]) diff --git a/changelog/fix_fix_an_error_for_style_redundant_regexp_escape.md b/changelog/fix_fix_an_error_for_style_redundant_regexp_escape.md deleted file mode 100644 index 24559f29988e..000000000000 --- a/changelog/fix_fix_an_error_for_style_redundant_regexp_escape.md +++ /dev/null @@ -1 +0,0 @@ -* [#10902](https://github.com/rubocop/rubocop/issues/10902): Fix an error for `Style/RedundantRegexpEscape` string with invalid byte sequence in UTF-8. ([@ydah][]) diff --git a/changelog/fix_numblock_regressions_in_omit_parentheses.md b/changelog/fix_numblock_regressions_in_omit_parentheses.md new file mode 100644 index 000000000000..39394afe206e --- /dev/null +++ b/changelog/fix_numblock_regressions_in_omit_parentheses.md @@ -0,0 +1 @@ +* [#12648](https://github.com/rubocop/rubocop/pull/12648): Fix numblock regressions in `omit_parentheses` `Style/MethodCallWithArgsParentheses`. ([@gsamokovarov][]) diff --git a/changelog/new_add_editor_mode_option.md b/changelog/new_add_editor_mode_option.md new file mode 100644 index 000000000000..58be37dcf62e --- /dev/null +++ b/changelog/new_add_editor_mode_option.md @@ -0,0 +1 @@ +* [#12682](https://github.com/rubocop/rubocop/issues/12682): Add `--editor-mode` CLI option. ([@koic][]) diff --git a/changelog/new_extend_autocorrect_option_for_lsp.md b/changelog/new_extend_autocorrect_option_for_lsp.md new file mode 100644 index 000000000000..f6eea4363cd1 --- /dev/null +++ b/changelog/new_extend_autocorrect_option_for_lsp.md @@ -0,0 +1 @@ +* [#12657](https://github.com/rubocop/rubocop/pull/12657): Support `AutoCorrect: contextual` option for LSP. ([@koic][]) diff --git a/changelog/new_make_offense_count_formatter_display_autocorrection_info.md b/changelog/new_make_offense_count_formatter_display_autocorrection_info.md new file mode 100644 index 000000000000..47b1fb348d2b --- /dev/null +++ b/changelog/new_make_offense_count_formatter_display_autocorrection_info.md @@ -0,0 +1 @@ +* [#12273](https://github.com/rubocop/rubocop/issues/12273): Make `OffenseCountFormatter` display autocorrection information. ([@koic][]) diff --git a/changelog/new_publish_lsp_enable_api.md b/changelog/new_publish_lsp_enable_api.md new file mode 100644 index 000000000000..501c889979e2 --- /dev/null +++ b/changelog/new_publish_lsp_enable_api.md @@ -0,0 +1 @@ +* [#12679](https://github.com/rubocop/rubocop/pull/12679): Publish `RuboCop::LSP.enable` API to enable LSP mode. ([@koic][]) diff --git a/changelog/new_support_built_in_lsp.md b/changelog/new_support_built_in_lsp.md deleted file mode 100644 index a5b255a520e5..000000000000 --- a/changelog/new_support_built_in_lsp.md +++ /dev/null @@ -1 +0,0 @@ -* [#11926](https://github.com/rubocop/rubocop/pull/11926): Support built-in LSP server. ([@koic][]) diff --git a/changelog/new_support_dot_config.md b/changelog/new_support_dot_config.md new file mode 100644 index 000000000000..dfdc1e896a87 --- /dev/null +++ b/changelog/new_support_dot_config.md @@ -0,0 +1 @@ +* [#12699](https://github.com/rubocop/rubocop/issues/12699): Support searching for `.rubocop.yml` and `rubocop/config.yml` in compliance with dot-config. ([@koic][]) diff --git a/codespell.txt b/codespell.txt index d8d7bcd5bd7a..70c149a49703 100644 --- a/codespell.txt +++ b/codespell.txt @@ -4,6 +4,7 @@ enviromnent filetest fo irregardless +mange ofo thi upto diff --git a/config/default.yml b/config/default.yml index 58e67c0ab3b0..29c7d57ee7e6 100644 --- a/config/default.yml +++ b/config/default.yml @@ -30,6 +30,7 @@ AllCops: - '**/*.rbx' - '**/*.ru' - '**/*.ruby' + - '**/*.schema' - '**/*.spec' - '**/*.thor' - '**/*.watchr' @@ -55,6 +56,7 @@ AllCops: - '**/Puppetfile' - '**/Rakefile' - '**/rakefile' + - '**/Schemafile' - '**/Snapfile' - '**/Steepfile' - '**/Thorfile' @@ -171,6 +173,16 @@ Bundler/DuplicatedGem: - '**/Gemfile' - '**/gems.rb' +Bundler/DuplicatedGroup: + Description: 'Checks for duplicate group entries in Gemfile.' + Enabled: true + Severity: warning + VersionAdded: '1.56' + Include: + - '**/*.gemfile' + - '**/Gemfile' + - '**/gems.rb' + Bundler/GemComment: Description: 'Add a comment describing each gem.' Enabled: false @@ -467,7 +479,9 @@ Layout/ClassStructure: Description: 'Enforces a configured order of definitions within a class body.' StyleGuide: '#consistent-classes' Enabled: false + SafeAutoCorrect: false VersionAdded: '0.52' + VersionChanged: '1.53' Categories: module_inclusion: - include @@ -542,7 +556,9 @@ Layout/ElseAlignment: Layout/EmptyComment: Description: 'Checks empty comment.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.53' + VersionChanged: '<>' AllowBorderComment: true AllowMarginComment: true @@ -575,6 +591,8 @@ Layout/EmptyLineBetweenDefs: EmptyLineBetweenMethodDefs: true EmptyLineBetweenClassDefs: true EmptyLineBetweenModuleDefs: true + # `DefLikeMacros` takes the name of any macro that you want to treat like a def. + DefLikeMacros: [] # `AllowAdjacentOneLineDefs` means that single line method definitions don't # need an empty line between them. `true` by default. AllowAdjacentOneLineDefs: true @@ -1335,6 +1353,10 @@ Layout/SpaceAroundOperators: SupportedStylesForExponentOperator: - space - no_space + EnforcedStyleForRationalLiterals: no_space + SupportedStylesForRationalLiterals: + - space + - no_space Layout/SpaceBeforeBlockBraces: Description: >- @@ -1530,7 +1552,6 @@ Lint/AmbiguousBlockAssociation: Description: >- Checks for ambiguous block association with method when param passed without parentheses. - StyleGuide: '#syntax' Enabled: true VersionAdded: '0.48' VersionChanged: '1.13' @@ -1806,16 +1827,18 @@ Lint/EmptyClass: Lint/EmptyConditionalBody: Description: 'Checks for the presence of `if`, `elsif` and `unless` branches without a body.' Enabled: true + AutoCorrect: contextual SafeAutoCorrect: false AllowComments: true VersionAdded: '0.89' - VersionChanged: '1.34' + VersionChanged: '<>' Lint/EmptyEnsure: Description: 'Checks for empty ensure block.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.10' - VersionChanged: '0.48' + VersionChanged: '<>' Lint/EmptyExpression: Description: 'Checks for empty expressions.' @@ -1837,8 +1860,9 @@ Lint/EmptyInPattern: Lint/EmptyInterpolation: Description: 'Checks for empty string interpolation.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.20' - VersionChanged: '0.45' + VersionChanged: '<>' Lint/EmptyWhen: Description: 'Checks for `when` branches with empty bodies.' @@ -1944,6 +1968,12 @@ Lint/InterpolationCheck: VersionAdded: '0.50' VersionChanged: '1.40' +Lint/ItWithoutArgumentsInBlock: + Description: 'Checks uses of `it` calls without arguments in block.' + Reference: 'https://bugs.ruby-lang.org/issues/18980' + Enabled: pending + VersionAdded: '1.59' + Lint/LambdaWithoutLiteralBlock: Description: 'Checks uses of lambda without a literal block.' Enabled: pending @@ -1954,6 +1984,11 @@ Lint/LiteralAsCondition: Enabled: true VersionAdded: '0.51' +Lint/LiteralAssignmentInCondition: + Description: 'Checks for literal assignments in the conditions.' + Enabled: pending + VersionAdded: '1.58' + Lint/LiteralInInterpolation: Description: 'Checks for literals used in interpolation.' Enabled: true @@ -1988,9 +2023,16 @@ Lint/MissingSuper: Checks for the presence of constructors and lifecycle callbacks without calls to `super`. Enabled: true + AllowedParentClasses: [] VersionAdded: '0.89' VersionChanged: '1.4' +Lint/MixedCaseRange: + Description: 'Checks for mixed-case character ranges since they include likely unintended characters.' + Enabled: pending + SafeAutoCorrect: false + VersionAdded: '1.53' + Lint/MixedRegexpCaptureTypes: Description: 'Do not mix named captures and numbered captures in a Regexp literal.' Enabled: true @@ -2140,10 +2182,17 @@ Lint/RedundantDirGlobSort: VersionChanged: '1.26' SafeAutoCorrect: false +Lint/RedundantRegexpQuantifiers: + Description: 'Checks for redundant quantifiers in Regexps.' + Enabled: pending + VersionAdded: '1.53' + Lint/RedundantRequireStatement: Description: 'Checks for unnecessary `require` statement.' Enabled: true + SafeAutoCorrect: false VersionAdded: '0.76' + VersionChanged: '1.57' Lint/RedundantSafeNavigation: Description: 'Checks for redundant safe navigation calls.' @@ -2351,7 +2400,9 @@ Lint/TopLevelReturnWithArgument: Lint/TrailingCommaInAttributeDeclaration: Description: 'Checks for trailing commas in attribute declarations.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.90' + VersionChanged: '<>' Lint/TripleQuotes: Description: 'Checks for useless triple quote constructs.' @@ -2411,8 +2462,9 @@ Lint/UnusedBlockArgument: Description: 'Checks for unused block arguments.' StyleGuide: '#underscore-unused-vars' Enabled: true + AutoCorrect: contextual VersionAdded: '0.21' - VersionChanged: '0.22' + VersionChanged: '<>' IgnoreEmptyBlocks: true AllowUnusedKeywordArguments: false @@ -2420,8 +2472,9 @@ Lint/UnusedMethodArgument: Description: 'Checks for unused method arguments.' StyleGuide: '#underscore-unused-vars' Enabled: true + AutoCorrect: contextual VersionAdded: '0.21' - VersionChanged: '0.81' + VersionChanged: '<>' AllowUnusedKeywordArguments: false IgnoreEmptyMethods: true IgnoreNotImplementedMethods: true @@ -2445,8 +2498,9 @@ Lint/UriRegexp: Lint/UselessAccessModifier: Description: 'Checks for useless access modifiers.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.20' - VersionChanged: '0.83' + VersionChanged: '<>' ContextCreatingMethods: [] MethodCreatingMethods: [] @@ -2454,8 +2508,9 @@ Lint/UselessAssignment: Description: 'Checks for useless assignment to a local variable.' StyleGuide: '#underscore-unused-vars' Enabled: true + AutoCorrect: contextual VersionAdded: '0.11' - VersionChanged: '1.51' + VersionChanged: '<>' SafeAutoCorrect: false Lint/UselessElseWithoutRescue: @@ -2467,8 +2522,9 @@ Lint/UselessElseWithoutRescue: Lint/UselessMethodDefinition: Description: 'Checks for useless method definitions.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.90' - VersionChanged: '0.91' + VersionChanged: '<>' Safe: false Lint/UselessRescue: @@ -2491,13 +2547,17 @@ Lint/UselessSetterCall: Lint/UselessTimes: Description: 'Checks for useless `Integer#times` calls.' Enabled: true - VersionAdded: '0.91' Safe: false + AutoCorrect: contextual + VersionAdded: '0.91' + VersionChanged: '<>' Lint/Void: Description: 'Possible use of operator/literal/variable in void context.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.9' + VersionChanged: '<>' CheckForMethodsWithNoSideEffects: false #################### Metrics ############################### @@ -3055,7 +3115,20 @@ Style/ArgumentsForwarding: StyleGuide: '#arguments-forwarding' Enabled: pending AllowOnlyRestArgument: true + UseAnonymousForwarding: true + RedundantRestArgumentNames: + - args + - arguments + RedundantKeywordRestArgumentNames: + - kwargs + - options + - opts + RedundantBlockArgumentNames: + - blk + - block + - proc VersionAdded: '1.1' + VersionChanged: '1.58' Style/ArrayCoercion: Description: >- @@ -3066,6 +3139,13 @@ Style/ArrayCoercion: Enabled: false VersionAdded: '0.88' +Style/ArrayFirstLast: + Description: 'Use `arr.first` and `arr.last` instead of `arr[0]` and `arr[-1]`.' + Reference: '#first-and-last' + Enabled: false + VersionAdded: '1.58' + Safe: false + Style/ArrayIntersect: Description: 'Use `array1.intersect?(array2)` instead of `(array1 & array2).any?`.' Enabled: 'pending' @@ -3326,7 +3406,9 @@ Style/ClassEqualityComparison: Description: 'Enforces the use of `Object#instance_of?` instead of class comparison for equality.' StyleGuide: '#instance-of-vs-class-comparison' Enabled: true + SafeAutoCorrect: false VersionAdded: '0.93' + VersionChanged: '1.57' AllowedMethods: - == - equal? @@ -3381,6 +3463,7 @@ Style/CollectionMethods: PreferredMethods: collect: 'map' collect!: 'map!' + collect_concat: 'flat_map' inject: 'reduce' detect: 'find' find_all: 'select' @@ -3634,8 +3717,9 @@ Style/EmptyCaseCondition: Style/EmptyElse: Description: 'Avoid empty else-clauses.' Enabled: true + AutoCorrect: contextual VersionAdded: '0.28' - VersionChanged: '0.32' + VersionChanged: '<>' EnforcedStyle: both # empty - warn only on empty `else` # nil - warn on `else` with nil in it @@ -3649,7 +3733,9 @@ Style/EmptyElse: Style/EmptyHeredoc: Description: 'Checks for using empty heredoc to reduce redundancy.' Enabled: pending + AutoCorrect: contextual VersionAdded: '1.32' + VersionChanged: '<>' Style/EmptyLambdaParameter: Description: 'Omit parens for empty lambda parameters.' @@ -3667,7 +3753,9 @@ Style/EmptyMethod: Description: 'Checks the formatting of empty method definitions.' StyleGuide: '#no-single-line-methods' Enabled: true + AutoCorrect: contextual VersionAdded: '0.46' + VersionChanged: '<>' EnforcedStyle: compact SupportedStyles: - compact @@ -3899,8 +3987,9 @@ Style/HashConversion: Description: 'Avoid Hash[] in favor of ary.to_h or literal hashes.' StyleGuide: '#avoid-hash-constructor' Enabled: pending + SafeAutoCorrect: false VersionAdded: '1.10' - VersionChanged: '1.11' + VersionChanged: '1.55' AllowSplatArgument: true Style/HashEachMethods: @@ -4101,12 +4190,6 @@ Style/InvertibleUnlessCondition: :none?: :any? :even?: :odd? :odd?: :even? - # `ActiveSupport` defines some common inverse methods. They are listed below, - # and not enabled by default. - # :present?: :blank? - # :blank?: :present? - # :include?: :exclude? - # :exclude?: :include? Style/IpAddresses: Description: "Don't include literal IP addresses in code." @@ -4650,6 +4733,7 @@ Style/OperatorMethodCall: Style/OptionHash: Description: "Don't use option hashes when you can use keyword arguments." + StyleGuide: '#keyword-arguments-vs-option-hashes' Enabled: false VersionAdded: '0.33' VersionChanged: '0.34' @@ -4772,8 +4856,9 @@ Style/RaiseArgs: Description: 'Checks the arguments passed to raise/fail.' StyleGuide: '#exception-class-messages' Enabled: true + Safe: false VersionAdded: '0.14' - VersionChanged: '1.2' + VersionChanged: '<>' EnforcedStyle: exploded SupportedStyles: - compact # raise Exception.new(msg) @@ -4793,12 +4878,16 @@ Style/RedundantArgument: Enabled: pending Safe: false VersionAdded: '1.4' - VersionChanged: '1.40' + VersionChanged: '1.55' Methods: # Array#join join: '' # Array#sum sum: 0 + # Kernel.#exit + exit: true + # Kernel.#exit! + exit!: false # String#split split: ' ' # String#chomp @@ -4843,6 +4932,11 @@ Style/RedundantConstantBase: Enabled: pending VersionAdded: '1.40' +Style/RedundantCurrentDirectoryInPath: + Description: 'Checks for uses a redundant current directory in path.' + Enabled: pending + VersionAdded: '1.53' + Style/RedundantDoubleSplatHashBraces: Description: 'Checks for redundant uses of double splat hash braces.' Enabled: pending @@ -4865,7 +4959,7 @@ Style/RedundantFetchBlock: Description: >- Use `fetch(key, value)` instead of `fetch(key) { value }` when value has Numeric, Rational, Complex, Symbol or String type, `false`, `true`, `nil` or is a constant. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#hashfetch-with-argument-vs-hashfetch--block-code' + Reference: 'https://github.com/fastruby/fast-ruby#hashfetch-with-argument-vs-hashfetch--block-code' Enabled: true Safe: false # If enabled, this cop will autocorrect usages of @@ -4887,7 +4981,9 @@ Style/RedundantFilterChain: Identifies usages of `any?`, `empty?`, `none?` or `one?` predicate methods chained to `select`/`filter`/`find_all` and change them to use predicate method instead. Enabled: pending + SafeAutoCorrect: false VersionAdded: '1.52' + VersionChanged: '1.57' Style/RedundantFreeze: Description: "Checks usages of Object#freeze on immutable objects." @@ -4903,10 +4999,11 @@ Style/RedundantHeredocDelimiterQuotes: Style/RedundantInitialize: Description: 'Checks for redundant `initialize` methods.' Enabled: pending + AutoCorrect: contextual Safe: false AllowComments: true VersionAdded: '1.27' - VersionChanged: '1.28' + VersionChanged: '<>' Style/RedundantInterpolation: Description: 'Checks for strings that are just an interpolated expression.' @@ -4931,6 +5028,11 @@ Style/RedundantPercentQ: Enabled: true VersionAdded: '0.76' +Style/RedundantRegexpArgument: + Description: 'Identifies places where argument can be replaced from a deterministic regexp to a string.' + Enabled: pending + VersionAdded: '1.53' + Style/RedundantRegexpCharacterClass: Description: 'Checks for unnecessary single-element Regexp character classes.' Enabled: true @@ -5043,6 +5145,15 @@ Style/ReturnNil: - return_nil VersionAdded: '0.50' +Style/ReturnNilInPredicateMethodDefinition: + Description: 'Checks if uses of `return` or `return nil` in predicate method definition.' + StyleGuide: '#bool-methods-qmark' + Enabled: pending + SafeAutoCorrect: false + AllowedMethods: [] + AllowedPatterns: [] + VersionAdded: '1.53' + Style/SafeNavigation: Description: >- Transforms usages of a method call safeguarded by @@ -5070,7 +5181,7 @@ Style/Sample: Description: >- Use `sample` instead of `shuffle.first`, `shuffle.last`, and `shuffle[Integer]`. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' + Reference: 'https://github.com/fastruby/fast-ruby#arrayshufflefirst-vs-arraysample-code' Enabled: true VersionAdded: '0.30' @@ -5135,6 +5246,12 @@ Style/SingleLineBlockParams: - acc - elem +Style/SingleLineDoEndBlock: + Description: 'Checks for single-line `do`...`end` blocks.' + StyleGuide: '#single-line-do-end-block' + Enabled: pending + VersionAdded: '1.57' + Style/SingleLineMethods: Description: 'Avoid single-line methods.' StyleGuide: '#no-single-line-methods' @@ -5144,7 +5261,8 @@ Style/SingleLineMethods: AllowIfMethodIsEmpty: true Style/SlicingWithRange: - Description: 'Checks array slicing is done with endless ranges when suitable.' + Description: 'Checks array slicing is done with redundant, endless, and beginless ranges when suitable.' + StyleGuide: '#slicing-with-ranges' Enabled: true VersionAdded: '0.83' Safe: false @@ -5271,6 +5389,12 @@ Style/StructInheritance: VersionAdded: '0.29' VersionChanged: '1.20' +Style/SuperWithArgsParentheses: + Description: 'Use parentheses for `super` with arguments.' + StyleGuide: '#super-with-args' + Enabled: pending + VersionAdded: '1.58' + Style/SwapValues: Description: 'Enforces the use of shorthand-style swapping of 2 variables.' StyleGuide: '#values-swapping' @@ -5527,6 +5651,11 @@ Style/WordArray: # The regular expression `WordRegex` decides what is considered a word. WordRegex: !ruby/regexp '/\A(?:\p{Word}|\p{Word}-\p{Word}|\n|\t)+\z/' +Style/YAMLFileRead: + Description: 'Checks for the use of `YAML.load`, `YAML.safe_load`, and `YAML.parse` with `File.read` argument.' + Enabled: pending + VersionAdded: '1.53' + Style/YodaCondition: Description: 'Forbid or enforce yoda conditions.' Reference: 'https://en.wikipedia.org/wiki/Yoda_conditions' diff --git a/config/obsoletion.yml b/config/obsoletion.yml index 80f7b20ace6f..7f1e16156a23 100644 --- a/config/obsoletion.yml +++ b/config/obsoletion.yml @@ -224,6 +224,11 @@ changed_parameters: - AllowedMethods - AllowedPatterns severity: warning + - cops: Style/ArgumentsForwarding + parameters: AllowOnlyRestArgument + reason: "`AllowOnlyRestArgument` has no effect with TargetRubyVersion >= 3.2." + severity: warning + minimum_ruby_version: 3.2 # Enforced styles that have been removed or replaced changed_enforced_styles: diff --git a/docs/modules/ROOT/pages/about/history.adoc b/docs/modules/ROOT/pages/about/history.adoc index 7509a6b31b17..7a302e39d71f 100644 --- a/docs/modules/ROOT/pages/about/history.adoc +++ b/docs/modules/ROOT/pages/about/history.adoc @@ -4,7 +4,7 @@ You don't know where you're going until you know where you've been. RuboCop was created by Bozhidar Batsov in the spring of 2012, as an attempt -to make it easy to apply consistently the guidelines from the community Ruby Style Guide. +to make it easy to apply consistently the guidelines from the community Ruby Style Guide.footnote:[This is the https://github.com/rubocop/ruby-style-guide/issues/54[GitHub issue] that started it all.] == Notable Milestones diff --git a/docs/modules/ROOT/pages/about/license.adoc b/docs/modules/ROOT/pages/about/license.adoc index e6530e752dba..3dbd80c93000 100644 --- a/docs/modules/ROOT/pages/about/license.adoc +++ b/docs/modules/ROOT/pages/about/license.adoc @@ -13,4 +13,4 @@ All the original Ruby code is available under the terms of the MIT license. == Copyright -Copyright (c) 2012-2023 Bozhidar Batsov and RuboCop contributors. +Copyright (c) 2012-2024 Bozhidar Batsov and RuboCop contributors. diff --git a/docs/modules/ROOT/pages/compatibility.adoc b/docs/modules/ROOT/pages/compatibility.adoc index df1ee9713a74..51ec36e9346c 100644 --- a/docs/modules/ROOT/pages/compatibility.adoc +++ b/docs/modules/ROOT/pages/compatibility.adoc @@ -13,8 +13,9 @@ NOTE: RuboCop might be working with other Ruby implementations as well, but it's == Support Matrix -RuboCop generally aims to follow MRI's own support policy - meaning RuboCop would support all officially supported MRI releases.footnote:[Typically the last 3 releases.] To give people extra time for a smooth transition, we've customarily provided support for about one year after EOL of MRI version. -This means that if Ruby 2.7 reaches its EOL in Spring 2023, it would be supported by RuboCop (at least) until Spring 2024.footnote:[At the core team's discretion this policy might be waived aside for MRI releases causing significant maintenance overhead.] +RuboCop generally aims to follow MRI's own support policy - meaning RuboCop would support all officially supported MRI releases.footnote:[Typically the last 3 releases.] To give people extra time for a smooth transition, we've customarily provided support for about one year after EOL of MRI version. footnote:[At the core team's discretion this policy might be waived aside for MRI releases causing significant maintenance overhead.] + +NOTE: There are major version incompatibilities between Ruby 2.7 and Ruby 3.0, and the latest stable version of Ruby on Rails, 7.1, still supports Ruby 2.7. Therefore, it might be early for RuboCop to drop support for Ruby 2.7 at this stage. As long as Rails 7.1 is in the "Bug Fix" phase of the https://guides.rubyonrails.org/maintenance_policy.html[Maintenance Policy for Ruby on Rails], RuboCop will continue supporting Ruby 2.7. The following table is the runtime support matrix. @@ -33,7 +34,7 @@ The following table is the runtime support matrix. | 3.0 | - | 3.1 | - | 3.2 | - -| 3.3 (experimental) | - +| 3.3 | - |=== RuboCop targets Ruby 2.0+ code analysis since RuboCop 1.30. It restored code analysis support that had been removed earlier by mistake, together with dropping runtime support for unsupported Ruby versions. diff --git a/docs/modules/ROOT/pages/configuration.adoc b/docs/modules/ROOT/pages/configuration.adoc index a6598f327ced..9da64132fa14 100644 --- a/docs/modules/ROOT/pages/configuration.adoc +++ b/docs/modules/ROOT/pages/configuration.adoc @@ -29,10 +29,12 @@ RuboCop will start looking for the configuration file in the directory where the inspected file is and continue its way up to the root directory. If it cannot be found until reaching the project's root directory, then it will -be searched for in the user's global config locations, which consists of a +be searched for in the https://dot-config.github.io[.config directory of the project root] +and the user's global config locations. The user's global config locations consist of a dotfile or a config file inside the https://specifications.freedesktop.org/basedir-spec/latest/index.html[XDG Base Directory specification]. +* `.config/.rubocop.yml` or `.config/rubocop/config.yml` at the project root * `~/.rubocop.yml` * `$XDG_CONFIG_HOME/rubocop/config.yml` (expands to `~/.config/rubocop/config.yml` if `$XDG_CONFIG_HOME` is not set) @@ -46,6 +48,8 @@ files: * `/path/to/project/lib/utils/.rubocop.yml` * `/path/to/project/lib/.rubocop.yml` * `/path/to/project/.rubocop.yml` +* `/path/to/project/.config/.rubocop.yml` +* `/path/to/project/.config/rubocop/config.yml` * `/.rubocop.yml` * `~/.rubocop.yml` * `~/.config/rubocop/config.yml` @@ -567,13 +571,46 @@ These details will only be seen when RuboCop is run with the `--extra-details` f === AutoCorrect -Cops that support the `--autocorrect` option can have that support -disabled. For example: +Cops that support the `--autocorrect` option offer flexible settings for autocorrection. +These settings can be specified in the configuration file as follows: + +- `always` +- `contextual` +- `disabled` + +==== `always (Default)` + +This setting enables autocorrection always by default. For backward compatibility, `true` is treated the same as `always`. + +[source,yaml] +---- +Style/PerlBackrefs: + AutoCorrect: always # or true +---- + +==== `contextual` + +This setting enables autocorrection when launched from the `rubocop` command, but it is not available through LSP. +e.g., `rubocop --lsp`, `rubocop --editor-mode`, or a program where `RuboCop::LSP.enable` has been applied. + +Inspections via the command line are treated as code that has been finalized. + +[source,yaml] +---- +Style/PerlBackrefs: + AutoCorrect: contextual +---- + +This setting prevents autocorrection during editing in the editor. + +==== `disabled` + +This setting disables autocorrection. For backward compatibility, `false` is treated the same as `disabled`. [source,yaml] ---- Style/PerlBackrefs: - AutoCorrect: false + AutoCorrect: disabled # or false ---- == Common configuration parameters @@ -610,7 +647,7 @@ AllCops: Otherwise, RuboCop will then check your project for a series of files where the version may be specified already. The files that will be looked for are -`.ruby-version`, `.tool-versions`, `Gemfile.lock`, and `*.gemspec`. +`*.gemspec`, `.ruby-version`, `.tool-versions`, and `Gemfile.lock`. If Gemspec file has an array for `required_ruby_version`, the lowest version will be used. If none of the files are found a default version value will be used. diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 9d03824c1eef..0aea3d2af73f 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -77,6 +77,7 @@ In the following section you find all available cops: === Department xref:cops_bundler.adoc[Bundler] * xref:cops_bundler.adoc#bundlerduplicatedgem[Bundler/DuplicatedGem] +* xref:cops_bundler.adoc#bundlerduplicatedgroup[Bundler/DuplicatedGroup] * xref:cops_bundler.adoc#bundlergemcomment[Bundler/GemComment] * xref:cops_bundler.adoc#bundlergemfilename[Bundler/GemFilename] * xref:cops_bundler.adoc#bundlergemversion[Bundler/GemVersion] @@ -252,12 +253,15 @@ In the following section you find all available cops: * xref:cops_lint.adoc#lintineffectiveaccessmodifier[Lint/IneffectiveAccessModifier] * xref:cops_lint.adoc#lintinheritexception[Lint/InheritException] * xref:cops_lint.adoc#lintinterpolationcheck[Lint/InterpolationCheck] +* xref:cops_lint.adoc#lintitwithoutargumentsinblock[Lint/ItWithoutArgumentsInBlock] * xref:cops_lint.adoc#lintlambdawithoutliteralblock[Lint/LambdaWithoutLiteralBlock] * xref:cops_lint.adoc#lintliteralascondition[Lint/LiteralAsCondition] +* xref:cops_lint.adoc#lintliteralassignmentincondition[Lint/LiteralAssignmentInCondition] * xref:cops_lint.adoc#lintliteralininterpolation[Lint/LiteralInInterpolation] * xref:cops_lint.adoc#lintloop[Lint/Loop] * xref:cops_lint.adoc#lintmissingcopenabledirective[Lint/MissingCopEnableDirective] * xref:cops_lint.adoc#lintmissingsuper[Lint/MissingSuper] +* xref:cops_lint.adoc#lintmixedcaserange[Lint/MixedCaseRange] * xref:cops_lint.adoc#lintmixedregexpcapturetypes[Lint/MixedRegexpCaptureTypes] * xref:cops_lint.adoc#lintmultiplecomparison[Lint/MultipleComparison] * xref:cops_lint.adoc#lintnestedmethoddefinition[Lint/NestedMethodDefinition] @@ -280,6 +284,7 @@ In the following section you find all available cops: * xref:cops_lint.adoc#lintredundantcopdisabledirective[Lint/RedundantCopDisableDirective] * xref:cops_lint.adoc#lintredundantcopenabledirective[Lint/RedundantCopEnableDirective] * xref:cops_lint.adoc#lintredundantdirglobsort[Lint/RedundantDirGlobSort] +* xref:cops_lint.adoc#lintredundantregexpquantifiers[Lint/RedundantRegexpQuantifiers] * xref:cops_lint.adoc#lintredundantrequirestatement[Lint/RedundantRequireStatement] * xref:cops_lint.adoc#lintredundantsafenavigation[Lint/RedundantSafeNavigation] * xref:cops_lint.adoc#lintredundantsplatexpansion[Lint/RedundantSplatExpansion] @@ -388,6 +393,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#styleandor[Style/AndOr] * xref:cops_style.adoc#styleargumentsforwarding[Style/ArgumentsForwarding] * xref:cops_style.adoc#stylearraycoercion[Style/ArrayCoercion] +* xref:cops_style.adoc#stylearrayfirstlast[Style/ArrayFirstLast] * xref:cops_style.adoc#stylearrayintersect[Style/ArrayIntersect] * xref:cops_style.adoc#stylearrayjoin[Style/ArrayJoin] * xref:cops_style.adoc#styleasciicomments[Style/AsciiComments] @@ -556,6 +562,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#styleredundantcondition[Style/RedundantCondition] * xref:cops_style.adoc#styleredundantconditional[Style/RedundantConditional] * xref:cops_style.adoc#styleredundantconstantbase[Style/RedundantConstantBase] +* xref:cops_style.adoc#styleredundantcurrentdirectoryinpath[Style/RedundantCurrentDirectoryInPath] * xref:cops_style.adoc#styleredundantdoublesplathashbraces[Style/RedundantDoubleSplatHashBraces] * xref:cops_style.adoc#styleredundanteach[Style/RedundantEach] * xref:cops_style.adoc#styleredundantexception[Style/RedundantException] @@ -569,6 +576,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#styleredundantlinecontinuation[Style/RedundantLineContinuation] * xref:cops_style.adoc#styleredundantparentheses[Style/RedundantParentheses] * xref:cops_style.adoc#styleredundantpercentq[Style/RedundantPercentQ] +* xref:cops_style.adoc#styleredundantregexpargument[Style/RedundantRegexpArgument] * xref:cops_style.adoc#styleredundantregexpcharacterclass[Style/RedundantRegexpCharacterClass] * xref:cops_style.adoc#styleredundantregexpconstructor[Style/RedundantRegexpConstructor] * xref:cops_style.adoc#styleredundantregexpescape[Style/RedundantRegexpEscape] @@ -584,6 +592,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#stylerescuemodifier[Style/RescueModifier] * xref:cops_style.adoc#stylerescuestandarderror[Style/RescueStandardError] * xref:cops_style.adoc#stylereturnnil[Style/ReturnNil] +* xref:cops_style.adoc#stylereturnnilinpredicatemethoddefinition[Style/ReturnNilInPredicateMethodDefinition] * xref:cops_style.adoc#stylesafenavigation[Style/SafeNavigation] * xref:cops_style.adoc#stylesample[Style/Sample] * xref:cops_style.adoc#styleselectbyregexp[Style/SelectByRegexp] @@ -593,6 +602,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#stylesignalexception[Style/SignalException] * xref:cops_style.adoc#stylesingleargumentdig[Style/SingleArgumentDig] * xref:cops_style.adoc#stylesinglelineblockparams[Style/SingleLineBlockParams] +* xref:cops_style.adoc#stylesinglelinedoendblock[Style/SingleLineDoEndBlock] * xref:cops_style.adoc#stylesinglelinemethods[Style/SingleLineMethods] * xref:cops_style.adoc#styleslicingwithrange[Style/SlicingWithRange] * xref:cops_style.adoc#stylesolenestedconditional[Style/SoleNestedConditional] @@ -608,6 +618,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#stylestringmethods[Style/StringMethods] * xref:cops_style.adoc#stylestrip[Style/Strip] * xref:cops_style.adoc#stylestructinheritance[Style/StructInheritance] +* xref:cops_style.adoc#stylesuperwithargsparentheses[Style/SuperWithArgsParentheses] * xref:cops_style.adoc#styleswapvalues[Style/SwapValues] * xref:cops_style.adoc#stylesymbolarray[Style/SymbolArray] * xref:cops_style.adoc#stylesymbolliteral[Style/SymbolLiteral] @@ -632,6 +643,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#stylewhileuntildo[Style/WhileUntilDo] * xref:cops_style.adoc#stylewhileuntilmodifier[Style/WhileUntilModifier] * xref:cops_style.adoc#stylewordarray[Style/WordArray] +* xref:cops_style.adoc#styleyamlfileread[Style/YAMLFileRead] * xref:cops_style.adoc#styleyodacondition[Style/YodaCondition] * xref:cops_style.adoc#styleyodaexpression[Style/YodaExpression] * xref:cops_style.adoc#stylezerolengthpredicate[Style/ZeroLengthPredicate] diff --git a/docs/modules/ROOT/pages/cops_bundler.adoc b/docs/modules/ROOT/pages/cops_bundler.adoc index 454d018e8c4a..945d14f71ef6 100644 --- a/docs/modules/ROOT/pages/cops_bundler.adoc +++ b/docs/modules/ROOT/pages/cops_bundler.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Bundler == Bundler/DuplicatedGem @@ -63,6 +69,88 @@ end | Array |=== +== Bundler/DuplicatedGroup + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Enabled +| Yes +| No +| 1.56 +| - +|=== + +A Gem group, or a set of groups, should be listed only once in a Gemfile. + +For example, if the values of `source`, `git`, `platforms`, or `path` +surrounding `group` are different, no offense will be registered: + +[source,ruby] +----- +platforms :ruby do + group :default do + gem 'openssl' + end +end + +platforms :jruby do + group :default do + gem 'jruby-openssl' + end +end +----- + +=== Examples + +[source,ruby] +---- +# bad +group :development do + gem 'rubocop' +end + +group :development do + gem 'rubocop-rails' +end + +# bad (same set of groups declared twice) +group :development, :test do + gem 'rubocop' +end + +group :test, :development do + gem 'rspec' +end + +# good +group :development do + gem 'rubocop' +end + +group :development, :test do + gem 'rspec' +end + +# good +gem 'rubocop', groups: [:development, :test] +gem 'rspec', groups: [:development, :test] +---- + +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| Severity +| `warning` +| String + +| Include +| `+**/*.gemfile+`, `+**/Gemfile+`, `+**/gems.rb+` +| Array +|=== + == Bundler/GemComment |=== @@ -439,8 +527,24 @@ gem 'rubocop' gem 'rubocop' gem 'rspec' +---- -# good only if TreatCommentsAsGroupSeparators is true +==== TreatCommentsAsGroupSeparators: true (default) + +[source,ruby] +---- +# good +# For code quality +gem 'rubocop' +# For tests +gem 'rspec' +---- + +==== TreatCommentsAsGroupSeparators: false + +[source,ruby] +---- +# bad # For code quality gem 'rubocop' # For tests diff --git a/docs/modules/ROOT/pages/cops_gemspec.adoc b/docs/modules/ROOT/pages/cops_gemspec.adoc index 7e126e2cd396..ee167a97cfaa 100644 --- a/docs/modules/ROOT/pages/cops_gemspec.adoc +++ b/docs/modules/ROOT/pages/cops_gemspec.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Gemspec == Gemspec/DependencyVersion @@ -362,8 +368,24 @@ spec.add_runtime_dependency 'rubocop' spec.add_runtime_dependency 'rubocop' spec.add_runtime_dependency 'rspec' +---- -# good only if TreatCommentsAsGroupSeparators is true +==== TreatCommentsAsGroupSeparators: true (default) + +[source,ruby] +---- +# good +# For code quality +spec.add_dependency 'rubocop' +# For tests +spec.add_dependency 'rspec' +---- + +==== TreatCommentsAsGroupSeparators: false + +[source,ruby] +---- +# bad # For code quality spec.add_dependency 'rubocop' # For tests diff --git a/docs/modules/ROOT/pages/cops_layout.adoc b/docs/modules/ROOT/pages/cops_layout.adoc index fb141300267f..2c595753eb59 100644 --- a/docs/modules/ROOT/pages/cops_layout.adoc +++ b/docs/modules/ROOT/pages/cops_layout.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Layout == Layout/AccessModifierIndentation @@ -632,9 +638,9 @@ end | Disabled | Yes -| Yes +| Yes (Unsafe) | 0.52 -| - +| 1.53 |=== Checks if the code style follows the ExpectedOrder configuration: @@ -702,6 +708,14 @@ automatically. - extend ---- +=== Safety + +Autocorrection is unsafe because class methods and module inclusion +can behave differently, based on which methods or constants have +already been defined. + +Constants will only be moved when they are assigned with literals. + === Examples [source,ruby] @@ -1322,7 +1336,23 @@ end | 0.59 |=== -Enforces empty line after guard clause +Enforces empty line after guard clause. + +This cop allows `# :nocov:` directive after guard clause because +SimpleCov excludes code from the coverage report by wrapping it in `# :nocov:`: + +[source,ruby] +---- +def foo + # :nocov: + return if condition + # :nocov: + bar +end +---- + +Refer to SimpleCov's documentation for more details: +https://github.com/simplecov-ruby/simplecov#ignoringskipping-code === Examples @@ -1618,6 +1648,10 @@ class ErrorC < BaseError; end | `true` | Boolean +| DefLikeMacros +| `[]` +| Array + | AllowAdjacentOneLineDefs | `true` | Boolean @@ -2739,7 +2773,10 @@ second_param 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. +The other elements' indentations are handled by `Layout/ArrayAlignment` cop. + +This cop will respect `Layout/ArrayAlignment` and will not work when +`EnforcedStyle: with_fixed_indentation` is specified for `Layout/ArrayAlignment`. 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 @@ -2764,7 +2801,7 @@ styles are 'consistent' and 'align_brackets'. Here are examples: # element are on separate lines is indented one step (two spaces) more # than the position inside the opening parenthesis. -#bad +# bad array = [ :value ] @@ -2772,7 +2809,7 @@ and_in_a_method_call([ :no_difference ]) -#good +# good array = [ :value ] @@ -2790,7 +2827,7 @@ but_in_a_method_call([ # separate lines is indented the same as an array literal which is not # defined inside a method call. -#bad +# bad # consistent array = [ :value @@ -2799,7 +2836,7 @@ but_in_a_method_call([ :its_like_this ]) -#good +# good array = [ :value ] @@ -2815,13 +2852,13 @@ and_in_a_method_call([ # The `align_brackets` style enforces that the opening and closing # brackets are indented to the same position. -#bad +# bad # align_brackets and_now_for_something = [ :completely_different ] -#good +# good # align_brackets and_now_for_something = [ :completely_different @@ -3689,6 +3726,8 @@ opening HEREDOC tag. == Layout/HeredocIndentation +NOTE: Required Ruby version: 2.3 + |=== | Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed @@ -3704,7 +3743,7 @@ 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. + avoid ``Layout/LineLength``'s offenses. === Examples @@ -6227,7 +6266,7 @@ foo &.bar foo &. bar foo &. bar&. buzz RuboCop:: Cop -RuboCop:: Cop:: Cop +RuboCop:: Cop:: Base :: RuboCop::Cop # good @@ -6239,7 +6278,7 @@ foo foo&.bar foo&.bar&.buzz RuboCop::Cop -RuboCop::Cop::Cop +RuboCop::Cop::Base ::RuboCop::Cop ---- @@ -6323,6 +6362,28 @@ a**b a ** b ---- +==== EnforcedStyleForRationalLiterals: no_space (default) + +[source,ruby] +---- +# bad +1 / 48r + +# good +1/48r +---- + +==== EnforcedStyleForRationalLiterals: space + +[source,ruby] +---- +# bad +1/48r + +# good +1 / 48r +---- + === Configurable attributes |=== @@ -6335,6 +6396,10 @@ a ** b | EnforcedStyleForExponentOperator | `no_space` | `space`, `no_space` + +| EnforcedStyleForRationalLiterals +| `no_space` +| `space`, `no_space` |=== === References diff --git a/docs/modules/ROOT/pages/cops_lint.adoc b/docs/modules/ROOT/pages/cops_lint.adoc index b345470909be..bbdd02a163f1 100644 --- a/docs/modules/ROOT/pages/cops_lint.adoc +++ b/docs/modules/ROOT/pages/cops_lint.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Lint == Lint/AmbiguousAssignment @@ -121,10 +127,6 @@ expect { do_something }.to not_change { object.attribute } | Array |=== -=== References - -* https://rubystyle.guide#syntax - == Lint/AmbiguousOperator |=== @@ -358,12 +360,12 @@ the author meant to use an assignment result as a condition. [source,ruby] ---- # bad -if some_var = true +if some_var = value do_something end # good -if some_var == true +if some_var == value do_something end ---- @@ -373,7 +375,7 @@ end [source,ruby] ---- # good -if (some_var = true) +if (some_var = value) do_something end ---- @@ -383,7 +385,7 @@ end [source,ruby] ---- # bad -if (some_var = true) +if (some_var = value) do_something end ---- @@ -444,10 +446,10 @@ BigDecimal(123.456, 3) Checks for places where binary operator has identical operands. It covers arithmetic operators: `-`, `/`, `%`; -comparison operators: `==`, `===`, `=~`, `>`, `>=`, `<`, `<=`; +comparison operators: `==`, `===`, `=~`, `>`, `>=`, `<`, ``<=``; bitwise operators: `|`, `^`, `&`; boolean operators: `&&`, `||` -and "spaceship" operator - `<=>`. +and "spaceship" operator - ``<=>``. Simple arithmetic operations are allowed by this cop: `+`, `*`, `**`, `<<` and `>>`. Although these can be rewritten in a different way, it should not be necessary to @@ -697,7 +699,7 @@ end | - |=== -Checks for overwriting an exception with an exception result by use `rescue =>`. +Checks for overwriting an exception with an exception result by use ``rescue =>``. You intended to write as `rescue StandardError`. However, you have written `rescue => StandardError`. @@ -1324,6 +1326,7 @@ end |=== Checks for duplicated keys in hash literals. +This cop considers both primitive types and constants for the hash keys. This cop mirrors a warning in Ruby 2.2. @@ -1755,7 +1758,7 @@ end 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. +to clarify what we're aiming for. Empty lambdas and procs are ignored by default. @@ -2550,6 +2553,20 @@ is almost never the desired semantics. Comparison via the `==/!=` operators chec floating-point value representation to be exactly the same, which is very unlikely if you perform any arithmetic operations involving precision loss. + # good - comparing against zero + x == 0.0 + x != 0.0 + + # 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/ + === Examples [source,ruby] @@ -2560,16 +2577,6 @@ 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/ ---- === References @@ -3026,6 +3033,46 @@ foo = 'something with #{interpolation} inside' foo = "something with #{interpolation} inside" ---- +== Lint/ItWithoutArgumentsInBlock + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| No +| 1.59 +| - +|=== + +Emulates the following Ruby warning in Ruby 3.3. + +[source,ruby] +---- +$ ruby -e '0.times { it }' +-e:1: warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; +use it() or self.it +---- + +`it` calls without arguments will refer to the first block param in Ruby 3.4. +So use `it()` or `self.it` to ensure compatibility. + +=== Examples + +[source,ruby] +---- +# bad +do_something { it } + +# good +do_something { it() } +do_something { self.it } +---- + +=== References + +* https://bugs.ruby-lang.org/issues/18980 + == Lint/LambdaWithoutLiteralBlock |=== @@ -3108,6 +3155,50 @@ while true end ---- +== Lint/LiteralAssignmentInCondition + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| No +| 1.58 +| - +|=== + +Checks for literal assignments in the conditions of `if`, `while`, and `until`. +It emulates the following Ruby warning: + +[source,console] +---- +$ ruby -we 'if x = true; end' +-e:1: warning: found `= literal' in conditional, should be == +---- + +As a lint cop, it cannot be determined if `==` is appropriate as intended, +therefore this cop does not provide autocorrection. + +=== Examples + +[source,ruby] +---- +# bad +if x = 42 + do_something +end + +# good +if x == 42 + do_something +end + +# good +if x = y + do_something +end +---- + == Lint/LiteralInInterpolation |=== @@ -3295,6 +3386,13 @@ challenging or verbose for no actual gain. Autocorrection is not supported because the position of `super` cannot be determined automatically. +`Object` and `BasicObject` are allowed by this cop because of their +stateless nature. However, sometimes you might want to allow other parent +classes from this cop, for example in the case of an abstract class that is +not meant to be called with `super`. In those cases, you can use the +`AllowedParentClasses` option to specify which classes should be allowed +*in addition to* `Object` and `BasicObject`. + === Examples [source,ruby] @@ -3343,6 +3441,74 @@ class Parent do_something end end + +# good +class ClassWithNoParent + def initialize + do_something + end +end +---- + +==== AllowedParentClasses: [MyAbstractClass] + +[source,ruby] +---- +# good +class MyConcreteClass < MyAbstractClass + def initialize + do_something + end +end +---- + +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| AllowedParentClasses +| `[]` +| Array +|=== + +== Lint/MixedCaseRange + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes (Unsafe) +| 1.53 +| - +|=== + +Checks for mixed-case character ranges since they include likely unintended characters. + +Offenses are registered for regexp character classes like `/[A-z]/` +as well as range objects like `('A'..'z')`. + +NOTE: Range objects cannot be autocorrected. + +=== Safety + +The cop autocorrects regexp character classes +by replacing one character range with two: `A-z` becomes `A-Za-z`. +In most cases this is probably what was originally intended +but it changes the regexp to no longer match symbols it used to include. +For this reason, this cop's autocorrect is unsafe (it will +change the behavior of the code). + +=== Examples + +[source,ruby] +---- +# bad +r = /[A-z]/ + +# good +r = /[A-Za-z]/ ---- == Lint/MixedRegexpCaptureTypes @@ -3879,7 +4045,7 @@ 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`). +`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. @@ -4474,6 +4640,51 @@ Dir['./lib/**/*.rb'].each do |file| end ---- +== Lint/RedundantRegexpQuantifiers + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| 1.53 +| - +|=== + +Checks for redundant quantifiers inside Regexp literals. + +It is always allowed when interpolation is used in a regexp literal, +because it's unknown what kind of string will be expanded as a result: + +[source,ruby] +---- +/(?:a*#{interpolation})?/x +---- + +=== Examples + +[source,ruby] +---- +# bad +/(?:x+)+/ + +# good +/(?:x)+/ + +# good +/(?:x+)/ + +# bad +/(?:x+)?/ + +# good +/(?:x)*/ + +# good +/(?:x*)/ +---- + == Lint/RedundantRequireStatement |=== @@ -4481,9 +4692,9 @@ end | Enabled | Yes -| Yes +| Yes (Unsafe) | 0.76 -| - +| 1.57 |=== Checks for unnecessary `require` statement. @@ -4507,6 +4718,11 @@ Below are the features that each `TargetRubyVersion` targets. This cop target those features. +=== Safety + +This cop's autocorrection is unsafe because if `require 'pp'` is removed from one file, +`NameError` can be encountered when another file uses `PP.pp`. + === Examples [source,ruby] @@ -4532,8 +4748,12 @@ require 'unloaded_feature' |=== 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. +Use cases where a constant, named in camel case for classes and modules is `nil` are rare, +and an offense is not detected when the receiver is a snake case constant. + +For all receivers, the `instance_of?`, `kind_of?`, `is_a?`, `eql?`, `respond_to?`, +and `equal?` methods are checked by default. +These are customizable with `AllowedMethods` option. The `AllowedMethods` option specifies nil-safe methods, in other words, it is a method that is allowed to skip safe navigation. @@ -4553,6 +4773,9 @@ will be autocorrected to never return `nil`. [source,ruby] ---- +# bad +CamelCaseConst&.do_something + # bad do_something if attrs&.respond_to?(:[]) @@ -4564,6 +4787,9 @@ while node&.is_a?(BeginNode) node = node.parent end +# good +CamelCaseConst.do_something + # good while node.is_a?(BeginNode) node = node.parent @@ -4571,6 +4797,22 @@ end # good - without `&.` this will always return `true` foo&.respond_to?(:to_a) + +# bad - for `nil`s conversion methods return default values for the type +foo&.to_h || {} +foo&.to_h { |k, v| [k, v] } || {} +foo&.to_a || [] +foo&.to_i || 0 +foo&.to_f || 0.0 +foo&.to_s || '' + +# good +foo.to_h +foo.to_h { |k, v| [k, v] } +foo.to_a +foo.to_i +foo.to_f +foo.to_s ---- ==== AllowedMethods: [nil_safe_method] @@ -5336,11 +5578,18 @@ Checks for self-assignments. foo = foo foo, bar = foo, bar Foo = Foo +hash['foo'] = hash['foo'] +obj.attr = obj.attr # good foo = bar foo, bar = bar, foo Foo = Bar +hash['foo'] = hash['bar'] +obj.attr = obj.attr2 + +# good (method calls possibly can return different results) +hash[foo] = hash[foo] ---- == Lint/SendWithMixinArgument @@ -5816,6 +6065,7 @@ all keys to be quoted). 'underscored_string'.to_sym :'underscored_symbol' 'hyphenated-string'.to_sym +"string_#{interpolation}".to_sym # good :string @@ -5823,6 +6073,7 @@ all keys to be quoted). :underscored_string :underscored_symbol :'hyphenated-string' +:"string_#{interpolation}" ---- ==== EnforcedStyle: strict (default) @@ -6953,12 +7204,18 @@ Checks for every useless assignment to local variable in every scope. The basic idea for this cop was from the warning of `ruby -cw`: - assigned but unused variable - foo +[source,console] +---- +assigned but unused variable - foo +---- Currently this cop has advanced logic that detects unreferenced reassignments and properly handles varied cases such as branch, loop, rescue, ensure, etc. +NOTE: Given the assignment `foo = 1, bar = 2`, removing unused variables +can lead to a syntax error, so this case is not autocorrected. + === Safety This cop's autocorrection is unsafe because removing assignment from @@ -7278,7 +7535,7 @@ end |=== Checks for uses of `Integer#times` that will never yield -(when the integer <= 0) or that will only ever yield once +(when the integer ``<= 0``) or that will only ever yield once (`1.times`). === Safety @@ -7316,6 +7573,16 @@ do_something(1) Checks for operators, variables, literals, lambda, proc and nonmutating methods used in void context. +`each` blocks are allowed to prevent false positives. +For example, the expression inside the `each` block below. +It's not void, especially when the receiver is an `Enumerator`: + +[source,ruby] +---- +enumerator = [1, 2, 3].filter +enumerator.each { |item| item >= 2 } #=> [2, 3] +---- + === Examples ==== CheckForMethodsWithNoSideEffects: false (default) diff --git a/docs/modules/ROOT/pages/cops_metrics.adoc b/docs/modules/ROOT/pages/cops_metrics.adoc index e8f97e8f2442..9d4ec65b4364 100644 --- a/docs/modules/ROOT/pages/cops_metrics.adoc +++ b/docs/modules/ROOT/pages/cops_metrics.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Metrics == Metrics/AbcSize @@ -19,9 +25,9 @@ and https://en.wikipedia.org/wiki/ABC_Software_Metric. Interpreting ABC size: -* <= 17 satisfactory -* 18..30 unsatisfactory -* > 30 dangerous +* ``<= 17`` satisfactory +* `18..30` unsatisfactory +* `>` 30 dangerous You can have repeated "attributes" calls count as a single "branch". For this purpose, attributes are any method with no argument; no attempt @@ -98,13 +104,12 @@ You can set constructs you want to fold with `CountAsOne`. Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct will be counted as one line regardless of its actual size. +NOTE: This cop does not apply for `Struct` definitions. NOTE: The `ExcludedMethods` configuration is deprecated and only kept for backwards compatibility. Please use `AllowedMethods` and `AllowedPatterns` instead. By default, there are no methods to allowed. -NOTE: This cop does not apply for `Struct` definitions. - === Examples ==== CountAsOne: ['array', 'heredoc', 'method_call'] diff --git a/docs/modules/ROOT/pages/cops_migration.adoc b/docs/modules/ROOT/pages/cops_migration.adoc index 03880f2578a5..07cae85ee071 100644 --- a/docs/modules/ROOT/pages/cops_migration.adoc +++ b/docs/modules/ROOT/pages/cops_migration.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Migration == Migration/DepartmentName diff --git a/docs/modules/ROOT/pages/cops_naming.adoc b/docs/modules/ROOT/pages/cops_naming.adoc index 5ad972417fa7..5cb176169bd8 100644 --- a/docs/modules/ROOT/pages/cops_naming.adoc +++ b/docs/modules/ROOT/pages/cops_naming.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Naming == Naming/AccessorMethodName diff --git a/docs/modules/ROOT/pages/cops_security.adoc b/docs/modules/ROOT/pages/cops_security.adoc index 221fb08748b8..5092bc7f2790 100644 --- a/docs/modules/ROOT/pages/cops_security.adoc +++ b/docs/modules/ROOT/pages/cops_security.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Security == Security/CompoundHash @@ -217,6 +223,7 @@ in a class and then used without a receiver in that class. # bad open(something) open("| #{something}") +open("| foo") URI.open(something) # good @@ -226,7 +233,6 @@ URI.parse(something).open # good (literal strings) open("foo.text") -open("| foo") URI.open("http://example.com") ---- diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index f4c383603541..aafaaf343e56 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -1,3 +1,9 @@ +//// + Do NOT edit this file by hand directly, as it is automatically generated. + + Please make any necessary changes to the cop documentation within the source files themselves. +//// + = Style == Style/AccessModifierDeclarations @@ -334,7 +340,7 @@ NOTE: Required Ruby version: 2.7 | Yes | Yes | 1.1 -| - +| 1.58 |=== In Ruby 2.7, arguments forwarding has been added. @@ -342,6 +348,21 @@ In Ruby 2.7, arguments forwarding has been added. This cop identifies places where `do_something(*args, &block)` can be replaced by `do_something(...)`. +In Ruby 3.2, anonymous args/kwargs forwarding has been added. + +This cop also identifies places where `use_args(*args)`/`use_kwargs(**kwargs)` can be +replaced by `use_args(*)`/`use_kwargs(**)`; if desired, this functionality can be disabled +by setting `UseAnonymousForwarding: false`. + +And this cop has `RedundantRestArgumentNames`, `RedundantKeywordRestArgumentNames`, +and `RedundantBlockArgumentNames` options. This configuration is a list of redundant names +that are sufficient for anonymizing meaningless naming. + +Meaningless names that are commonly used can be anonymized by default: +e.g., `*args`, `**options`, `&block`, and so on. + +Names not on this list are likely to be meaningful and are allowed by default. + === Examples [source,ruby] @@ -362,7 +383,35 @@ def foo(...) end ---- -==== AllowOnlyRestArgument: true (default) +==== UseAnonymousForwarding: true (default, only relevant for Ruby >= 3.2) + +[source,ruby] +---- +# bad +def foo(*args, **kwargs) + args_only(*args) + kwargs_only(**kwargs) +end + +# good +def foo(*, **) + args_only(*) + kwargs_only(**) +end +---- + +==== UseAnonymousForwarding: false (only relevant for Ruby >= 3.2) + +[source,ruby] +---- +# good +def foo(*args, **kwargs) + args_only(*args) + kwargs_only(**kwargs) +end +---- + +==== AllowOnlyRestArgument: true (default, only relevant for Ruby < 3.2) [source,ruby] ---- @@ -376,7 +425,7 @@ def foo(**kwargs) end ---- -==== AllowOnlyRestArgument: false +==== AllowOnlyRestArgument: false (only relevant for Ruby < 3.2) [source,ruby] ---- @@ -392,6 +441,51 @@ def foo(**kwargs) end ---- +==== RedundantRestArgumentNames: ['args', 'arguments'] (default) + +[source,ruby] +---- +# bad +def foo(*args) + bar(*args) +end + +# good +def foo(*) + bar(*) +end +---- + +==== RedundantKeywordRestArgumentNames: ['kwargs', 'options', 'opts'] (default) + +[source,ruby] +---- +# bad +def foo(**kwargs) + bar(**kwargs) +end + +# good +def foo(**) + bar(**) +end +---- + +==== RedundantBlockArgumentNames: ['blk', 'block', 'proc'] (default) + +[source,ruby] +---- +# bad - But it is good with `EnforcedStyle: explicit` set for `Naming/BlockForwarding`. +def foo(&block) + bar(&block) +end + +# good +def foo(&) + bar(&) +end +---- + === Configurable attributes |=== @@ -400,6 +494,22 @@ end | AllowOnlyRestArgument | `true` | Boolean + +| UseAnonymousForwarding +| `true` +| Boolean + +| RedundantRestArgumentNames +| `args`, `arguments` +| Array + +| RedundantKeywordRestArgumentNames +| `kwargs`, `options`, `opts` +| Array + +| RedundantBlockArgumentNames +| `blk`, `block`, `proc` +| Array |=== === References @@ -462,6 +572,49 @@ Array(paths).each { |path| do_something(path) } * https://rubystyle.guide#array-coercion +== Style/ArrayFirstLast + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Disabled +| No +| Yes (Unsafe) +| 1.58 +| - +|=== + +Identifies usages of `arr[0]` and `arr[-1]` and suggests to change +them to use `arr.first` and `arr.last` instead. + +The cop is disabled by default due to safety concerns. + +=== Safety + +This cop is unsafe because `[0]` or `[-1]` can be called on a Hash, +which returns a value for `0` or `-1` key, but changing these to use +`.first` or `.last` will return first/last tuple instead. Also, String +does not implement `first`/`last` methods. + +=== Examples + +[source,ruby] +---- +# bad +arr[0] +arr[-1] + +# good +arr.first +arr.last +arr[0] = 2 +arr[0][-2] +---- + +=== References + +* #first-and-last + == Style/ArrayIntersect NOTE: Required Ruby version: 3.1 @@ -484,6 +637,15 @@ can be replaced by `array1.intersect?(array2)`. The `array1.intersect?(array2)` method is faster than `(array1 & array2).any?` and is more readable. +In cases like the following, compatibility is not ensured, +so it will not be detected when using block argument. + +[source,ruby] +---- +([1] & [1,2]).any? { |x| false } # => false +[1].intersect?([1,2]) { |x| false } # => true +---- + === Safety This cop cannot guarantee that `array1` and `array2` are @@ -656,6 +818,14 @@ f = File.open('file') File.open('file') do |f| # ... end + +# bad +f = Tempfile.open('temp') + +# good +Tempfile.open('temp') do |f| + # ... +end ---- == Style/BarePercentLiterals @@ -1372,9 +1542,9 @@ var.kind_of?(String) | Enabled | Yes -| Yes +| Yes (Unsafe) | 0.93 -| - +| 1.57 |=== Enforces the use of `Object#instance_of?` instead of class comparison @@ -1382,6 +1552,12 @@ for equality. `==`, `equal?`, and `eql?` custom method definitions are allowed by default. These are customizable with `AllowedMethods` option. +=== Safety + +This cop's autocorrection is unsafe because there is no guarantee that +the constant `Foo` exists when autocorrecting `var.class.name == 'Foo'` to +`var.instance_of?(Foo)`. + === Examples [source,ruby] @@ -1681,6 +1857,8 @@ array.delete_if(&:nil?) array.reject { |e| e.nil? } array.delete_if { |e| e.nil? } array.select { |e| !e.nil? } +array.grep_v(nil) +array.grep_v(NilClass) # good array.compact @@ -1750,6 +1928,7 @@ this cop may register false positives. # bad items.collect items.collect! +items.collect_concat items.inject items.detect items.find_all @@ -1758,6 +1937,7 @@ items.member? # good items.map items.map! +items.flat_map items.reduce items.find items.select @@ -1770,7 +1950,7 @@ items.include? | Name | Default value | Configurable values | PreferredMethods -| `{"collect"=>"map", "collect!"=>"map!", "inject"=>"reduce", "detect"=>"find", "find_all"=>"select", "member?"=>"include?"}` +| `{"collect"=>"map", "collect!"=>"map!", "collect_concat"=>"flat_map", "inject"=>"reduce", "detect"=>"find", "find_all"=>"select", "member?"=>"include?"}` | | MethodsAcceptingSymbol @@ -4682,13 +4862,25 @@ end |=== Enforces the use of a single string formatting utility. -Valid options include Kernel#format, Kernel#sprintf and String#%. +Valid options include `Kernel#format`, `Kernel#sprintf`, and `String#%`. -The detection of String#% cannot be implemented in a reliable +The detection of `String#%` cannot be implemented in a reliable 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. +Autocorrection will be applied when using argument is a literal or known built-in conversion +methods such as `to_d`, `to_f`, `to_h`, `to_i`, `to_r`, `to_s`, and `to_sym` on variables, +provided that their return value is not an array. For example, when using `to_s`, +`'%s' % [1, 2, 3].to_s` can be autocorrected without any incompatibility: + +[source,ruby] +---- +'%s' % [1, 2, 3] #=> '1' +format('%s', [1, 2, 3]) #=> '[1, 2, 3]' +'%s' % [1, 2, 3].to_s #=> '[1, 2, 3]' +---- + === Examples ==== EnforcedStyle: format (default) @@ -5164,6 +5356,25 @@ end # good foo || raise('exception') if something ok + +# bad +define_method(:test) do + if something + work + end +end + +# good +define_method(:test) do + return unless something + + work +end + +# also good +define_method(:test) do + work if something +end ---- ==== AllowConsecutiveConditionals: false (default) @@ -5298,9 +5509,9 @@ EnforcedStyle. | Pending | Yes -| Yes +| Yes (Unsafe) | 1.10 -| 1.11 +| 1.55 |=== Checks the usage of pre-2.1 `Hash[args]` method of converting enumerables and @@ -5310,6 +5521,17 @@ Correction code from splat argument (`Hash[*ary]`) is not simply determined. For `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. +=== Safety + +This cop's autocorrection is unsafe because `ArgumentError` occurs +if the number of elements is odd: + +[source,ruby] +---- +Hash[[[1, 2], [3]]] #=> {1=>2, 3=>nil} +[[1, 2], [5]].to_h #=> wrong array length at 1 (expected 2, was 1) (ArgumentError) +---- + === Examples [source,ruby] @@ -5387,10 +5609,16 @@ but not fully resolve, this safety issue. ---- # bad hash.keys.each { |k| p k } -hash.values.each { |v| p v } +hash.each { |k, unused_value| p k } # good hash.each_key { |k| p k } + +# bad +hash.values.each { |v| p v } +hash.each { |unused_key, v| p v } + +# good hash.each_value { |v| p v } ---- @@ -7367,6 +7595,9 @@ Checks for unwanted parentheses in parameterless method calls. This cop can be customized allowed methods with `AllowedMethods`. By default, there are no methods to allowed. +NOTE: This cop allows the use of `it()` without arguments in blocks, +as in `0.times { it() }`, following `Lint/ItWithoutArgumentsInBlock` cop. + === Examples [source,ruby] @@ -7806,12 +8037,12 @@ defining `respond_to_missing?`. [source,ruby] ---- -#bad +# bad def method_missing(name, *args) # ... end -#good +# good def respond_to_missing?(name, include_private) # ... end @@ -9904,8 +10135,8 @@ allow(test_double).to receive(:a).and_return('b') |=== Checks for redundant dot before operator method call. -The target operator methods are `|`, `^`, `&`, `<=>`, `==`, `===`, `=~`, `>`, `>=`, `<`, -`<=`, `<<`, `>>`, `+`, `-`, `*`, `/`, `%`, `**`, `~`, `!`, `!=`, and `!~`. +The target operator methods are `|`, `^`, `&`, ``<=>``, `==`, `===`, `=~`, `>`, `>=`, `<`, +``<=``, `<<`, `>>`, `+`, `-`, `*`, `/`, `%`, `**`, `~`, `!`, `!=`, and `!~`. === Examples @@ -9969,6 +10200,10 @@ end | Array |=== +=== References + +* https://rubystyle.guide#keyword-arguments-vs-option-hashes + == Style/OptionalArguments |=== @@ -10678,7 +10913,7 @@ rand(1...7) | No | Yes (Unsafe) | 1.4 -| 1.40 +| 1.55 |=== Checks for a redundant argument passed to certain methods. @@ -10717,6 +10952,8 @@ This cop is unsafe because of the following limitations: array.join('') [1, 2, 3].join("") array.sum(0) +exit(true) +exit!(false) string.split(" ") "first\nsecond".split(" ") string.chomp("\n") @@ -10727,6 +10964,8 @@ A.foo(2) array.join [1, 2, 3].join array.sum +exit +exit! string.split "first second".split string.chomp @@ -10740,7 +10979,7 @@ A.foo | Name | Default value | Configurable values | Methods -| `{"join"=>"", "sum"=>0, "split"=>" ", "chomp"=>"\n", "chomp!"=>"\n"}` +| `{"join"=>"", "sum"=>0, "exit"=>true, "exit!"=>false, "split"=>" ", "chomp"=>"\n", "chomp!"=>"\n"}` | |=== @@ -11072,6 +11311,31 @@ module A end ---- +== Style/RedundantCurrentDirectoryInPath + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| 1.53 +| - +|=== + +Checks for uses a redundant current directory in path. + +=== Examples + +[source,ruby] +---- +# bad +require_relative './path/to/feature' + +# good +require_relative 'path/to/feature' +---- + == Style/RedundantDoubleSplatHashBraces |=== @@ -11095,6 +11359,12 @@ do_something(**{foo: bar, baz: qux}) # good do_something(foo: bar, baz: qux) + +# bad +do_something(**{foo: bar, baz: qux}.merge(options)) + +# good +do_something(foo: bar, baz: qux, **options) ---- == Style/RedundantEach @@ -11155,20 +11425,23 @@ array.each_with_object { |v, o| do_something(v, o) } Checks for RuntimeError as the argument of raise/fail. -It checks for code like this: - === Examples [source,ruby] ---- -# Bad +# bad raise RuntimeError, 'message' - -# Bad raise RuntimeError.new('message') -# Good +# good raise 'message' + +# bad - message is not a string +raise RuntimeError, Object.new +raise RuntimeError.new(Object.new) + +# good +raise Object.new.to_s ---- === References @@ -11244,7 +11517,7 @@ ENV.fetch(:key, VALUE) === References -* https://github.com/JuanitoFatas/fast-ruby#hashfetch-with-argument-vs-hashfetch--block-code +* https://github.com/fastruby/fast-ruby#hashfetch-with-argument-vs-hashfetch--block-code == Style/RedundantFileExtensionInRequire @@ -11294,14 +11567,21 @@ require_relative '../foo.so' | Pending | Yes -| Yes +| Yes (Unsafe) | 1.52 -| - +| 1.57 |=== Identifies usages of `any?`, `empty?` or `none?` predicate methods chained to `select`/`filter`/`find_all` and change them to use predicate method instead. +=== Safety + +This cop's autocorrection is unsafe because `array.select.any?` evaluates all elements +through the `select` method, while `array.any?` uses short-circuit evaluation. +In other words, `array.select.any?` guarantees the evaluation of every element, +but `array.any?` does not necessarily evaluate all of them. + === Examples [source,ruby] @@ -11330,6 +11610,9 @@ arr.select { |x| x > 1 }.any?(&:odd?) ---- # good arr.select { |x| x > 1 }.many? + +# good +arr.select { |x| x > 1 }.present? ---- ==== AllCops:ActiveSupportExtensionsEnabled: true @@ -11341,6 +11624,12 @@ arr.select { |x| x > 1 }.many? # good arr.many? { |x| x > 1 } + +# bad +arr.select { |x| x > 1 }.present? + +# good +arr.any? { |x| x > 1 } ---- == Style/RedundantFreeze @@ -11720,6 +12009,52 @@ question = '"What did you say?"' * https://rubystyle.guide#percent-q +== Style/RedundantRegexpArgument + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| 1.53 +| - +|=== + +Identifies places where argument can be replaced from +a deterministic regexp to a string. + +=== Examples + +[source,ruby] +---- +# bad +'foo'.byteindex(/f/) +'foo'.byterindex(/f/) +'foo'.gsub(/f/, 'x') +'foo'.gsub!(/f/, 'x') +'foo'.partition(/f/) +'foo'.rpartition(/f/) +'foo'.scan(/f/) +'foo'.split(/f/) +'foo'.start_with?(/f/) +'foo'.sub(/f/, 'x') +'foo'.sub!(/f/, 'x') + +# good +'foo'.byteindex('f') +'foo'.byterindex('f') +'foo'.gsub('f', 'x') +'foo'.gsub!('f', 'x') +'foo'.partition('f') +'foo'.rpartition('f') +'foo'.scan('f') +'foo'.split('f') +'foo'.start_with?('f') +'foo'.sub('f', 'x') +'foo'.sub!('f', 'x') +---- + == Style/RedundantRegexpCharacterClass |=== @@ -11869,11 +12204,16 @@ def test return something end -# good +# bad def test return something if something_else end +# good +def test + something if something_else +end + # good def test if x @@ -11943,7 +12283,8 @@ Note, with using explicit self you can only send messages with public or protected scope, you cannot send private messages this way. Note we allow uses of `self` with operators because it would be awkward -otherwise. +otherwise. Also allows the use of `self.it` without arguments in blocks, +as in `0.times { self.it }`, following `Lint/ItWithoutArgumentsInBlock` cop. === Examples @@ -12077,7 +12418,7 @@ after which only the first or last element is used. This cop is unsafe, because `sort...last` and `max` may not return the same element in all cases. -In an enumerable where there are multiple elements where `a <=> b == 0`, +In an enumerable where there are multiple elements where ``a <=> b == 0``, or where the transformation done by the `sort_by` block has the same result, `sort.last` and `max` (or `sort_by.last` and `max_by`) will return different elements. `sort.last` will return the last @@ -12602,9 +12943,13 @@ end | - |=== -Enforces consistency between 'return nil' and 'return'. +Enforces consistency between `return nil` and `return`. -Supported styles are: return, return_nil. +This cop is disabled by default. Because there seems to be a perceived semantic difference +between `return` and `return nil`. The former can be seen as just halting evaluation, +while the latter might be used when the return value is of specific concern. + +Supported styles are `return` and `return_nil`. === Examples @@ -12648,6 +12993,93 @@ end | `return`, `return_nil` |=== +== Style/ReturnNilInPredicateMethodDefinition + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes (Unsafe) +| 1.53 +| - +|=== + +Checks if `return` or `return nil` is used in predicate method definitions. + +=== Safety + +Autocorrection is marked as unsafe because the change of the return value +from `nil` to `false` could potentially lead to incompatibility issues. + +=== Examples + +[source,ruby] +---- +# bad +def foo? + return if condition + + do_something? +end + +# bad +def foo? + return nil if condition + + do_something? +end + +# good +def foo? + return false if condition + + do_something? +end +---- + +==== AllowedMethods: ['foo?'] + +[source,ruby] +---- +# good +def foo? + return if condition + + do_something? +end +---- + +==== AllowedPatterns: [/foo/] + +[source,ruby] +---- +# good +def foo? + return if condition + + do_something? +end +---- + +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| AllowedMethods +| `[]` +| Array + +| AllowedPatterns +| `[]` +| Array +|=== + +=== References + +* https://rubystyle.guide#bool-methods-qmark + == Style/SafeNavigation NOTE: Required Ruby version: 2.3 @@ -12807,7 +13239,7 @@ Identifies usages of `shuffle.first`, === References -* https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code +* https://github.com/fastruby/fast-ruby#arrayshufflefirst-vs-arraysample-code == Style/SelectByRegexp @@ -13142,8 +13574,11 @@ explicit_receiver.raise | - |=== -Sometimes using dig method ends up with just a single -argument. In such cases, dig should be replaced with []. +Sometimes using `dig` method ends up with just a single +argument. In such cases, dig should be replaced with `[]`. + +Since replacing `hash&.dig(:key)` with `hash[:key]` could potentially lead to error, +calls to the `dig` method using safe navigation will be ignored. === Safety @@ -13226,6 +13661,47 @@ end | Array |=== +== Style/SingleLineDoEndBlock + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| 1.57 +| - +|=== + +Checks for single-line `do`...`end` block. + +In practice a single line `do`...`end` is autocorrected when `EnforcedStyle: semantic` +in `Style/BlockDelimiters`. The autocorrection maintains the `do` ... `end` syntax to +preserve semantics and does not change it to `{`...`}` block. + +=== Examples + +[source,ruby] +---- +# bad +foo do |arg| bar(arg) end + +# good +foo do |arg| + bar(arg) +end + +# bad +->(arg) do bar(arg) end + +# good +->(arg) { bar(arg) } +---- + +=== References + +* https://rubystyle.guide#single-line-do-end-block + == Style/SingleLineMethods |=== @@ -13306,8 +13782,9 @@ NOTE: Required Ruby version: 2.6 | - |=== -Checks that arrays are sliced with endless ranges instead of -`ary[start..-1]` on Ruby 2.6+. +Checks that arrays are not sliced with the redundant `ary[0..-1]`, replacing it with `ary`, +and ensures arrays are sliced with endless ranges instead of `ary[start..-1]` on Ruby 2.6+, +and with beginless ranges instead of `ary[nil..end]` on Ruby 2.7+. === Safety @@ -13328,12 +13805,32 @@ sum[-3..] # Hangs forever [source,ruby] ---- # bad -items[1..-1] +items[0..-1] +items[0..nil] +items[0...nil] # good -items[1..] +items + +# bad +items[1..-1] # Ruby 2.6+ +items[1..nil] # Ruby 2.6+ + +# good +items[1..] # Ruby 2.6+ + +# bad +items[nil..42] # Ruby 2.7+ + +# good +items[..42] # Ruby 2.7+ +items[0..42] # Ruby 2.7+ ---- +=== References + +* https://rubystyle.guide#slicing-with-ranges + == Style/SoleNestedConditional |=== @@ -13915,7 +14412,7 @@ Checks if uses of quotes match the configured preference. | - |=== -Checks that quotes inside the string interpolation +Checks that quotes inside string, symbol, and regexp interpolations match the configured preference. === Examples @@ -13925,10 +14422,20 @@ match the configured preference. [source,ruby] ---- # bad -result = "Tests #{success ? "PASS" : "FAIL"}" +string = "Tests #{success ? "PASS" : "FAIL"}" +symbol = :"Tests #{success ? "PASS" : "FAIL"}" +heredoc = <<~TEXT + Tests #{success ? "PASS" : "FAIL"} +TEXT +regexp = /Tests #{success ? "PASS" : "FAIL"}/ # good -result = "Tests #{success ? 'PASS' : 'FAIL'}" +string = "Tests #{success ? 'PASS' : 'FAIL'}" +symbol = :"Tests #{success ? 'PASS' : 'FAIL'}" +heredoc = <<~TEXT + Tests #{success ? 'PASS' : 'FAIL'} +TEXT +regexp = /Tests #{success ? 'PASS' : 'FAIL'}/ ---- ==== EnforcedStyle: double_quotes @@ -13936,10 +14443,20 @@ result = "Tests #{success ? 'PASS' : 'FAIL'}" [source,ruby] ---- # bad -result = "Tests #{success ? 'PASS' : 'FAIL'}" +string = "Tests #{success ? 'PASS' : 'FAIL'}" +symbol = :"Tests #{success ? 'PASS' : 'FAIL'}" +heredoc = <<~TEXT + Tests #{success ? 'PASS' : 'FAIL'} +TEXT +regexp = /Tests #{success ? 'PASS' : 'FAIL'}/ # good -result = "Tests #{success ? "PASS" : "FAIL"}" +string = "Tests #{success ? "PASS" : "FAIL"}" +symbol = :"Tests #{success ? "PASS" : "FAIL"}" +heredoc = <<~TEXT + Tests #{success ? "PASS" : "FAIL"} +TEXT +regexp = /Tests #{success ? "PASS" : "FAIL"}/ ---- === Configurable attributes @@ -14059,6 +14576,37 @@ end * https://rubystyle.guide#no-extend-struct-new +== Style/SuperWithArgsParentheses + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| 1.58 +| - +|=== + +Enforces the presence of parentheses in `super` containing arguments. + +`super` is a keyword and is provided as a distinct cop from those designed for method call. + +=== Examples + +[source,ruby] +---- +# bad +super name, age + +# good +super(name, age) +---- + +=== References + +* https://rubystyle.guide#super-with-args + == Style/SwapValues |=== @@ -14132,6 +14680,15 @@ array of 2 or fewer elements. # bad [:foo, :bar, :baz] + +# bad (contains spaces) +%i[foo\ bar baz\ quux] + +# bad (contains [] with spaces) +%i[foo \[ \]] + +# bad (contains () with spaces) +%i(foo \( \)) ---- ==== EnforcedStyle: brackets @@ -14235,6 +14792,42 @@ call(&:bar) # ArgumentError: wrong number of arguments (given 1, expected 0) ---- +It is also unsafe because `Symbol#to_proc` does not work with +`protected` methods which would otherwise be accessible. + +For example: + +[source,ruby] +---- +class Box + def initialize + @secret = rand + end + + def normal_matches?(*others) + others.map { |other| other.secret }.any?(secret) + end + + def symbol_to_proc_matches?(*others) + others.map(&:secret).any?(secret) + end + + protected + + attr_reader :secret +end + +boxes = [Box.new, Box.new] +Box.new.normal_matches?(*boxes) +# => false +boxes.first.normal_matches?(*boxes) +# => true +Box.new.symbol_to_proc_matches?(*boxes) +# => NoMethodError: protected method `secret' called for # +boxes.first.symbol_to_proc_matches?(*boxes) +# => NoMethodError: protected method `secret' called for # +---- + === Examples [source,ruby] @@ -15727,6 +16320,42 @@ array of 2 or fewer elements. * https://rubystyle.guide#percent-w +== Style/YAMLFileRead + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes +| 1.53 +| - +|=== + +Checks for the use of `YAML.load`, `YAML.safe_load`, and `YAML.parse` with +`File.read` argument. + +NOTE: `YAML.safe_load_file` was introduced in Ruby 3.0. + +=== Examples + +[source,ruby] +---- +# bad +YAML.load(File.read(path)) +YAML.parse(File.read(path)) + +# good +YAML.load_file(path) +YAML.parse_file(path) + +# bad +YAML.safe_load(File.read(path)) # Ruby 3.0 and newer + +# good +YAML.safe_load_file(path) # Ruby 3.0 and newer +---- + == Style/YodaCondition |=== @@ -15875,25 +16504,26 @@ have the same result if reversed. === Examples -==== SupportedOperators: ['*', '+', '&''] +==== SupportedOperators: ['*', '+', '&', '|', '^'] (default) [source,ruby] ---- # bad -1 + x 10 * y +1 + x 1 & z +1 | x +1 ^ x 1 + CONST # good -60 * 24 -x + 1 y * 10 +x + 1 z & 1 +x | 1 +x ^ 1 CONST + 1 - -# good -1 | x +60 * 24 ---- === Configurable attributes diff --git a/docs/modules/ROOT/pages/development.adoc b/docs/modules/ROOT/pages/development.adoc index b7bf01e1dfcd..a9789204a9a6 100644 --- a/docs/modules/ROOT/pages/development.adoc +++ b/docs/modules/ROOT/pages/development.adoc @@ -15,13 +15,10 @@ Use the bundled rake task `new_cop` to generate a cop template: [source,sh] ---- $ bundle exec rake 'new_cop[Department/Name]' -Files created: - - lib/rubocop/cop/department/name.rb - - spec/rubocop/cop/department/name_spec.rb -File modified: - - `require_relative 'rubocop/cop/department/name'` added into lib/rubocop.rb - - A configuration for the cop is added into config/default.yml - +[create] lib/rubocop/cop/department/name.rb +[create] spec/rubocop/cop/department/name_spec.rb +[modify] lib/rubocop.rb - `require_relative 'rubocop/cop/department/name'` was injected. +[modify] A configuration for the cop is added into config/default.yml. Do 4 steps: 1. Modify the description of Department/Name in config/default.yml 2. Implement your new cop in the generated file! diff --git a/docs/modules/ROOT/pages/extensions.adoc b/docs/modules/ROOT/pages/extensions.adoc index 4af0b7db93c2..0377f1d4f0c8 100644 --- a/docs/modules/ROOT/pages/extensions.adoc +++ b/docs/modules/ROOT/pages/extensions.adoc @@ -216,14 +216,14 @@ Normally, RuboCop extracts one Ruby code from one Ruby file, however there are m Ruby extractor must be a callable object that takes a `RuboCop::ProcessedSource` and returns an `Array` of `Hash` that contains Ruby source codes and their offsets from original source code, or returns `nil` for unrelated file. [source,ruby] ---- +---- ruby_extractor.call(processed_source) ---- +---- An example returned value from a Ruby extractor would be as follows: -[source] ---- +[source,ruby] +---- [ { offset: 2, @@ -232,16 +232,16 @@ An example returned value from a Ruby extractor would be as follows: { offset: 10, processed_source: # - }, + } ] ---- +---- On the extension side, the code would be something like this: [source,ruby] ---- +---- RuboCop::Runner.ruby_extractors.unshift(ruby_extractor) ---- +---- `RuboCop::Runners.ruby_extractors` is processed from the beginning and ends when one of them returns a non-nil value. By default, there is a Ruby extractor that returns the given Ruby source code with offset 0, so you can unshift any Ruby extractor before it. diff --git a/docs/modules/ROOT/pages/formatters.adoc b/docs/modules/ROOT/pages/formatters.adoc index ab8626dcdc40..eebe99c1021a 100644 --- a/docs/modules/ROOT/pages/formatters.adoc +++ b/docs/modules/ROOT/pages/formatters.adoc @@ -305,17 +305,17 @@ cops and the number of offenses found for each by running: ---- $ rubocop --format offenses -36 Layout/LineLength -18 Style/StringLiterals +36 Layout/LineLength [Safe Correctable] +18 Style/StringLiterals [Safe Correctable] 13 Style/Documentation -10 Style/ExpandPathArguments -8 Style/EmptyMethod -6 Layout/IndentationConsistency +10 Style/ExpandPathArguments [Safe Correctable] +8 Style/EmptyMethod [Safe Correctable] +6 Layout/IndentationConsistency [Safe Correctable] 4 Lint/SuppressedException -3 Layout/EmptyLinesAroundAccessModifier -2 Layout/ExtraSpacing -1 Layout/AccessModifierIndentation -1 Style/ClassAndModuleChildren +3 Layout/EmptyLinesAroundAccessModifier [Safe Correctable] +2 Layout/ExtraSpacing [Safe Correctable] +1 Layout/AccessModifierIndentation [Safe Correctable] +1 Style/ClassAndModuleChildren [Unsafe Correctable] -- 102 Total in 31 files ---- diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc index c06fb766f7b8..b72d7cf5ce35 100644 --- a/docs/modules/ROOT/pages/installation.adoc +++ b/docs/modules/ROOT/pages/installation.adoc @@ -22,7 +22,7 @@ in your `Gemfile`: [source,rb] ---- -gem 'rubocop', '~> 1.52', require: false +gem 'rubocop', '~> 1.60', require: false ---- .A Modular RuboCop diff --git a/docs/modules/ROOT/pages/integration_with_other_tools.adoc b/docs/modules/ROOT/pages/integration_with_other_tools.adoc index ea926809fe63..f151bb0d2756 100644 --- a/docs/modules/ROOT/pages/integration_with_other_tools.adoc +++ b/docs/modules/ROOT/pages/integration_with_other_tools.adoc @@ -27,14 +27,15 @@ has designed to address this problem and provide lightning fast editor integrati === LSP -https://microsoft.github.io/language-server-protocol/[The Language Server Protocol] is the modern standard for providing cross-editor support for various programming languages. The following Ruby LSP servers are using RuboCop internally to provide -code linting functionality: +https://microsoft.github.io/language-server-protocol/[The Language Server Protocol] is the modern standard for providing cross-editor support for various programming languages. The RuboCop xref:usage/lsp.adoc[LSP] functionality has designed to provide built-in language server. The following is a LSP client for interacting with the built-in server: + +- https://github.com/rubocop/vscode-rubocop[vscode-rubocop] + +And the following Ruby LSP servers are using RuboCop internally to provide code linting functionality: - https://github.com/Shopify/ruby-lsp[ruby-lsp] - https://solargraph.org/[Solargraph] -And the RuboCop xref:usage/lsp.adoc[LSP] functionality has designed to provide built-in language server. - === Emacs https://github.com/rubocop/rubocop-emacs[rubocop.el] is a simple @@ -47,9 +48,9 @@ RuboCop and uses it by default when available. === Vim RuboCop is supported by -https://github.com/scrooloose/syntastic[syntastic], +https://github.com/vim-syntastic/syntastic[syntastic], https://github.com/neomake/neomake[neomake], -and https://github.com/w0rp/ale[ale]. +and https://github.com/dense-analysis/ale[ale]. There is also the https://github.com/ngmy/vim-rubocop[vim-rubocop] plugin. @@ -59,9 +60,11 @@ Helix supports Solargraph natively to provide LSP features. For formatting suppo === Sublime Text -If you're a ST user you might find the -https://github.com/pderichs/sublime_rubocop[Sublime RuboCop plugin] -useful. +For ST you might find the +https://github.com/SublimeLinter/SublimeLinter-rubocop[SublimeLinter-rubocop] or the +https://github.com/pderichs/sublime_rubocop[Sublime RuboCop] plugin useful. + +You may also consider using one of the LSP servers mentioned above which utilize RuboCop by using the https://github.com/sublimelsp/LSP[Sublime-LSP] plugin and follow its https://lsp.sublimetext.io/language_servers/#ruby-ruby-on-rails[documentation] for configuration. === Brackets @@ -78,7 +81,7 @@ Installation instructions can be found https://github.com/mrdougal/textmate2-rub === Atom The https://github.com/AtomLinter/linter-rubocop[linter-rubocop] plugin for Atom's -https://github.com/AtomLinter/Linter[linter] runs RuboCop and highlights the offenses in Atom. +https://github.com/steelbrain/linter[linter] runs RuboCop and highlights the offenses in Atom. === LightTable @@ -102,7 +105,7 @@ RuboCop integration for your favorite editor. == Git pre-commit hook integration with overcommit -https://github.com/brigade/overcommit[overcommit] is a fully configurable and +https://github.com/sds/overcommit[overcommit] is a fully configurable and extendable Git commit hook manager. To use RuboCop with overcommit, add the following to your `.overcommit.yml` file: @@ -145,21 +148,21 @@ entries in `additional_dependencies`: If you're fond of https://github.com/guard/guard[Guard] you might like -https://github.com/yujinakayama/guard-rubocop[guard-rubocop]. It +https://github.com/rubocop/guard-rubocop[guard-rubocop]. It allows you to automatically check Ruby code style with RuboCop when files are modified. == Mega-Linter integration -You can use https://nvuillam.github.io/mega-linter/[Mega-Linter] +You can use https://megalinter.io/latest/[Mega-Linter] to run RuboCop automatically on every PR, and also lint all file types detected in your repository. Please follow the -https://nvuillam.github.io/mega-linter/installation[installation instructions] +https://megalinter.io/latest/installation[installation instructions] to activate RuboCop without any additional configuration. -https://nvuillam.github.io/mega-linter/flavors/ruby/[Mega-Linter's Ruby flavor] +https://megalinter.io/latest/flavors/ruby/[Mega-Linter's Ruby flavor] is optimized for Ruby linting. == Rake integration diff --git a/docs/modules/ROOT/pages/usage/basic_usage.adoc b/docs/modules/ROOT/pages/usage/basic_usage.adoc index 1cae5d09664f..9380783387e3 100644 --- a/docs/modules/ROOT/pages/usage/basic_usage.adoc +++ b/docs/modules/ROOT/pages/usage/basic_usage.adoc @@ -168,6 +168,9 @@ $ rubocop --only Rails/Blank,Layout/HeredocIndentation,Naming/FileName | `-D/--[no-]display-cop-names` | Displays cop names in offense messages. Default is true. +| `--display-time` +| Display elapsed time in seconds. + | `--display-only-fail-level-offenses` | Only output offense messages at the specified `--fail-level` or above. @@ -196,7 +199,7 @@ $ rubocop --only Rails/Blank,Layout/HeredocIndentation,Naming/FileName | Inspect files in order of modification time and stops after first file with offenses. | `--fail-level` -| Minimum xref:configuration.adoc#severity[severity] for exit with error code. Full severity name or upper case initial can be given. Normally, autocorrected offenses are ignored. Use `A` or `autocorrect` if you'd like them to trigger failure. +| Minimum xref:configuration.adoc#severity[severity] for exit with error code. Full severity name or upper case initial can be given. Normally, autocorrected offenses are ignored. Use `A` or `autocorrect` if you'd like any autocorrectable offense to trigger failure, regardless of severity. | `--force-exclusion` | Force excluding files specified in the configuration `Exclude` even if they are explicitly passed as arguments. @@ -267,6 +270,9 @@ $ rubocop --only Rails/Blank,Layout/HeredocIndentation,Naming/FileName | `-s/--stdin` | Pipe source from STDIN. This is useful for editor integration. Takes one argument, a path, relative to the root of the project. RuboCop will use this path to determine which cops are enabled (via eg. Include/Exclude), and so that certain cops like Naming/FileName can be checked. +| `--editor-mode` +| Optimize real-time feedback in editors, adjusting behaviors for editing experience. Editors that run RuboCop directly (e.g., by shelling out) encounter the same issues as with `--lsp`. This option is designed for such editors. + | `-S/--display-style-guide` | Display style guide URLs in offense messages. diff --git a/docs/modules/ROOT/pages/usage/lsp.adoc b/docs/modules/ROOT/pages/usage/lsp.adoc index b89b9c0ccd97..0ca0f0c6f768 100644 --- a/docs/modules/ROOT/pages/usage/lsp.adoc +++ b/docs/modules/ROOT/pages/usage/lsp.adoc @@ -7,29 +7,22 @@ https://microsoft.github.io/language-server-protocol/[The Language Server Protoc This feature enables extremely fast interactions through the LSP. Offense detection and autocorrection are performed in real-time by editors and IDEs using the language server. -The xref:usage/lsp.adoc[Server Mode] is primarily used to speed up RuboCop runs in the terminal. +The xref:usage/server.adoc[Server Mode] is primarily used to speed up RuboCop runs in the terminal. Therefore, if you want real-time feedback from RuboCop in your editor or IDE, opting to use this language server instead of the server mode will not only provide a fast and efficient solution, but also offer a straightforward setup for integration. -== Run as a Language Server - -Run `rubocop --lsp` command from LSP client. - -When the language server is started, the command displays the language server's PID: +== Examples of LSP Client -```console -$ ps aux | grep rubocop -user 17414 0.0 0.2 5557716 144376 ?? Ss 4:48PM 0:02.13 /Users/user/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/bin/rubocop --lsp -``` +Here are examples of LSP client configurations. -== Autocorrection +=== VS Code -The language server supports `textDocument/formatting` method and is autocorrectable. The autocorrection is safe. +https://github.com/rubocop/vscode-rubocop[vscode-rubocop] integrates RuboCop into VS Code. -== Examples of LSP Client +You can install this VS Code extension from the https://marketplace.visualstudio.com/items?itemName=rubocop.vscode-rubocop[Visual Studio Marketplace]. -Here are examples of LSP client configurations. +For VS Code-based IDEs like VSCodium or Eclipse Theia, the extension can be installed from the https://open-vsx.org/extension/rubocop/vscode-rubocop[Open VSX Registry]. === Emacs (Eglot) @@ -53,3 +46,225 @@ Below is an example of additional setting for autocorrecting on save: If you run into problems, first use "M-x eglot-reconnect" to reconnect to the language server. See Eglot's official documentation for more information. + +=== Emacs (LSP Mode) + +https://github.com/emacs-lsp/lsp-mode[LSP Mode] is an Emacs client/library for the Language Server Protocol. + +You can get the new `lsp-mode` package from https://melpa.org/#/lsp-mode[MELPA]. + +See LSP Mode official documentation for more information: +https://emacs-lsp.github.io/lsp-mode/page/lsp-rubocop/ + +=== Vim and Neovim (coc.nvim) + +https://github.com/neoclide/coc.nvim[coc.nvim] is an extension host for Vim and Neovim, powered by Node.js. +It allows the loading of extensions similar to VSCode and provides hosting for language servers. + +Add the following to your coc.nvim configuration file. For example, in Vim, it would be `~/.vim/coc-settings.json`, +and in Neovim, it would be `~/.config/nvim/coc-settings.json`: + +```json +{ + "languageserver": { + "rubocop": { + "command": "bundle", + "args" : ["exec", "rubocop", "--lsp"], + "filetypes": ["ruby"], + "rootPatterns": [".git", "Gemfile"], + "requireRootPattern": true + } + } +} +``` + +Below is an example of additional setting for autocorrecting on save: + +```json +{ + "coc.preferences.formatOnSave": true +} +``` + +See coc.nvim's official documentation for more information. + +=== Neovim (nvim-lspconfig) + +https://github.com/neovim/nvim-lspconfig[nvim-lspconfig] provides quickstart configs for Neovim's LSP. + +Add the following to your nvim-lspconfig configuration file (e.g. `~/.config/nvim/init.lua`): + +```lua +vim.opt.signcolumn = "yes" +vim.api.nvim_create_autocmd("FileType", { + pattern = "ruby", + callback = function() + vim.lsp.start { + name = "rubocop", + cmd = { "bundle", "exec", "rubocop", "--lsp" }, + } + end, +}) +``` + +Below is an example of additional setting for autocorrecting on save: + +```lua +vim.api.nvim_create_autocmd("BufWritePre", { + pattern = "*.rb", + callback = function() + vim.lsp.buf.format() + end, +}) +``` + +See nvim-lspconfig's official documentation for more information. + +=== Helix + +https://github.com/helix-editor/helix[Helix] is a post-modern modal text editor with built-in language server support. + +Add the following to your Helix language configuration file (e.g. `~/.config/helix/languages.toml`): + +Helix 23.10 or later: + +```toml +[language-server.rubocop] +command = "bundle" +args = ["exec", "rubocop", "--lsp"] + +[[language]] +name = "ruby" +auto-format = true +language-servers = [ + { name = "rubocop" } +] +``` + +Before Helix 23.10 or earlier: + +```toml +[[language]] +name = "ruby" +language-server = { command = "bundle", args = ["exec", "rubocop", "--lsp"] } +auto-format = true +``` + +See Helix's official documentation for more information: +https://docs.helix-editor.com/languages.html + +=== Sublime Text + +For https://www.sublimetext.com/[Sublime Text] LSP support is available through the https://github.com/sublimelsp/LSP[Sublime-LSP] plugin. +Add the following to its settings (accessible via `Preferences → Package Settings → LSP → Settings`) to enable RuboCop: + +```json +{ + "clients": { + "rubocop": { + "enabled": true, + "command": ["bundle", "exec", "rubocop", "--lsp"], + "selector": "source.ruby | text.html.ruby | text.html.rails", + }, + }, +} +``` + +== Autocorrection + +The language server supports `textDocument/formatting` method and is autocorrectable. The autocorrection is safe by default (`rubocop -a`). + +LSP client can switch to unsafe autocorrection (`rubocop -A`) by passing the following `safeAutocorrect` parameter in the `initialize` request. + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "initialize", + "params": { + "initializationOptions": { + "safeAutocorrect": false + } + } +} +``` + +For detailed instructions on setting the parameter, please refer to the configuration methods of your LSP client. + +NOTE: The `safeAutocorrect` parameter was introduced in RuboCop 1.54. + +As execute commands in the `workspace/executeCommand` parameters, it provides `rubocop.formatAutocorrects` for safe autocorrections (`rubocop -a`) and +`rubocop.formatAutocorrectsAll` for unsafe autocorrections (`rubocop -A`). +These parameters take precedence over the `initializationOptions:safeAutocorrect` value set in the `initialize` parameter. + +NOTE: The `rubocop.formatAutocorrectsAll` execute command was introduced in RuboCop 1.56. + +== Lint Mode + +LSP client can run lint cops by passing the following `lintMode` parameter in the `initialize` request +if you only want to enable the feature as a linter like `ruby -w`: + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "initialize", + "params": { + "initializationOptions": { + "lintMode": true + } + } +} +``` + +Furthermore, enabling autocorrect in a LSP client at the time of saving equates to the effect of `rubocop -l` option. + +For detailed instructions on setting the parameter, please refer to the configuration methods of your LSP client. + +NOTE: The `lintMode` parameter was introduced in RuboCop 1.55. + +== Layout Mode + +LSP client can run layout cops by passing the following `layoutMode` parameter in the `initialize` request +if you only want to enable the feature as a formatter: + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "initialize", + "params": { + "initializationOptions": { + "layoutMode": true + } + } +} +``` + +Furthermore, enabling autocorrect in a LSP client at the time of saving equates to the effect of `rubocop -x` option. + +For detailed instructions on setting the parameter, please refer to the configuration methods of your LSP client. + +NOTE: The `layoutMode` parameter was introduced in RuboCop 1.55. + +== Run as a Language Server + +Run `rubocop --lsp` command from LSP client. + +When the language server is started, the command displays the language server's PID: + +```console +$ ps aux | grep rubocop +user 17414 0.0 0.2 5557716 144376 ?? Ss 4:48PM 0:02.13 /Users/user/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/bin/rubocop --lsp +``` + +NOTE: `rubocop --lsp` is for starting LSP client, so users don't manually execute it. + +== Language Server Development + +RuboCop provides APIs for developers of original language server or tools analogous to LSP, using RuboCop as the backend, instead of the RuboCop's built-in LSP. + +- `RuboCop::LSP.enable` enables LSP mode, customizing for LSP-specific features such as autocorrection and short offense message. +- `RuboCop::LSP.disable` disables LSP mode, which can be particularly useful for testing. + +When implementing custom cops, `RuboCop::LSP.enabled?` can be used to achieve behavior that considers these states. diff --git a/lib/rubocop.rb b/lib/rubocop.rb index ee161a62a565..b5f498d713e1 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -139,6 +139,7 @@ require_relative 'rubocop/cop/mixin/def_node' # relies on visibility_help require_relative 'rubocop/cop/utils/format_string' +require_relative 'rubocop/cop/utils/regexp_ranges' require_relative 'rubocop/cop/migration/department_name' @@ -161,6 +162,7 @@ require_relative 'rubocop/cop/correctors/unused_arg_corrector' require_relative 'rubocop/cop/bundler/duplicated_gem' +require_relative 'rubocop/cop/bundler/duplicated_group' require_relative 'rubocop/cop/bundler/gem_comment' require_relative 'rubocop/cop/bundler/gem_filename' require_relative 'rubocop/cop/bundler/gem_version' @@ -330,12 +332,15 @@ require_relative 'rubocop/cop/lint/ineffective_access_modifier' require_relative 'rubocop/cop/lint/inherit_exception' require_relative 'rubocop/cop/lint/interpolation_check' +require_relative 'rubocop/cop/lint/it_without_arguments_in_block' require_relative 'rubocop/cop/lint/lambda_without_literal_block' require_relative 'rubocop/cop/lint/literal_as_condition' +require_relative 'rubocop/cop/lint/literal_assignment_in_condition' require_relative 'rubocop/cop/lint/literal_in_interpolation' require_relative 'rubocop/cop/lint/loop' require_relative 'rubocop/cop/lint/missing_cop_enable_directive' require_relative 'rubocop/cop/lint/missing_super' +require_relative 'rubocop/cop/lint/mixed_case_range' require_relative 'rubocop/cop/lint/mixed_regexp_capture_types' require_relative 'rubocop/cop/lint/multiple_comparison' require_relative 'rubocop/cop/lint/nested_method_definition' @@ -358,6 +363,7 @@ require_relative 'rubocop/cop/lint/redundant_cop_disable_directive' require_relative 'rubocop/cop/lint/redundant_cop_enable_directive' require_relative 'rubocop/cop/lint/redundant_dir_glob_sort' +require_relative 'rubocop/cop/lint/redundant_regexp_quantifiers' require_relative 'rubocop/cop/lint/redundant_require_statement' require_relative 'rubocop/cop/lint/redundant_safe_navigation' require_relative 'rubocop/cop/lint/redundant_splat_expansion' @@ -450,6 +456,7 @@ require_relative 'rubocop/cop/style/and_or' require_relative 'rubocop/cop/style/arguments_forwarding' require_relative 'rubocop/cop/style/array_coercion' +require_relative 'rubocop/cop/style/array_first_last' require_relative 'rubocop/cop/style/array_intersect' require_relative 'rubocop/cop/style/array_join' require_relative 'rubocop/cop/style/ascii_comments' @@ -562,6 +569,7 @@ require_relative 'rubocop/cop/style/redundant_array_constructor' require_relative 'rubocop/cop/style/redundant_assignment' require_relative 'rubocop/cop/style/redundant_constant_base' +require_relative 'rubocop/cop/style/redundant_current_directory_in_path' require_relative 'rubocop/cop/style/redundant_double_splat_hash_braces' require_relative 'rubocop/cop/style/redundant_each' require_relative 'rubocop/cop/style/redundant_fetch_block' @@ -570,10 +578,12 @@ require_relative 'rubocop/cop/style/redundant_heredoc_delimiter_quotes' require_relative 'rubocop/cop/style/redundant_initialize' require_relative 'rubocop/cop/style/redundant_line_continuation' +require_relative 'rubocop/cop/style/redundant_regexp_argument' require_relative 'rubocop/cop/style/redundant_regexp_constructor' require_relative 'rubocop/cop/style/redundant_self_assignment' require_relative 'rubocop/cop/style/redundant_self_assignment_branch' require_relative 'rubocop/cop/style/require_order' +require_relative 'rubocop/cop/style/single_line_do_end_block' require_relative 'rubocop/cop/style/sole_nested_conditional' require_relative 'rubocop/cop/style/static_class' require_relative 'rubocop/cop/style/map_compact_with_conditional_block' @@ -648,6 +658,7 @@ require_relative 'rubocop/cop/style/rescue_modifier' require_relative 'rubocop/cop/style/rescue_standard_error' require_relative 'rubocop/cop/style/return_nil' +require_relative 'rubocop/cop/style/return_nil_in_predicate_method_definition' require_relative 'rubocop/cop/style/safe_navigation' require_relative 'rubocop/cop/style/sample' require_relative 'rubocop/cop/style/select_by_regexp' @@ -670,6 +681,7 @@ require_relative 'rubocop/cop/style/string_methods' require_relative 'rubocop/cop/style/strip' require_relative 'rubocop/cop/style/struct_inheritance' +require_relative 'rubocop/cop/style/super_with_args_parentheses' require_relative 'rubocop/cop/style/swap_values' require_relative 'rubocop/cop/style/symbol_array' require_relative 'rubocop/cop/style/symbol_literal' @@ -694,6 +706,7 @@ require_relative 'rubocop/cop/style/while_until_do' require_relative 'rubocop/cop/style/while_until_modifier' require_relative 'rubocop/cop/style/word_array' +require_relative 'rubocop/cop/style/yaml_file_read' require_relative 'rubocop/cop/style/yoda_condition' require_relative 'rubocop/cop/style/yoda_expression' require_relative 'rubocop/cop/style/zero_length_predicate' diff --git a/lib/rubocop/cli.rb b/lib/rubocop/cli.rb index e595149cbac4..18b818bfabfb 100644 --- a/lib/rubocop/cli.rb +++ b/lib/rubocop/cli.rb @@ -11,8 +11,8 @@ class CLI STATUS_ERROR = 2 STATUS_INTERRUPTED = Signal.list['INT'] + 128 DEFAULT_PARALLEL_OPTIONS = %i[ - color debug display_style_guide display_time display_only_fail_level_offenses - display_only_failed except extra_details fail_level fix_layout format + color config debug display_style_guide display_time display_only_fail_level_offenses + display_only_failed editor_mode except extra_details fail_level fix_layout format ignore_disable_comments lint only only_guide_cops require safe autocorrect safe_autocorrect autocorrect_all ].freeze @@ -151,6 +151,7 @@ def parallel_by_default! def act_on_options set_options_to_config_loader + handle_editor_mode @config_store.options_config = @options[:config] if @options[:config] @config_store.force_default_config! if @options[:force_default_config] @@ -174,6 +175,10 @@ def set_options_to_config_loader ConfigLoader.ignore_unrecognized_cops = @options[:ignore_unrecognized_cops] end + def handle_editor_mode + RuboCop::LSP.enable if @options[:editor_mode] + end + # rubocop:disable Metrics/CyclomaticComplexity def handle_exiting_options return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o } diff --git a/lib/rubocop/cli/command/auto_generate_config.rb b/lib/rubocop/cli/command/auto_generate_config.rb index 8b5954bea64a..91066ed1d1d3 100644 --- a/lib/rubocop/cli/command/auto_generate_config.rb +++ b/lib/rubocop/cli/command/auto_generate_config.rb @@ -10,6 +10,7 @@ class AutoGenerateConfig < Base AUTO_GENERATED_FILE = '.rubocop_todo.yml' YAML_OPTIONAL_DOC_START = /\A---(\s+#|\s*\z)/.freeze + PLACEHOLDER = '###rubocop:inherit_here' PHASE_1 = 'Phase 1 of 2: run Layout/LineLength cop' PHASE_2 = 'Phase 2 of 2: run all cops' @@ -125,15 +126,19 @@ def add_inheritance_from_auto_generated_file(config_file) def existing_configuration(config_file) File.read(config_file, encoding: Encoding::UTF_8) - .sub(/^inherit_from: *[^\n]+/, '') - .sub(/^inherit_from: *(\n *- *[^\n]+)+/, '') + .sub(/^inherit_from: *[^\n]+/, PLACEHOLDER) + .sub(/^inherit_from: *(\n *- *[^\n]+)+/, PLACEHOLDER) end def write_config_file(file_name, file_string, rubocop_yml_contents) lines = /\S/.match?(rubocop_yml_contents) ? rubocop_yml_contents.split("\n", -1) : [] - doc_start_index = lines.index { |line| YAML_OPTIONAL_DOC_START.match?(line) } || -1 - lines.insert(doc_start_index + 1, "inherit_from:#{file_string}\n") - File.write(file_name, lines.join("\n")) + unless rubocop_yml_contents&.include?(PLACEHOLDER) + doc_start_index = lines.index { |line| YAML_OPTIONAL_DOC_START.match?(line) } || -1 + lines.insert(doc_start_index + 1, PLACEHOLDER) + end + File.write(file_name, lines.join("\n") + .sub(/#{PLACEHOLDER}\n*/o, "inherit_from:#{file_string}\n\n") + .sub(/\n\n+\Z/, "\n")) end def relative_path_to_todo_from_options_config diff --git a/lib/rubocop/cli/command/lsp.rb b/lib/rubocop/cli/command/lsp.rb index ff208a4c07c9..c4eb17103e16 100644 --- a/lib/rubocop/cli/command/lsp.rb +++ b/lib/rubocop/cli/command/lsp.rb @@ -7,11 +7,11 @@ class CLI module Command # Start Language Server Protocol of RuboCop. # @api private - class Lsp < Base + class LSP < Base self.command_name = :lsp def run - RuboCop::Lsp::Server.new(@config_store).start + RuboCop::LSP::Server.new(@config_store).start end end end diff --git a/lib/rubocop/config.rb b/lib/rubocop/config.rb index dcac75dcee27..76625303177d 100644 --- a/lib/rubocop/config.rb +++ b/lib/rubocop/config.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'pathname' - # FIXME: Moving Rails department code to RuboCop Rails will remove # the following rubocop:disable comment. # rubocop:disable Metrics/ClassLength diff --git a/lib/rubocop/config_finder.rb b/lib/rubocop/config_finder.rb index c990969f197d..96a9df2dd7d2 100644 --- a/lib/rubocop/config_finder.rb +++ b/lib/rubocop/config_finder.rb @@ -17,8 +17,8 @@ class << self attr_writer :project_root def find_config_path(target_dir) - find_project_dotfile(target_dir) || find_user_dotfile || find_user_xdg_config || - DEFAULT_FILE + find_project_dotfile(target_dir) || find_project_root_dot_config || + find_user_dotfile || find_user_xdg_config || DEFAULT_FILE end # Returns the path RuboCop inferred as the root of the project. No file @@ -41,19 +41,29 @@ def find_project_dotfile(target_dir) find_file_upwards(DOTFILE, target_dir, project_root) end + def find_project_root_dot_config + return unless project_root + + dotfile = File.join(project_root, '.config', DOTFILE) + return dotfile if File.exist?(dotfile) + + xdg_config = File.join(project_root, '.config', 'rubocop', XDG_CONFIG) + xdg_config if File.exist?(xdg_config) + end + def find_user_dotfile return unless ENV.key?('HOME') file = File.join(Dir.home, DOTFILE) - return file if File.exist?(file) + file if File.exist?(file) end def find_user_xdg_config xdg_config_home = expand_path(ENV.fetch('XDG_CONFIG_HOME', '~/.config')) xdg_config = File.join(xdg_config_home, 'rubocop', XDG_CONFIG) - return xdg_config if File.exist?(xdg_config) + xdg_config if File.exist?(xdg_config) end def expand_path(path) diff --git a/lib/rubocop/config_loader.rb b/lib/rubocop/config_loader.rb index d6a810110069..b7c6eee5460a 100644 --- a/lib/rubocop/config_loader.rb +++ b/lib/rubocop/config_loader.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'erb' -require 'pathname' require 'yaml' require_relative 'config_finder' diff --git a/lib/rubocop/config_loader_resolver.rb b/lib/rubocop/config_loader_resolver.rb index 045171950fb9..5e433c3d35e4 100644 --- a/lib/rubocop/config_loader_resolver.rb +++ b/lib/rubocop/config_loader_resolver.rb @@ -33,7 +33,7 @@ def resolve_inheritance(path, hash, file, debug) # rubocop:disable Metrics/Metho inherit_mode: determine_inherit_mode(hash, k)) end hash[k] = v - fix_include_paths(base_config.loaded_path, hash, k, v) if v.key?('Include') + fix_include_paths(base_config.loaded_path, hash, path, k, v) if v.key?('Include') end end end @@ -42,12 +42,13 @@ def resolve_inheritance(path, hash, file, debug) # rubocop:disable Metrics/Metho # base configuration are relative to the directory where the base configuration file is. For the # derived configuration, we need to make those paths relative to where the derived configuration # file is. - def fix_include_paths(base_config_path, hash, key, value) + def fix_include_paths(base_config_path, hash, path, key, value) return unless File.basename(base_config_path).start_with?('.rubocop') base_dir = File.dirname(base_config_path) + derived_dir = File.dirname(path) hash[key]['Include'] = value['Include'].map do |include_path| - PathUtil.relative_path(File.join(base_dir, include_path), Dir.pwd) + PathUtil.relative_path(File.join(base_dir, include_path), derived_dir) end end diff --git a/lib/rubocop/config_obsoletion.rb b/lib/rubocop/config_obsoletion.rb index 0788c4b95932..c8e97b99379c 100644 --- a/lib/rubocop/config_obsoletion.rb +++ b/lib/rubocop/config_obsoletion.rb @@ -15,6 +15,8 @@ class ConfigObsoletion 'changed_parameters' => ChangedParameter, 'changed_enforced_styles' => ChangedEnforcedStyles }.freeze + LOAD_RULES_CACHE = {} # rubocop:disable Style/MutableConstant + private_constant :LOAD_RULES_CACHE attr_reader :rules, :warnings @@ -48,16 +50,17 @@ def reject_obsolete! # Default rules for obsoletions are in config/obsoletion.yml # Additional rules files can be added with `RuboCop::ConfigObsoletion.files << filename` def load_rules # rubocop:disable Metrics/AbcSize - rules = self.class.files.each_with_object({}) do |filename, hash| - hash.merge!(YAML.safe_load(File.read(filename))) do |_key, first, second| - case first - when Hash - first.merge(second) - when Array - first.concat(second) + rules = LOAD_RULES_CACHE[self.class.files] ||= + self.class.files.each_with_object({}) do |filename, hash| + hash.merge!(YAML.safe_load(File.read(filename))) do |_key, first, second| + case first + when Hash + first.merge(second) + when Array + first.concat(second) + end end end - end cop_rules = rules.slice(*COP_RULE_CLASSES.keys) parameter_rules = rules.slice(*PARAMETER_RULE_CLASSES.keys) diff --git a/lib/rubocop/config_obsoletion/parameter_rule.rb b/lib/rubocop/config_obsoletion/parameter_rule.rb index f2d742a1a8c8..8df4655b9e57 100644 --- a/lib/rubocop/config_obsoletion/parameter_rule.rb +++ b/lib/rubocop/config_obsoletion/parameter_rule.rb @@ -19,7 +19,7 @@ def parameter_rule? end def violated? - config[cop]&.key?(parameter) + applies_to_current_ruby_version? && config[cop]&.key?(parameter) end def warning? @@ -28,6 +28,14 @@ def warning? private + def applies_to_current_ruby_version? + minimum_ruby_version = metadata['minimum_ruby_version'] + + return true unless minimum_ruby_version + + config.target_ruby_version >= minimum_ruby_version + end + def alternative metadata['alternative'] end diff --git a/lib/rubocop/config_validator.rb b/lib/rubocop/config_validator.rb index a3fd71ea08ab..a2d8e95cf9c6 100644 --- a/lib/rubocop/config_validator.rb +++ b/lib/rubocop/config_validator.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'pathname' - module RuboCop # Handles validation of configuration, for example cop names, parameter # names, and Ruby versions. @@ -20,6 +18,7 @@ class ConfigValidator # @api private CONFIG_CHECK_KEYS = %w[Enabled Safe SafeAutoCorrect AutoCorrect].to_set.freeze CONFIG_CHECK_DEPARTMENTS = %w[pending override_department].freeze + CONFIG_CHECK_AUTOCORRECTS = %w[always contextual disabled].freeze private_constant :CONFIG_CHECK_KEYS, :CONFIG_CHECK_DEPARTMENTS def_delegators :@config, :smart_loaded_path, :for_all_cops @@ -250,23 +249,31 @@ def reject_conflicting_safe_settings end end + # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity def check_cop_config_value(hash, parent = nil) hash.each do |key, value| check_cop_config_value(value, key) if value.is_a?(Hash) next unless CONFIG_CHECK_KEYS.include?(key) && value.is_a?(String) - next if key == 'Enabled' && CONFIG_CHECK_DEPARTMENTS.include?(value) + if key == 'Enabled' && !CONFIG_CHECK_DEPARTMENTS.include?(value) + supposed_values = 'a boolean' + elsif key == 'AutoCorrect' && !CONFIG_CHECK_AUTOCORRECTS.include?(value) + supposed_values = '`always`, `contextual`, `disabled`, or a boolean' + else + next + end - raise ValidationError, msg_not_boolean(parent, key, value) + raise ValidationError, param_error_message(parent, key, value, supposed_values) end end + # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity # FIXME: Handling colors in exception messages like this is ugly. - def msg_not_boolean(parent, key, value) + def param_error_message(parent, key, value, supposed_values) "#{Rainbow('').reset}" \ - "Property #{Rainbow(key).yellow} of cop #{Rainbow(parent).yellow} " \ - "is supposed to be a boolean and #{Rainbow(value).yellow} is not." + "Property #{Rainbow(key).yellow} of #{Rainbow(parent).yellow} cop " \ + "is supposed to be #{supposed_values} and #{Rainbow(value).yellow} is not." end end end diff --git a/lib/rubocop/cop/autocorrect_logic.rb b/lib/rubocop/cop/autocorrect_logic.rb index a645ff5fe0b5..6afab6b39899 100644 --- a/lib/rubocop/cop/autocorrect_logic.rb +++ b/lib/rubocop/cop/autocorrect_logic.rb @@ -32,7 +32,12 @@ def autocorrect_enabled? # allow turning off autocorrect on a cop by cop basis return true unless cop_config - return false if cop_config['AutoCorrect'] == false + # `false` is the same as `disabled` for backward compatibility. + return false if ['disabled', false].include?(cop_config['AutoCorrect']) + + # When LSP is enabled, it is considered as editing source code, + # and autocorrection with `AutoCorrect: contextual` will not be performed. + return false if contextual_autocorrect? && LSP.enabled? # :safe_autocorrect is a derived option based on several command-line # arguments - see RuboCop::Options#add_autocorrection_options @@ -82,7 +87,9 @@ def surrounding_percent_array(offense_range) node.array_type? && node.percent_literal? end - percent_array.map(&:source_range).find { |range| range.overlaps?(offense_range) } + percent_array.map(&:source_range).find do |range| + offense_range.begin_pos > range.begin_pos && range.overlaps?(offense_range) + end end def range_of_first_line(range) diff --git a/lib/rubocop/cop/base.rb b/lib/rubocop/cop/base.rb index 0c1afbd6e6dc..0b99eddd23b2 100644 --- a/lib/rubocop/cop/base.rb +++ b/lib/rubocop/cop/base.rb @@ -53,7 +53,7 @@ class Base # rubocop:disable Metrics/ClassLength # List of cops that should not try to autocorrect at the same # time as this cop # - # @return [Array] + # @return [Array] # # @api public def self.autocorrect_incompatible_with @@ -305,6 +305,17 @@ def begin_investigation(processed_source, offset: 0, original: processed_source) @current_original = original end + # @api private + def always_autocorrect? + # `true` is the same as `'always'` for backward compatibility. + ['always', true].include?(cop_config.fetch('AutoCorrect', 'always')) + end + + # @api private + def contextual_autocorrect? + cop_config.fetch('AutoCorrect', 'always') == 'contextual' + end + def inspect # :nodoc: "#<#{self.class.name}:#{object_id} @config=#{@config} @options=#{@options}>" end @@ -389,7 +400,7 @@ def correct(range) def use_corrector(range, corrector) if autocorrect? attempt_correction(range, corrector) - elsif corrector && cop_config.fetch('AutoCorrect', true) + elsif corrector && (always_autocorrect? || (contextual_autocorrect? && !LSP.enabled?)) :uncorrected else :unsupported diff --git a/lib/rubocop/cop/bundler/duplicated_gem.rb b/lib/rubocop/cop/bundler/duplicated_gem.rb index eca531593550..2efa8d4a00dc 100644 --- a/lib/rubocop/cop/bundler/duplicated_gem.rb +++ b/lib/rubocop/cop/bundler/duplicated_gem.rb @@ -4,6 +4,7 @@ module RuboCop module Cop module Bundler # A Gem's requirements should be listed only once in a Gemfile. + # # @example # # bad # gem 'rubocop' diff --git a/lib/rubocop/cop/bundler/duplicated_group.rb b/lib/rubocop/cop/bundler/duplicated_group.rb new file mode 100644 index 000000000000..a2ffe8ebb8da --- /dev/null +++ b/lib/rubocop/cop/bundler/duplicated_group.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Bundler + # A Gem group, or a set of groups, should be listed only once in a Gemfile. + # + # For example, if the values of `source`, `git`, `platforms`, or `path` + # surrounding `group` are different, no offense will be registered: + # + # [source,ruby] + # ----- + # platforms :ruby do + # group :default do + # gem 'openssl' + # end + # end + # + # platforms :jruby do + # group :default do + # gem 'jruby-openssl' + # end + # end + # ----- + # + # @example + # # bad + # group :development do + # gem 'rubocop' + # end + # + # group :development do + # gem 'rubocop-rails' + # end + # + # # bad (same set of groups declared twice) + # group :development, :test do + # gem 'rubocop' + # end + # + # group :test, :development do + # gem 'rspec' + # end + # + # # good + # group :development do + # gem 'rubocop' + # end + # + # group :development, :test do + # gem 'rspec' + # end + # + # # good + # gem 'rubocop', groups: [:development, :test] + # gem 'rspec', groups: [:development, :test] + # + class DuplicatedGroup < Base + include RangeHelp + + MSG = 'Gem group `%s` already defined on line ' \ + '%d of the Gemfile.' + SOURCE_BLOCK_NAMES = %i[source git platforms path].freeze + + # @!method group_declarations(node) + def_node_search :group_declarations, '(send nil? :group ...)' + + def on_new_investigation + return if processed_source.blank? + + duplicated_group_nodes.each do |nodes| + nodes[1..].each do |node| + group_name = node.arguments.map(&:source).join(', ') + + register_offense(node, group_name, nodes.first.first_line) + end + end + end + + private + + def duplicated_group_nodes + group_declarations = group_declarations(processed_source.ast) + group_keys = group_declarations.group_by do |node| + source_key = find_source_key(node) + group_attributes = group_attributes(node).sort.join + + "#{source_key}#{group_attributes}" + end + + group_keys.values.select { |nodes| nodes.size > 1 } + end + + def register_offense(node, group_name, line_of_first_occurrence) + line_range = node.loc.column...node.loc.last_column + offense_location = source_range(processed_source.buffer, node.first_line, line_range) + message = format( + MSG, + group_name: group_name, + line_of_first_occurrence: line_of_first_occurrence + ) + add_offense(offense_location, message: message) + end + + def find_source_key(node) + source_block = node.each_ancestor(:block).find do |block_node| + SOURCE_BLOCK_NAMES.include?(block_node.method_name) + end + + return unless source_block + + "#{source_block.method_name}#{source_block.send_node.first_argument&.source}" + end + + def group_attributes(node) + node.arguments.map do |argument| + if argument.hash_type? + argument.pairs.map(&:source).sort.join(', ') + else + argument.respond_to?(:value) ? argument.value.to_s : argument.source + end + end + end + end + end + end +end diff --git a/lib/rubocop/cop/bundler/gem_comment.rb b/lib/rubocop/cop/bundler/gem_comment.rb index 590244a1fd7a..f14e60d75a21 100644 --- a/lib/rubocop/cop/bundler/gem_comment.rb +++ b/lib/rubocop/cop/bundler/gem_comment.rb @@ -150,7 +150,7 @@ def version_specified_gem?(node) # Version specifications that restrict all updates going forward. This excludes versions # like ">= 1.0" or "!= 2.0.3". def restrictive_version_specified_gem?(node) - return unless version_specified_gem?(node) + return false unless version_specified_gem?(node) node.arguments[1..] .any? { |arg| arg&.str_type? && RESTRICTIVE_VERSION_PATTERN.match?(arg.value) } @@ -161,9 +161,9 @@ def contains_checked_options?(node) end def gem_options(node) - return [] unless node.arguments.last&.type == :hash + return [] unless node.last_argument&.type == :hash - node.arguments.last.keys.map(&:value) + node.last_argument.keys.map(&:value) end end end diff --git a/lib/rubocop/cop/bundler/gem_version.rb b/lib/rubocop/cop/bundler/gem_version.rb index dfe8f2ab6451..d5992af88dd5 100644 --- a/lib/rubocop/cop/bundler/gem_version.rb +++ b/lib/rubocop/cop/bundler/gem_version.rb @@ -105,13 +105,13 @@ def offense?(node) end def required_offense?(node) - return unless required_style? + return false unless required_style? !includes_version_specification?(node) && !includes_commit_reference?(node) end def forbidden_offense?(node) - return unless forbidden_style? + return false unless forbidden_style? includes_version_specification?(node) || includes_commit_reference?(node) end diff --git a/lib/rubocop/cop/bundler/ordered_gems.rb b/lib/rubocop/cop/bundler/ordered_gems.rb index 4b60ba70a962..305c8e5f212c 100644 --- a/lib/rubocop/cop/bundler/ordered_gems.rb +++ b/lib/rubocop/cop/bundler/ordered_gems.rb @@ -19,7 +19,15 @@ module Bundler # # gem 'rspec' # - # # good only if TreatCommentsAsGroupSeparators is true + # @example TreatCommentsAsGroupSeparators: true (default) + # # good + # # For code quality + # gem 'rubocop' + # # For tests + # gem 'rspec' + # + # @example TreatCommentsAsGroupSeparators: false + # # bad # # For code quality # gem 'rubocop' # # For tests diff --git a/lib/rubocop/cop/correctors/each_to_for_corrector.rb b/lib/rubocop/cop/correctors/each_to_for_corrector.rb index 4763a491c8f7..e464a558f370 100644 --- a/lib/rubocop/cop/correctors/each_to_for_corrector.rb +++ b/lib/rubocop/cop/correctors/each_to_for_corrector.rb @@ -34,18 +34,14 @@ def correction end def offending_range + begin_range = block_node.source_range.begin + if block_node.arguments? - replacement_range(argument_node.source_range.end_pos) + begin_range.join(argument_node.source_range.end) else - replacement_range(block_node.loc.begin.end_pos) + begin_range.join(block_node.loc.begin.end) end end - - def replacement_range(end_pos) - Parser::Source::Range.new(block_node.source_range.source_buffer, - block_node.source_range.begin_pos, - end_pos) - end end end end diff --git a/lib/rubocop/cop/correctors/for_to_each_corrector.rb b/lib/rubocop/cop/correctors/for_to_each_corrector.rb index 4f711c49bc5a..275aa1269e77 100644 --- a/lib/rubocop/cop/correctors/for_to_each_corrector.rb +++ b/lib/rubocop/cop/correctors/for_to_each_corrector.rb @@ -15,6 +15,8 @@ def initialize(for_node) end def call(corrector) + offending_range = for_node.source_range.begin.join(end_range) + corrector.replace(offending_range, correction) end @@ -40,11 +42,11 @@ def requires_parentheses? collection_node.range_type? || collection_node.or_type? || collection_node.and_type? end - def end_position + def end_range if for_node.do? - keyword_begin.end_pos + keyword_begin.end else - collection_end.end_pos + collection_end.end end end @@ -59,16 +61,6 @@ def collection_end collection_node.source_range end end - - def offending_range - replacement_range(end_position) - end - - def replacement_range(end_pos) - Parser::Source::Range.new(for_node.source_range.source_buffer, - for_node.source_range.begin_pos, - end_pos) - end end end end diff --git a/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb b/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb index 2ee0b879b2bd..6d690736deb0 100644 --- a/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +++ b/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb @@ -14,12 +14,15 @@ def call(corrector) # Check for unparenthesized args' preceding and trailing whitespaces. remove_unparenthesized_whitespace(corrector) - # Avoid correcting to `lambdado` by inserting whitespace - # if none exists before or after the lambda arguments. - insert_separating_space(corrector) + if block_node.block_type? + # Avoid correcting to `lambdado` by inserting whitespace + # if none exists before or after the lambda arguments. + insert_separating_space(corrector) + + remove_arguments(corrector) + end replace_selector(corrector) - remove_arguments(corrector) replace_delimiters(corrector) diff --git a/lib/rubocop/cop/exclude_limit.rb b/lib/rubocop/cop/exclude_limit.rb index 6d2ff8ae3ffa..913019eac63d 100644 --- a/lib/rubocop/cop/exclude_limit.rb +++ b/lib/rubocop/cop/exclude_limit.rb @@ -8,7 +8,7 @@ module ExcludeLimit # The parameter name given is transformed into a method name (eg. `Max` # becomes `self.max=` and `MinDigits` becomes `self.min_digits=`). def exclude_limit(parameter_name, method_name: transform(parameter_name)) - define_method("#{method_name}=") do |value| + define_method(:"#{method_name}=") do |value| cfg = config_to_allow_offenses cfg[:exclude_limit] ||= {} current_max = cfg[:exclude_limit][parameter_name] diff --git a/lib/rubocop/cop/gemspec/dependency_version.rb b/lib/rubocop/cop/gemspec/dependency_version.rb index 679eaf5311b4..f238aa13ac8c 100644 --- a/lib/rubocop/cop/gemspec/dependency_version.rb +++ b/lib/rubocop/cop/gemspec/dependency_version.rb @@ -126,13 +126,13 @@ def offense?(node) end def required_offense?(node) - return unless required_style? + return false unless required_style? !includes_version_specification?(node) && !includes_commit_reference?(node) end def forbidden_offense?(node) - return unless forbidden_style? + return false unless forbidden_style? includes_version_specification?(node) || includes_commit_reference?(node) end diff --git a/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb b/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb index cf292473d8e3..7ab262e8ac88 100644 --- a/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb +++ b/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb @@ -43,7 +43,7 @@ class DeprecatedAttributeAssignment < Base def on_block(block_node) return unless gem_specification(block_node) - block_parameter = block_node.arguments.first.source + block_parameter = block_node.first_argument.source assignment = block_node.descendants.detect do |node| use_deprecated_attributes?(node, block_parameter) @@ -65,7 +65,7 @@ def node_and_method_name(node, attribute) lhs, _op, _rhs = *node [lhs, attribute] else - [node, "#{attribute}=".to_sym] + [node, :"#{attribute}="] end end diff --git a/lib/rubocop/cop/gemspec/ordered_dependencies.rb b/lib/rubocop/cop/gemspec/ordered_dependencies.rb index 79663ce3a940..0c9f43c9a4c2 100644 --- a/lib/rubocop/cop/gemspec/ordered_dependencies.rb +++ b/lib/rubocop/cop/gemspec/ordered_dependencies.rb @@ -45,7 +45,15 @@ module Gemspec # # spec.add_runtime_dependency 'rspec' # - # # good only if TreatCommentsAsGroupSeparators is true + # @example TreatCommentsAsGroupSeparators: true (default) + # # good + # # For code quality + # spec.add_dependency 'rubocop' + # # For tests + # spec.add_dependency 'rspec' + # + # @example TreatCommentsAsGroupSeparators: false + # # bad # # For code quality # spec.add_dependency 'rubocop' # # For tests diff --git a/lib/rubocop/cop/generator/require_file_injector.rb b/lib/rubocop/cop/generator/require_file_injector.rb index 1facbc9cdfd3..2a097ef5bd70 100644 --- a/lib/rubocop/cop/generator/require_file_injector.rb +++ b/lib/rubocop/cop/generator/require_file_injector.rb @@ -67,7 +67,7 @@ def injectable_require_directive def require_path path = source_path.relative_path_from(root_file_path.dirname) - path.to_s.sub('.rb', '') + path.to_s.delete_suffix('.rb') end end end diff --git a/lib/rubocop/cop/internal_affairs.rb b/lib/rubocop/cop/internal_affairs.rb index 504a73fba3dd..6a2b399db3a9 100644 --- a/lib/rubocop/cop/internal_affairs.rb +++ b/lib/rubocop/cop/internal_affairs.rb @@ -12,6 +12,7 @@ require_relative 'internal_affairs/method_name_end_with' require_relative 'internal_affairs/method_name_equal' require_relative 'internal_affairs/node_destructuring' +require_relative 'internal_affairs/node_first_or_last_argument' require_relative 'internal_affairs/node_matcher_directive' require_relative 'internal_affairs/node_type_predicate' require_relative 'internal_affairs/numblock_handler' @@ -19,6 +20,7 @@ require_relative 'internal_affairs/processed_source_buffer_name' require_relative 'internal_affairs/redundant_context_config_parameter' require_relative 'internal_affairs/redundant_described_class_as_subject' +require_relative 'internal_affairs/redundant_expect_offense_arguments' require_relative 'internal_affairs/redundant_let_rubocop_config_new' require_relative 'internal_affairs/redundant_location_argument' require_relative 'internal_affairs/redundant_message_argument' diff --git a/lib/rubocop/cop/internal_affairs/example_description.rb b/lib/rubocop/cop/internal_affairs/example_description.rb index 2de112afbcc1..c42c57d33205 100644 --- a/lib/rubocop/cop/internal_affairs/example_description.rb +++ b/lib/rubocop/cop/internal_affairs/example_description.rb @@ -26,9 +26,7 @@ module InternalAffairs # expect_no_offenses('...') # end class ExampleDescription < Base - class << self - attr_accessor :descriptions - end + extend AutoCorrector MSG = 'Description does not match use of `%s`.' @@ -39,26 +37,36 @@ class << self expect_no_corrections ].to_set.freeze - EXPECT_NO_OFFENSES_INCORRECT_DESCRIPTIONS = [ - /^(adds|registers|reports|finds) (an? )?offense/, - /^(flags|handles|works)\b/ - ].freeze + EXPECT_NO_OFFENSES_DESCRIPTION_MAPPING = { + /\A(adds|registers|reports|finds) (an? )?offense/ => 'does not register an offense', + /\A(flags|handles|works)\b/ => 'does not register' + }.freeze + + EXPECT_OFFENSE_DESCRIPTION_MAPPING = { + /\A(does not|doesn't) (register|find|flag|report)/ => 'registers', + /\A(does not|doesn't) add (a|an|any )?offense/ => 'registers an offense', + /\Aaccepts\b/ => 'registers' + }.freeze - EXPECT_OFFENSE_INCORRECT_DESCRIPTIONS = [ - /^(does not|doesn't) (register|find|flag|report)/, - /^(does not|doesn't) add (a|an|any )?offense/ - ].freeze + EXPECT_NO_CORRECTIONS_DESCRIPTION_MAPPING = { + /\A(auto[- ]?)?correct/ => 'does not correct' + }.freeze - EXPECT_NO_CORRECTIONS_INCORRECT_DESCRIPTIONS = [/^(auto[- ]?)?correct/].freeze + EXPECT_CORRECTION_DESCRIPTION_MAPPING = { + /\b(does not|doesn't) (auto[- ]?)?correct/ => 'autocorrects' + }.freeze - EXPECT_CORRECTION_INCORRECT_DESCRIPTIONS = [ - /\b(does not|doesn't) (auto[- ]?)?correct/ - ].freeze + EXAMPLE_DESCRIPTION_MAPPING = { + expect_no_offenses: EXPECT_NO_OFFENSES_DESCRIPTION_MAPPING, + expect_offense: EXPECT_OFFENSE_DESCRIPTION_MAPPING, + expect_no_corrections: EXPECT_NO_CORRECTIONS_DESCRIPTION_MAPPING, + expect_correction: EXPECT_CORRECTION_DESCRIPTION_MAPPING + }.freeze - # @!method offense_example?(node) - def_node_matcher :offense_example?, <<~PATTERN + # @!method offense_example(node) + def_node_matcher :offense_example, <<~PATTERN (block - (send _ {:it :specify} $_description) + (send _ {:it :specify} $...) _args `(send nil? %RESTRICT_ON_SEND ...) ) @@ -66,21 +74,34 @@ class << self def on_send(node) parent = node.each_ancestor(:block).first - return unless parent && (description = offense_example?(parent)) + return unless parent && (current_description = offense_example(parent)&.first) method_name = node.method_name message = format(MSG, method_name: method_name) - regexp_group = self.class.const_get("#{method_name}_incorrect_descriptions".upcase) - check_description(description, regexp_group, message) + description_map = EXAMPLE_DESCRIPTION_MAPPING[method_name] + check_description(current_description, description_map, message) end private - def check_description(description, regexps, message) - return unless regexps.any? { |regexp| regexp.match?(string_contents(description)) } + def check_description(current_description, description_map, message) + description_text = string_contents(current_description) + return unless (new_description = correct_description(description_text, description_map)) + + add_offense(current_description, message: message) do |corrector| + corrector.replace(current_description, "'#{new_description}'") + end + end + + def correct_description(current_description, description_map) + description_map.each do |incorrect_description_pattern, preferred_description| + if incorrect_description_pattern.match?(current_description) + return current_description.gsub(incorrect_description_pattern, preferred_description) + end + end - add_offense(description, message: message) + nil end def string_contents(node) diff --git a/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb b/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb index d2345f78df5c..ca0621e2fd19 100644 --- a/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +++ b/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb @@ -51,7 +51,9 @@ def on_send(node) def extract_receiver(node) receiver = node.receiver - receiver = receiver.receiver if receiver.method?(:loc) || receiver.method?(:source_range) + if receiver.send_type? && (receiver.method?(:loc) || receiver.method?(:source_range)) + receiver = receiver.receiver + end receiver.source end end diff --git a/lib/rubocop/cop/internal_affairs/method_name_end_with.rb b/lib/rubocop/cop/internal_affairs/method_name_end_with.rb index 18b4fa044235..648f61eda293 100644 --- a/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +++ b/lib/rubocop/cop/internal_affairs/method_name_end_with.rb @@ -30,6 +30,7 @@ class MethodNameEndWith < Base extend AutoCorrector MSG = 'Use `%s` instead of `%s`.' + RESTRICT_ON_SEND = %i[end_with?].freeze SUGGEST_METHOD_FOR_SUFFIX = { '=' => 'assignment_method?', '!' => 'bang_method?', @@ -51,14 +52,15 @@ class MethodNameEndWith < Base def on_send(node) method_name_end_with?(node) do |method_name_node, end_with_arg| + next unless method_name_node.receiver + + preferred_method = SUGGEST_METHOD_FOR_SUFFIX[end_with_arg.value] range = range(method_name_node, node) - message = format( - MSG, - method_name: SUGGEST_METHOD_FOR_SUFFIX[end_with_arg.value], - method_suffix: range.source - ) + message = format(MSG, method_name: preferred_method, method_suffix: range.source) - add_offense(range, message: message) + add_offense(range, message: message) do |corrector| + corrector.replace(range, preferred_method) + end end end alias on_csend on_send diff --git a/lib/rubocop/cop/internal_affairs/method_name_equal.rb b/lib/rubocop/cop/internal_affairs/method_name_equal.rb index da6388d8d6c2..ea3f56ec74c6 100644 --- a/lib/rubocop/cop/internal_affairs/method_name_equal.rb +++ b/lib/rubocop/cop/internal_affairs/method_name_equal.rb @@ -12,38 +12,37 @@ module InternalAffairs # # good # node.method?(:do_something) # + # # bad + # node.method_name != :do_something + # + # # good + # !node.method?(:do_something) + # class MethodNameEqual < Base - include RangeHelp extend AutoCorrector - MSG = 'Use `method?(%s)` instead of `method_name == %s`.' - RESTRICT_ON_SEND = %i[==].freeze + MSG = 'Use `%s` instead.' + RESTRICT_ON_SEND = %i[== !=].freeze - # @!method method_name?(node) - def_node_matcher :method_name?, <<~PATTERN + # @!method method_name(node) + def_node_matcher :method_name, <<~PATTERN (send - $(send - (...) :method_name) :== - $...) + (send + (...) :method_name) {:== :!=} + $_) PATTERN def on_send(node) - method_name?(node) do |method_name_node, method_name_arg| - message = format(MSG, method_name: method_name_arg.first.source) + method_name(node) do |method_name_arg| + bang = node.method?(:!=) ? '!' : '' + prefer = "#{bang}#{node.receiver.receiver.source}.method?(#{method_name_arg.source})" + message = format(MSG, prefer: prefer) - range = range(method_name_node, node) - - add_offense(range, message: message) do |corrector| - corrector.replace(range, "method?(#{method_name_arg.first.source})") + add_offense(node, message: message) do |corrector| + corrector.replace(node, prefer) end end end - - private - - def range(method_name_node, node) - range_between(method_name_node.loc.selector.begin_pos, node.source_range.end_pos) - end end end end diff --git a/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb b/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb new file mode 100644 index 000000000000..63395afc5883 --- /dev/null +++ b/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module InternalAffairs + # Checks for the use of `node.arguments.first` or `node.arguments.last` and + # suggests the use of `node.first_argument` or `node.last_argument` instead. + # + # @example + # # bad + # node.arguments.first + # node.arguments[0] + # node.arguments.last + # node.arguments[-1] + # + # # good + # node.first_argument + # node.last_argument + # + class NodeFirstOrLastArgument < Base + extend AutoCorrector + include RangeHelp + + MSG = 'Use `#%s` instead of `#%s`.' + RESTRICT_ON_SEND = %i[arguments].freeze + + # @!method arguments_first_or_last?(node) + def_node_matcher :arguments_first_or_last?, <<~PATTERN + { + (send (send !nil? :arguments) ${:first :last}) + (send (send !nil? :arguments) :[] (int ${0 -1})) + } + PATTERN + + def on_send(node) + arguments_first_or_last?(node.parent) do |end_or_index| + range = range_between(node.loc.selector.begin_pos, node.parent.source_range.end_pos) + correct = case end_or_index + when :first, 0 then 'first_argument' + when :last, -1 then 'last_argument' + else raise "Unknown end_or_index: #{end_or_index}" + end + message = format(MSG, correct: correct, incorrect: range.source) + + add_offense(range, message: message) do |corrector| + corrector.replace(range, correct) + end + end + end + end + end + end +end diff --git a/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb b/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb index 1f7c74c2c28e..980b489bbb57 100644 --- a/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +++ b/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb @@ -40,7 +40,7 @@ def on_send(node) return if node.arguments.none? return unless valid_method_name?(node) - actual_name = node.arguments.first.value + actual_name = node.first_argument.value directives = method_directives(node) return too_many_directives(node) if directives.size > 1 @@ -53,7 +53,7 @@ def on_send(node) private def valid_method_name?(node) - node.arguments.first.str_type? || node.arguments.first.sym_type? + node.first_argument.str_type? || node.first_argument.sym_type? end def method_directives(node) @@ -117,11 +117,11 @@ def pattern_arguments(pattern) def add_newline?(node) # Determine if a blank line should be inserted before the new directive # in order to spread out pattern matchers - return if node.sibling_index&.zero? - return unless node.parent + return false if node.sibling_index&.zero? + return false unless node.parent prev_sibling = node.parent.child_nodes[node.sibling_index - 1] - return unless prev_sibling && pattern_matcher?(prev_sibling) + return false unless prev_sibling && pattern_matcher?(prev_sibling) node.loc.line == last_line(prev_sibling) + 1 end diff --git a/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb b/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb new file mode 100644 index 000000000000..4424cb07c491 --- /dev/null +++ b/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module InternalAffairs + # Checks for redundant arguments of `RuboCop::RSpec::ExpectOffense`'s methods. + # + # @example + # + # # bad + # expect_no_offenses('code', keyword: keyword) + # + # # good + # expect_no_offenses('code') + # + class RedundantExpectOffenseArguments < Base + extend AutoCorrector + + MSG = 'Remove the redundant arguments.' + RESTRICT_ON_SEND = %i[expect_no_offenses].freeze + + def on_send(node) + return if node.arguments.one? || !node.arguments[1]&.hash_type? + + range = node.first_argument.source_range.end.join(node.last_argument.source_range.end) + + add_offense(range) do |corrector| + corrector.remove(range) + end + end + end + end + end +end diff --git a/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb b/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb index b7af63dcd917..edadd0c65838 100644 --- a/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +++ b/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb @@ -14,6 +14,12 @@ module InternalAffairs # node.method_name # # # bad + # node.send_node.method?(:method_name) + # + # # good + # node.method?(:method_name) + # + # # bad # node.send_node.receiver # # # good @@ -24,11 +30,14 @@ class RedundantMethodDispatchNode < Base extend AutoCorrector MSG = 'Remove the redundant `send_node`.' - RESTRICT_ON_SEND = %i[method_name receiver].freeze + RESTRICT_ON_SEND = %i[method_name method? receiver].freeze # @!method dispatch_method(node) def_node_matcher :dispatch_method, <<~PATTERN - (send $(send _ :send_node) _) + { + (send $(send _ :send_node) {:method_name :receiver}) + (send $(send _ :send_node) :method? _) + } PATTERN def on_send(node) diff --git a/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb b/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb index 5c6f25faec59..c31a4b571a6a 100644 --- a/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +++ b/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb @@ -27,6 +27,8 @@ class UselessMessageAssertion < Base PATTERN def on_new_investigation + return if processed_source.blank? + assertions_using_described_class_msg.each { |node| add_offense(node) } end diff --git a/lib/rubocop/cop/layout/argument_alignment.rb b/lib/rubocop/cop/layout/argument_alignment.rb index 80e8459e283a..4c4016c0f40b 100644 --- a/lib/rubocop/cop/layout/argument_alignment.rb +++ b/lib/rubocop/cop/layout/argument_alignment.rb @@ -79,7 +79,7 @@ def flattened_arguments(node) def arguments_with_last_arg_pairs(node) items = node.arguments[0..-2] - last_arg = node.arguments.last + last_arg = node.last_argument if last_arg.hash_type? && !last_arg.braces? items += last_arg.pairs diff --git a/lib/rubocop/cop/layout/class_structure.rb b/lib/rubocop/cop/layout/class_structure.rb index 3013a2e44d8c..0156bc8e2f33 100644 --- a/lib/rubocop/cop/layout/class_structure.rb +++ b/lib/rubocop/cop/layout/class_structure.rb @@ -68,6 +68,13 @@ module Layout # - extend # ---- # + # @safety + # Autocorrection is unsafe because class methods and module inclusion + # can behave differently, based on which methods or constants have + # already been defined. + # + # Constants will only be moved when they are assigned with literals. + # # @example # # bad # # Expect extend be before constant diff --git a/lib/rubocop/cop/layout/closing_heredoc_indentation.rb b/lib/rubocop/cop/layout/closing_heredoc_indentation.rb index 13a3d2d7de00..74525ecb4a96 100644 --- a/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +++ b/lib/rubocop/cop/layout/closing_heredoc_indentation.rb @@ -72,7 +72,7 @@ def opening_indentation(node) end def argument_indentation_correct?(node) - return unless node.argument? || node.chained? + return false unless node.argument? || node.chained? opening_indentation( find_node_used_heredoc_argument(node.parent) diff --git a/lib/rubocop/cop/layout/dot_position.rb b/lib/rubocop/cop/layout/dot_position.rb index e731c51002a8..5a14850a0703 100644 --- a/lib/rubocop/cop/layout/dot_position.rb +++ b/lib/rubocop/cop/layout/dot_position.rb @@ -32,7 +32,7 @@ def self.autocorrect_incompatible_with end def on_send(node) - return unless node.dot? || ampersand_dot?(node) + return unless node.dot? || node.safe_navigation? return correct_style_detected if proper_dot_position?(node) @@ -133,10 +133,6 @@ def selector_range(node) # l.(1) has no selector, so we use the opening parenthesis instead node.loc.selector || node.loc.begin end - - def ampersand_dot?(node) - node.loc.respond_to?(:dot) && node.loc.dot && node.loc.dot.is?('&.') - end end end end diff --git a/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb b/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb index 048547e5c294..b309c3df825f 100644 --- a/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +++ b/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb @@ -3,7 +3,23 @@ module RuboCop module Cop module Layout - # Enforces empty line after guard clause + # Enforces empty line after guard clause. + # + # This cop allows `# :nocov:` directive after guard clause because + # SimpleCov excludes code from the coverage report by wrapping it in `# :nocov:`: + # + # [source,ruby] + # ---- + # def foo + # # :nocov: + # return if condition + # # :nocov: + # bar + # end + # ---- + # + # Refer to SimpleCov's documentation for more details: + # https://github.com/simplecov-ruby/simplecov#ignoringskipping-code # # @example # @@ -42,19 +58,22 @@ class EmptyLineAfterGuardClause < Base MSG = 'Add empty line after guard clause.' END_OF_HEREDOC_LINE = 1 + SIMPLE_DIRECTIVE_COMMENT_PATTERN = /\A# *:nocov:\z/.freeze def on_if(node) return if correct_style?(node) return if multiple_statements_on_line?(node) if node.modifier_form? && (heredoc_node = last_heredoc_argument(node)) - return if next_line_empty_or_enable_directive_comment?(heredoc_line(node, heredoc_node)) + if next_line_empty_or_allowed_directive_comment?(heredoc_line(node, heredoc_node)) + return + end add_offense(heredoc_node.loc.heredoc_end) do |corrector| autocorrect(corrector, heredoc_node) end else - return if next_line_empty_or_enable_directive_comment?(node.last_line) + return if next_line_empty_or_allowed_directive_comment?(node.last_line) add_offense(offense_location(node)) { |corrector| autocorrect(corrector, node) } end @@ -70,7 +89,7 @@ def autocorrect(corrector, node) end next_line = node_range.last_line + 1 - if next_line_enable_directive_comment?(next_line) + if next_line_allowed_directive_comment?(next_line) node_range = processed_source.comment_at_line(next_line) end @@ -88,21 +107,21 @@ def contains_guard_clause?(node) node.if_branch&.guard_clause? end - def next_line_empty_or_enable_directive_comment?(line) + def next_line_empty_or_allowed_directive_comment?(line) return true if next_line_empty?(line) next_line = line + 1 - next_line_enable_directive_comment?(next_line) && next_line_empty?(next_line) + next_line_allowed_directive_comment?(next_line) && next_line_empty?(next_line) end def next_line_empty?(line) processed_source[line].blank? end - def next_line_enable_directive_comment?(line) + def next_line_allowed_directive_comment?(line) return false unless (comment = processed_source.comment_at_line(line)) - DirectiveComment.new(comment).enabled? + DirectiveComment.new(comment).enabled? || simplecov_directive_comment?(comment) end def next_line_rescue_or_ensure?(node) @@ -137,7 +156,7 @@ def last_heredoc_argument(node) return node if node end - return last_heredoc_argument(n.receiver) if n.respond_to?(:receiver) + last_heredoc_argument(n.receiver) if n.respond_to?(:receiver) end def last_heredoc_argument_node(node) @@ -145,6 +164,8 @@ def last_heredoc_argument_node(node) if node.if_branch.and_type? node.if_branch.children.first + elsif use_heredoc_in_condition?(node.condition) + node.condition else node.if_branch.children.last end @@ -161,6 +182,12 @@ def heredoc?(node) node.respond_to?(:heredoc?) && node.heredoc? end + def use_heredoc_in_condition?(condition) + condition.descendants.any? do |descendant| + descendant.respond_to?(:heredoc?) && descendant.heredoc? + end + end + def offense_location(node) if node.loc.respond_to?(:end) && node.loc.end node.loc.end @@ -175,6 +202,12 @@ def multiple_statements_on_line?(node) parent.begin_type? && parent.single_line? end + + # SimpleCov excludes code from the coverage report by wrapping it in `# :nocov:`: + # https://github.com/simplecov-ruby/simplecov#ignoringskipping-code + def simplecov_directive_comment?(comment) + SIMPLE_DIRECTIVE_COMMENT_PATTERN.match?(comment.text) + end end end end diff --git a/lib/rubocop/cop/layout/empty_line_between_defs.rb b/lib/rubocop/cop/layout/empty_line_between_defs.rb index 715a609e74d6..21be7ca7c9ff 100644 --- a/lib/rubocop/cop/layout/empty_line_between_defs.rb +++ b/lib/rubocop/cop/layout/empty_line_between_defs.rb @@ -135,7 +135,8 @@ def check_defs(nodes) return if nodes.all?(&:single_line?) && cop_config['AllowAdjacentOneLineDefs'] correction_node = nodes.last - location = correction_node.loc.keyword.join(correction_node.loc.name) + + location = def_location(correction_node) add_offense(location, message: message(correction_node, count: count)) do |corrector| autocorrect(corrector, *nodes, count) end @@ -159,10 +160,28 @@ def autocorrect(corrector, prev_def, node, count) private + def def_location(correction_node) + if correction_node.block_type? + correction_node.source_range.join(correction_node.children.first.source_range) + else + correction_node.loc.keyword.join(correction_node.loc.name) + end + end + def candidate?(node) - return unless node + return false unless node - method_candidate?(node) || class_candidate?(node) || module_candidate?(node) + method_candidate?(node) || class_candidate?(node) || module_candidate?(node) || + macro_candidate?(node) + end + + def empty_line_between_macros + cop_config.fetch('DefLikeMacros', []).map(&:to_sym) + end + + def macro_candidate?(node) + node.block_type? && node.children.first.macro? && + empty_line_between_macros.include?(node.children.first.method_name) end def method_candidate?(node) @@ -226,7 +245,11 @@ def lines_between_defs(first_def_node, second_def_node) end def def_start(node) - node.loc.keyword.line + if node.block_type? && node.children.first.send_type? + node.source_range.line + else + node.loc.keyword.line + end end def def_end(node) diff --git a/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb b/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb index 89efc8873797..ea1d4917d22b 100644 --- a/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +++ b/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb @@ -68,6 +68,8 @@ def on_def(node) check_body(node.body, node.loc.line) end alias on_defs on_def + alias on_block on_def + alias on_numblock on_def def on_kwbegin(node) body, = *node diff --git a/lib/rubocop/cop/layout/end_alignment.rb b/lib/rubocop/cop/layout/end_alignment.rb index 6e75b78593a1..4c2eac3c7962 100644 --- a/lib/rubocop/cop/layout/end_alignment.rb +++ b/lib/rubocop/cop/layout/end_alignment.rb @@ -20,7 +20,9 @@ module Layout # 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. + # to align with the start of the line. `Layout/DefEndAlignment` cop also aligns with + # `EnforcedStyleAlignWith: start_of_line` by default. + # These style can be configured by each cop. # # @example EnforcedStyleAlignWith: keyword (default) # # bad @@ -83,7 +85,11 @@ def on_class(node) end def on_sclass(node) - check_other_alignment(node) + if node.parent&.assignment? + check_asgn_alignment(node.parent, node) + else + check_other_alignment(node) + end end def on_module(node) @@ -163,7 +169,13 @@ def alignment_node(node) when :keyword node when :variable - alignment_node_for_variable_style(node) + align_to = alignment_node_for_variable_style(node) + + while (parent = align_to.parent) && parent.send_type? && same_line?(align_to, parent) + align_to = parent + end + + align_to else start_line_range(node) end diff --git a/lib/rubocop/cop/layout/extra_spacing.rb b/lib/rubocop/cop/layout/extra_spacing.rb index 2281542a5fb6..16cd665ab51e 100644 --- a/lib/rubocop/cop/layout/extra_spacing.rb +++ b/lib/rubocop/cop/layout/extra_spacing.rb @@ -49,19 +49,13 @@ def on_new_investigation private - def aligned_locations(locs) # rubocop:disable Metrics/AbcSize + def aligned_locations(locs) return [] if locs.empty? - aligned = Set[locs.first.line, locs.last.line] - locs.each_cons(3) do |before, loc, after| - col = loc.column - aligned << loc.line if col == before.column || col == after.column + aligned = Set.new + locs.each_cons(2) do |loc1, loc2| + aligned << loc1.line << loc2.line if loc1.column == loc2.column end - - # if locs.size > 2 and the size of variable `aligned` - # has not increased from its initial value, there are not aligned lines. - return [] if locs.size > 2 && aligned.size == 2 - aligned end diff --git a/lib/rubocop/cop/layout/first_array_element_indentation.rb b/lib/rubocop/cop/layout/first_array_element_indentation.rb index 9c309d2e2dc3..c45e7661c42a 100644 --- a/lib/rubocop/cop/layout/first_array_element_indentation.rb +++ b/lib/rubocop/cop/layout/first_array_element_indentation.rb @@ -5,7 +5,10 @@ module Cop module Layout # 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. + # The other elements' indentations are handled by `Layout/ArrayAlignment` cop. + # + # This cop will respect `Layout/ArrayAlignment` and will not work when + # `EnforcedStyle: with_fixed_indentation` is specified for `Layout/ArrayAlignment`. # # 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 @@ -25,7 +28,7 @@ module Layout # # element are on separate lines is indented one step (two spaces) more # # than the position inside the opening parenthesis. # - # #bad + # # bad # array = [ # :value # ] @@ -33,7 +36,7 @@ module Layout # :no_difference # ]) # - # #good + # # good # array = [ # :value # ] @@ -47,7 +50,7 @@ module Layout # # separate lines is indented the same as an array literal which is not # # defined inside a method call. # - # #bad + # # bad # # consistent # array = [ # :value @@ -56,7 +59,7 @@ module Layout # :its_like_this # ]) # - # #good + # # good # array = [ # :value # ] @@ -68,13 +71,13 @@ module Layout # # The `align_brackets` style enforces that the opening and closing # # brackets are indented to the same position. # - # #bad + # # bad # # align_brackets # and_now_for_something = [ # :completely_different # ] # - # #good + # # good # # align_brackets # and_now_for_something = [ # :completely_different @@ -93,6 +96,8 @@ def on_array(node) end def on_send(node) + return if style != :consistent && enforce_first_argument_with_fixed_indentation? + each_argument_node(node, :array) do |array_node, left_parenthesis| check(array_node, left_parenthesis) end @@ -174,6 +179,16 @@ def message_for_right_bracket(indent_base_type) 'where the left bracket is.' end end + + def enforce_first_argument_with_fixed_indentation? + return false unless array_alignment_config['Enabled'] + + array_alignment_config['EnforcedStyle'] == 'with_fixed_indentation' + end + + def array_alignment_config + config.for_cop('Layout/ArrayAlignment') + end end end end diff --git a/lib/rubocop/cop/layout/first_parameter_indentation.rb b/lib/rubocop/cop/layout/first_parameter_indentation.rb index dfec906f0b40..1f1c2b553275 100644 --- a/lib/rubocop/cop/layout/first_parameter_indentation.rb +++ b/lib/rubocop/cop/layout/first_parameter_indentation.rb @@ -72,7 +72,7 @@ def check(def_node) return if ignored_node?(def_node) left_parenthesis = def_node.arguments.loc.begin - first_elem = def_node.arguments.first + first_elem = def_node.first_argument return unless first_elem return if same_line?(first_elem, left_parenthesis) diff --git a/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb b/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb index 546c14cc9af6..b4b42846174a 100644 --- a/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +++ b/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb @@ -182,7 +182,7 @@ def fix_closing_parenthesis(node, corrector) end def add_correct_closing_paren(node, corrector) - corrector.insert_after(node.arguments.last, ')') + corrector.insert_after(node.last_argument, ')') end def remove_incorrect_closing_paren(node, corrector) @@ -271,7 +271,7 @@ def fix_external_trailing_comma(node, corrector) def add_correct_external_trailing_comma(node, corrector) return unless external_trailing_comma?(node) - corrector.insert_after(node.arguments.last, ',') + corrector.insert_after(node.last_argument, ',') end def remove_incorrect_external_trailing_comma(node, corrector) diff --git a/lib/rubocop/cop/layout/heredoc_indentation.rb b/lib/rubocop/cop/layout/heredoc_indentation.rb index eb6d382fa743..d76f5b8d52af 100644 --- a/lib/rubocop/cop/layout/heredoc_indentation.rb +++ b/lib/rubocop/cop/layout/heredoc_indentation.rb @@ -8,7 +8,7 @@ module Layout # # 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. + # avoid ``Layout/LineLength``'s offenses. # # @example # # bad @@ -25,6 +25,9 @@ class HeredocIndentation < Base include Alignment include Heredoc extend AutoCorrector + extend TargetRubyVersion + + minimum_target_ruby_version 2.3 TYPE_MSG = 'Use %d spaces for indentation in a ' \ 'heredoc by using `<<~` instead of `%s`.' diff --git a/lib/rubocop/cop/layout/indentation_style.rb b/lib/rubocop/cop/layout/indentation_style.rb index 73a674db0a8d..e2b936f733a6 100644 --- a/lib/rubocop/cop/layout/indentation_style.rb +++ b/lib/rubocop/cop/layout/indentation_style.rb @@ -76,7 +76,7 @@ def find_offense(line, lineno) def autocorrect_lambda_for_tabs(corrector, range) spaces = ' ' * configured_indentation_width - corrector.replace(range, range.source.gsub(/\t/, spaces)) + corrector.replace(range, range.source.gsub("\t", spaces)) end def autocorrect_lambda_for_spaces(corrector, range) diff --git a/lib/rubocop/cop/layout/indentation_width.rb b/lib/rubocop/cop/layout/indentation_width.rb index 2cfcc46df508..415f2311b203 100644 --- a/lib/rubocop/cop/layout/indentation_width.rb +++ b/lib/rubocop/cop/layout/indentation_width.rb @@ -354,7 +354,7 @@ def skip_check?(base_loc, body_node) # Don't check indentation if the line doesn't start with the body. # For example, lines like "else do_something". first_char_pos_on_line = body_node.source_range.source_line =~ /\S/ - return true unless body_node.loc.column == first_char_pos_on_line + body_node.loc.column != first_char_pos_on_line end def offending_range(body_node, indentation) @@ -366,10 +366,10 @@ def offending_range(body_node, indentation) end def starts_with_access_modifier?(body_node) - return unless body_node.begin_type? + return false unless body_node.begin_type? starting_node = body_node.children.first - return unless starting_node + return false unless starting_node starting_node.send_type? && starting_node.bare_access_modifier? end diff --git a/lib/rubocop/cop/layout/leading_comment_space.rb b/lib/rubocop/cop/layout/leading_comment_space.rb index 9ea0ca0e4196..c1035029bd53 100644 --- a/lib/rubocop/cop/layout/leading_comment_space.rb +++ b/lib/rubocop/cop/layout/leading_comment_space.rb @@ -57,7 +57,7 @@ class LeadingCommentSpace < Base def on_new_investigation processed_source.comments.each do |comment| - next unless /\A#+[^#\s=+-]/.match?(comment.text) + next unless /\A(?!#\+\+|#--)(#+[^#\s=])/.match?(comment.text) next if comment.loc.line == 1 && allowed_on_first_line?(comment) next if doxygen_comment_style?(comment) next if gemfile_ruby_comment?(comment) diff --git a/lib/rubocop/cop/layout/line_continuation_leading_space.rb b/lib/rubocop/cop/layout/line_continuation_leading_space.rb index b642087a1623..afa297a2b347 100644 --- a/lib/rubocop/cop/layout/line_continuation_leading_space.rb +++ b/lib/rubocop/cop/layout/line_continuation_leading_space.rb @@ -57,16 +57,13 @@ def on_dstr(node) end_of_first_line = node.source_range.begin_pos - node.source_range.column - raw_lines(node).each_cons(2) do |raw_line_one, raw_line_two| + lines = raw_lines(node) + lines.each_cons(2).with_index(node.first_line) do |(raw_line_one, raw_line_two), line_num| end_of_first_line += raw_line_one.length - next unless continuation?(raw_line_one) + next unless continuation?(raw_line_one, line_num, node) - if enforced_style_leading? - investigate_leading_style(raw_line_one, raw_line_two, end_of_first_line) - else - investigate_trailing_style(raw_line_one, raw_line_two, end_of_first_line) - end + investigate(raw_line_one, raw_line_two, end_of_first_line) end end @@ -76,6 +73,14 @@ def raw_lines(node) processed_source.raw_source.lines[node.first_line - 1, line_range(node).size] end + def investigate(first_line, second_line, end_of_first_line) + if enforced_style_leading? + investigate_leading_style(first_line, second_line, end_of_first_line) + else + investigate_trailing_style(first_line, second_line, end_of_first_line) + end + end + def investigate_leading_style(first_line, second_line, end_of_first_line) matches = first_line.match(LEADING_STYLE_OFFENSE) return if matches.nil? @@ -98,8 +103,11 @@ def investigate_trailing_style(first_line, second_line, end_of_first_line) end end - def continuation?(line) - line.end_with?("\\\n") + def continuation?(line, line_num, node) + return false unless line.end_with?("\\\n") + + # Ensure backslash isn't part of a token spanning to the next line. + node.children.none? { |c| (c.first_line...c.last_line).cover?(line_num) && c.multiline? } end def autocorrect(corrector, offense_range, insert_pos, spaces) diff --git a/lib/rubocop/cop/layout/line_continuation_spacing.rb b/lib/rubocop/cop/layout/line_continuation_spacing.rb index 536b4448fbcd..f400b01bfb47 100644 --- a/lib/rubocop/cop/layout/line_continuation_spacing.rb +++ b/lib/rubocop/cop/layout/line_continuation_spacing.rb @@ -109,7 +109,7 @@ def ignored_literal_ranges(ast) # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def comment_ranges(comments) - comments.map(&:loc).map(&:expression) + comments.map(&:source_range) end def last_line(processed_source) diff --git a/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb b/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb index d78dcb0757c3..aa445ad6a9b1 100644 --- a/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +++ b/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb @@ -84,6 +84,8 @@ def on_dstr(node) return unless strings_concatenated_with_backslash?(node) children = node.children + return if children.empty? + if style == :aligned && !always_indented?(node) check_aligned(children, 1) else diff --git a/lib/rubocop/cop/layout/multiline_method_call_indentation.rb b/lib/rubocop/cop/layout/multiline_method_call_indentation.rb index a82640fae8d8..aa6c1c723abd 100644 --- a/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +++ b/lib/rubocop/cop/layout/multiline_method_call_indentation.rb @@ -75,7 +75,7 @@ def relevant_node?(send_node) def right_hand_side(send_node) dot = send_node.loc.dot selector = send_node.loc.selector - if send_node.dot? && selector && same_line?(dot, selector) + if (send_node.dot? || send_node.safe_navigation?) && selector && same_line?(dot, selector) dot.join(selector) elsif selector selector @@ -179,10 +179,10 @@ def syntactic_alignment_base(lhs, rhs) # a.b # .c def semantic_alignment_base(node, rhs) - return unless rhs.source.start_with?('.') + return unless rhs.source.start_with?('.', '&.') node = semantic_alignment_node(node) - return unless node&.loc&.selector + return unless node&.loc&.selector && node.loc.dot node.loc.dot.join(node.loc.selector) end @@ -204,6 +204,10 @@ def semantic_alignment_node(node) dot_right_above = get_dot_right_above(node) return dot_right_above if dot_right_above + if (multiline_block_chain_node = find_multiline_block_chain_node(node)) + return multiline_block_chain_node + end + node = first_call_has_a_dot(node) return if node.loc.dot.line != node.first_line @@ -219,6 +223,17 @@ def get_dot_right_above(node) end end + def find_multiline_block_chain_node(node) + return unless (block_node = node.each_descendant(:block, :numblock).first) + return unless block_node.multiline? && block_node.parent.call_type? + + if node.receiver.call_type? + node.receiver + else + block_node.parent + end + end + def first_call_has_a_dot(node) # descend to root of method chain node = node.receiver while node.receiver diff --git a/lib/rubocop/cop/layout/redundant_line_break.rb b/lib/rubocop/cop/layout/redundant_line_break.rb index 6d029e92f8ed..c6406ed34101 100644 --- a/lib/rubocop/cop/layout/redundant_line_break.rb +++ b/lib/rubocop/cop/layout/redundant_line_break.rb @@ -48,6 +48,10 @@ class RedundantLineBreak < Base MSG = 'Redundant line break detected.' + def on_lvasgn(node) + super unless end_with_percent_blank_string?(processed_source) + end + def on_send(node) # Include "the whole expression". node = node.parent while node.parent&.send_type? || @@ -58,9 +62,14 @@ def on_send(node) register_offense(node) end + alias on_csend on_send private + def end_with_percent_blank_string?(processed_source) + processed_source.buffer.source.end_with?("%\n\n") + end + def check_assignment(node, _rhs) return unless offense?(node) @@ -76,7 +85,13 @@ def register_offense(node) def offense?(node) node.multiline? && !too_long?(node) && suitable_as_single_line?(node) && - !configured_to_not_be_inspected?(node) + !index_access_call_chained?(node) && !configured_to_not_be_inspected?(node) + end + + def index_access_call_chained?(node) + return false unless node.send_type? && node.method?(:[]) + + node.children.first.send_type? && node.children.first.method?(:[]) end def configured_to_not_be_inspected?(node) @@ -98,9 +113,9 @@ def single_line_block_chain_enabled? def suitable_as_single_line?(node) !comment_within?(node) && - node.each_descendant(:if, :case, :kwbegin, :def).none? && + node.each_descendant(:if, :case, :kwbegin, :def, :defs).none? && node.each_descendant(:dstr, :str).none? { |n| n.heredoc? || n.value.include?("\n") } && - node.each_descendant(:begin).none? { |b| !b.single_line? } + node.each_descendant(:begin, :sym).none? { |b| !b.single_line? } end def convertible_block?(node) @@ -110,7 +125,9 @@ def convertible_block?(node) end def comment_within?(node) - processed_source.comments.map(&:loc).map(&:line).any? do |comment_line_number| + comment_line_numbers = processed_source.comments.map { |comment| comment.loc.line } + + comment_line_numbers.any? do |comment_line_number| comment_line_number >= node.first_line && comment_line_number <= node.last_line end end @@ -125,7 +142,7 @@ def to_single_line(source) .gsub(/" *\\\n\s*'/, %q(" + ')) # Double quote, backslash, and then single quote .gsub(/' *\\\n\s*"/, %q(' + ")) # Single quote, backslash, and then double quote .gsub(/(["']) *\\\n\s*\1/, '') # Double or single quote, backslash, then same quote - .gsub(/\n\s*(?=\.\w)/, '') # Extra space within method chaining + .gsub(/\n\s*(?=(&)?\.\w)/, '') # Extra space within method chaining which includes `&.` .gsub(/\s*\\?\n\s*/, ' ') # Any other line break, with or without backslash end diff --git a/lib/rubocop/cop/layout/rescue_ensure_alignment.rb b/lib/rubocop/cop/layout/rescue_ensure_alignment.rb index 3b8a741c7e54..23d7ea20a196 100644 --- a/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +++ b/lib/rubocop/cop/layout/rescue_ensure_alignment.rb @@ -29,7 +29,7 @@ class RescueEnsureAlignment < Base MSG = '`%s` at %d, %d is not ' \ 'aligned with `%s` at ' \ '%d, %d.' - ANCESTOR_TYPES = %i[kwbegin def defs class module block].freeze + ANCESTOR_TYPES = %i[kwbegin def defs class module block numblock].freeze ANCESTOR_TYPES_WITH_ACCESS_MODIFIERS = %i[def defs].freeze ALTERNATIVE_ACCESS_MODIFIERS = %i[public_class_method private_class_method].freeze @@ -95,7 +95,7 @@ def format_message(alignment_node, alignment_loc, kw_loc) def alignment_source(node, starting_loc) ending_loc = case node.type - when :block, :kwbegin + when :block, :numblock, :kwbegin node.loc.begin when :def, :defs, :class, :module, :lvasgn, :ivasgn, :cvasgn, :gvasgn, :casgn @@ -104,8 +104,8 @@ def alignment_source(node, starting_loc) mlhs_node, = *node mlhs_node.source_range else - # It is a wrapper with access modifier. - node.child_nodes.first.loc.name + # It is a wrapper with receiver of object attribute or access modifier. + node.receiver&.source_range || node.child_nodes.first.loc.name end range_between(starting_loc.begin_pos, ending_loc.end_pos).source diff --git a/lib/rubocop/cop/layout/single_line_block_chain.rb b/lib/rubocop/cop/layout/single_line_block_chain.rb index d329df92a009..fecc79eda05d 100644 --- a/lib/rubocop/cop/layout/single_line_block_chain.rb +++ b/lib/rubocop/cop/layout/single_line_block_chain.rb @@ -25,10 +25,15 @@ class SingleLineBlockChain < Base MSG = 'Put method call on a separate line if chained to a single line block.' + def self.autocorrect_incompatible_with + [Style::MapToHash] + end + def on_send(node) range = offending_range(node) add_offense(range) { |corrector| corrector.insert_before(range, "\n") } if range end + alias on_csend on_send private diff --git a/lib/rubocop/cop/layout/space_after_comma.rb b/lib/rubocop/cop/layout/space_after_comma.rb index 773ac1147157..e51a26373abb 100644 --- a/lib/rubocop/cop/layout/space_after_comma.rb +++ b/lib/rubocop/cop/layout/space_after_comma.rb @@ -24,7 +24,15 @@ def space_style_before_rcurly end def kind(token) - 'comma' if token.comma? + 'comma' if token.comma? && !before_semicolon?(token) + end + + private + + def before_semicolon?(token) + tokens = processed_source.tokens + + tokens[tokens.index(token) + 1].semicolon? end end end diff --git a/lib/rubocop/cop/layout/space_after_not.rb b/lib/rubocop/cop/layout/space_after_not.rb index 41822579e1d0..1f3fdaf1bb03 100644 --- a/lib/rubocop/cop/layout/space_after_not.rb +++ b/lib/rubocop/cop/layout/space_after_not.rb @@ -31,7 +31,7 @@ def on_send(node) private def whitespace_after_operator?(node) - node.receiver.loc.column - node.loc.column > 1 + node.receiver.source_range.begin_pos - node.source_range.begin_pos > 1 end end end diff --git a/lib/rubocop/cop/layout/space_around_method_call_operator.rb b/lib/rubocop/cop/layout/space_around_method_call_operator.rb index 319242c78de1..03c088d17403 100644 --- a/lib/rubocop/cop/layout/space_around_method_call_operator.rb +++ b/lib/rubocop/cop/layout/space_around_method_call_operator.rb @@ -19,7 +19,7 @@ module Layout # foo &. bar # foo &. bar&. buzz # RuboCop:: Cop - # RuboCop:: Cop:: Cop + # RuboCop:: Cop:: Base # :: RuboCop::Cop # # # good @@ -31,7 +31,7 @@ module Layout # foo&.bar # foo&.bar&.buzz # RuboCop::Cop - # RuboCop::Cop::Cop + # RuboCop::Cop::Base # ::RuboCop::Cop # class SpaceAroundMethodCallOperator < Base diff --git a/lib/rubocop/cop/layout/space_around_operators.rb b/lib/rubocop/cop/layout/space_around_operators.rb index 207457bb48af..f00f02248eee 100644 --- a/lib/rubocop/cop/layout/space_around_operators.rb +++ b/lib/rubocop/cop/layout/space_around_operators.rb @@ -50,6 +50,20 @@ module Layout # # # good # a ** b + # + # @example EnforcedStyleForRationalLiterals: no_space (default) + # # bad + # 1 / 48r + # + # # good + # 1/48r + # + # @example EnforcedStyleForRationalLiterals: space + # # bad + # 1/48r + # + # # good + # 1 / 48r class SpaceAroundOperators < Base include PrecedingFollowingAlignment include RangeHelp @@ -64,7 +78,7 @@ def self.autocorrect_incompatible_with end def on_sclass(node) - check_operator(:sclass, node.loc.operator, node.source_range) + check_operator(:sclass, node.loc.operator, node) end def on_pair(node) @@ -72,14 +86,14 @@ def on_pair(node) return if hash_table_style? && !node.parent.pairs_on_same_line? - check_operator(:pair, node.loc.operator, node.source_range) + check_operator(:pair, node.loc.operator, node) end def on_if(node) return unless node.ternary? - check_operator(:if, node.loc.question, node.if_branch.source_range) - check_operator(:if, node.loc.colon, node.else_branch.source_range) + check_operator(:if, node.loc.question, node.if_branch) + check_operator(:if, node.loc.colon, node.else_branch) end def on_resbody(node) @@ -87,7 +101,7 @@ def on_resbody(node) _, variable, = *node - check_operator(:resbody, node.loc.assoc, variable.source_range) + check_operator(:resbody, node.loc.assoc, variable) end def on_send(node) @@ -96,7 +110,7 @@ def on_send(node) if node.setter_method? on_special_asgn(node) elsif regular_operator?(node) - check_operator(:send, node.loc.selector, node.first_argument.source_range) + check_operator(:send, node.loc.selector, node.first_argument) end end @@ -105,7 +119,7 @@ def on_assignment(node) return unless rhs - check_operator(:assignment, node.loc.operator, rhs.source_range) + check_operator(:assignment, node.loc.operator, rhs) end def on_casgn(node) @@ -113,7 +127,7 @@ def on_casgn(node) return unless right - check_operator(:assignment, node.loc.operator, right.source_range) + check_operator(:assignment, node.loc.operator, right) end def on_binary(node) @@ -121,7 +135,7 @@ def on_binary(node) return unless rhs - check_operator(:binary, node.loc.operator, rhs.source_range) + check_operator(:binary, node.loc.operator, rhs) end def on_special_asgn(node) @@ -129,13 +143,13 @@ def on_special_asgn(node) return unless right - check_operator(:special_asgn, node.loc.operator, right.source_range) + check_operator(:special_asgn, node.loc.operator, right) end def on_match_pattern(node) return if target_ruby_version < 3.0 - check_operator(:match_pattern, node.loc.operator, node.source_range) + check_operator(:match_pattern, node.loc.operator, node) end alias on_or on_binary @@ -153,7 +167,9 @@ def on_match_pattern(node) private def regular_operator?(send_node) - !send_node.unary_operation? && !send_node.dot? && operator_with_regular_syntax?(send_node) + return false if send_node.unary_operation? || send_node.dot? || send_node.double_colon? + + operator_with_regular_syntax?(send_node) end def operator_with_regular_syntax?(send_node) @@ -166,7 +182,7 @@ def check_operator(type, operator, right_operand) offense(type, operator, with_space, right_operand) do |msg| add_offense(operator, message: msg) do |corrector| - autocorrect(corrector, with_space) + autocorrect(corrector, with_space, right_operand) end end end @@ -176,11 +192,15 @@ def offense(type, operator, with_space, right_operand) yield msg if msg end - def autocorrect(corrector, range) - if range.source.include?('**') && !space_around_exponent_operator? + def autocorrect(corrector, range, right_operand) + range_source = range.source + + if range_source.include?('**') && !space_around_exponent_operator? corrector.replace(range, '**') - elsif range.source.end_with?("\n") - corrector.replace(range, " #{range.source.strip}\n") + elsif range_source.include?('/') && !space_around_slash_operator?(right_operand) + corrector.replace(range, '/') + elsif range_source.end_with?("\n") + corrector.replace(range, " #{range_source.strip}\n") else enclose_operator_with_space(corrector, range) end @@ -200,14 +220,14 @@ def enclose_operator_with_space(corrector, range) end def offense_message(type, operator, with_space, right_operand) - if should_not_have_surrounding_space?(operator) + if should_not_have_surrounding_space?(operator, right_operand) return if with_space.is?(operator.source) "Space around operator `#{operator.source}` detected." elsif !/^\s.*\s$/.match?(with_space.source) "Surrounding space missing for operator `#{operator.source}`." elsif excess_leading_space?(type, operator, with_space) || - excess_trailing_space?(right_operand, with_space) + excess_trailing_space?(right_operand.source_range, with_space) "Operator `#{operator.source}` should be surrounded " \ 'by a single space.' end @@ -245,12 +265,24 @@ def space_around_exponent_operator? cop_config['EnforcedStyleForExponentOperator'] == 'space' end + def space_around_slash_operator?(right_operand) + return true unless right_operand.rational_type? + + cop_config['EnforcedStyleForRationalLiterals'] == 'space' + end + def force_equal_sign_alignment? config.for_cop('Layout/ExtraSpacing')['ForceEqualSignAlignment'] end - def should_not_have_surrounding_space?(operator) - operator.is?('**') ? !space_around_exponent_operator? : false + def should_not_have_surrounding_space?(operator, right_operand) + if operator.is?('**') + !space_around_exponent_operator? + elsif operator.is?('/') + !space_around_slash_operator?(right_operand) + else + false + end end end end diff --git a/lib/rubocop/cop/layout/space_inside_parens.rb b/lib/rubocop/cop/layout/space_inside_parens.rb index caeb3c244c96..2d577cf5e15f 100644 --- a/lib/rubocop/cop/layout/space_inside_parens.rb +++ b/lib/rubocop/cop/layout/space_inside_parens.rb @@ -168,7 +168,7 @@ def can_be_ignored?(token1, token2) # follows, and that the rules for space inside don't apply. return true if token2.comment? - return true unless same_line?(token1, token2) && !token1.space_after? + !same_line?(token1, token2) || token1.space_after? end end end diff --git a/lib/rubocop/cop/layout/space_inside_range_literal.rb b/lib/rubocop/cop/layout/space_inside_range_literal.rb index 4419dba0fd70..bf85cd67a120 100644 --- a/lib/rubocop/cop/layout/space_inside_range_literal.rb +++ b/lib/rubocop/cop/layout/space_inside_range_literal.rb @@ -35,7 +35,7 @@ def on_erange(node) def check(node) expression = node.source op = node.loc.operator.source - escaped_op = op.gsub(/\./, '\.') + escaped_op = op.gsub('.', '\.') # account for multiline range literals expression.sub!(/#{escaped_op}\n\s*/, op) diff --git a/lib/rubocop/cop/layout/trailing_empty_lines.rb b/lib/rubocop/cop/layout/trailing_empty_lines.rb index 73f6c6a7ce90..dd76435ec4ba 100644 --- a/lib/rubocop/cop/layout/trailing_empty_lines.rb +++ b/lib/rubocop/cop/layout/trailing_empty_lines.rb @@ -51,6 +51,7 @@ def on_new_investigation # there could be good reasons why it needs to end with a certain # number of newlines. return if ends_in_end?(processed_source) + return if end_with_percent_blank_string?(processed_source) whitespace_at_end = buffer.source[/\s*\Z/] blank_lines = whitespace_at_end.count("\n") - 1 @@ -86,6 +87,10 @@ def ends_in_end?(processed_source) extra&.strip&.start_with?('__END__') end + def end_with_percent_blank_string?(processed_source) + processed_source.buffer.source.end_with?("%\n\n") + end + def message(wanted_blank_lines, blank_lines) case blank_lines when -1 diff --git a/lib/rubocop/cop/lint/assignment_in_condition.rb b/lib/rubocop/cop/lint/assignment_in_condition.rb index 319f6c9ce0f0..d90bd310617e 100644 --- a/lib/rubocop/cop/lint/assignment_in_condition.rb +++ b/lib/rubocop/cop/lint/assignment_in_condition.rb @@ -17,24 +17,24 @@ module Lint # # @example # # bad - # if some_var = true + # if some_var = value # do_something # end # # # good - # if some_var == true + # if some_var == value # do_something # end # # @example AllowSafeAssignment: true (default) # # good - # if (some_var = true) + # if (some_var = value) # do_something # end # # @example AllowSafeAssignment: false # # bad - # if (some_var = true) + # if (some_var = value) # do_something # end # diff --git a/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb b/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb index 37732b1a8adb..dcd4cd09a212 100644 --- a/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +++ b/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb @@ -6,10 +6,10 @@ module Lint # Checks for places where binary operator has identical operands. # # It covers arithmetic operators: `-`, `/`, `%`; - # comparison operators: `==`, `===`, `=~`, `>`, `>=`, `<`, `<=`; + # comparison operators: `==`, `===`, `=~`, `>`, `>=`, `<`, ``<=``; # bitwise operators: `|`, `^`, `&`; # boolean operators: `&&`, `||` - # and "spaceship" operator - `<=>`. + # and "spaceship" operator - ``<=>``. # # Simple arithmetic operations are allowed by this cop: `+`, `*`, `**`, `<<` and `>>`. # Although these can be rewritten in a different way, it should not be necessary to diff --git a/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb b/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb index 3dcbec2ec630..2256784149de 100644 --- a/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +++ b/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb @@ -3,7 +3,7 @@ module RuboCop module Cop module Lint - # Checks for overwriting an exception with an exception result by use `rescue =>`. + # Checks for overwriting an exception with an exception result by use ``rescue =>``. # # You intended to write as `rescue StandardError`. # However, you have written `rescue => StandardError`. diff --git a/lib/rubocop/cop/lint/debugger.rb b/lib/rubocop/cop/lint/debugger.rb index 6cf1a13f2a33..f178d4828b46 100644 --- a/lib/rubocop/cop/lint/debugger.rb +++ b/lib/rubocop/cop/lint/debugger.rb @@ -66,12 +66,10 @@ module Lint # end class Debugger < Base MSG = 'Remove debugger entry point `%s`.' + BLOCK_TYPES = %i[block numblock kwbegin].freeze def on_send(node) - return unless debugger_method?(node) - - # Basically, debugger methods are not used as a method argument without arguments. - return if node.arguments.empty? && node.each_ancestor(:send, :csend).any? + return if !debugger_method?(node) || assumed_usage_context?(node) add_offense(node) end @@ -90,11 +88,21 @@ def debugger_methods end def debugger_method?(send_node) - return if send_node.parent&.send_type? && send_node.parent.receiver == send_node + return false if send_node.parent&.send_type? && send_node.parent.receiver == send_node debugger_methods.include?(chained_method_name(send_node)) end + def assumed_usage_context?(node) + # Basically, debugger methods are not used as a method argument without arguments. + return false unless node.arguments.empty? && node.each_ancestor(:send, :csend).any? + return true if assumed_argument?(node) + + node.each_ancestor.none? do |ancestor| + BLOCK_TYPES.include?(ancestor.type) || ancestor.lambda_or_proc? + end + end + def chained_method_name(send_node) chained_method_name = send_node.method_name.to_s receiver = send_node.receiver @@ -105,6 +113,12 @@ def chained_method_name(send_node) end chained_method_name end + + def assumed_argument?(node) + parent = node.parent + + parent.call_type? || parent.literal? || parent.pair_type? + end end end end diff --git a/lib/rubocop/cop/lint/duplicate_hash_key.rb b/lib/rubocop/cop/lint/duplicate_hash_key.rb index 8c572fcc8940..e0b42e71f9b2 100644 --- a/lib/rubocop/cop/lint/duplicate_hash_key.rb +++ b/lib/rubocop/cop/lint/duplicate_hash_key.rb @@ -4,6 +4,7 @@ module RuboCop module Cop module Lint # Checks for duplicated keys in hash literals. + # This cop considers both primitive types and constants for the hash keys. # # This cop mirrors a warning in Ruby 2.2. # @@ -24,7 +25,7 @@ class DuplicateHashKey < Base MSG = 'Duplicated key in hash literal.' def on_hash(node) - keys = node.keys.select(&:recursive_basic_literal?) + keys = node.keys.select { |key| key.recursive_basic_literal? || key.const_type? } return unless duplicates?(keys) diff --git a/lib/rubocop/cop/lint/duplicate_methods.rb b/lib/rubocop/cop/lint/duplicate_methods.rb index 12c4dd6003a5..ffd584188ce9 100644 --- a/lib/rubocop/cop/lint/duplicate_methods.rb +++ b/lib/rubocop/cop/lint/duplicate_methods.rb @@ -253,7 +253,7 @@ def possible_dsl?(node) # Assume that if a method definition is inside any block call which # we can't identify, it could be a DSL node.each_ancestor(:block).any? do |ancestor| - ancestor.method_name != :class_eval && !ancestor.class_constructor? + !ancestor.method?(:class_eval) && !ancestor.class_constructor? end end diff --git a/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb b/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb index c4f82a713599..7b6fec46e2d5 100644 --- a/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +++ b/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb @@ -24,6 +24,8 @@ class DuplicateRegexpCharacterClassElement < Base MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class' + OCTAL_DIGITS_AFTER_ESCAPE = 2 + def on_regexp(node) each_repeated_character_class_element_loc(node) do |loc| add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector| @@ -32,35 +34,57 @@ def on_regexp(node) end end - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def each_repeated_character_class_element_loc(node) node.parsed_tree&.each_expression do |expr| next if skip_expression?(expr) seen = Set.new - enum = expr.expressions.to_enum - expression_count = expr.expressions.count + group_expressions(node, expr.expressions) do |group| + group_source = group.map(&:to_s).join - expression_count.times do |current_number| - current_child = enum.next - next if within_interpolation?(node, current_child) + yield source_range(group) if seen.include?(group_source) - current_child_source = current_child.to_s - next_child = enum.peek if current_number + 1 < expression_count + seen << group_source + end + end + end - if seen.include?(current_child_source) - next if start_with_escaped_zero_number?(current_child_source, next_child.to_s) + private - yield current_child.expression - end + def group_expressions(node, expressions) + # Create a mutable list to simplify state tracking while we iterate. + expressions = expressions.to_a - seen << current_child_source - end + until expressions.empty? + # With we may need to compose a group of multiple expressions. + group = [expressions.shift] + next if within_interpolation?(node, group.first) + + # With regexp_parser < 2.7 escaped octal sequences may be up to 3 + # separate expressions ("\\0", "0", "1"). + pop_octal_digits(group, expressions) if escaped_octal?(group.first.to_s) + + yield(group) end end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - private + def pop_octal_digits(current_child, expressions) + OCTAL_DIGITS_AFTER_ESCAPE.times do + next_child = expressions.first + break unless octal?(next_child.to_s) + + current_child << expressions.shift + end + end + + def source_range(children) + return children.first.expression if children.size == 1 + + range_between( + children.first.expression.begin_pos, + children.last.expression.begin_pos + children.last.to_s.length + ) + end def skip_expression?(expr) expr.type != :set || expr.token == :intersection @@ -75,9 +99,12 @@ def within_interpolation?(node, child) interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) } end - def start_with_escaped_zero_number?(current_child, next_child) - # Represents escaped code from `"\00"` (`"\u0000"`) to `"\07"` (`"\a"`). - current_child == '\\0' && next_child.match?(/[0-7]/) + def escaped_octal?(string) + string.length == 2 && string[0] == '\\' && octal?(string[1]) + end + + def octal?(char) + ('0'..'7').cover?(char) end def interpolation_locs(node) diff --git a/lib/rubocop/cop/lint/empty_block.rb b/lib/rubocop/cop/lint/empty_block.rb index b401efd5d02a..87073ba113d9 100644 --- a/lib/rubocop/cop/lint/empty_block.rb +++ b/lib/rubocop/cop/lint/empty_block.rb @@ -5,7 +5,7 @@ module Cop module Lint # 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. + # to clarify what we're aiming for. # # Empty lambdas and procs are ignored by default. # diff --git a/lib/rubocop/cop/lint/empty_conditional_body.rb b/lib/rubocop/cop/lint/empty_conditional_body.rb index 97a7ca078576..39135a59e56a 100644 --- a/lib/rubocop/cop/lint/empty_conditional_body.rb +++ b/lib/rubocop/cop/lint/empty_conditional_body.rb @@ -104,7 +104,7 @@ def remove_empty_branch(corrector, node) def correct_other_branches(corrector, node) return unless require_other_branches_correction?(node) - if node.else_branch&.if_type? + if node.else_branch&.if_type? && !node.else_branch.modifier_form? # Replace an orphaned `elsif` with `if` corrector.replace(node.else_branch.loc.keyword, 'if') else diff --git a/lib/rubocop/cop/lint/erb_new_arguments.rb b/lib/rubocop/cop/lint/erb_new_arguments.rb index 1a44130264b5..6bfadbe57bc4 100644 --- a/lib/rubocop/cop/lint/erb_new_arguments.rb +++ b/lib/rubocop/cop/lint/erb_new_arguments.rb @@ -106,7 +106,7 @@ def on_send(node) private def autocorrect(corrector, node) - str_arg = node.arguments[0].source + str_arg = node.first_argument.source kwargs = build_kwargs(node) overridden_kwargs = override_by_legacy_args(kwargs, node) @@ -121,11 +121,11 @@ def correct_arguments?(arguments) end def build_kwargs(node) - return [nil, nil] unless node.arguments.last.hash_type? + return [nil, nil] unless node.last_argument.hash_type? trim_mode_arg, eoutvar_arg = nil - node.arguments.last.pairs.each do |pair| + node.last_argument.pairs.each do |pair| case pair.key.source when 'trim_mode' trim_mode_arg = "trim_mode: #{pair.value.source}" diff --git a/lib/rubocop/cop/lint/float_comparison.rb b/lib/rubocop/cop/lint/float_comparison.rb index eacc56976bf1..99cc78ccf8d0 100644 --- a/lib/rubocop/cop/lint/float_comparison.rb +++ b/lib/rubocop/cop/lint/float_comparison.rb @@ -18,6 +18,10 @@ module Lint # # good - using BigDecimal # x.to_d == 0.1.to_d # + # # good - comparing against zero + # x == 0.0 + # x != 0.0 + # # # good # (x - 0.1).abs < Float::EPSILON # @@ -39,6 +43,8 @@ class FloatComparison < Base def on_send(node) lhs, _method, rhs = *node + return if literal_zero?(lhs) || literal_zero?(rhs) + add_offense(node) if float?(lhs) || float?(rhs) end @@ -59,6 +65,10 @@ def float?(node) end end + def literal_zero?(node) + node&.numeric_type? && node.value.zero? + end + # rubocop:disable Metrics/PerceivedComplexity def check_send(node) if node.arithmetic_operation? diff --git a/lib/rubocop/cop/lint/hash_compare_by_identity.rb b/lib/rubocop/cop/lint/hash_compare_by_identity.rb index 5305e48277d4..8cb8661866e9 100644 --- a/lib/rubocop/cop/lint/hash_compare_by_identity.rb +++ b/lib/rubocop/cop/lint/hash_compare_by_identity.rb @@ -35,12 +35,13 @@ class HashCompareByIdentity < Base # @!method id_as_hash_key?(node) def_node_matcher :id_as_hash_key?, <<~PATTERN - (send _ {:key? :has_key? :fetch :[] :[]=} (send _ :object_id) ...) + (call _ {:key? :has_key? :fetch :[] :[]=} (send _ :object_id) ...) PATTERN def on_send(node) add_offense(node) if id_as_hash_key?(node) end + alias on_csend on_send end end end diff --git a/lib/rubocop/cop/lint/heredoc_method_call_position.rb b/lib/rubocop/cop/lint/heredoc_method_call_position.rb index 0c3c5a2b3074..5b423de9b76d 100644 --- a/lib/rubocop/cop/lint/heredoc_method_call_position.rb +++ b/lib/rubocop/cop/lint/heredoc_method_call_position.rb @@ -65,7 +65,7 @@ def heredoc_node_descendent_receiver(node) end def send_node?(node) - return nil unless node + return false unless node node.call_type? end diff --git a/lib/rubocop/cop/lint/it_without_arguments_in_block.rb b/lib/rubocop/cop/lint/it_without_arguments_in_block.rb new file mode 100644 index 000000000000..1460457d10fd --- /dev/null +++ b/lib/rubocop/cop/lint/it_without_arguments_in_block.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Lint + # Emulates the following Ruby warning in Ruby 3.3. + # + # [source,ruby] + # ---- + # $ ruby -e '0.times { it }' + # -e:1: warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; + # use it() or self.it + # ---- + # + # `it` calls without arguments will refer to the first block param in Ruby 3.4. + # So use `it()` or `self.it` to ensure compatibility. + # + # @example + # + # # bad + # do_something { it } + # + # # good + # do_something { it() } + # do_something { self.it } + # + class ItWithoutArgumentsInBlock < Base + include NodePattern::Macros + + MSG = '`it` calls without arguments will refer to the first block param in Ruby 3.4; ' \ + 'use `it()` or `self.it`.' + + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + return unless (body = node.body) + return unless node.arguments.empty_and_without_delimiters? + + if body.send_type? && deprecated_it_method?(body) + add_offense(body) + else + body.each_descendant(:send).each do |send_node| + next unless deprecated_it_method?(send_node) + + add_offense(send_node) + end + end + end + + def deprecated_it_method?(node) + return false unless node.method?(:it) + + !node.receiver && node.arguments.empty? && !node.parenthesized? && !node.block_literal? + end + end + end + end +end diff --git a/lib/rubocop/cop/lint/literal_assignment_in_condition.rb b/lib/rubocop/cop/lint/literal_assignment_in_condition.rb new file mode 100644 index 000000000000..a922ba916af7 --- /dev/null +++ b/lib/rubocop/cop/lint/literal_assignment_in_condition.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Lint + # Checks for literal assignments in the conditions of `if`, `while`, and `until`. + # It emulates the following Ruby warning: + # + # [source,console] + # ---- + # $ ruby -we 'if x = true; end' + # -e:1: warning: found `= literal' in conditional, should be == + # ---- + # + # As a lint cop, it cannot be determined if `==` is appropriate as intended, + # therefore this cop does not provide autocorrection. + # + # @example + # + # # bad + # if x = 42 + # do_something + # end + # + # # good + # if x == 42 + # do_something + # end + # + # # good + # if x = y + # do_something + # end + # + class LiteralAssignmentInCondition < Base + MSG = "Don't use literal assignment `= %s` in conditional, " \ + 'should be `==` or non-literal operand.' + + def on_if(node) + traverse_node(node.condition) do |asgn_node| + next unless asgn_node.loc.operator + + rhs = asgn_node.to_a.last + next if !all_literals?(rhs) || parallel_assignment_with_splat_operator?(rhs) + + range = offense_range(asgn_node, rhs) + + add_offense(range, message: format(MSG, literal: rhs.source)) + end + end + alias on_while on_if + alias on_until on_if + + private + + def traverse_node(node, &block) + yield node if AST::Node::EQUALS_ASSIGNMENTS.include?(node.type) + + node.each_child_node { |child| traverse_node(child, &block) } + end + + def all_literals?(node) + case node.type + when :dstr, :xstr + false + when :array + node.values.all? { |value| all_literals?(value) } + when :hash + (node.values + node.keys).all? { |item| all_literals?(item) } + else + node.respond_to?(:literal?) && node.literal? + end + end + + def parallel_assignment_with_splat_operator?(node) + node.array_type? && node.values.first&.splat_type? + end + + def offense_range(asgn_node, rhs) + asgn_node.loc.operator.join(rhs.source_range.end) + end + end + end + end +end diff --git a/lib/rubocop/cop/lint/literal_in_interpolation.rb b/lib/rubocop/cop/lint/literal_in_interpolation.rb index ab2e2539d066..e1d18ba8a91c 100644 --- a/lib/rubocop/cop/lint/literal_in_interpolation.rb +++ b/lib/rubocop/cop/lint/literal_in_interpolation.rb @@ -34,7 +34,7 @@ def on_interpolation(begin_node) # interpolation should not be removed if the expanded value # contains a space character. expanded_value = autocorrected_value(final_node) - return if in_array_percent_literal?(begin_node) && /\s/.match?(expanded_value) + return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value) add_offense(final_node) do |corrector| return if final_node.dstr_type? # nested, fixed in next iteration diff --git a/lib/rubocop/cop/lint/missing_super.rb b/lib/rubocop/cop/lint/missing_super.rb index 8894a7059ebd..52aec37ea8ba 100644 --- a/lib/rubocop/cop/lint/missing_super.rb +++ b/lib/rubocop/cop/lint/missing_super.rb @@ -14,6 +14,13 @@ module Lint # Autocorrection is not supported because the position of `super` cannot be # determined automatically. # + # `Object` and `BasicObject` are allowed by this cop because of their + # stateless nature. However, sometimes you might want to allow other parent + # classes from this cop, for example in the case of an abstract class that is + # not meant to be called with `super`. In those cases, you can use the + # `AllowedParentClasses` option to specify which classes should be allowed + # *in addition to* `Object` and `BasicObject`. + # # @example # # bad # class Employee < Person @@ -60,6 +67,21 @@ module Lint # end # end # + # # good + # class ClassWithNoParent + # def initialize + # do_something + # end + # end + # + # @example AllowedParentClasses: [MyAbstractClass] + # # good + # class MyConcreteClass < MyAbstractClass + # def initialize + # do_something + # end + # end + # class MissingSuper < Base CONSTRUCTOR_MSG = 'Call `super` to initialize state of the parent class.' CALLBACK_MSG = 'Call `super` to invoke callback defined in the parent class.' @@ -103,7 +125,7 @@ def offender?(node) end def callback_method_def?(node) - return unless CALLBACKS.include?(node.method_name) + return false unless CALLBACKS.include?(node.method_name) node.each_ancestor(:class, :sclass, :module).first end @@ -116,16 +138,20 @@ def inside_class_with_stateful_parent?(node) if (block_node = node.each_ancestor(:block, :numblock).first) return false unless (super_class = class_new_block(block_node)) - !stateless_class?(super_class) + !allowed_class?(super_class) elsif (class_node = node.each_ancestor(:class).first) - class_node.parent_class && !stateless_class?(class_node.parent_class) + class_node.parent_class && !allowed_class?(class_node.parent_class) else false end end - def stateless_class?(node) - STATELESS_CLASSES.include?(node.const_name) + def allowed_class?(node) + allowed_classes.include?(node.const_name) + end + + def allowed_classes + @allowed_classes ||= STATELESS_CLASSES + cop_config.fetch('AllowedParentClasses', []) end end end diff --git a/lib/rubocop/cop/lint/mixed_case_range.rb b/lib/rubocop/cop/lint/mixed_case_range.rb new file mode 100644 index 000000000000..b7817981483f --- /dev/null +++ b/lib/rubocop/cop/lint/mixed_case_range.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Lint + # Checks for mixed-case character ranges since they include likely unintended characters. + # + # Offenses are registered for regexp character classes like `/[A-z]/` + # as well as range objects like `('A'..'z')`. + # + # NOTE: Range objects cannot be autocorrected. + # + # @safety + # The cop autocorrects regexp character classes + # by replacing one character range with two: `A-z` becomes `A-Za-z`. + # In most cases this is probably what was originally intended + # but it changes the regexp to no longer match symbols it used to include. + # For this reason, this cop's autocorrect is unsafe (it will + # change the behavior of the code). + # + # @example + # + # # bad + # r = /[A-z]/ + # + # # good + # r = /[A-Za-z]/ + class MixedCaseRange < Base + extend AutoCorrector + include RangeHelp + + MSG = 'Ranges from upper to lower case ASCII letters may include unintended ' \ + 'characters. Instead of `A-z` (which also includes several symbols) ' \ + 'specify each range individually: `A-Za-z` and individually specify any symbols.' + RANGES = [('a'..'z').freeze, ('A'..'Z').freeze].freeze + + def on_irange(node) + return unless node.children.compact.all?(&:str_type?) + + range_start, range_end = node.children + + return if range_start.nil? || range_end.nil? + + add_offense(node) if unsafe_range?(range_start.value, range_end.value) + end + alias on_erange on_irange + + def on_regexp(node) + each_unsafe_regexp_range(node) do |loc| + add_offense(loc) do |corrector| + corrector.replace(loc, rewrite_regexp_range(loc.source)) + end + end + end + + def each_unsafe_regexp_range(node) + node.parsed_tree&.each_expression do |expr| + next if skip_expression?(expr) + + range_pairs(expr).reject do |range_start, range_end| + next if skip_range?(range_start, range_end) + + next unless unsafe_range?(range_start.text, range_end.text) + + yield(build_source_range(range_start, range_end)) + end + end + end + + private + + def build_source_range(range_start, range_end) + range_between(range_start.expression.begin_pos, range_end.expression.end_pos) + end + + def range_for(char) + RANGES.detect do |range| + range.include?(char) + end + end + + def range_pairs(expr) + RuboCop::Cop::Utils::RegexpRanges.new(expr).pairs + end + + def unsafe_range?(range_start, range_end) + return false if range_start.length != 1 || range_end.length != 1 + + range_for(range_start) != range_for(range_end) + end + + def skip_expression?(expr) + !(expr.type == :set && expr.token == :character) + end + + def skip_range?(range_start, range_end) + [range_start, range_end].any? do |bound| + bound.type != :literal + end + end + + def rewrite_regexp_range(source) + open, close = source.split('-') + first = [open, range_for(open).end] + second = [range_for(close).begin, close] + "#{first.uniq.join('-')}#{second.uniq.join('-')}" + end + end + end + end +end diff --git a/lib/rubocop/cop/lint/next_without_accumulator.rb b/lib/rubocop/cop/lint/next_without_accumulator.rb index da811e60ad0b..383940a54bfc 100644 --- a/lib/rubocop/cop/lint/next_without_accumulator.rb +++ b/lib/rubocop/cop/lint/next_without_accumulator.rb @@ -34,35 +34,20 @@ def on_block(node) add_offense(void_next) if void_next end end - - def on_numblock(node) - on_numblock_body_of_reduce(node) do |body| - void_next = body.each_node(:next).find do |n| - n.children.empty? && parent_numblock_node(n) == node - end - - add_offense(void_next) if void_next - end - end + alias on_numblock on_block private # @!method on_block_body_of_reduce(node) def_node_matcher :on_block_body_of_reduce, <<~PATTERN - (block (send _recv {:reduce :inject} !sym) _blockargs $(begin ...)) - PATTERN - - # @!method on_numblock_body_of_reduce(node) - def_node_matcher :on_numblock_body_of_reduce, <<~PATTERN - (numblock (send _recv {:reduce :inject} !sym) _argscount $(begin ...)) + { + (block (call _recv {:reduce :inject} !sym) _blockargs $(begin ...)) + (numblock (call _recv {:reduce :inject} !sym) _argscount $(begin ...)) + } PATTERN def parent_block_node(node) - node.each_ancestor(:block).first - end - - def parent_numblock_node(node) - node.each_ancestor(:numblock).first + node.each_ancestor(:block, :numblock).first end end end diff --git a/lib/rubocop/cop/lint/non_atomic_file_operation.rb b/lib/rubocop/cop/lint/non_atomic_file_operation.rb index 2a8bbdfbbd48..553287f34726 100644 --- a/lib/rubocop/cop/lint/non_atomic_file_operation.rb +++ b/lib/rubocop/cop/lint/non_atomic_file_operation.rb @@ -43,7 +43,6 @@ module Lint # class NonAtomicFileOperation < Base extend AutoCorrector - include Alignment MSG_REMOVE_FILE_EXIST_CHECK = 'Remove unnecessary existence check ' \ '`%s.%s`.' @@ -51,18 +50,20 @@ class NonAtomicFileOperation < Base MAKE_FORCE_METHODS = %i[makedirs mkdir_p mkpath].freeze MAKE_METHODS = %i[mkdir].freeze REMOVE_FORCE_METHODS = %i[rm_f rm_rf].freeze - REMOVE_METHODS = %i[remove remove_dir remove_entry remove_entry_secure - delete unlink remove_file rm rmdir safe_unlink].freeze - RESTRICT_ON_SEND = (MAKE_METHODS + MAKE_FORCE_METHODS + REMOVE_METHODS + - REMOVE_FORCE_METHODS).freeze + REMOVE_METHODS = %i[remove delete unlink remove_file rm rmdir safe_unlink].freeze + RECURSIVE_REMOVE_METHODS = %i[remove_dir remove_entry remove_entry_secure].freeze + RESTRICT_ON_SEND = ( + MAKE_METHODS + MAKE_FORCE_METHODS + REMOVE_METHODS + RECURSIVE_REMOVE_METHODS + + REMOVE_FORCE_METHODS + ).freeze # @!method send_exist_node(node) - def_node_search :send_exist_node, <<-PATTERN + def_node_search :send_exist_node, <<~PATTERN $(send (const nil? {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...) PATTERN # @!method receiver_and_method_name(node) - def_node_matcher :receiver_and_method_name, <<-PATTERN + def_node_matcher :receiver_and_method_name, <<~PATTERN (send (const nil? $_) $_ ...) PATTERN @@ -140,6 +141,8 @@ def replacement_method(node) 'mkdir_p' elsif REMOVE_METHODS.include?(node.method_name) 'rm_f' + elsif RECURSIVE_REMOVE_METHODS.include?(node.method_name) + 'rm_rf' else node.method_name end diff --git a/lib/rubocop/cop/lint/non_deterministic_require_order.rb b/lib/rubocop/cop/lint/non_deterministic_require_order.rb index 6a5aeac60bc1..eaade72b3e21 100644 --- a/lib/rubocop/cop/lint/non_deterministic_require_order.rb +++ b/lib/rubocop/cop/lint/non_deterministic_require_order.rb @@ -94,7 +94,7 @@ def on_block_pass(node) parent_node = node.parent add_offense(parent_node) do |corrector| - if parent_node.arguments.last&.block_pass_type? + if parent_node.last_argument&.block_pass_type? correct_block_pass(corrector, parent_node) else correct_block(corrector, parent_node) @@ -116,7 +116,7 @@ def correct_block(corrector, node) def correct_block_pass(corrector, node) if unsorted_dir_glob_pass?(node) - block_arg = node.arguments.last + block_arg = node.last_argument corrector.remove(last_arg_range(node)) corrector.insert_after(node, ".sort.each(#{block_arg.source})") @@ -130,9 +130,7 @@ def correct_block_pass(corrector, node) # @return [Parser::Source::Range] # def last_arg_range(node) - node.arguments.last.source_range.with( - begin_pos: node.arguments[-2].source_range.end_pos - ) + node.last_argument.source_range.with(begin_pos: node.arguments[-2].source_range.end_pos) end def unsorted_dir_loop?(node) diff --git a/lib/rubocop/cop/lint/number_conversion.rb b/lib/rubocop/cop/lint/number_conversion.rb index b966ab4db69e..73880285a1f1 100644 --- a/lib/rubocop/cop/lint/number_conversion.rb +++ b/lib/rubocop/cop/lint/number_conversion.rb @@ -9,7 +9,7 @@ module Lint # # 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`). + # `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. # @@ -91,19 +91,24 @@ class NumberConversion < Base # @!method to_method(node) def_node_matcher :to_method, <<~PATTERN - (send $_ ${#{METHODS}}) + (call $_ ${#{METHODS}}) PATTERN # @!method to_method_symbol(node) def_node_matcher :to_method_symbol, <<~PATTERN - {(send _ $_ ${(sym ${#{METHODS}})} ...) - (send _ $_ ${(block_pass (sym ${#{METHODS}}))} ...)} + (call _ $_ ${ + { + (sym ${#{METHODS}}) + (block_pass (sym ${#{METHODS}})) + } + } ...) PATTERN def on_send(node) handle_conversion_method(node) handle_as_symbol(node) end + alias on_csend on_send private diff --git a/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb b/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb new file mode 100644 index 000000000000..82e8fbcad97e --- /dev/null +++ b/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Lint + # Checks for redundant quantifiers inside Regexp literals. + # + # It is always allowed when interpolation is used in a regexp literal, + # because it's unknown what kind of string will be expanded as a result: + # + # [source,ruby] + # ---- + # /(?:a*#{interpolation})?/x + # ---- + # + # @example + # # bad + # /(?:x+)+/ + # + # # good + # /(?:x)+/ + # + # # good + # /(?:x+)/ + # + # # bad + # /(?:x+)?/ + # + # # good + # /(?:x)*/ + # + # # good + # /(?:x*)/ + class RedundantRegexpQuantifiers < Base + include RangeHelp + extend AutoCorrector + + MSG_REDUNDANT_QUANTIFIER = 'Replace redundant quantifiers ' \ + '`%s` and `%s` ' \ + 'with a single `%s`.' + + def on_regexp(node) + return if node.interpolation? + + each_redundantly_quantified_pair(node) do |group, child| + replacement = merged_quantifier(group, child) + add_offense( + quantifier_range(group, child), + message: message(group, child, replacement) + ) do |corrector| + # drop outer quantifier + corrector.replace(group.loc.quantifier, '') + # replace inner quantifier + corrector.replace(child.loc.quantifier, replacement) + end + end + end + + private + + def each_redundantly_quantified_pair(node) + seen = Set.new + node.parsed_tree&.each_expression do |(expr)| + next if seen.include?(expr) || !redundant_group?(expr) || !mergeable_quantifier(expr) + + expr.each_expression do |(subexp)| + seen << subexp + break unless redundantly_quantifiable?(subexp) + + yield(expr, subexp) if mergeable_quantifier(subexp) + end + end + end + + def redundant_group?(expr) + expr.is?(:passive, :group) && expr.count { |child| child.type != :free_space } == 1 + end + + def redundantly_quantifiable?(node) + redundant_group?(node) || character_set?(node) || node.terminal? + end + + def character_set?(expr) + expr.is?(:character, :set) + end + + def mergeable_quantifier(expr) + # Merging reluctant or possessive quantifiers would be more complex, + # and Ruby does not emit warnings for these cases. + return unless expr.quantifier&.greedy? + + # normalize quantifiers, e.g. "{1,}" => "+" + case expr.quantity + when [0, -1] + '*' + when [0, 1] + '?' + when [1, -1] + '+' + end + end + + def merged_quantifier(exp1, exp2) + quantifier1 = mergeable_quantifier(exp1) + quantifier2 = mergeable_quantifier(exp2) + if quantifier1 == quantifier2 + # (?:a+)+ equals (?:a+) ; (?:a*)* equals (?:a*) ; # (?:a?)? equals (?:a?) + quantifier1 + else + # (?:a+)*, (?:a+)?, (?:a*)+, (?:a*)?, (?:a?)+, (?:a?)* - all equal (?:a*) + '*' + end + end + + def quantifier_range(group, child) + range_between(child.loc.quantifier.begin_pos, group.loc.quantifier.end_pos) + end + + def message(group, child, replacement) + format( + MSG_REDUNDANT_QUANTIFIER, + inner_quantifier: child.quantifier.to_s, + outer_quantifier: group.quantifier.to_s, + replacement: replacement + ) + end + end + end + end +end diff --git a/lib/rubocop/cop/lint/redundant_require_statement.rb b/lib/rubocop/cop/lint/redundant_require_statement.rb index 1777024059b2..95133baf5c1a 100644 --- a/lib/rubocop/cop/lint/redundant_require_statement.rb +++ b/lib/rubocop/cop/lint/redundant_require_statement.rb @@ -24,6 +24,10 @@ module Lint # # This cop target those features. # + # @safety + # This cop's autocorrection is unsafe because if `require 'pp'` is removed from one file, + # `NameError` can be encountered when another file uses `PP.pp`. + # # @example # # bad # require 'unloaded_feature' @@ -49,6 +53,11 @@ class RedundantRequireStatement < Base (str #redundant_feature?)) PATTERN + # @!method pp_const?(node) + def_node_matcher :pp_const?, <<~PATTERN + (const {nil? cbase} :PP) + PATTERN + def on_send(node) return unless redundant_require_statement?(node) @@ -72,16 +81,16 @@ def redundant_feature?(feature_name) feature_name == 'enumerator' || (target_ruby_version >= 2.1 && feature_name == 'thread') || (target_ruby_version >= 2.2 && RUBY_22_LOADED_FEATURES.include?(feature_name)) || - (target_ruby_version >= 2.5 && feature_name == 'pp' && !use_pretty_print_method?) || + (target_ruby_version >= 2.5 && feature_name == 'pp' && !need_to_require_pp?) || (target_ruby_version >= 2.7 && feature_name == 'ruby2_keywords') || (target_ruby_version >= 3.1 && feature_name == 'fiber') || (target_ruby_version >= 3.2 && feature_name == 'set') end # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - def use_pretty_print_method? + def need_to_require_pp? processed_source.ast.each_descendant(:send).any? do |node| - PRETTY_PRINT_METHODS.include?(node.method_name) + pp_const?(node.receiver) || PRETTY_PRINT_METHODS.include?(node.method_name) end end end diff --git a/lib/rubocop/cop/lint/redundant_safe_navigation.rb b/lib/rubocop/cop/lint/redundant_safe_navigation.rb index 349e3bd29b79..0c49dbe16a9d 100644 --- a/lib/rubocop/cop/lint/redundant_safe_navigation.rb +++ b/lib/rubocop/cop/lint/redundant_safe_navigation.rb @@ -4,8 +4,13 @@ module RuboCop module Cop module Lint # 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. + # Use cases where a constant, named in camel case for classes and modules is `nil` are rare, + # and an offense is not detected when the receiver is a constant. The detection also applies + # to literal receivers, except for `nil`. + # + # For all receivers, the `instance_of?`, `kind_of?`, `is_a?`, `eql?`, `respond_to?`, + # and `equal?` methods are checked by default. + # These are customizable with `AllowedMethods` option. # # The `AllowedMethods` option specifies nil-safe methods, # in other words, it is a method that is allowed to skip safe navigation. @@ -22,6 +27,9 @@ module Lint # # @example # # bad + # CamelCaseConst&.do_something + # + # # bad # do_something if attrs&.respond_to?(:[]) # # # good @@ -33,6 +41,9 @@ module Lint # end # # # good + # CamelCaseConst.do_something + # + # # good # while node.is_a?(BeginNode) # node = node.parent # end @@ -40,6 +51,22 @@ module Lint # # good - without `&.` this will always return `true` # foo&.respond_to?(:to_a) # + # # bad - for `nil`s conversion methods return default values for the type + # foo&.to_h || {} + # foo&.to_h { |k, v| [k, v] } || {} + # foo&.to_a || [] + # foo&.to_i || 0 + # foo&.to_f || 0.0 + # foo&.to_s || '' + # + # # good + # foo.to_h + # foo.to_h { |k, v| [k, v] } + # foo.to_a + # foo.to_i + # foo.to_f + # foo.to_s + # # @example AllowedMethods: [nil_safe_method] # # bad # do_something if attrs&.nil_safe_method(:[]) @@ -50,28 +77,65 @@ module Lint # class RedundantSafeNavigation < Base include AllowedMethods - include RangeHelp extend AutoCorrector - MSG = 'Redundant safe navigation detected.' + MSG = 'Redundant safe navigation detected, use `.` instead.' + MSG_LITERAL = 'Redundant safe navigation with default literal detected.' NIL_SPECIFIC_METHODS = (nil.methods - Object.new.methods).to_set.freeze + SNAKE_CASE = /\A[[:digit:][:upper:]_]+\z/.freeze + # @!method respond_to_nil_specific_method?(node) def_node_matcher :respond_to_nil_specific_method?, <<~PATTERN (csend _ :respond_to? (sym %NIL_SPECIFIC_METHODS)) PATTERN + # @!method conversion_with_default?(node) + def_node_matcher :conversion_with_default?, <<~PATTERN + { + (or $(csend _ :to_h) (hash)) + (or (block $(csend _ :to_h) ...) (hash)) + (or $(csend _ :to_a) (array)) + (or $(csend _ :to_i) (int 0)) + (or $(csend _ :to_f) (float 0.0)) + (or $(csend _ :to_s) (str empty?)) + } + PATTERN + + # rubocop:disable Metrics/AbcSize def on_csend(node) - return unless check?(node) && allowed_method?(node.method_name) - return if respond_to_nil_specific_method?(node) + unless assume_receiver_instance_exists?(node.receiver) + return unless check?(node) && allowed_method?(node.method_name) + return if respond_to_nil_specific_method?(node) + end - range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos) - add_offense(range) { |corrector| corrector.replace(node.loc.dot, '.') } + range = node.loc.dot + add_offense(range) { |corrector| corrector.replace(range, '.') } end + def on_or(node) + conversion_with_default?(node) do |send_node| + range = send_node.loc.dot.begin.join(node.source_range.end) + + add_offense(range, message: MSG_LITERAL) do |corrector| + corrector.replace(send_node.loc.dot, '.') + + range_with_default = node.lhs.source_range.end.begin.join(node.source_range.end) + corrector.remove(range_with_default) + end + end + end + # rubocop:enable Metrics/AbcSize + private + def assume_receiver_instance_exists?(receiver) + return true if receiver.const_type? && !receiver.source.match?(SNAKE_CASE) + + receiver.literal? && !receiver.nil_type? + end + def check?(node) parent = node.parent return false unless parent diff --git a/lib/rubocop/cop/lint/redundant_with_index.rb b/lib/rubocop/cop/lint/redundant_with_index.rb index 229963dba420..cee5b1d969cb 100644 --- a/lib/rubocop/cop/lint/redundant_with_index.rb +++ b/lib/rubocop/cop/lint/redundant_with_index.rb @@ -56,10 +56,10 @@ def on_block(node) def_node_matcher :redundant_with_index?, <<~PATTERN { (block - $(send _ {:each_with_index :with_index} ...) + $(call _ {:each_with_index :with_index} ...) (args (arg _)) ...) (numblock - $(send _ {:each_with_index :with_index} ...) 1 ...) + $(call _ {:each_with_index :with_index} ...) 1 ...) } PATTERN diff --git a/lib/rubocop/cop/lint/redundant_with_object.rb b/lib/rubocop/cop/lint/redundant_with_object.rb index 39aa24e0d7bd..18dd7106bc53 100644 --- a/lib/rubocop/cop/lint/redundant_with_object.rb +++ b/lib/rubocop/cop/lint/redundant_with_object.rb @@ -56,9 +56,9 @@ def on_block(node) def_node_matcher :redundant_with_object?, <<~PATTERN { (block - $(send _ {:each_with_object :with_object} _) (args (arg _)) ...) + $(call _ {:each_with_object :with_object} _) (args (arg _)) ...) (numblock - $(send _ {:each_with_object :with_object} _) 1 ...) + $(call _ {:each_with_object :with_object} _) 1 ...) } PATTERN diff --git a/lib/rubocop/cop/lint/rescue_type.rb b/lib/rubocop/cop/lint/rescue_type.rb index 0fed979a607a..67db9926ba51 100644 --- a/lib/rubocop/cop/lint/rescue_type.rb +++ b/lib/rubocop/cop/lint/rescue_type.rb @@ -59,9 +59,7 @@ def on_resbody(node) def autocorrect(corrector, node) rescued, _, _body = *node - range = Parser::Source::Range.new(node.source_range.source_buffer, - node.loc.keyword.end_pos, - rescued.source_range.end_pos) + range = node.loc.keyword.end.join(rescued.source_range.end) corrector.replace(range, correction(*rescued)) end diff --git a/lib/rubocop/cop/lint/safe_navigation_chain.rb b/lib/rubocop/cop/lint/safe_navigation_chain.rb index 3134ca64c803..e08fb9ccf75b 100644 --- a/lib/rubocop/cop/lint/safe_navigation_chain.rb +++ b/lib/rubocop/cop/lint/safe_navigation_chain.rb @@ -45,10 +45,9 @@ def on_send(node) bad_method?(node) do |safe_nav, method| return if nil_methods.include?(method) || PLUS_MINUS_METHODS.include?(node.method_name) - location = - Parser::Source::Range.new(node.source_range.source_buffer, - safe_nav.source_range.end_pos, - node.source_range.end_pos) + begin_range = node.loc.dot || safe_nav.source_range.end + location = begin_range.join(node.source_range.end) + add_offense(location) do |corrector| autocorrect(corrector, offense_range: location, send_node: node) end @@ -82,16 +81,23 @@ def add_safe_navigation_operator(offense_range:, send_node:) def autocorrect(corrector, offense_range:, send_node:) corrector.replace( offense_range, - add_safe_navigation_operator( - offense_range: offense_range, - send_node: send_node - ) + add_safe_navigation_operator(offense_range: offense_range, send_node: send_node) ) + + corrector.wrap(send_node, '(', ')') if require_parentheses?(send_node) end def brackets?(send_node) send_node.method?(:[]) || send_node.method?(:[]=) end + + def require_parentheses?(send_node) + return false unless send_node.comparison_method? + return false unless (node = send_node.parent) + + (node.respond_to?(:logical_operator?) && node.logical_operator?) || + (node.respond_to?(:comparison_method?) && node.comparison_method?) + end end end end diff --git a/lib/rubocop/cop/lint/script_permission.rb b/lib/rubocop/cop/lint/script_permission.rb index d724220f939a..03477e614be2 100644 --- a/lib/rubocop/cop/lint/script_permission.rb +++ b/lib/rubocop/cop/lint/script_permission.rb @@ -46,14 +46,14 @@ def on_new_investigation message = format_message_from(processed_source) add_offense(comment, message: message) do - autocorrect(comment) if autocorrect_requested? + autocorrect if autocorrect_requested? end end private - def autocorrect(comment) - FileUtils.chmod('+x', comment.source_range.source_buffer.name) + def autocorrect + FileUtils.chmod('+x', processed_source.file_path) end def executable?(processed_source) diff --git a/lib/rubocop/cop/lint/self_assignment.rb b/lib/rubocop/cop/lint/self_assignment.rb index dc4843404818..2c1c44ab4b3d 100644 --- a/lib/rubocop/cop/lint/self_assignment.rb +++ b/lib/rubocop/cop/lint/self_assignment.rb @@ -10,11 +10,18 @@ module Lint # foo = foo # foo, bar = foo, bar # Foo = Foo + # hash['foo'] = hash['foo'] + # obj.attr = obj.attr # # # good # foo = bar # foo, bar = bar, foo # Foo = Bar + # hash['foo'] = hash['bar'] + # obj.attr = obj.attr2 + # + # # good (method calls possibly can return different results) + # hash[foo] = hash[foo] # class SelfAssignment < Base MSG = 'Self-assignment detected.' @@ -26,6 +33,15 @@ class SelfAssignment < Base gvasgn: :gvar }.freeze + def on_send(node) + if node.method?(:[]=) + handle_key_assignment(node) if node.arguments.size == 2 + elsif node.assignment_method? + handle_attribute_assignment(node) if node.arguments.size == 1 + end + end + alias on_csend on_send + def on_lvasgn(node) lhs, rhs = *node return unless rhs @@ -72,6 +88,28 @@ def rhs_matches_lhs?(rhs, lhs) rhs.type == ASSIGNMENT_TYPE_TO_RHS_TYPE[lhs.type] && rhs.children.first == lhs.children.first end + + def handle_key_assignment(node) + value_node = node.arguments[1] + + if value_node.send_type? && value_node.method?(:[]) && + node.receiver == value_node.receiver && + !node.first_argument.call_type? && + node.first_argument == value_node.first_argument + add_offense(node) + end + end + + def handle_attribute_assignment(node) + first_argument = node.first_argument + return unless first_argument.respond_to?(:arguments) && first_argument.arguments.empty? + + if first_argument.call_type? && + node.receiver == first_argument.receiver && + first_argument.method_name.to_s == node.method_name.to_s.delete_suffix('=') + add_offense(node) + end + end end end end diff --git a/lib/rubocop/cop/lint/shadowed_argument.rb b/lib/rubocop/cop/lint/shadowed_argument.rb index 9cd9a744b32b..26f19c09d210 100644 --- a/lib/rubocop/cop/lint/shadowed_argument.rb +++ b/lib/rubocop/cop/lint/shadowed_argument.rb @@ -123,6 +123,7 @@ def assignment_without_argument_usage(argument) # Shorthand assignments always use their arguments next false if assignment_node.shorthand_asgn? + next false unless assignment_node.parent node_within_block_or_conditional = node_within_block_or_conditional?(assignment_node.parent, argument.scope.node) diff --git a/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb b/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb index 8207a2b8da27..b58e799d20e8 100644 --- a/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +++ b/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb @@ -68,7 +68,7 @@ def before_declaring_variable(variable, variable_table) def same_conditions_node_different_branch?(variable, outer_local_variable) variable_node = variable_node(variable) - return false unless variable_node.conditional? + return false unless node_or_its_ascendant_conditional?(variable_node) outer_local_variable_node = find_conditional_node_from_ascendant(outer_local_variable.declaration_node) @@ -96,6 +96,12 @@ def find_conditional_node_from_ascendant(node) find_conditional_node_from_ascendant(parent) end + + def node_or_its_ascendant_conditional?(node) + return true if node.conditional? + + !!find_conditional_node_from_ascendant(node) + end end end end diff --git a/lib/rubocop/cop/lint/struct_new_override.rb b/lib/rubocop/cop/lint/struct_new_override.rb index e74ea6243fb8..f08129c73c40 100644 --- a/lib/rubocop/cop/lint/struct_new_override.rb +++ b/lib/rubocop/cop/lint/struct_new_override.rb @@ -32,25 +32,25 @@ class StructNewOverride < Base # @!method struct_new(node) def_node_matcher :struct_new, <<~PATTERN (send - (const ${nil? cbase} :Struct) :new ...) + (const {nil? cbase} :Struct) :new ...) PATTERN def on_send(node) - return unless struct_new(node) do - node.arguments.each_with_index do |arg, index| - # Ignore if the first argument is a class name - next if index.zero? && arg.str_type? + return unless struct_new(node) - # Ignore if the argument is not a member name - next unless STRUCT_MEMBER_NAME_TYPES.include?(arg.type) + node.arguments.each_with_index do |arg, index| + # Ignore if the first argument is a class name + next if index.zero? && arg.str_type? - member_name = arg.value + # Ignore if the argument is not a member name + next unless STRUCT_MEMBER_NAME_TYPES.include?(arg.type) - next unless STRUCT_METHOD_NAMES.include?(member_name.to_sym) + member_name = arg.value - message = format(MSG, member_name: member_name.inspect, method_name: member_name.to_s) - add_offense(arg, message: message) - end + next unless STRUCT_METHOD_NAMES.include?(member_name.to_sym) + + message = format(MSG, member_name: member_name.inspect, method_name: member_name.to_s) + add_offense(arg, message: message) end end end diff --git a/lib/rubocop/cop/lint/suppressed_exception.rb b/lib/rubocop/cop/lint/suppressed_exception.rb index d2872d928bbb..33a1170191e0 100644 --- a/lib/rubocop/cop/lint/suppressed_exception.rb +++ b/lib/rubocop/cop/lint/suppressed_exception.rb @@ -117,9 +117,9 @@ def on_resbody(node) def comment_between_rescue_and_end?(node) ancestor = node.each_ancestor(:kwbegin, :def, :defs, :block, :numblock).first - return unless ancestor + return false unless ancestor - end_line = ancestor.loc.end.line + end_line = ancestor.loc.end&.line || ancestor.loc.last_line processed_source[node.first_line...end_line].any? { |line| comment_line?(line) } end diff --git a/lib/rubocop/cop/lint/symbol_conversion.rb b/lib/rubocop/cop/lint/symbol_conversion.rb index 67d1d29bf9ee..6553389a8693 100644 --- a/lib/rubocop/cop/lint/symbol_conversion.rb +++ b/lib/rubocop/cop/lint/symbol_conversion.rb @@ -19,6 +19,7 @@ module Lint # 'underscored_string'.to_sym # :'underscored_symbol' # 'hyphenated-string'.to_sym + # "string_#{interpolation}".to_sym # # # good # :string @@ -26,6 +27,7 @@ module Lint # :underscored_string # :underscored_symbol # :'hyphenated-string' + # :"string_#{interpolation}" # # @example EnforcedStyle: strict (default) # @@ -75,9 +77,12 @@ class SymbolConversion < Base def on_send(node) return unless node.receiver - return unless node.receiver.str_type? || node.receiver.sym_type? - register_offense(node, correction: node.receiver.value.to_sym.inspect) + if node.receiver.str_type? || node.receiver.sym_type? + register_offense(node, correction: node.receiver.value.to_sym.inspect) + elsif node.receiver.dstr_type? + register_offense(node, correction: ":\"#{node.receiver.value.to_sym}\"") + end end def on_sym(node) @@ -124,7 +129,7 @@ def properly_quoted?(source, value) source == value || # `Symbol#inspect` uses double quotes, but allow single-quoted # symbols to work as well. - source.tr("'", '"') == value + source.gsub('"', '\"').tr("'", '"') == value end def requires_quotes?(sym_node) diff --git a/lib/rubocop/cop/lint/syntax.rb b/lib/rubocop/cop/lint/syntax.rb index 457fac1c6e4f..6b7a2df71ba1 100644 --- a/lib/rubocop/cop/lint/syntax.rb +++ b/lib/rubocop/cop/lint/syntax.rb @@ -17,9 +17,12 @@ def on_other_file private def add_offense_from_diagnostic(diagnostic, ruby_version) - message = - "#{diagnostic.message}\n(Using Ruby #{ruby_version} parser; " \ - 'configure using `TargetRubyVersion` parameter, under `AllCops`)' + message = if LSP.enabled? + diagnostic.message + else + "#{diagnostic.message}\n(Using Ruby #{ruby_version} parser; " \ + 'configure using `TargetRubyVersion` parameter, under `AllCops`)' + end add_offense(diagnostic.location, message: message, severity: diagnostic.level) end diff --git a/lib/rubocop/cop/lint/to_enum_arguments.rb b/lib/rubocop/cop/lint/to_enum_arguments.rb index 8269cf22fafa..3d360ebc4dbf 100644 --- a/lib/rubocop/cop/lint/to_enum_arguments.rb +++ b/lib/rubocop/cop/lint/to_enum_arguments.rb @@ -74,7 +74,7 @@ def arguments_match?(arguments, def_node) end end - # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength def argument_match?(send_arg, def_arg) def_arg_name = def_arg.children[0] @@ -87,12 +87,14 @@ def argument_match?(send_arg, def_arg) send_arg.hash_type? && send_arg.pairs.any? { |pair| passing_keyword_arg?(pair, def_arg_name) } when :kwrestarg - send_arg.each_child_node(:kwsplat).any? { |child| child.source == def_arg.source } + send_arg.each_child_node(:kwsplat, :forwarded_kwrestarg).any? do |child| + child.source == def_arg.source + end when :forward_arg send_arg.forwarded_args_type? end end - # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength end end end diff --git a/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb b/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb index 5f2d6084b917..aae8a8716a03 100644 --- a/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +++ b/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb @@ -34,7 +34,7 @@ class TrailingCommaInAttributeDeclaration < Base MSG = 'Avoid leaving a trailing comma in attribute declarations.' def on_send(node) - return unless node.attribute_accessor? && node.arguments.last.def_type? + return unless node.attribute_accessor? && node.last_argument.def_type? trailing_comma = trailing_comma_range(node) diff --git a/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb b/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb index 59de1c3786b1..24fa303d4131 100644 --- a/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +++ b/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb @@ -69,8 +69,8 @@ class UnmodifiedReduceAccumulator < Base # @!method reduce_with_block?(node) def_node_matcher :reduce_with_block?, <<~PATTERN { - (block (send _recv {:reduce :inject} ...) args ...) - (numblock (send _recv {:reduce :inject} ...) ...) + (block (call _recv {:reduce :inject} ...) args ...) + (numblock (call _recv {:reduce :inject} ...) ...) } PATTERN diff --git a/lib/rubocop/cop/lint/useless_access_modifier.rb b/lib/rubocop/cop/lint/useless_access_modifier.rb index b89a9d178a90..f916dcf47333 100644 --- a/lib/rubocop/cop/lint/useless_access_modifier.rb +++ b/lib/rubocop/cop/lint/useless_access_modifier.rb @@ -256,7 +256,7 @@ def method_definition?(child) def any_method_definition?(child) cop_config.fetch('MethodCreatingMethods', []).any? do |m| - matcher_name = "#{m}_method?".to_sym + matcher_name = :"#{m}_method?" unless respond_to?(matcher_name) self.class.def_node_matcher matcher_name, <<~PATTERN {def (send nil? :#{m} ...)} @@ -279,7 +279,7 @@ def eval_call?(child) def any_context_creating_methods?(child) cop_config.fetch('ContextCreatingMethods', []).any? do |m| - matcher_name = "#{m}_block?".to_sym + matcher_name = :"#{m}_block?" unless respond_to?(matcher_name) self.class.def_node_matcher matcher_name, <<~PATTERN ({block numblock} (send {nil? const} {:#{m}} ...) ...) diff --git a/lib/rubocop/cop/lint/useless_assignment.rb b/lib/rubocop/cop/lint/useless_assignment.rb index 83902ad65707..c006f2d624ef 100644 --- a/lib/rubocop/cop/lint/useless_assignment.rb +++ b/lib/rubocop/cop/lint/useless_assignment.rb @@ -7,12 +7,18 @@ module Lint # scope. # The basic idea for this cop was from the warning of `ruby -cw`: # - # assigned but unused variable - foo + # [source,console] + # ---- + # assigned but unused variable - foo + # ---- # # Currently this cop has advanced logic that detects unreferenced # reassignments and properly handles varied cases such as branch, loop, # rescue, ensure, etc. # + # NOTE: Given the assignment `foo = 1, bar = 2`, removing unused variables + # can lead to a syntax error, so this case is not autocorrected. + # # @safety # This cop's autocorrection is unsafe because removing assignment from # operator assignment can cause NameError if this assignment has been used to declare @@ -51,25 +57,24 @@ def after_leaving_scope(scope, _variable_table) scope.variables.each_value { |variable| check_for_unused_assignments(variable) } end + # rubocop:disable Metrics/AbcSize def check_for_unused_assignments(variable) return if variable.should_be_unused? variable.assignments.each do |assignment| - next if assignment.used? + next if assignment.used? || part_of_ignored_node?(assignment.node) message = message_for_useless_assignment(assignment) + range = offense_range(assignment) - location = if assignment.regexp_named_capture? - assignment.node.children.first.source_range - else - assignment.node.loc.name - end - - add_offense(location, message: message) do |corrector| - autocorrect(corrector, assignment) + add_offense(range, message: message) do |corrector| + autocorrect(corrector, assignment) unless sequential_assignment?(assignment.node) end + + ignore_node(assignment.node) if chained_assignment?(assignment.node) end end + # rubocop:enable Metrics/AbcSize def message_for_useless_assignment(assignment) variable = assignment.variable @@ -77,6 +82,28 @@ def message_for_useless_assignment(assignment) format(MSG, variable: variable.name) + message_specification(assignment, variable).to_s end + def offense_range(assignment) + if assignment.regexp_named_capture? + assignment.node.children.first.source_range + else + assignment.node.loc.name + end + end + + def sequential_assignment?(node) + if node.lvasgn_type? && node.expression&.array_type? && + node.each_descendant.any?(&:assignment?) + return true + end + return false unless node.parent + + sequential_assignment?(node.parent) + end + + def chained_assignment?(node) + node.respond_to?(:expression) && node.expression&.lvasgn_type? + end + def message_specification(assignment, variable) if assignment.multiple_assignment? multiple_assignment_message(variable.name) @@ -96,8 +123,7 @@ def operator_assignment_message(scope, assignment) return_value_node = return_value_node_of_scope(scope) return unless assignment.meta_assignment_node.equal?(return_value_node) - " Use `#{assignment.operator.sub(/=$/, '')}` " \ - "instead of `#{assignment.operator}`." + " Use `#{assignment.operator.delete_suffix('=')}` instead of `#{assignment.operator}`." end def similar_name_message(variable) diff --git a/lib/rubocop/cop/lint/useless_times.rb b/lib/rubocop/cop/lint/useless_times.rb index bde3385f4cf2..e51d83607176 100644 --- a/lib/rubocop/cop/lint/useless_times.rb +++ b/lib/rubocop/cop/lint/useless_times.rb @@ -4,7 +4,7 @@ module RuboCop module Cop module Lint # Checks for uses of `Integer#times` that will never yield - # (when the integer <= 0) or that will only ever yield once + # (when the integer ``<= 0``) or that will only ever yield once # (`1.times`). # # @safety diff --git a/lib/rubocop/cop/lint/void.rb b/lib/rubocop/cop/lint/void.rb index 9e1af8f966b7..82eabb9b2fce 100644 --- a/lib/rubocop/cop/lint/void.rb +++ b/lib/rubocop/cop/lint/void.rb @@ -6,6 +6,16 @@ module Lint # Checks for operators, variables, literals, lambda, proc and nonmutating # methods used in void context. # + # `each` blocks are allowed to prevent false positives. + # For example, the expression inside the `each` block below. + # It's not void, especially when the receiver is an `Enumerator`: + # + # [source,ruby] + # ---- + # enumerator = [1, 2, 3].filter + # enumerator.each { |item| item >= 2 } #=> [2, 3] + # ---- + # # @example CheckForMethodsWithNoSideEffects: false (default) # # bad # def some_method @@ -47,6 +57,7 @@ class Void < Base OP_MSG = 'Operator `%s` used in void context.' VAR_MSG = 'Variable `%s` used in void context.' + CONST_MSG = 'Constant `%s` used in void context.' LIT_MSG = 'Literal `%s` used in void context.' SELF_MSG = '`self` used in void context.' EXPRESSION_MSG = '`%s` used in void context.' @@ -72,6 +83,7 @@ def on_block(node) return unless node.body && !node.body.begin_type? return unless in_void_context?(node.body) + check_void_op(node.body) { node.method?(:each) } check_expression(node.body) end @@ -87,11 +99,18 @@ def on_begin(node) def check_begin(node) expressions = *node expressions.pop unless in_void_context?(node) - expressions.each { |expr| check_expression(expr) } + expressions.each do |expr| + check_void_op(expr) do + block_node = node.each_ancestor(:block).first + + block_node&.method?(:each) + end + + check_expression(expr) + end end def check_expression(expr) - check_void_op(expr) check_literal(expr) check_var(expr) check_self(expr) @@ -101,8 +120,9 @@ def check_expression(expr) check_nonmutating(expr) end - def check_void_op(node) + def check_void_op(node, &block) return unless node.send_type? && OPERATORS.include?(node.method_name) + return if block && yield(node) add_offense(node.loc.selector, message: format(OP_MSG, op: node.method_name)) do |corrector| @@ -113,17 +133,26 @@ def check_void_op(node) def check_var(node) return unless node.variable? || node.const_type? - add_offense(node.loc.name, - message: format(VAR_MSG, var: node.loc.name.source)) do |corrector| - autocorrect_void_var(corrector, node) + if node.const_type? + template = node.special_keyword? ? VAR_MSG : CONST_MSG + + offense_range = node + message = format(template, var: node.source) + else + offense_range = node.loc.name + message = format(VAR_MSG, var: node.loc.name.source) + end + + add_offense(offense_range, message: message) do |corrector| + autocorrect_void_expression(corrector, node) end end def check_literal(node) - return if !node.literal? || node.xstr_type? || node.range_type? + return if !entirely_literal?(node) || node.xstr_type? || node.range_type? add_offense(node, message: format(LIT_MSG, lit: node.source)) do |corrector| - autocorrect_void_literal(corrector, node) + autocorrect_void_expression(corrector, node) end end @@ -131,7 +160,7 @@ def check_self(node) return unless node.self_type? add_offense(node, message: SELF_MSG) do |corrector| - autocorrect_void_self(corrector, node) + autocorrect_void_expression(corrector, node) end end @@ -144,7 +173,7 @@ def check_void_expression(node) end def check_nonmutating(node) - return unless node.respond_to?(:method_name) + return if !node.send_type? && !node.block_type? && !node.numblock_type? method_name = node.method_name return unless NONMUTATING_METHODS.include?(method_name) @@ -181,18 +210,6 @@ def autocorrect_void_op(corrector, node) end end - def autocorrect_void_var(corrector, node) - corrector.remove(range_with_surrounding_space(range: node.loc.name, side: :left)) - end - - def autocorrect_void_literal(corrector, node) - corrector.remove(range_with_surrounding_space(range: node.source_range, side: :left)) - end - - def autocorrect_void_self(corrector, node) - corrector.remove(range_with_surrounding_space(range: node.source_range, side: :left)) - end - def autocorrect_void_expression(corrector, node) corrector.remove(range_with_surrounding_space(range: node.source_range, side: :left)) end @@ -205,6 +222,19 @@ def autocorrect_nonmutating_send(corrector, node, suggestion) end corrector.replace(send_node.loc.selector, suggestion) end + + def entirely_literal?(node) + case node.type + when :array + node.each_value.all? { |value| entirely_literal?(value) } + when :hash + return false unless node.each_key.all? { |key| entirely_literal?(key) } + + node.each_value.all? { |value| entirely_literal?(value) } + else + node.literal? + end + end end end end diff --git a/lib/rubocop/cop/metrics/abc_size.rb b/lib/rubocop/cop/metrics/abc_size.rb index fc623fad0a16..970aff5f9fb8 100644 --- a/lib/rubocop/cop/metrics/abc_size.rb +++ b/lib/rubocop/cop/metrics/abc_size.rb @@ -10,9 +10,9 @@ module Metrics # # Interpreting ABC size: # - # * <= 17 satisfactory - # * 18..30 unsatisfactory - # * > 30 dangerous + # * ``<= 17`` satisfactory + # * `18..30` unsatisfactory + # * `>` 30 dangerous # # You can have repeated "attributes" calls count as a single "branch". # For this purpose, attributes are any method with no argument; no attempt diff --git a/lib/rubocop/cop/metrics/block_length.rb b/lib/rubocop/cop/metrics/block_length.rb index 11c9957cff3f..29c413ddb8a3 100644 --- a/lib/rubocop/cop/metrics/block_length.rb +++ b/lib/rubocop/cop/metrics/block_length.rb @@ -12,6 +12,7 @@ module Metrics # Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct # will be counted as one line regardless of its actual size. # + # NOTE: This cop does not apply for `Struct` definitions. # # NOTE: The `ExcludedMethods` configuration is deprecated and only kept # for backwards compatibility. Please use `AllowedMethods` and `AllowedPatterns` @@ -40,7 +41,6 @@ module Metrics # ) # end # 6 points # - # NOTE: This cop does not apply for `Struct` definitions. class BlockLength < Base include CodeLength include AllowedMethods diff --git a/lib/rubocop/cop/metrics/class_length.rb b/lib/rubocop/cop/metrics/class_length.rb index 3aedb63a4fb7..f58f55616f16 100644 --- a/lib/rubocop/cop/metrics/class_length.rb +++ b/lib/rubocop/cop/metrics/class_length.rb @@ -11,6 +11,8 @@ module Metrics # Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct # will be counted as one line regardless of its actual size. # + # NOTE: This cop also applies for `Struct` definitions. + # # @example CountAsOne: ['array', 'heredoc', 'method_call'] # # class Foo @@ -34,15 +36,18 @@ module Metrics # ) # end # 6 points # - # - # NOTE: This cop also applies for `Struct` definitions. class ClassLength < Base include CodeLength def on_class(node) check_code_length(node) end - alias on_sclass on_class + + def on_sclass(node) + return if node.each_ancestor(:class).any? + + on_class(node) + end def on_casgn(node) parent = node.parent diff --git a/lib/rubocop/cop/metrics/method_length.rb b/lib/rubocop/cop/metrics/method_length.rb index 17133269c527..6ea2a85a592a 100644 --- a/lib/rubocop/cop/metrics/method_length.rb +++ b/lib/rubocop/cop/metrics/method_length.rb @@ -54,7 +54,7 @@ def on_def(node) alias on_defs on_def def on_block(node) - return unless node.send_node.method?(:define_method) + return unless node.method?(:define_method) check_code_length(node) end diff --git a/lib/rubocop/cop/metrics/utils/code_length_calculator.rb b/lib/rubocop/cop/metrics/utils/code_length_calculator.rb index de94a0ea6032..2ae7897d0daf 100644 --- a/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +++ b/lib/rubocop/cop/metrics/utils/code_length_calculator.rb @@ -10,7 +10,7 @@ class CodeLengthCalculator include Util FOLDABLE_TYPES = %i[array hash heredoc send csend].freeze - CLASSLIKE_TYPES = %i[class module sclass].freeze + CLASSLIKE_TYPES = %i[class module].freeze private_constant :FOLDABLE_TYPES, :CLASSLIKE_TYPES def initialize(node, processed_source, count_comments: false, foldable_types: []) @@ -63,7 +63,7 @@ def normalize_foldable_types(types) types end - def code_length(node) + def code_length(node) # rubocop:disable Metrics/MethodLength if classlike_node?(node) classlike_code_length(node) elsif heredoc_node?(node) @@ -72,7 +72,14 @@ def code_length(node) body = extract_body(node) return 0 unless body - body.source.each_line.count { |line| !irrelevant_line?(line) } + source = + if node_with_heredoc?(body) + source_from_node_with_heredoc(body) + else + body.source.lines + end + + source.count { |line| !irrelevant_line?(line) } end end @@ -138,7 +145,7 @@ def foldable_node?(node) def extract_body(node) case node.type - when :class, :module, :block, :numblock, :def, :defs + when :class, :module, :sclass, :block, :numblock, :def, :defs node.body when :casgn _scope, _name, value = *node @@ -175,6 +182,27 @@ def parenthesized?(node) def another_args?(node) node.call_type? && node.arguments.count > 1 end + + def node_with_heredoc?(node) + node.each_descendant(:str, :dstr).any? { |descendant| heredoc_node?(descendant) } + end + + def source_from_node_with_heredoc(node) + last_line = -1 + node.each_descendant do |descendant| + next unless descendant.source + + descendant_last_line = + if heredoc_node?(descendant) + descendant.loc.heredoc_end.line + else + descendant.last_line + end + + last_line = [last_line, descendant_last_line].max + end + @processed_source[(node.first_line - 1)..(last_line - 1)] + end end end end diff --git a/lib/rubocop/cop/migration/department_name.rb b/lib/rubocop/cop/migration/department_name.rb index a8428b06d4cb..7a91ae15553d 100644 --- a/lib/rubocop/cop/migration/department_name.rb +++ b/lib/rubocop/cop/migration/department_name.rb @@ -16,7 +16,7 @@ class DepartmentName < Base # The token that makes up a disable comment. # The allowed specification for comments after `# rubocop: disable` is # `DepartmentName/CopName` or` all`. - DISABLING_COPS_CONTENT_TOKEN = %r{[A-z]+/[A-z]+|all}.freeze + DISABLING_COPS_CONTENT_TOKEN = %r{[A-Za-z]+/[A-Za-z]+|all}.freeze def on_new_investigation processed_source.comments.each do |comment| @@ -67,7 +67,7 @@ def valid_content_token?(content_token) end def contain_unexpected_character_for_department_name?(name) - name.match?(%r{[^A-z/, ]}) + name.match?(%r{[^A-Za-z/, ]}) end def qualified_legacy_cop_name(cop_name) diff --git a/lib/rubocop/cop/mixin/check_line_breakable.rb b/lib/rubocop/cop/mixin/check_line_breakable.rb index 34c5325b286c..edbeb82223dc 100644 --- a/lib/rubocop/cop/mixin/check_line_breakable.rb +++ b/lib/rubocop/cop/mixin/check_line_breakable.rb @@ -218,7 +218,7 @@ def process_args(args) # @api private def already_on_multiple_lines?(node) - return node.first_line != node.arguments.last.last_line if node.def_type? + return node.first_line != node.last_argument.last_line if node.def_type? !node.single_line? end diff --git a/lib/rubocop/cop/mixin/comments_help.rb b/lib/rubocop/cop/mixin/comments_help.rb index 7db370aac87d..701c486582fd 100644 --- a/lib/rubocop/cop/mixin/comments_help.rb +++ b/lib/rubocop/cop/mixin/comments_help.rb @@ -25,7 +25,7 @@ def comments_in_range(node) def comments_contain_disables?(node, cop_name) disabled_ranges = processed_source.disabled_line_ranges[cop_name] - return unless disabled_ranges + return false unless disabled_ranges node_range = node.source_range.line...find_end_line(node) @@ -62,25 +62,29 @@ def buffer # Returns the end line of a node, which might be a comment and not part of the AST # End line is considered either the line at which another node starts, or # the line at which the parent node ends. - # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Lint/DuplicateBranch + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity def find_end_line(node) - if node.if_type? && node.else? - node.loc.else.line - elsif node.if_type? && node.ternary? - node.else_branch.loc.line - elsif node.if_type? && node.elsif? - node.each_ancestor(:if).find(&:if?).loc.end.line + if node.if_type? + if node.else? + node.loc.else.line + elsif node.ternary? + node.else_branch.loc.line + elsif node.elsif? + node.each_ancestor(:if).find(&:if?).loc.end.line + end elsif node.block_type? || node.numblock_type? node.loc.end.line elsif (next_sibling = node.right_sibling) && next_sibling.is_a?(AST::Node) next_sibling.loc.line elsif (parent = node.parent) - parent.loc.respond_to?(:end) && parent.loc.end ? parent.loc.end.line : parent.loc.line - else - node.loc.end.line - end + if parent.loc.respond_to?(:end) && parent.loc.end + parent.loc.end.line + else + parent.loc.line + end + end || node.loc.end.line end - # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Lint/DuplicateBranch + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity end end end diff --git a/lib/rubocop/cop/mixin/configurable_formatting.rb b/lib/rubocop/cop/mixin/configurable_formatting.rb index 43f06b3408d7..0022a76ae9fc 100644 --- a/lib/rubocop/cop/mixin/configurable_formatting.rb +++ b/lib/rubocop/cop/mixin/configurable_formatting.rb @@ -18,6 +18,7 @@ def report_opposing_styles(node, name) alternative_styles.each do |alternative| return unexpected_style_detected(alternative) if valid_name?(node, name, alternative) end + unrecognized_style_detected end def valid_name?(node, name, given_style = style) diff --git a/lib/rubocop/cop/mixin/def_node.rb b/lib/rubocop/cop/mixin/def_node.rb index 9d385e421d3e..9d98b0aabd22 100644 --- a/lib/rubocop/cop/mixin/def_node.rb +++ b/lib/rubocop/cop/mixin/def_node.rb @@ -19,7 +19,7 @@ def preceding_non_public_modifier?(node) # @!method non_public_modifier?(node) def_node_matcher :non_public_modifier?, <<~PATTERN - (send nil? {:private :protected} ({def defs} ...)) + (send nil? {:private :protected :private_class_method} ({def defs} ...)) PATTERN end end diff --git a/lib/rubocop/cop/mixin/end_keyword_alignment.rb b/lib/rubocop/cop/mixin/end_keyword_alignment.rb index bb5150a37c4b..a8e90b086a89 100644 --- a/lib/rubocop/cop/mixin/end_keyword_alignment.rb +++ b/lib/rubocop/cop/mixin/end_keyword_alignment.rb @@ -67,7 +67,7 @@ def style_parameter_name end def variable_alignment?(whole_expression, rhs, end_alignment_style) - return if end_alignment_style == :keyword + return false if end_alignment_style == :keyword !line_break_before_keyword?(whole_expression, rhs) end diff --git a/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb b/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb index 651af331fddb..b2c888d85bc4 100644 --- a/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +++ b/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb @@ -48,18 +48,21 @@ def on_pair(node) def register_offense(node, message, replacement) # rubocop:disable Metrics/AbcSize add_offense(node.value, message: message) do |corrector| - if (def_node = def_node_that_require_parentheses(node)) - last_argument = def_node.last_argument - if last_argument.nil? || !last_argument.hash_type? - next corrector.replace(node, replacement) - end - - white_spaces = range_between(def_node.selector.end_pos, - def_node.first_argument.source_range.begin_pos) - corrector.replace(white_spaces, '(') - corrector.insert_after(last_argument, ')') if node == last_argument.pairs.last - end corrector.replace(node, replacement) + + next unless (def_node = def_node_that_require_parentheses(node)) + + last_argument = def_node.last_argument + if last_argument.nil? || !last_argument.hash_type? + next corrector.replace(node, replacement) + end + + white_spaces = range_between(def_node.selector.end_pos, + def_node.first_argument.source_range.begin_pos) + next if node.parent.braces? + + corrector.replace(white_spaces, '(') + corrector.insert_after(last_argument, ')') if node == last_argument.pairs.last end end diff --git a/lib/rubocop/cop/mixin/heredoc.rb b/lib/rubocop/cop/mixin/heredoc.rb index 2ed16bb661bb..52f64f5cd7db 100644 --- a/lib/rubocop/cop/mixin/heredoc.rb +++ b/lib/rubocop/cop/mixin/heredoc.rb @@ -26,11 +26,15 @@ def indent_level(str) end def delimiter_string(node) - node.source.match(OPENING_DELIMITER).captures[1] + return '' unless (match = node.source.match(OPENING_DELIMITER)) + + match.captures[1] end def heredoc_type(node) - node.source.match(OPENING_DELIMITER).captures[0] + return '' unless (match = node.source.match(OPENING_DELIMITER)) + + match.captures[0] end end end diff --git a/lib/rubocop/cop/mixin/multiline_expression_indentation.rb b/lib/rubocop/cop/mixin/multiline_expression_indentation.rb index fee6dd7fcf62..cd33bdbf9275 100644 --- a/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +++ b/lib/rubocop/cop/mixin/multiline_expression_indentation.rb @@ -20,16 +20,17 @@ def on_send(node) range = offending_range(node, lhs, rhs, style) check(range, node, lhs, rhs) end + alias on_csend on_send private - # In a chain of method calls, we regard the top send node as the base + # In a chain of method calls, we regard the top call node as the base # for indentation of all lines following the first. For example: # a. # b c { block }. <-- b is indented relative to a # d <-- d is indented relative to a def left_hand_side(lhs) - while lhs.parent&.send_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method? + while lhs.parent&.call_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method? lhs = lhs.parent end lhs diff --git a/lib/rubocop/cop/mixin/percent_literal.rb b/lib/rubocop/cop/mixin/percent_literal.rb index 48f9fb19999f..b38814592784 100644 --- a/lib/rubocop/cop/mixin/percent_literal.rb +++ b/lib/rubocop/cop/mixin/percent_literal.rb @@ -9,7 +9,7 @@ module PercentLiteral private def percent_literal?(node) - return unless (begin_source = begin_source(node)) + return false unless (begin_source = begin_source(node)) begin_source.start_with?('%') end diff --git a/lib/rubocop/cop/mixin/preceding_following_alignment.rb b/lib/rubocop/cop/mixin/preceding_following_alignment.rb index d7a30d42bdd8..790968e2512b 100644 --- a/lib/rubocop/cop/mixin/preceding_following_alignment.rb +++ b/lib/rubocop/cop/mixin/preceding_following_alignment.rb @@ -75,21 +75,19 @@ def aligned_comment_lines end def aligned_token?(range, line) - aligned_words?(range, line) || - aligned_char?(range, line) || - aligned_assignment?(range, line) + aligned_words?(range, line) || aligned_assignment?(range, line) end def aligned_operator?(range, line) - (aligned_identical?(range, line) || aligned_assignment?(range, line)) + aligned_identical?(range, line) || aligned_assignment?(range, line) end def aligned_words?(range, line) - /\s\S/.match?(line[range.column - 1, 2]) - end + left_edge = range.column + return true if /\s\S/.match?(line[left_edge - 1, 2]) - def aligned_char?(range, line) - line[range.column] == range.source[0] + token = range.source + token == line[left_edge, token.length] end def aligned_assignment?(range, line) diff --git a/lib/rubocop/cop/mixin/space_before_punctuation.rb b/lib/rubocop/cop/mixin/space_before_punctuation.rb index ecf1d49b0abd..830cf67144cb 100644 --- a/lib/rubocop/cop/mixin/space_before_punctuation.rb +++ b/lib/rubocop/cop/mixin/space_before_punctuation.rb @@ -36,7 +36,7 @@ def space_missing?(token1, token2) end def space_required_after?(token) - token.left_curly_brace? && space_required_after_lcurly? + (token.left_curly_brace? || token.type == :tLAMBEG) && space_required_after_lcurly? end def space_required_after_lcurly? diff --git a/lib/rubocop/cop/mixin/string_help.rb b/lib/rubocop/cop/mixin/string_help.rb index 60bcb716cad1..d8f4c8ebec84 100644 --- a/lib/rubocop/cop/mixin/string_help.rb +++ b/lib/rubocop/cop/mixin/string_help.rb @@ -30,8 +30,10 @@ def on_regexp(node) private def inside_interpolation?(node) - # A :begin node inside a :dstr node is an interpolation. - node.ancestors.drop_while { |a| !a.begin_type? }.any?(&:dstr_type?) + # A :begin node inside a :dstr, :dsym, or :regexp node is an interpolation. + node.ancestors + .drop_while { |a| !a.begin_type? } + .any? { |a| a.dstr_type? || a.dsym_type? || a.regexp_type? } end end end diff --git a/lib/rubocop/cop/mixin/trailing_comma.rb b/lib/rubocop/cop/mixin/trailing_comma.rb index 79554e8f1766..4d6b04c715eb 100644 --- a/lib/rubocop/cop/mixin/trailing_comma.rb +++ b/lib/rubocop/cop/mixin/trailing_comma.rb @@ -106,7 +106,7 @@ def allowed_multiline_argument?(node) end def elements(node) - return node.children unless %i[csend send].include?(node.type) + return node.children unless node.call_type? node.arguments.flat_map do |argument| # For each argument, if it is a multi-line hash without braces, diff --git a/lib/rubocop/cop/naming/block_forwarding.rb b/lib/rubocop/cop/naming/block_forwarding.rb index 83b9eb25b1bf..b314e2a41f5d 100644 --- a/lib/rubocop/cop/naming/block_forwarding.rb +++ b/lib/rubocop/cop/naming/block_forwarding.rb @@ -48,24 +48,32 @@ class BlockForwarding < Base MSG = 'Use %