diff --git a/.codespellrc b/.codespellrc index cf385a135cf1..be1d59487530 100644 --- a/.codespellrc +++ b/.codespellrc @@ -10,5 +10,6 @@ ignore-words-list = ba, irregardless, mange, ofo, + seach, thi, upto, diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b21e463ef324..916060f5a238 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -39,7 +39,7 @@ output by `rubocop -V`, include them as well. Here's an example: ``` $ [bundle exec] rubocop -V -1.79.2 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] +1.80.0 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] - rubocop-performance 1.22.1 - rubocop-rspec 3.1.0 ``` diff --git a/.github/workflows/github_release.yml b/.github/workflows/github_release.yml index 2607babd6251..379dcebf60c4 100644 --- a/.github/workflows/github_release.yml +++ b/.github/workflows/github_release.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Create GitHub Release uses: ncipollo/release-action@v1 diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index e9e651c899e8..819944f72663 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -15,7 +15,7 @@ jobs: name: Ruby runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: ruby-version: ruby # Latest stable CRuby version @@ -31,7 +31,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: ruby # Latest stable CRuby version - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Package and install RuboCop locally run: | gem build @@ -53,7 +53,7 @@ jobs: name: Yaml runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Yamllint uses: karancode/yamllint-github-action@v3.0.0 with: diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 8ab9ee1d39d0..168ad9147ce2 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -24,7 +24,7 @@ jobs: ruby: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4', 'head'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -42,7 +42,7 @@ jobs: name: Spec - JRuby runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: ruby-version: 'jruby' # Latest stable JRuby version @@ -62,7 +62,7 @@ jobs: - 'ruby' # Latest stable CRuby version steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -88,7 +88,7 @@ jobs: - 'ruby' # Latest stable CRuby version steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -102,7 +102,7 @@ jobs: name: Documentation Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: ruby-version: ruby # Latest stable CRuby version @@ -114,7 +114,7 @@ jobs: runs-on: ubuntu-latest name: Prism steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: # Specify the minimum Ruby version 2.7 required for Prism to run. @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-latest name: RSpec 4 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - 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 ef255974413f..8cafc6a653f1 100644 --- a/.github/workflows/spell_checking.yml +++ b/.github/workflows/spell_checking.yml @@ -14,15 +14,5 @@ jobs: name: Check spelling of all files with codespell runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: codespell-project/actions-codespell@v2 - - misspell: - name: Check spelling of all files in commit with misspell - runs-on: ubuntu-latest - steps: - - 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 - run: git ls-files --empty-directory | xargs ./misspell -i 'enviromnent' -error diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f674d13233..a2058da5337f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,32 @@ ## master (unreleased) +## 1.80.0 (2025-08-22) + +### Bug fixes + +* [#14469](https://github.com/rubocop/rubocop/issues/14469): Fix an incorrect autocorrect for `Style/BitwisePredicate` when using `&` with LHS flags in conjunction with `==` for comparisons. ([@koic][]) +* [#14459](https://github.com/rubocop/rubocop/pull/14459): Fix wrong autocorrect for `Style/For` with save navigation in the collection. ([@earlopain][]) +* [#14435](https://github.com/rubocop/rubocop/issues/14435): Fix false negatives for regexp cops when `Lint/DuplicateRegexpCharacterClassElement` is enabled. ([@earlopain][]) +* [#14419](https://github.com/rubocop/rubocop/issues/14419): Fix false positives for `Lint/UselessAssignment` when duplicate assignments appear in nested `if` branches inside a loop and the variable is used outside `while` loop. ([@koic][]) +* [#14468](https://github.com/rubocop/rubocop/issues/14468): Fix false positives for `Naming/MethodName` when an operator method is defined using a string. ([@koic][]) +* [#14427](https://github.com/rubocop/rubocop/pull/14427): Fix false positives for `Style/RedundantParentheses` when `do`...`end` block is wrapped in parentheses as a method argument. ([@koic][]) +* [#14441](https://github.com/rubocop/rubocop/issues/14441): Better hash access handling in `Style/SafeNavigation`. ([@issyl0][]) +* [#14443](https://github.com/rubocop/rubocop/issues/14443): Fix false positive in `Layout/EmptyLinesAfterModuleInclusion` when `include` does not have exactly one argument. ([@issyl0][]) +* [#14424](https://github.com/rubocop/rubocop/pull/14424): Fix `Style/SafeNavigation` cop to preserve existing safe navigation in fixed code. ([@martinemde][]) +* [#14455](https://github.com/rubocop/rubocop/pull/14455): Follow module inclusion with nonzero args with an empty line. ([@issyl0][]) +* [#14445](https://github.com/rubocop/rubocop/issues/14445): Fix false positives for `Lint/UselessAssignment` with `for` loops when the variable is referenced in the collection. ([@earlopain][]) +* [#14447](https://github.com/rubocop/rubocop/pull/14447): Fix wrong autocorrect for `Style/RedundantCondition` with a parenthesised method call in the condition. ([@earlopain][]) + +### Changes + +* [#14428](https://github.com/rubocop/rubocop/pull/14428): Enhance `Lint/SelfAssignment` to handle indexed assignment with multiple arguments. ([@viralpraxis][]) +* [#14464](https://github.com/rubocop/rubocop/pull/14464): Exclude `AutoCorrect` and `Include` from configuration parameters. ([@r7kamura][]) +* [#14472](https://github.com/rubocop/rubocop/pull/14472): Make `Style/RedundantBegin` aware of `case` pattern matching. ([@koic][]) +* [#14448](https://github.com/rubocop/rubocop/pull/14448): Register array intersection size checks as offenses under `Style/ArrayIntersect`. ([@lovro-bikic][]) +* [#14431](https://github.com/rubocop/rubocop/pull/14431): Support LSP `TextDocumentSyncKind.Incremental`. ([@tmtm][]) +* [#14453](https://github.com/rubocop/rubocop/issues/14453): Update `Style/RedundantBegin` to register `begin` blocks inside `if`, `unless`, `case`, `while` and `until` as redundant. ([@dvandersluis][]) + ## 1.79.2 (2025-08-05) ### Bug fixes @@ -4290,3 +4316,5 @@ [@girasquid]: https://github.com/girasquid [@hakanensari]: https://github.com/hakanensari [@jvlara]: https://github.com/jvlara +[@tmtm]: https://github.com/tmtm +[@martinemde]: https://github.com/martinemde diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c650390f94e2..0a6b8ea3d1f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ do so. ```console $ rubocop -V -1.79.2 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] +1.80.0 (using Parser 3.3.5.0, rubocop-ast 1.32.3, analyzing as Ruby 3.3, running on ruby 3.3.5) [x86_64-linux] - rubocop-performance 1.22.1 - rubocop-rspec 3.1.0 ``` @@ -49,21 +49,7 @@ $ rubocop -V ### Spell Checking -We are running [misspell](https://github.com/client9/misspell) which is mainly written in -[Golang](https://golang.org/) to check spelling with [GitHub Actions](https://github.com/rubocop/rubocop/blob/master/.github/workflows/spell_checking.yml). -Correct commonly misspelled English words quickly with `misspell`. `misspell` is different from most other spell checkers -because it doesn't use a custom dictionary. You can run `misspell` locally against all files with: - -```console -$ find . -type f | xargs ./misspell -i 'enviromnent' -error -``` - -Notable `misspell` help options or flags are: - -* `-i` string: ignore the following corrections, comma separated -* `-w`: Overwrite file with corrections (default is just to display) - -We also run [codespell](https://github.com/codespell-project/codespell) with GitHub Actions to check spelling and +We are running[codespell](https://github.com/codespell-project/codespell) with GitHub Actions to check spelling and [codespell](https://pypi.org/project/codespell/) runs against a [small custom dictionary](https://github.com/rubocop/rubocop/blob/master/.codespellrc). If you have `codespell` locally available in your `$PATH`, `bundle exec rake` will run it for you. diff --git a/README.md b/README.md index 52c30097803b..73f18d381ae3 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi in your `Gemfile`: ```rb -gem 'rubocop', '~> 1.79', require: false +gem 'rubocop', '~> 1.80', require: false ``` See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details. diff --git a/docs/antora.yml b/docs/antora.yml index 84f3553fdcf7..d313e5883ed1 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -2,6 +2,6 @@ name: rubocop title: RuboCop # We always provide version without patch here (e.g. 1.1), # as patch versions should not appear in the docs. -version: '1.79' +version: '1.80' nav: - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/pages/cops_layout.adoc b/docs/modules/ROOT/pages/cops_layout.adoc index 2ec69019094a..a75e35ea6a3f 100644 --- a/docs/modules/ROOT/pages/cops_layout.adoc +++ b/docs/modules/ROOT/pages/cops_layout.adoc @@ -6149,6 +6149,8 @@ The `aligned` style checks that operators are aligned if they are part of an `if condition, an explicit `return` statement, etc. In other contexts, the second operand should be indented regardless of enforced style. +In both styles, operators should be aligned when an assignment begins on the next line. + [#examples-layoutmultilineoperationindentation] === Examples diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index 8c89fcf7e8ec..a9ad8a024dd8 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -851,6 +851,8 @@ This cop identifies places where: * `(array1 & array2).any?` * `(array1.intersection(array2)).any?` * `array1.any? { |elem| array2.member?(elem) }` +* `(array1 & array2).count > 0` +* `(array1 & array2).size > 0` can be replaced with `array1.intersect?(array2)`. @@ -896,6 +898,19 @@ array1.none? { |elem| array2.member?(elem) } # good array1.intersect?(array2) +!array1.intersect?(array2) + +# bad +(array1 & array2).count > 0 +(array1 & array2).count.positive? +(array1 & array2).count != 0 + +(array1 & array2).count == 0 +(array1 & array2).count.zero? + +# good +array1.intersect?(array2) + !array1.intersect?(array2) ---- diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc index 38885d572301..1be97a557f6a 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.79', require: false +gem 'rubocop', '~> 1.80', 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 e792183f44d9..ee61bccdbc12 100644 --- a/docs/modules/ROOT/pages/integration_with_other_tools.adoc +++ b/docs/modules/ROOT/pages/integration_with_other_tools.adoc @@ -125,7 +125,7 @@ following to your `.pre-commit-config.yaml` file: [source,yaml] ---- - repo: https://github.com/rubocop/rubocop - rev: v1.79.2 + rev: v1.80.0 hooks: - id: rubocop ---- @@ -136,7 +136,7 @@ entries in `additional_dependencies`: [source,yaml] ---- - repo: https://github.com/rubocop/rubocop - rev: v1.79.2 + rev: v1.80.0 hooks: - id: rubocop additional_dependencies: diff --git a/docs/modules/ROOT/pages/usage/caching.adoc b/docs/modules/ROOT/pages/usage/caching.adoc index 6269ca8de442..da2ca35e2077 100644 --- a/docs/modules/ROOT/pages/usage/caching.adoc +++ b/docs/modules/ROOT/pages/usage/caching.adoc @@ -20,6 +20,11 @@ bearing on which offenses are reported * version of the `rubocop` program (or to be precise, anything in the source code of the invoked `rubocop` program) +In rare cases, you may need to invalidate the cache when changing +external dependencies. This happens when your cop depends on external +configuration. In this case, override the method +`RuboCop::Cop::Base#external_dependency_checksum`. + == Enabling and Disabling the Cache The caching functionality is enabled if the configuration parameter diff --git a/exe/rubocop b/exe/rubocop index f9bf90424c25..14efcc57f63c 100755 --- a/exe/rubocop +++ b/exe/rubocop @@ -12,13 +12,6 @@ if RuboCop::Server.running? exit_status = RuboCop::Server::ClientCommand::Exec.new.run else require 'rubocop' - - cli = RuboCop::CLI.new - - time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) - exit_status = cli.run - elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - time_start - - puts "Finished in #{elapsed_time} seconds" if cli.options[:debug] || cli.options[:display_time] + exit_status = RuboCop::CLI.new.run end exit exit_status diff --git a/lib/rubocop/cli.rb b/lib/rubocop/cli.rb index f4a34c1928da..0ac351d602d3 100644 --- a/lib/rubocop/cli.rb +++ b/lib/rubocop/cli.rb @@ -37,6 +37,8 @@ def initialize # # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def run(args = ARGV) + time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @options, paths = Options.new.parse(args) @env = Environment.new(@options, @config_store, paths) @@ -72,6 +74,9 @@ def run(args = ARGV) warn e.message warn e.backtrace STATUS_ERROR + ensure + elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - time_start + puts "Finished in #{elapsed_time} seconds" if @options[:debug] || @options[:display_time] end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize diff --git a/lib/rubocop/cop/correctors/for_to_each_corrector.rb b/lib/rubocop/cop/correctors/for_to_each_corrector.rb index cd33923fc0ae..4e44f438b23f 100644 --- a/lib/rubocop/cop/correctors/for_to_each_corrector.rb +++ b/lib/rubocop/cop/correctors/for_to_each_corrector.rb @@ -6,7 +6,7 @@ module Cop class ForToEachCorrector extend NodePattern::Macros - CORRECTION = '%s.each do |%s|' + CORRECTION = '%s%seach do |%s|' def initialize(for_node) @for_node = for_node @@ -25,7 +25,12 @@ def call(corrector) attr_reader :for_node, :variable_node, :collection_node def correction - format(CORRECTION, collection: collection_source, argument: variable_node.source) + format( + CORRECTION, + collection: collection_source, + dot: collection_node.csend_type? ? '&.' : '.', + argument: variable_node.source + ) end def collection_source diff --git a/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb b/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb index 0c09609550f5..eb232c523a97 100644 --- a/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +++ b/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb @@ -38,7 +38,7 @@ class EmptyLinesAfterModuleInclusion < Base RESTRICT_ON_SEND = MODULE_INCLUSION_METHODS def on_send(node) - return if node.receiver + return if node.receiver || node.arguments.empty? return if node.parent&.type?(:send, :any_block) return if next_line_empty_or_enable_directive_comment?(node.last_line) diff --git a/lib/rubocop/cop/layout/multiline_operation_indentation.rb b/lib/rubocop/cop/layout/multiline_operation_indentation.rb index b36d97507dc7..30a4871a4554 100644 --- a/lib/rubocop/cop/layout/multiline_operation_indentation.rb +++ b/lib/rubocop/cop/layout/multiline_operation_indentation.rb @@ -10,6 +10,8 @@ module Layout # condition, an explicit `return` statement, etc. In other contexts, the second operand should # be indented regardless of enforced style. # + # In both styles, operators should be aligned when an assignment begins on the next line. + # # @example EnforcedStyle: aligned (default) # # bad # if a + 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 48c05c4e9314..780d9401c599 100644 --- a/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +++ b/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb @@ -24,8 +24,6 @@ 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| @@ -40,9 +38,9 @@ def each_repeated_character_class_element_loc(node) seen = Set.new group_expressions(node, expr.expressions) do |group| - group_source = group.map(&:to_s).join + group_source = group.to_s - yield source_range(group) if seen.include?(group_source) + yield group.expression if seen.include?(group_source) seen << group_source end @@ -52,40 +50,13 @@ def each_repeated_character_class_element_loc(node) private def group_expressions(node, expressions) - # Create a mutable list to simplify state tracking while we iterate. - expressions = expressions.to_a - - 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 - - def pop_octal_digits(current_child, expressions) - OCTAL_DIGITS_AFTER_ESCAPE.times do - next_child = expressions.first - break unless octal?(next_child.to_s) + expressions.each do |expression| + next if within_interpolation?(node, expression) - current_child << expressions.shift + yield(expression) 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 end @@ -99,14 +70,6 @@ def within_interpolation?(node, child) interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) } end - 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) @interpolation_locs ||= {} diff --git a/lib/rubocop/cop/lint/missing_cop_enable_directive.rb b/lib/rubocop/cop/lint/missing_cop_enable_directive.rb index d286548bbaeb..6dce1ff72972 100644 --- a/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +++ b/lib/rubocop/cop/lint/missing_cop_enable_directive.rb @@ -52,10 +52,9 @@ def on_new_investigation each_missing_enable do |cop, line_range| next if acceptable_range?(cop, line_range) - range = source_range(processed_source.buffer, line_range.min, 0..0) comment = processed_source.comment_at_line(line_range.begin) - add_offense(range, message: message(cop, comment)) + add_offense(comment, message: message(cop, comment)) end end diff --git a/lib/rubocop/cop/lint/self_assignment.rb b/lib/rubocop/cop/lint/self_assignment.rb index 51ee1d7dc01f..445162093b26 100644 --- a/lib/rubocop/cop/lint/self_assignment.rb +++ b/lib/rubocop/cop/lint/self_assignment.rb @@ -45,7 +45,7 @@ def on_send(node) return if allow_rbs_inline_annotation? && rbs_inline_annotation?(node.receiver) if node.method?(:[]=) - handle_key_assignment(node) if node.arguments.size == 2 + handle_key_assignment(node) elsif node.assignment_method? handle_attribute_assignment(node) if node.arguments.size == 1 end @@ -105,12 +105,13 @@ def rhs_matches_lhs?(rhs, lhs) end def handle_key_assignment(node) - value_node = node.arguments[1] + value_node = node.last_argument + node_arguments = node.arguments[0...-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 + node_arguments.none?(&:call_type?) && + node_arguments == value_node.arguments add_offense(node) end end diff --git a/lib/rubocop/cop/naming/method_name.rb b/lib/rubocop/cop/naming/method_name.rb index 5b1789223a0e..c03a66ecd254 100644 --- a/lib/rubocop/cop/naming/method_name.rb +++ b/lib/rubocop/cop/naming/method_name.rb @@ -198,7 +198,7 @@ def handle_method_name(node, name) if forbidden_name?(name.to_s) register_forbidden_name(node) - elsif !OPERATOR_METHODS.include?(name) + elsif !OPERATOR_METHODS.include?(name.to_sym) check_name(node, name, range_position(node)) end end diff --git a/lib/rubocop/cop/style/array_intersect.rb b/lib/rubocop/cop/style/array_intersect.rb index 80c5379daddb..c59d89e62b7e 100644 --- a/lib/rubocop/cop/style/array_intersect.rb +++ b/lib/rubocop/cop/style/array_intersect.rb @@ -10,6 +10,8 @@ module Style # * `(array1 & array2).any?` # * `(array1.intersection(array2)).any?` # * `array1.any? { |elem| array2.member?(elem) }` + # * `(array1 & array2).count > 0` + # * `(array1 & array2).size > 0` # # can be replaced with `array1.intersect?(array2)`. # @@ -51,6 +53,19 @@ module Style # array1.intersect?(array2) # !array1.intersect?(array2) # + # # bad + # (array1 & array2).count > 0 + # (array1 & array2).count.positive? + # (array1 & array2).count != 0 + # + # (array1 & array2).count == 0 + # (array1 & array2).count.zero? + # + # # good + # array1.intersect?(array2) + # + # !array1.intersect?(array2) + # # @example AllCops:ActiveSupportExtensionsEnabled: false (default) # # good # (array1 & array2).present? @@ -73,9 +88,11 @@ class ArrayIntersect < Base PREDICATES = %i[any? empty? none?].to_set.freeze ACTIVE_SUPPORT_PREDICATES = (PREDICATES + %i[present? blank?]).freeze + ARRAY_SIZE_METHODS = %i[count length size].to_set.freeze + # @!method bad_intersection_check?(node, predicates) def_node_matcher :bad_intersection_check?, <<~PATTERN - (call + $(call { (begin (send $_ :& $_)) (call $_ :intersection $_) @@ -84,6 +101,20 @@ class ArrayIntersect < Base ) PATTERN + # @!method intersection_size_check?(node, predicates) + def_node_matcher :intersection_size_check?, <<~PATTERN + (call + $(call + { + (begin (send $_ :& $_)) + (call $_ :intersection $_) + } + %ARRAY_SIZE_METHODS + ) + {$:> (int 0) | $:positive? | $:!= (int 0) | $:== (int 0) | $:zero?} + ) + PATTERN + # @!method any_none_block_intersection(node) def_node_matcher :any_none_block_intersection, <<~PATTERN { @@ -104,15 +135,15 @@ class ArrayIntersect < Base PATTERN MSG = 'Use `%s` instead of `%s`.' - STRAIGHT_METHODS = %i[present? any?].freeze - NEGATED_METHODS = %i[blank? empty? none?].freeze + STRAIGHT_METHODS = %i[present? any? > positive? !=].freeze + NEGATED_METHODS = %i[blank? empty? none? == zero?].freeze RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze def on_send(node) return if node.block_literal? - return unless (receiver, argument, method_name = bad_intersection?(node)) + return unless (dot_node, receiver, argument, method_name = bad_intersection?(node)) - dot = node.loc.dot.source + dot = dot_node.loc.dot.source bang = straight?(method_name) ? '' : '!' replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})" @@ -135,13 +166,16 @@ def on_block(node) private def bad_intersection?(node) - predicates = if active_support_extensions_enabled? - ACTIVE_SUPPORT_PREDICATES - else - PREDICATES - end + bad_intersection_check?(node, bad_intersection_predicates) || + intersection_size_check?(node) + end - bad_intersection_check?(node, predicates) + def bad_intersection_predicates + if active_support_extensions_enabled? + ACTIVE_SUPPORT_PREDICATES + else + PREDICATES + end end def straight?(method_name) diff --git a/lib/rubocop/cop/style/bitwise_predicate.rb b/lib/rubocop/cop/style/bitwise_predicate.rb index 9eee7c7d9778..41910e55de28 100644 --- a/lib/rubocop/cop/style/bitwise_predicate.rb +++ b/lib/rubocop/cop/style/bitwise_predicate.rb @@ -70,18 +70,25 @@ class BitwisePredicate < Base (send _ :& _)) PATTERN + # rubocop:disable Metrics/AbcSize def on_send(node) return unless node.receiver&.begin_type? return unless (preferred_method = preferred_method(node)) bit_operation = node.receiver.children.first lhs, _operator, rhs = *bit_operation - preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})" + + preferred = if preferred_method == 'allbits?' && lhs.source == node.first_argument.source + "#{rhs.source}.allbits?(#{lhs.source})" + else + "#{lhs.source}.#{preferred_method}(#{rhs.source})" + end add_offense(node, message: format(MSG, preferred: preferred)) do |corrector| corrector.replace(node, preferred) end end + # rubocop:enable Metrics/AbcSize private diff --git a/lib/rubocop/cop/style/infinite_loop.rb b/lib/rubocop/cop/style/infinite_loop.rb index b215dcfaf069..7174ac3b3dfd 100644 --- a/lib/rubocop/cop/style/infinite_loop.rb +++ b/lib/rubocop/cop/style/infinite_loop.rb @@ -68,7 +68,7 @@ def while_or_until(node) end def autocorrect(corrector, node) - if node.type?(:while_post, :until_post) + if node.post_condition_loop? replace_begin_end_with_modifier(corrector, node) elsif node.modifier_form? replace_source(corrector, node.source_range, modifier_replacement(node)) diff --git a/lib/rubocop/cop/style/redundant_begin.rb b/lib/rubocop/cop/style/redundant_begin.rb index b921ff653f76..351a54af8719 100644 --- a/lib/rubocop/cop/style/redundant_begin.rb +++ b/lib/rubocop/cop/style/redundant_begin.rb @@ -85,6 +85,29 @@ def on_def(node) end alias on_defs on_def + def on_if(node) + return if node.modifier_form? + + inspect_branches(node) + end + + def on_case(node) + inspect_branches(node) + end + alias on_case_match on_case + + def on_while(node) + return if node.modifier_form? + + body = node.body + + return unless body&.kwbegin_type? + return if body.rescue_node || body.ensure_node + + register_offense(body) + end + alias on_until on_while + def on_block(node) return if target_ruby_version < 2.5 return if node.send_node.lambda_literal? @@ -199,6 +222,15 @@ def valid_context_using_only_begin?(node) def valid_begin_assignment?(node) node.parent&.assignment? && !node.children.one? end + + def inspect_branches(node) + node.branches.each do |branch| + next unless branch&.kwbegin_type? + next if branch.rescue_node || branch.ensure_node + + register_offense(branch) + end + end end end end diff --git a/lib/rubocop/cop/style/redundant_condition.rb b/lib/rubocop/cop/style/redundant_condition.rb index e578b85ae18b..cd34ffb27a54 100644 --- a/lib/rubocop/cop/style/redundant_condition.rb +++ b/lib/rubocop/cop/style/redundant_condition.rb @@ -247,7 +247,7 @@ def if_source(if_branch, arithmetic_operation) "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}" elsif if_branch.true_type? condition = if_branch.parent.condition - return condition.source if condition.arguments.empty? + return condition.source if condition.arguments.empty? || condition.parenthesized? wrap_arguments_with_parens(condition) else diff --git a/lib/rubocop/cop/style/redundant_parentheses.rb b/lib/rubocop/cop/style/redundant_parentheses.rb index c70cab503b0c..552fd1885caf 100644 --- a/lib/rubocop/cop/style/redundant_parentheses.rb +++ b/lib/rubocop/cop/style/redundant_parentheses.rb @@ -220,7 +220,7 @@ def allow_in_multiline_conditions? end def call_node?(node) - node.call_type? || (node.any_block_type? && !node.lambda_or_proc?) + node.call_type? || (node.any_block_type? && node.braces? && !node.lambda_or_proc?) end def check_send(begin_node, node) diff --git a/lib/rubocop/cop/style/safe_navigation.rb b/lib/rubocop/cop/style/safe_navigation.rb index ffe39e0f3fbc..7e7779cf46c6 100644 --- a/lib/rubocop/cop/style/safe_navigation.rb +++ b/lib/rubocop/cop/style/safe_navigation.rb @@ -142,6 +142,7 @@ class SafeNavigation < Base # rubocop:disable Metrics/ClassLength # @!method strip_begin(node) def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }' + # rubocop:disable Metrics/AbcSize def on_if(node) return if allowed_if_condition?(node) @@ -155,9 +156,11 @@ def on_if(node) removal_ranges = [begin_range(node, body), end_range(node, body)] report_offense(node, method_chain, method_call, *removal_ranges) do |corrector| + corrector.replace(receiver, checked_variable.source) if checked_variable.csend_type? corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation? end end + # rubocop:enable Metrics/AbcSize def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)| @@ -335,8 +338,16 @@ def matching_nodes?(left, right) def matching_call_nodes?(left, right) return false unless left && right.respond_to?(:call_type?) + return false unless left.call_type? && right.call_type? - left.call_type? && right.call_type? && left.children == right.children + # Compare receiver and method name, but ignore the difference between + # safe navigation method call (`&.`) and dot method call (`.`). + left_receiver, left_method, *left_args = left.children + right_receiver, right_method, *right_args = right.children + + left_method == right_method && + matching_nodes?(left_receiver, right_receiver) && + left_args == right_args end def chain_length(method_chain, method) diff --git a/lib/rubocop/cop/style/symbol_array.rb b/lib/rubocop/cop/style/symbol_array.rb index 66d5fbd5f717..8728a7804fce 100644 --- a/lib/rubocop/cop/style/symbol_array.rb +++ b/lib/rubocop/cop/style/symbol_array.rb @@ -81,7 +81,7 @@ def complex_content?(node) content = *sym content = content.map { |c| c.is_a?(AST::Node) ? c.source : c }.join - content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s\(\)]*\))/, '') + content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s()]*\))/, '') content.include?(' ') || DELIMITERS.any? do |delimiter| content_without_delimiter_pairs.include?(delimiter) diff --git a/lib/rubocop/cop/variable_force.rb b/lib/rubocop/cop/variable_force.rb index 8af505effa71..64b926873f0d 100644 --- a/lib/rubocop/cop/variable_force.rb +++ b/lib/rubocop/cop/variable_force.rb @@ -238,11 +238,16 @@ def process_variable_referencing(node) end def process_loop(node) - if POST_CONDITION_LOOP_TYPES.include?(node.type) + if node.post_condition_loop? # See the comment at the end of file for this behavior. condition_node, body_node = *node process_node(body_node) process_node(condition_node) + elsif node.for_type? + # In `for item in items` the rightmost expression is evaluated first. + process_node(node.collection) + process_node(node.variable) + process_node(node.body) if node.body else process_children(node) end @@ -356,17 +361,14 @@ def descendant_reference(node) end def reference_assignments(loop_assignments, loop_node) - node = loop_assignments.first.node - # If inside a branching statement, mark all as referenced. # Otherwise, mark only the last assignment as referenced. # Note that `rescue` must be considered as branching because of # the `retry` keyword. - if node.each_ancestor(*BRANCH_NODES).any? || node.parent.each_descendant(*BRANCH_NODES).any? - loop_assignments.each { |assignment| assignment.reference!(loop_node) } - else - loop_assignments.last&.reference!(loop_node) + loop_assignments.each do |assignment| + assignment.reference!(loop_node) if assignment.node.each_ancestor(*BRANCH_NODES).any? end + loop_assignments.last&.reference!(loop_node) end def scanned_node?(node) diff --git a/lib/rubocop/cop/variable_force/variable.rb b/lib/rubocop/cop/variable_force/variable.rb index 95bc36aa91eb..cc2d38194dcc 100644 --- a/lib/rubocop/cop/variable_force/variable.rb +++ b/lib/rubocop/cop/variable_force/variable.rb @@ -79,7 +79,7 @@ def in_modifier_conditional?(assignment) parent = parent.parent if parent&.begin_type? return false if parent.nil? - parent.type?(:if, :while, :until) && parent.modifier_form? + parent.basic_conditional? && parent.modifier_form? end def capture_with_block! diff --git a/lib/rubocop/formatter/disabled_config_formatter.rb b/lib/rubocop/formatter/disabled_config_formatter.rb index 6b74eb45138e..b105a1dc97fb 100644 --- a/lib/rubocop/formatter/disabled_config_formatter.rb +++ b/lib/rubocop/formatter/disabled_config_formatter.rb @@ -4,7 +4,7 @@ module RuboCop module Formatter # This formatter displays a YAML configuration file where all cops that # detected any offenses are configured to not detect the offense. - class DisabledConfigFormatter < BaseFormatter + class DisabledConfigFormatter < BaseFormatter # rubocop:disable Metrics/ClassLength include PathUtil HEADING = <<~COMMENTS @@ -17,6 +17,22 @@ class DisabledConfigFormatter < BaseFormatter # versions of RuboCop, may require this file to be generated again. COMMENTS + EXCLUDED_CONFIG_KEYS = %w[ + AutoCorrect + Description + Enabled + Exclude + Include + Reference + References + Safe + SafeAutoCorrect + StyleGuide + VersionAdded + VersionChanged + VersionRemoved + ].freeze + @config_to_allow_offenses = {} @detected_styles = {} @@ -163,10 +179,7 @@ def supports_unsafe_autocorrect?(cop_class, default_cfg) end def cop_config_params(default_cfg, cfg) - default_cfg.keys - - %w[Description StyleGuide Reference References Enabled Exclude Safe - SafeAutoCorrect VersionAdded VersionChanged VersionRemoved] - - cfg.keys + default_cfg.keys - EXCLUDED_CONFIG_KEYS - cfg.keys end def output_cop_param_comments(output_buffer, params, default_cfg) diff --git a/lib/rubocop/lsp/routes.rb b/lib/rubocop/lsp/routes.rb index be96dcaa0858..8916a0db38f8 100644 --- a/lib/rubocop/lsp/routes.rb +++ b/lib/rubocop/lsp/routes.rb @@ -51,7 +51,7 @@ def for(name) capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new( document_formatting_provider: true, text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new( - change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL, + change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::INCREMENTAL, open_close: true ) ) @@ -76,7 +76,12 @@ def for(name) handle 'textDocument/didChange' do |request| params = request[:params] - result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text]) + file_uri = params[:textDocument][:uri] + text = @text_cache[file_uri] + params[:contentChanges].each do |content| + text = change_text(text, content[:text], content[:range]) + end + result = diagnostic(file_uri, text) @server.write(result) end @@ -219,6 +224,30 @@ def diagnostic(file_uri, text) } end + def change_text(orig_text, text, range) + return text unless range + + start_pos = text_pos(orig_text, range[:start]) + end_pos = text_pos(orig_text, range[:end]) + text_bin = orig_text.b + text_bin[start_pos...end_pos] = text.b + text_bin.force_encoding(orig_text.encoding) + end + + def text_pos(text, range) + line = range[:line] + char = range[:character] + pos = 0 + text.each_line.with_index do |l, i| + if i == line + pos += l.encode('utf-16be').b[0, char * 2].encode('utf-8', 'utf-16be').bytesize + return pos + end + pos += l.bytesize + end + pos + end + def convert_file_uri_to_path(uri) URI.decode_www_form_component(uri.delete_prefix('file://')) end diff --git a/lib/rubocop/target_finder.rb b/lib/rubocop/target_finder.rb index 223cb35479f7..690017f2d998 100644 --- a/lib/rubocop/target_finder.rb +++ b/lib/rubocop/target_finder.rb @@ -42,14 +42,12 @@ def target_files_in_dir(base_dir = Dir.pwd) # Support Windows: Backslashes from command-line -> forward slashes base_dir = base_dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR all_files = find_files(base_dir, File::FNM_DOTMATCH) - # use file.include? for performance optimization - hidden_files = all_files.select { |file| file.include?(HIDDEN_PATH_SUBSTRING) }.sort base_dir_config = @config_store.for(base_dir) - target_files = if base_dir.include?(HIDDEN_PATH_SUBSTRING) + target_files = if hidden_path?(base_dir) all_files.select { |file| ruby_file?(file) } else - all_files.select { |file| to_inspect?(file, hidden_files, base_dir_config) } + all_files.select { |file| to_inspect?(file, base_dir_config) } end target_files.sort_by!(&order) @@ -74,18 +72,20 @@ def find_files(base_dir, flags) private - def to_inspect?(file, hidden_files, base_dir_config) + def to_inspect?(file, base_dir_config) return false if base_dir_config.file_to_exclude?(file) - return true if !hidden_files.bsearch do |hidden_file| - file <=> hidden_file - end && ruby_file?(file) + return true if !hidden_path?(file) && ruby_file?(file) base_dir_config.file_to_include?(file) end + def hidden_path?(path) + path.include?(HIDDEN_PATH_SUBSTRING) + end + def wanted_dir_patterns(base_dir, exclude_pattern, flags) # Escape glob characters in base_dir to avoid unwanted behavior. - base_dir = base_dir.gsub(/[\\\{\}\[\]\*\?]/) do |reserved_glob_character| + base_dir = base_dir.gsub(/[\\{}\[\]*?]/) do |reserved_glob_character| "\\#{reserved_glob_character}" end diff --git a/lib/rubocop/version.rb b/lib/rubocop/version.rb index ebcd211474b1..3c14466a06e9 100644 --- a/lib/rubocop/version.rb +++ b/lib/rubocop/version.rb @@ -3,7 +3,7 @@ module RuboCop # This module holds the RuboCop version information. module Version - STRING = '1.79.2' + STRING = '1.80.0' MSG = '%s (using %s, ' \ 'rubocop-ast %s, ' \ diff --git a/relnotes/v1.80.0.md b/relnotes/v1.80.0.md new file mode 100644 index 000000000000..1cc61c023cb0 --- /dev/null +++ b/relnotes/v1.80.0.md @@ -0,0 +1,33 @@ +### Bug fixes + +* [#14469](https://github.com/rubocop/rubocop/issues/14469): Fix an incorrect autocorrect for `Style/BitwisePredicate` when using `&` with LHS flags in conjunction with `==` for comparisons. ([@koic][]) +* [#14459](https://github.com/rubocop/rubocop/pull/14459): Fix wrong autocorrect for `Style/For` with save navigation in the collection. ([@earlopain][]) +* [#14435](https://github.com/rubocop/rubocop/issues/14435): Fix false negatives for regexp cops when `Lint/DuplicateRegexpCharacterClassElement` is enabled. ([@earlopain][]) +* [#14419](https://github.com/rubocop/rubocop/issues/14419): Fix false positives for `Lint/UselessAssignment` when duplicate assignments appear in nested `if` branches inside a loop and the variable is used outside `while` loop. ([@koic][]) +* [#14468](https://github.com/rubocop/rubocop/issues/14468): Fix false positives for `Naming/MethodName` when an operator method is defined using a string. ([@koic][]) +* [#14427](https://github.com/rubocop/rubocop/pull/14427): Fix false positives for `Style/RedundantParentheses` when `do`...`end` block is wrapped in parentheses as a method argument. ([@koic][]) +* [#14441](https://github.com/rubocop/rubocop/issues/14441): Better hash access handling in `Style/SafeNavigation`. ([@issyl0][]) +* [#14443](https://github.com/rubocop/rubocop/issues/14443): Fix false positive in `Layout/EmptyLinesAfterModuleInclusion` when `include` does not have exactly one argument. ([@issyl0][]) +* [#14424](https://github.com/rubocop/rubocop/pull/14424): Fix `Style/SafeNavigation` cop to preserve existing safe navigation in fixed code. ([@martinemde][]) +* [#14455](https://github.com/rubocop/rubocop/pull/14455): Follow module inclusion with nonzero args with an empty line. ([@issyl0][]) +* [#14445](https://github.com/rubocop/rubocop/issues/14445): Fix false positives for `Lint/UselessAssignment` with `for` loops when the variable is referenced in the collection. ([@earlopain][]) +* [#14447](https://github.com/rubocop/rubocop/pull/14447): Fix wrong autocorrect for `Style/RedundantCondition` with a parenthesised method call in the condition. ([@earlopain][]) + +### Changes + +* [#14428](https://github.com/rubocop/rubocop/pull/14428): Enhance `Lint/SelfAssignment` to handle indexed assignment with multiple arguments. ([@viralpraxis][]) +* [#14464](https://github.com/rubocop/rubocop/pull/14464): Exclude `AutoCorrect` and `Include` from configuration parameters. ([@r7kamura][]) +* [#14472](https://github.com/rubocop/rubocop/pull/14472): Make `Style/RedundantBegin` aware of `case` pattern matching. ([@koic][]) +* [#14448](https://github.com/rubocop/rubocop/pull/14448): Register array intersection size checks as offenses under `Style/ArrayIntersect`. ([@lovro-bikic][]) +* [#14431](https://github.com/rubocop/rubocop/pull/14431): Support LSP `TextDocumentSyncKind.Incremental`. ([@tmtm][]) +* [#14453](https://github.com/rubocop/rubocop/issues/14453): Update `Style/RedundantBegin` to register `begin` blocks inside `if`, `unless`, `case`, `while` and `until` as redundant. ([@dvandersluis][]) + +[@koic]: https://github.com/koic +[@earlopain]: https://github.com/earlopain +[@issyl0]: https://github.com/issyl0 +[@martinemde]: https://github.com/martinemde +[@viralpraxis]: https://github.com/viralpraxis +[@r7kamura]: https://github.com/r7kamura +[@lovro-bikic]: https://github.com/lovro-bikic +[@tmtm]: https://github.com/tmtm +[@dvandersluis]: https://github.com/dvandersluis diff --git a/spec/rubocop/cli/autocorrect_spec.rb b/spec/rubocop/cli/autocorrect_spec.rb index 3658a2d9c924..fadea629da39 100644 --- a/spec/rubocop/cli/autocorrect_spec.rb +++ b/spec/rubocop/cli/autocorrect_spec.rb @@ -3974,4 +3974,34 @@ def foo RESULT expect($stderr.string).to eq('') end + + it 'registers an offense and corrects for `Style/RedundantRegexpEscape` when `NewCops: enable`' do + create_file('.rubocop.yml', <<~YAML) + AllCops: + NewCops: enable + Style/FrozenStringLiteralComment: + Enabled: false + YAML + source = <<~'RUBY' + /[\.-]/ + RUBY + create_file('example.rb', source) + expect(cli.run(['--autocorrect-all'])).to eq(0) + expect($stdout.string).to eq(<<~'RESULT') + Inspecting 1 file + C + + Offenses: + + example.rb:1:3: C: [Corrected] Style/RedundantRegexpEscape: Redundant escape inside regexp literal + /[\.-]/ + ^^ + + 1 file inspected, 1 offense detected, 1 offense corrected + RESULT + expect($stderr.string).to eq('') + expect(File.read('example.rb')).to eq(<<~RUBY) + /[.-]/ + RUBY + end end diff --git a/spec/rubocop/cli/options_spec.rb b/spec/rubocop/cli/options_spec.rb index 7dc4a21dd8aa..c56b155e133e 100644 --- a/spec/rubocop/cli/options_spec.rb +++ b/spec/rubocop/cli/options_spec.rb @@ -1091,9 +1091,9 @@ def on_send(node) create_file('example1.rb', "\tputs 0") expect(cli.run(['--debug', 'example1.rb'])).to eq(1) home = File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) - expect($stdout.string.lines.grep(/configuration/).map(&:chomp)) - .to eq(["For #{abs('')}: " \ - "Default configuration from #{home}/config/default.yml"]) + expect($stdout.string) + .to include("For #{abs('')}: " \ + "Default configuration from #{home}/config/default.yml") end it 'shows cop names' do @@ -1101,9 +1101,9 @@ def on_send(node) file = abs('example1.rb') expect(cli.run(['--format', 'emacs', '--debug', 'example1.rb'])).to eq(1) - expect($stdout.string.lines.to_a[-1]) - .to eq("#{file}:1:7: C: [Correctable] Layout/TrailingWhitespace: Trailing " \ - "whitespace detected.\n") + expect($stdout.string) + .to include("#{file}:1:7: C: [Correctable] Layout/TrailingWhitespace: Trailing " \ + "whitespace detected.\n") end end @@ -1114,13 +1114,15 @@ def on_send(node) context 'without --display-time' do it 'does not display elapsed time in seconds' do - expect(`rubocop example1.rb`).not_to match(regex) + expect(cli.run(['example1.rb'])).to eq(0) + expect($stdout.string).not_to match(regex) end end context 'with --display-time' do it 'displays elapsed time in seconds' do - expect(`rubocop --display-time example1.rb`).to match(regex) + expect(cli.run(['--display-time', 'example1.rb'])).to eq(0) + expect($stdout.string).to match(regex) end end end diff --git a/spec/rubocop/cli_spec.rb b/spec/rubocop/cli_spec.rb index 4546a7e8776d..63f0af1eff2d 100644 --- a/spec/rubocop/cli_spec.rb +++ b/spec/rubocop/cli_spec.rb @@ -2066,14 +2066,8 @@ def method(foo, bar, qux, fred, arg5, f) end #{'#' * 85} require: unknownlibrary YAML - regexp = - if RUBY_ENGINE == 'jruby' - /no such file to load -- unknownlibrary/ - else - /cannot load such file -- unknownlibrary/ - end expect(cli.run([])).to eq(2) - expect($stderr.string).to match(regexp) + expect($stderr.string).to match(/cannot load such file -- unknownlibrary/) end end diff --git a/spec/rubocop/cop/layout/empty_lines_after_module_inclusion_spec.rb b/spec/rubocop/cop/layout/empty_lines_after_module_inclusion_spec.rb index 85d15ab85137..4d857d2fb455 100644 --- a/spec/rubocop/cop/layout/empty_lines_after_module_inclusion_spec.rb +++ b/spec/rubocop/cop/layout/empty_lines_after_module_inclusion_spec.rb @@ -94,6 +94,26 @@ def do_something RUBY end + it "registers an offense and corrects when #{method} has multiple arguments" do + expect_offense(<<~RUBY, method: method) + class Foo + #{method} Bar, Baz + ^{method}^^^^^^^^^ Add an empty line after module inclusion. + def do_something + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo + #{method} Bar, Baz + + def do_something + end + end + RUBY + end + it "registers an offense and corrects for code that immediately follows #{method} inside a class" do expect_offense(<<~RUBY, method: method) class Bar @@ -242,6 +262,16 @@ module Bar RUBY end + it 'does not register an offense when `include` has zero arguments' do + expect_no_offenses(<<~RUBY) + class Foo + includes = [include, sdk_include].compact.map do |inc| + inc + "blah" + end.join(' ') + end + RUBY + end + it 'does not register an offense when module inclusion is called with modifier' do expect_no_offenses(<<~RUBY) class Foo diff --git a/spec/rubocop/cop/lint/missing_cop_enable_directive_spec.rb b/spec/rubocop/cop/lint/missing_cop_enable_directive_spec.rb index 484deb96f7fa..2e9a2acacf0c 100644 --- a/spec/rubocop/cop/lint/missing_cop_enable_directive_spec.rb +++ b/spec/rubocop/cop/lint/missing_cop_enable_directive_spec.rb @@ -8,7 +8,7 @@ it 'registers an offense when a cop is disabled and never re-enabled' do expect_offense(<<~RUBY) # rubocop:disable Layout/SpaceAroundOperators - ^ Re-enable Layout/SpaceAroundOperators cop with `# rubocop:enable` after disabling it. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Re-enable Layout/SpaceAroundOperators cop with `# rubocop:enable` after disabling it. x = 0 # Some other code RUBY @@ -26,7 +26,7 @@ it 'registers an offense when a department is disabled and never re-enabled' do expect_offense(<<~RUBY) # rubocop:disable Layout - ^ Re-enable Layout department with `# rubocop:enable` after disabling it. + ^^^^^^^^^^^^^^^^^^^^^^^^ Re-enable Layout department with `# rubocop:enable` after disabling it. x = 0 # Some other code RUBY @@ -48,7 +48,7 @@ it 'registers an offense when a cop is disabled for too many lines' do expect_offense(<<~RUBY) # rubocop:disable Layout/SpaceAroundOperators - ^ Re-enable Layout/SpaceAroundOperators cop within 2 lines after disabling it. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Re-enable Layout/SpaceAroundOperators cop within 2 lines after disabling it. x = 0 y = 1 # Some other code @@ -59,7 +59,7 @@ it 'registers an offense when a cop is disabled and never re-enabled' do expect_offense(<<~RUBY) # rubocop:disable Layout/SpaceAroundOperators - ^ Re-enable Layout/SpaceAroundOperators cop within 2 lines after disabling it. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Re-enable Layout/SpaceAroundOperators cop within 2 lines after disabling it. x = 0 # Some other code RUBY @@ -78,7 +78,7 @@ it 'registers an offense when a department is disabled for too many lines' do expect_offense(<<~RUBY) # rubocop:disable Layout - ^ Re-enable Layout department within 2 lines after disabling it. + ^^^^^^^^^^^^^^^^^^^^^^^^ Re-enable Layout department within 2 lines after disabling it. x = 0 y = 1 # Some other code @@ -89,7 +89,7 @@ it 'registers an offense when a department is disabled and never re-enabled' do expect_offense(<<~RUBY) # rubocop:disable Layout - ^ Re-enable Layout department within 2 lines after disabling it. + ^^^^^^^^^^^^^^^^^^^^^^^^ Re-enable Layout department within 2 lines after disabling it. x = 0 # Some other code RUBY diff --git a/spec/rubocop/cop/lint/self_assignment_spec.rb b/spec/rubocop/cop/lint/self_assignment_spec.rb index bfe3dedb3eb6..61fe60da4445 100644 --- a/spec/rubocop/cop/lint/self_assignment_spec.rb +++ b/spec/rubocop/cop/lint/self_assignment_spec.rb @@ -319,6 +319,32 @@ RUBY end + it 'registers an offense when ussing `[]=` self-assignment with multiple key arguments' do + expect_offense(<<~RUBY) + matrix[1, 2] = matrix[1, 2] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Self-assignment detected. + RUBY + end + + it 'does not register an offense when ussing `[]=` self-assignment with multiple key arguments with arguments mismatch' do + expect_no_offenses(<<~RUBY) + matrix[1, 2] = matrix[1, 3] + RUBY + end + + it 'does not register an offense when ussing `[]=` self-assignment with multiple key arguments with method call argument' do + expect_no_offenses(<<~RUBY) + matrix[1, foo] = matrix[1, foo] + RUBY + end + + it 'registers an offense when ussing `[]=` self-assignment with using zero key arguments' do + expect_offense(<<~RUBY) + singleton[] = singleton[] + ^^^^^^^^^^^^^^^^^^^^^^^^^ Self-assignment detected. + RUBY + end + describe 'RBS::Inline annotation' do context 'when config option is enabled' do let(:cop_config) { { 'AllowRBSInlineAnnotation' => true } } diff --git a/spec/rubocop/cop/lint/useless_assignment_spec.rb b/spec/rubocop/cop/lint/useless_assignment_spec.rb index df3a04328a23..f23b4bcb45e9 100644 --- a/spec/rubocop/cop/lint/useless_assignment_spec.rb +++ b/spec/rubocop/cop/lint/useless_assignment_spec.rb @@ -270,6 +270,34 @@ module x::Foo end end + context 'when a variable is assigned before `for`' do + it 'registers an offense when it is not referenced' do + expect_offense(<<~RUBY) + node = foo + ^^^^ Useless assignment to variable - `node`. + for node in bar + return node if baz? + end + RUBY + + expect_correction(<<~RUBY) + foo + for node in bar + return node if baz? + end + RUBY + end + + it 'registers no offense when the variable is referenced in the collection' do + expect_no_offenses(<<~RUBY) + node = foo + for node in node.children + return node if bar? + end + RUBY + end + end + context 'when a variable is assigned and unreferenced in `for` with multiple variables' do it 'registers an offense' do expect_offense(<<~RUBY) @@ -1187,6 +1215,19 @@ def some_method end RUBY end + + it 'registers an offense when the reassignment is the last statement' do + expect_offense(<<~RUBY) + foo = [1, 2] + foo = foo.map { |i| i + 1 } + ^^^ Useless assignment to variable - `foo`. + RUBY + + expect_correction(<<~RUBY) + foo = [1, 2] + foo.map { |i| i + 1 } + RUBY + end end context 'when a variable is reassigned with binary operator assignment and referenced' do @@ -2415,6 +2456,33 @@ def some_method(environment) end end + context 'when duplicate assignments appear in nested `if` branches inside a loop and the variable is used outside `while` loop' do + context 'while loop' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + def parse_options + index = -1 + while loop_cond + index += 1 + + if first_cond + index += 1 + else + if second_cond + index += 1 + else + if third_cond + index += 1 + end + end + end + end + end + RUBY + end + end + end + context 'when duplicate assignments in `rescue` branch with `retry`' do it 'does not register an offense' do expect_no_offenses(<<~RUBY) diff --git a/spec/rubocop/cop/naming/method_name_spec.rb b/spec/rubocop/cop/naming/method_name_spec.rb index 464678f18659..de1209e7f8a2 100644 --- a/spec/rubocop/cop/naming/method_name_spec.rb +++ b/spec/rubocop/cop/naming/method_name_spec.rb @@ -496,6 +496,13 @@ def %{identifier}; true; end end RUBY end + + it 'does not register an offense when an operator method is defined using a string' do + expect_no_offenses(<<~RUBY) + #{name} '`' do + end + RUBY + end end end diff --git a/spec/rubocop/cop/style/array_intersect_spec.rb b/spec/rubocop/cop/style/array_intersect_spec.rb index 24d7d93de3e3..9a25fa0d6733 100644 --- a/spec/rubocop/cop/style/array_intersect_spec.rb +++ b/spec/rubocop/cop/style/array_intersect_spec.rb @@ -85,6 +85,75 @@ ([1, 2, 3] & [4, 5, 6]).blank? RUBY end + + described_class::ARRAY_SIZE_METHODS.each do |method| + it "registers an offense when using `.#{method} > 0`" do + expect_offense(<<~RUBY, method: method) + (a & b).#{method} > 0 + ^^^^^^^^^{method}^^^^ Use `a.intersect?(b)` instead of `(a & b).#{method} > 0`. + RUBY + + expect_correction(<<~RUBY) + a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method} == 0`" do + expect_offense(<<~RUBY, method: method) + (a & b).#{method} == 0 + ^^^^^^^^^{method}^^^^^ Use `!a.intersect?(b)` instead of `(a & b).#{method} == 0`. + RUBY + + expect_correction(<<~RUBY) + !a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method} != 0`" do + expect_offense(<<~RUBY, method: method) + (a & b).#{method} != 0 + ^^^^^^^^^{method}^^^^^ Use `a.intersect?(b)` instead of `(a & b).#{method} != 0`. + RUBY + + expect_correction(<<~RUBY) + a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method}.zero?`" do + expect_offense(<<~RUBY, method: method) + (a & b).#{method}.zero? + ^^^^^^^^^{method}^^^^^^ Use `!a.intersect?(b)` instead of `(a & b).#{method}.zero?`. + RUBY + + expect_correction(<<~RUBY) + !a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method}.positive?`" do + expect_offense(<<~RUBY, method: method) + (a & b).#{method}.positive? + ^^^^^^^^^{method}^^^^^^^^^^ Use `a.intersect?(b)` instead of `(a & b).#{method}.positive?`. + RUBY + + expect_correction(<<~RUBY) + a.intersect?(b) + RUBY + end + + it "does not register an offense when using `.#{method} > 1`" do + expect_no_offenses(<<~RUBY) + (a & b).#{method} > 1 + RUBY + end + + it "does not register an offense when using `.#{method} == 1`" do + expect_no_offenses(<<~RUBY) + (a & b).#{method} == 1 + RUBY + end + end end context 'with Array#intersection' do @@ -143,6 +212,86 @@ array1.intersection(array2, array3).any? RUBY end + + described_class::ARRAY_SIZE_METHODS.each do |method| + it "registers an offense when using `.#{method} > 0`" do + expect_offense(<<~RUBY, method: method) + a.intersection(b).#{method} > 0 + ^^^^^^^^^^^^^^^^^^^{method}^^^^ Use `a.intersect?(b)` instead of `a.intersection(b).#{method} > 0`. + RUBY + + expect_correction(<<~RUBY) + a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method} == 0`" do + expect_offense(<<~RUBY, method: method) + a.intersection(b).#{method} == 0 + ^^^^^^^^^^^^^^^^^^^{method}^^^^^ Use `!a.intersect?(b)` instead of `a.intersection(b).#{method} == 0`. + RUBY + + expect_correction(<<~RUBY) + !a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method} != 0`" do + expect_offense(<<~RUBY, method: method) + a.intersection(b).#{method} != 0 + ^^^^^^^^^^^^^^^^^^^{method}^^^^^ Use `a.intersect?(b)` instead of `a.intersection(b).#{method} != 0`. + RUBY + + expect_correction(<<~RUBY) + a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method}.zero?`" do + expect_offense(<<~RUBY, method: method) + a.intersection(b).#{method}.zero? + ^^^^^^^^^^^^^^^^^^^{method}^^^^^^ Use `!a.intersect?(b)` instead of `a.intersection(b).#{method}.zero?`. + RUBY + + expect_correction(<<~RUBY) + !a.intersect?(b) + RUBY + end + + it "registers an offense when using `.#{method}.positive?`" do + expect_offense(<<~RUBY, method: method) + a.intersection(b).#{method}.positive? + ^^^^^^^^^^^^^^^^^^^{method}^^^^^^^^^^ Use `a.intersect?(b)` instead of `a.intersection(b).#{method}.positive?`. + RUBY + + expect_correction(<<~RUBY) + a.intersect?(b) + RUBY + end + + it "registers an offense when using `&.#{method}&.positive?`" do + expect_offense(<<~RUBY, method: method) + a&.intersection(b)&.#{method}&.positive? + ^^^^^^^^^^^^^^^^^^^^^{method}^^^^^^^^^^^ Use `a&.intersect?(b)` instead of `a&.intersection(b)&.#{method}&.positive?`. + RUBY + + expect_correction(<<~RUBY) + a&.intersect?(b) + RUBY + end + + it "does not register an offense when using `.#{method} > 1`" do + expect_no_offenses(<<~RUBY) + a.intersection(b).#{method} > 1 + RUBY + end + + it "does not register an offense when using `.#{method} == 1`" do + expect_no_offenses(<<~RUBY) + a.intersection(b).#{method} == 1 + RUBY + end + end end context 'with Array#any?' do diff --git a/spec/rubocop/cop/style/bitwise_predicate_spec.rb b/spec/rubocop/cop/style/bitwise_predicate_spec.rb index 13ad9b00120d..2c66ae5101f5 100644 --- a/spec/rubocop/cop/style/bitwise_predicate_spec.rb +++ b/spec/rubocop/cop/style/bitwise_predicate_spec.rb @@ -91,11 +91,11 @@ it 'registers an offense when using `&` with LHS flags in conjunction with `==` for comparisons' do expect_offense(<<~RUBY) (flags & variable) == flags - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Replace with `flags.allbits?(variable)` for comparison with bit flags. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Replace with `variable.allbits?(flags)` for comparison with bit flags. RUBY expect_correction(<<~RUBY) - flags.allbits?(variable) + variable.allbits?(flags) RUBY end diff --git a/spec/rubocop/cop/style/for_spec.rb b/spec/rubocop/cop/style/for_spec.rb index 70fe955de883..41012de3afc3 100644 --- a/spec/rubocop/cop/style/for_spec.rb +++ b/spec/rubocop/cop/style/for_spec.rb @@ -370,6 +370,19 @@ def func end RUBY end + + it 'corrects to `each` with safe navigation if collection ends with safe navigation' do + expect_offense(<<~RUBY) + for item in foo&.items + ^^^^^^^^^^^^^^^^^^^^^^ Prefer `each` over `for`. + end + RUBY + + expect_correction(<<~RUBY) + foo&.items&.each do |item| + end + RUBY + end end it 'accepts multiline each' do diff --git a/spec/rubocop/cop/style/redundant_begin_spec.rb b/spec/rubocop/cop/style/redundant_begin_spec.rb index 9da8dbb81edc..50bd6aa5f3d0 100644 --- a/spec/rubocop/cop/style/redundant_begin_spec.rb +++ b/spec/rubocop/cop/style/redundant_begin_spec.rb @@ -265,6 +265,346 @@ def method RUBY end + it 'registers and corrects an offense when a multiline `begin` block is inside `if`' do + expect_offense(<<~RUBY) + if condition + begin + ^^^^^ Redundant `begin` block detected. + foo + bar + end + end + RUBY + + expect_correction(<<~RUBY) + if condition + #{trailing_whitespace} + foo + bar + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `unless`' do + expect_offense(<<~RUBY) + unless condition + begin + ^^^^^ Redundant `begin` block detected. + foo + bar + end + end + RUBY + + expect_correction(<<~RUBY) + unless condition + #{trailing_whitespace} + foo + bar + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `elsif`' do + expect_offense(<<~RUBY) + if condition + foo + elsif condition2 + begin + ^^^^^ Redundant `begin` block detected. + bar + baz + end + end + RUBY + + expect_correction(<<~RUBY) + if condition + foo + elsif condition2 + #{trailing_whitespace} + bar + baz + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `else`' do + expect_offense(<<~RUBY) + if condition + foo + else + begin + ^^^^^ Redundant `begin` block detected. + bar + baz + end + end + RUBY + + expect_correction(<<~RUBY) + if condition + foo + else + #{trailing_whitespace} + bar + baz + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `case`/`when`' do + expect_offense(<<~RUBY) + case condition + when foo + begin + ^^^^^ Redundant `begin` block detected. + bar + baz + end + end + RUBY + + expect_correction(<<~RUBY) + case condition + when foo + #{trailing_whitespace} + bar + baz + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `case`/`when`/`else`' do + expect_offense(<<~RUBY) + case condition + when foo + bar + else + begin + ^^^^^ Redundant `begin` block detected. + baz + quux + end + end + RUBY + + expect_correction(<<~RUBY) + case condition + when foo + bar + else + #{trailing_whitespace} + baz + quux + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `case`/`in`' do + expect_offense(<<~RUBY) + case condition + in foo + begin + ^^^^^ Redundant `begin` block detected. + bar + baz + end + end + RUBY + + expect_correction(<<~RUBY) + case condition + in foo + #{trailing_whitespace} + bar + baz + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `case`/`in`/`else`' do + expect_offense(<<~RUBY) + case condition + in foo + bar + else + begin + ^^^^^ Redundant `begin` block detected. + baz + quux + end + end + RUBY + + expect_correction(<<~RUBY) + case condition + in foo + bar + else + #{trailing_whitespace} + baz + quux + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `while`' do + expect_offense(<<~RUBY) + while condition + begin + ^^^^^ Redundant `begin` block detected. + foo + bar + end + end + RUBY + + expect_correction(<<~RUBY) + while condition + #{trailing_whitespace} + foo + bar + #{trailing_whitespace} + end + RUBY + end + + it 'registers and corrects an offense when a multiline `begin` block is inside `until`' do + expect_offense(<<~RUBY) + until condition + begin + ^^^^^ Redundant `begin` block detected. + foo + bar + end + end + RUBY + + expect_correction(<<~RUBY) + until condition + #{trailing_whitespace} + foo + bar + #{trailing_whitespace} + end + RUBY + end + + it 'does not register an offense when using `begin` with `rescue` inside an `if` statement' do + expect_no_offenses(<<~RUBY) + if condition + begin + foo + bar + rescue StandardError + baz + end + end + RUBY + end + + it 'does not register an offense when using `begin` with `ensure` inside an `if` statement' do + expect_no_offenses(<<~RUBY) + if condition + begin + foo + bar + ensure + baz + end + end + RUBY + end + + it 'does not register an offense when using `begin` with `rescue` inside an `case` statement' do + expect_no_offenses(<<~RUBY) + case condition + when foo + begin + bar + baz + rescue StandardError + quux + end + end + RUBY + end + + it 'does not register an offense when using `begin` with `ensure` inside an `case` statement' do + expect_no_offenses(<<~RUBY) + case condition + when foo + begin + bar + baz + ensure + quux + end + end + RUBY + end + + it 'does not register an offense when using `begin` with `rescue` inside an `while` statement' do + expect_no_offenses(<<~RUBY) + while condition + begin + foo + bar + rescue StandardError + baz + end + end + RUBY + end + + it 'does not register an offense when using `begin` with `ensure` inside an `while` statement' do + expect_no_offenses(<<~RUBY) + while condition + begin + foo + bar + ensure + baz + end + end + RUBY + end + + it 'does not register an offense when using `begin` with `rescue` inside an `until` statement' do + expect_no_offenses(<<~RUBY) + until condition + begin + foo + bar + rescue StandardError + baz + end + end + RUBY + end + + it 'does not register an offense when using `begin` with `ensure` inside an `until` statement' do + expect_no_offenses(<<~RUBY) + until condition + begin + foo + bar + ensure + baz + end + end + RUBY + end + it 'does not register an offense when using `begin` with multiple statement for or assignment' do expect_no_offenses(<<~RUBY) var ||= begin diff --git a/spec/rubocop/cop/style/redundant_condition_spec.rb b/spec/rubocop/cop/style/redundant_condition_spec.rb index 222d6e310d00..f80545360486 100644 --- a/spec/rubocop/cop/style/redundant_condition_spec.rb +++ b/spec/rubocop/cop/style/redundant_condition_spec.rb @@ -678,6 +678,21 @@ def do_something(foo, ...) RUBY end + it 'registers an offense and autocorrects when true is used the the true branch and the condition is a parenthesized predicate call with arguments' do + expect_offense(<<~RUBY) + if foo?(arg) + ^^^^^^^^^^^^ Use double pipes `||` instead. + true + else + bar + end + RUBY + + expect_correction(<<~RUBY) + foo?(arg) || bar + RUBY + end + it 'registers an offense and autocorrects when true is used as the true branch and the condition takes arguments with safe navigation' do expect_offense(<<~RUBY) if obj&.foo? arg diff --git a/spec/rubocop/cop/style/redundant_format_spec.rb b/spec/rubocop/cop/style/redundant_format_spec.rb index 2ef40cbc28ca..35f16939a4fb 100644 --- a/spec/rubocop/cop/style/redundant_format_spec.rb +++ b/spec/rubocop/cop/style/redundant_format_spec.rb @@ -189,7 +189,7 @@ it_behaves_like 'offending format specifier', '% d', '5', "' 5'" it_behaves_like 'offending format specifier', '%+d', '5', "'+5'" it_behaves_like 'offending format specifier', '%.3d', '10', "'010'" - it_behaves_like 'offending format specifier', '%.d', '0', "''", broken_on: :jruby + it_behaves_like 'offending format specifier', '%.d', '0', "''" it_behaves_like 'offending format specifier', '%05d', '5', "'00005'" it_behaves_like 'offending format specifier', '%.2f', '5', "'5.00'" it_behaves_like 'offending format specifier', '%10.2f', '5', "' 5.00'" diff --git a/spec/rubocop/cop/style/redundant_parentheses_spec.rb b/spec/rubocop/cop/style/redundant_parentheses_spec.rb index f44067f8f630..8f1863f5ae0a 100644 --- a/spec/rubocop/cop/style/redundant_parentheses_spec.rb +++ b/spec/rubocop/cop/style/redundant_parentheses_spec.rb @@ -683,6 +683,23 @@ RUBY end + it 'registers an offense when braces block is wrapped in parentheses as a method argument' do + expect_offense(<<~RUBY) + foo (x.select { |item| item }).y + ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use parentheses around a method call. + RUBY + + expect_correction(<<~RUBY) + foo x.select { |item| item }.y + RUBY + end + + it 'does not register an offense when `do`...`end` block is wrapped in parentheses as a method argument' do + expect_no_offenses(<<~RUBY) + foo (x.select do |item| item end).y + RUBY + end + it 'registers a multiline expression around block wrapped in parens with a chained method' do expect_offense(<<~RUBY) ( diff --git a/spec/rubocop/cop/style/safe_navigation_spec.rb b/spec/rubocop/cop/style/safe_navigation_spec.rb index 062345e97649..9fa4398402c5 100644 --- a/spec/rubocop/cop/style/safe_navigation_spec.rb +++ b/spec/rubocop/cop/style/safe_navigation_spec.rb @@ -28,6 +28,10 @@ expect_no_offenses('foo && foo[:bar]') end + it 'allows hash key access and then a different hash key access' do + expect_no_offenses('return if foo[:bar] && foo[:baz].blank?') + end + it 'allows an object check before a negated predicate' do expect_no_offenses('foo && !foo.bar?') end @@ -1437,6 +1441,41 @@ def foobar #{variable}&.one&.two(baz) { |e| e.qux } RUBY end + + it 'corrects a ternary expression with safe navigation object check followed by a chained method call' do + expect_offense(<<~RUBY, variable: variable) + %{variable}&.bar ? %{variable}.bar.baz : nil + ^{variable}^^^^^^^^^{variable}^^^^^^^^^^^^^^ Use safe navigation (`&.`) instead [...] + RUBY + + expect_correction(<<~RUBY) + #{variable}&.bar&.baz + RUBY + end + + it 'corrects an object check with safe navigation followed by a chained method call' do + expect_offense(<<~RUBY, variable: variable) + %{variable}&.bar && %{variable}.bar.baz + ^{variable}^^^^^^^^^^{variable}^^^^^^^^ Use safe navigation (`&.`) instead [...] + RUBY + + expect_correction(<<~RUBY) + #{variable}&.bar&.baz + RUBY + end + + it 'corrects a check with safe navigation followed by a chained method call' do + expect_offense(<<~RUBY, variable: variable) + if %{variable}&.bar + ^^^^{variable}^^^^^ Use safe navigation (`&.`) instead [...] + %{variable}.bar.baz + end + RUBY + + expect_correction(<<~RUBY) + #{variable}&.bar&.baz + RUBY + end end end diff --git a/spec/rubocop/lsp/server_spec.rb b/spec/rubocop/lsp/server_spec.rb index e1d2e7a8b6e2..9ccff385302f 100644 --- a/spec/rubocop/lsp/server_spec.rb +++ b/spec/rubocop/lsp/server_spec.rb @@ -40,7 +40,7 @@ id: 2, result: { capabilities: { - textDocumentSync: { openClose: true, change: 1 }, + textDocumentSync: { openClose: true, change: 2 }, documentFormattingProvider: true } } @@ -1319,6 +1319,57 @@ end end + describe 'formatting via multiple entries of `contentChanges`' do + let(:requests) do + [ + { + jsonrpc: '2.0', + method: 'textDocument/didOpen', + params: { + textDocument: { + languageId: 'ruby', + text: "puts 'hi'", + uri: 'file:///path/to/file.rb', + version: 0 + } + } + }, { + jsonrpc: '2.0', + method: 'textDocument/didChange', + params: { + contentChanges: [{ text: "puts 'first'" }, { text: "puts 'last'" }], + textDocument: { + uri: 'file:///path/to/file.rb', + version: 10 + } + } + }, { + jsonrpc: '2.0', + id: 20, + method: 'textDocument/formatting', + params: { + options: { insertSpaces: true, tabSize: 2 }, + textDocument: { uri: 'file:///path/to/file.rb' } + } + } + ] + end + + it 'handles requests' do + expect(messages.count).to eq(3) + expect(messages.last).to eq( + jsonrpc: '2.0', id: 20, result: [ + { + newText: "puts 'last'\n", + range: { + end: { character: 0, line: 1 }, start: { character: 0, line: 0 } + } + } + ] + ) + end + end + describe 'formatting via formatting path on ignored path' do let(:requests) do [ @@ -1362,6 +1413,65 @@ end end + describe 'did change with multibyte character' do + let(:requests) do + [ + { + jsonrpc: '2.0', + method: 'textDocument/didOpen', + params: { + textDocument: { + languageId: 'ruby', + text: "puts '🍣🍺'", + uri: 'file:///path/to/file.rb', + version: 0 + } + } + }, { + jsonrpc: '2.0', + method: 'textDocument/didChange', + params: { + contentChanges: [ + { + text: '💎', + range: { + start: { line: 0, character: 6 }, + end: { line: 0, character: 10 } + } + } + ], + textDocument: { + uri: 'file:///path/to/file.rb', + version: 10 + } + } + }, { + jsonrpc: '2.0', + id: 20, + method: 'textDocument/formatting', + params: { + options: { insertSpaces: true, tabSize: 2 }, + textDocument: { uri: 'file:///path/to/file.rb' } + } + } + ] + end + + it 'handles requests' do + expect(messages.count).to eq(3) + expect(messages.last).to eq( + jsonrpc: '2.0', id: 20, result: [ + { + newText: "puts '💎'\n", + range: { + end: { character: 0, line: 1 }, start: { character: 0, line: 0 } + } + } + ] + ) + end + end + context 'when an internal error occurs' do before do allow_any_instance_of(RuboCop::LSP::Routes).to receive(:for).with('initialize').and_raise # rubocop:disable RSpec/AnyInstance