diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7a953523dc1a..b21e463ef324 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.1 (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.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] - rubocop-performance 1.22.1 - rubocop-rspec 3.1.0 ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 117f519ca3d1..86f674d13233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,23 @@ ## master (unreleased) +## 1.79.2 (2025-08-05) + +### Bug fixes + +* [#11664](https://github.com/rubocop/rubocop/issues/11664): Cache wasn't getting used when using parallelization. ([@jvlara][]) +* [#14411](https://github.com/rubocop/rubocop/issues/14411): Fix false negatives for `Layout/EmptyLinesAroundClassBody` when a class body starts with a blank line and defines a multiline superclass. ([@koic][]) +* [#14413](https://github.com/rubocop/rubocop/issues/14413): Fix a false positive for `Layout/EmptyLinesAroundArguments` with multiline strings that contain only whitespace. ([@earlopain][]) +* [#14408](https://github.com/rubocop/rubocop/pull/14408): Fix false-positive for `Layout/EmptyLinesAfterModuleInclusion` when inclusion is called with modifier. ([@r7kamura][]) +* [#14402](https://github.com/rubocop/rubocop/issues/14402): Fix false positives for `Lint/UselessAssignment` when duplicate assignments appear in `if` branch inside a loop and the variable is used outside `while` loop. ([@koic][]) +* [#14416](https://github.com/rubocop/rubocop/issues/14416): Fix false positives for `Style/MapToHash` when using `to_h` with block argument. ([@koic][]) +* [#14418](https://github.com/rubocop/rubocop/pull/14418): Fix false positives for `Style/MapToSet` when using `to_set` with block argument. ([@koic][]) +* [#14420](https://github.com/rubocop/rubocop/issues/14420): Fix false positives for `Style/SafeNavigation` when ternary expression with operator method call with method chain. ([@koic][]) + +### Changes + +* [#14407](https://github.com/rubocop/rubocop/pull/14407): Register offense for parentheses around method calls with blocks in `Style/RedundantParentheses`. ([@lovro-bikic][]) + ## 1.79.1 (2025-07-31) ### Bug fixes @@ -4272,3 +4289,4 @@ [@5hun-s]: https://github.com/5hun-s [@girasquid]: https://github.com/girasquid [@hakanensari]: https://github.com/hakanensari +[@jvlara]: https://github.com/jvlara diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c54eaad9052..c650390f94e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ do so. ```console $ rubocop -V -1.79.1 (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.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] - rubocop-performance 1.22.1 - rubocop-rspec 3.1.0 ``` diff --git a/docs/modules/ROOT/pages/cops_layout.adoc b/docs/modules/ROOT/pages/cops_layout.adoc index 2318d03135f0..2ec69019094a 100644 --- a/docs/modules/ROOT/pages/cops_layout.adoc +++ b/docs/modules/ROOT/pages/cops_layout.adoc @@ -868,7 +868,7 @@ end | Name | Default value | Configurable values | Categories -| `{"module_inclusion"=>["include", "prepend", "extend"]}` +| `{"module_inclusion" => ["include", "prepend", "extend"]}` | | ExpectedOrder diff --git a/docs/modules/ROOT/pages/cops_lint.adoc b/docs/modules/ROOT/pages/cops_lint.adoc index 6ace6650f9e7..8916b7463832 100644 --- a/docs/modules/ROOT/pages/cops_lint.adoc +++ b/docs/modules/ROOT/pages/cops_lint.adoc @@ -1122,11 +1122,11 @@ require 'my_debugger/start' | Name | Default value | Configurable values | DebuggerMethods -| `{"Kernel"=>["binding.irb", "Kernel.binding.irb"], "Byebug"=>["byebug", "remote_byebug", "Kernel.byebug", "Kernel.remote_byebug"], "Capybara"=>["page.save_and_open_page", "page.save_and_open_screenshot", "page.save_page", "page.save_screenshot", "save_and_open_page", "save_and_open_screenshot", "save_page", "save_screenshot"], "debug.rb"=>["binding.b", "binding.break", "Kernel.binding.b", "Kernel.binding.break"], "Pry"=>["binding.pry", "binding.remote_pry", "binding.pry_remote", "Kernel.binding.pry", "Kernel.binding.remote_pry", "Kernel.binding.pry_remote", "Pry.rescue", "pry"], "Rails"=>["debugger", "Kernel.debugger"], "RubyJard"=>["jard"], "WebConsole"=>["binding.console"]}` +| `{"Kernel" => ["binding.irb", "Kernel.binding.irb"], "Byebug" => ["byebug", "remote_byebug", "Kernel.byebug", "Kernel.remote_byebug"], "Capybara" => ["page.save_and_open_page", "page.save_and_open_screenshot", "page.save_page", "page.save_screenshot", "save_and_open_page", "save_and_open_screenshot", "save_page", "save_screenshot"], "debug.rb" => ["binding.b", "binding.break", "Kernel.binding.b", "Kernel.binding.break"], "Pry" => ["binding.pry", "binding.remote_pry", "binding.pry_remote", "Kernel.binding.pry", "Kernel.binding.remote_pry", "Kernel.binding.pry_remote", "Pry.rescue", "pry"], "Rails" => ["debugger", "Kernel.debugger"], "RubyJard" => ["jard"], "WebConsole" => ["binding.console"]}` | | DebuggerRequires -| `{"debug.rb"=>["debug/open", "debug/start"]}` +| `{"debug.rb" => ["debug/open", "debug/start"]}` | |=== @@ -1233,7 +1233,7 @@ Etc::Passwd | Name | Default value | Configurable values | DeprecatedConstants -| `{"NIL"=>{"Alternative"=>"nil", "DeprecatedVersion"=>"2.4"}, "TRUE"=>{"Alternative"=>"true", "DeprecatedVersion"=>"2.4"}, "FALSE"=>{"Alternative"=>"false", "DeprecatedVersion"=>"2.4"}, "Net::HTTPServerException"=>{"Alternative"=>"Net::HTTPClientException", "DeprecatedVersion"=>"2.6"}, "Random::DEFAULT"=>{"Alternative"=>"Random.new", "DeprecatedVersion"=>"3.0"}, "Struct::Group"=>{"Alternative"=>"Etc::Group", "DeprecatedVersion"=>"3.0"}, "Struct::Passwd"=>{"Alternative"=>"Etc::Passwd", "DeprecatedVersion"=>"3.0"}}` +| `{"NIL" => {"Alternative" => "nil", "DeprecatedVersion" => "2.4"}, "TRUE" => {"Alternative" => "true", "DeprecatedVersion" => "2.4"}, "FALSE" => {"Alternative" => "false", "DeprecatedVersion" => "2.4"}, "Net::HTTPServerException" => {"Alternative" => "Net::HTTPClientException", "DeprecatedVersion" => "2.6"}, "Random::DEFAULT" => {"Alternative" => "Random.new", "DeprecatedVersion" => "3.0"}, "Struct::Group" => {"Alternative" => "Etc::Group", "DeprecatedVersion" => "3.0"}, "Struct::Passwd" => {"Alternative" => "Etc::Passwd", "DeprecatedVersion" => "3.0"}}` | |=== @@ -7484,7 +7484,7 @@ values.sort { |*x| x[0] <=> x[1] } | Name | Default value | Configurable values | Methods -| `{"chunk_while"=>2, "each_with_index"=>2, "each_with_object"=>2, "inject"=>2, "max"=>2, "min"=>2, "minmax"=>2, "reduce"=>2, "slice_when"=>2, "sort"=>2}` +| `{"chunk_while" => 2, "each_with_index" => 2, "each_with_object" => 2, "inject" => 2, "max" => 2, "min" => 2, "minmax" => 2, "reduce" => 2, "slice_when" => 2, "sort" => 2}` | |=== diff --git a/docs/modules/ROOT/pages/cops_naming.adoc b/docs/modules/ROOT/pages/cops_naming.adoc index 563d53310ec7..8d145cce592e 100644 --- a/docs/modules/ROOT/pages/cops_naming.adoc +++ b/docs/modules/ROOT/pages/cops_naming.adoc @@ -806,7 +806,7 @@ TeslaVehicle | Boolean | FlaggedTerms -| `{"whitelist"=>{"Regex"=>/white[-_\s]?list/, "Suggestions"=>["allowlist", "permit"]}, "blacklist"=>{"Regex"=>/black[-_\s]?list/, "Suggestions"=>["denylist", "block"]}, "slave"=>{"WholeWord"=>true, "Suggestions"=>["replica", "secondary", "follower"]}}` +| `{"whitelist" => {"Regex" => /white[-_\s]?list/, "Suggestions" => ["allowlist", "permit"]}, "blacklist" => {"Regex" => /black[-_\s]?list/, "Suggestions" => ["denylist", "block"]}, "slave" => {"WholeWord" => true, "Suggestions" => ["replica", "secondary", "follower"]}}` | |=== diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index da84c977f408..8c89fcf7e8ec 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -2357,7 +2357,7 @@ items.include? | Name | Default value | Configurable values | PreferredMethods -| `{"collect"=>"map", "collect!"=>"map!", "collect_concat"=>"flat_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 @@ -8171,11 +8171,11 @@ end | Name | Default value | Configurable values | InverseMethods -| `{:any?=>:none?, :even?=>:odd?, :===>:!=, :=~=>:!~, :<=>:>=, :>=>:<=}` +| `{any?: :none?, even?: :odd?, "==": :!=, "=~": :!~, "<": :>=, ">": :<=}` | | InverseBlocks -| `{:select=>:reject, :select!=>:reject!}` +| `{select: :reject, select!: :reject!}` | |=== @@ -8253,7 +8253,7 @@ foo if !condition | Name | Default value | Configurable values | InverseMethods -| `{:!==>:==, :>=>:<=, :<==>:>, :<=>:>=, :>==>:<, :!~=>:=~, :zero?=>:nonzero?, :nonzero?=>:zero?, :any?=>:none?, :none?=>:any?, :even?=>:odd?, :odd?=>:even?}` +| `{"!=": :==, ">": :<=, "<=": :>, "<": :>=, ">=": :<, "!~": :=~, zero?: :nonzero?, nonzero?: :zero?, any?: :none?, none?: :any?, even?: :odd?, odd?: :even?}` | |=== @@ -12774,7 +12774,7 @@ default. | Name | Default value | Configurable values | PreferredDelimiters -| `{"default"=>"()", "%i"=>"[]", "%I"=>"[]", "%r"=>"{}", "%w"=>"[]", "%W"=>"[]"}` +| `{"default" => "()", "%i" => "[]", "%I" => "[]", "%r" => "{}", "%w" => "[]", "%W" => "[]"}` | |=== @@ -13260,7 +13260,7 @@ A.foo | Name | Default value | Configurable values | Methods -| `{"join"=>"", "sum"=>0, "exit"=>true, "exit!"=>false, "split"=>" ", "chomp"=>"\n", "chomp!"=>"\n"}` +| `{"join" => "", "sum" => 0, "exit" => true, "exit!" => false, "split" => " ", "chomp" => "\n", "chomp!" => "\n"}` | |=== @@ -16463,7 +16463,7 @@ end | Name | Default value | Configurable values | Methods -| `{"reduce"=>["acc", "elem"]}`, `{"inject"=>["acc", "elem"]}` +| `{"reduce" => ["acc", "elem"]}`, `{"inject" => ["acc", "elem"]}` | Array |=== @@ -17379,7 +17379,7 @@ from the `String` class. | Name | Default value | Configurable values | PreferredMethods -| `{"intern"=>"to_sym"}` +| `{"intern" => "to_sym"}` | |=== diff --git a/docs/modules/ROOT/pages/integration_with_other_tools.adoc b/docs/modules/ROOT/pages/integration_with_other_tools.adoc index 93eb1347d4fb..e792183f44d9 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.1 + rev: v1.79.2 hooks: - id: rubocop ---- @@ -136,7 +136,7 @@ entries in `additional_dependencies`: [source,yaml] ---- - repo: https://github.com/rubocop/rubocop - rev: v1.79.1 + rev: v1.79.2 hooks: - id: rubocop additional_dependencies: 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 50899f4700fa..0c09609550f5 100644 --- a/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +++ b/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb @@ -83,6 +83,8 @@ def require_empty_line?(node) end def allowed_method?(node) + node = node.body if node.respond_to?(:modifier_form?) && node.modifier_form? + return false unless node.send_type? MODULE_INCLUSION_METHODS.include?(node.method_name) diff --git a/lib/rubocop/cop/layout/empty_lines_around_arguments.rb b/lib/rubocop/cop/layout/empty_lines_around_arguments.rb index 3536fac95f10..2e8bd08a5cc2 100644 --- a/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +++ b/lib/rubocop/cop/layout/empty_lines_around_arguments.rb @@ -62,40 +62,19 @@ def receiver_and_method_call_on_different_lines?(node) node.receiver && node.receiver.loc.last_line != node.loc.selector&.line end - def empty_lines(node) - lines = processed_lines(node) - lines.select! { |code, _| code.empty? } - lines.map { |_, line| line } - end - - def extra_lines(node) - empty_lines(node).each do |line| - range = source_range(processed_source.buffer, line, 0) - yield(range) - end - end - - def processed_lines(node) - line_numbers(node).each_with_object([]) do |num, array| - array << [processed_source.lines[num - 1], num] + def extra_lines(node, &block) + node.arguments.each do |arg| + empty_range_for_starting_point(arg.source_range.begin, &block) end - end - def line_numbers(node) - inner_lines = [] - line_nums = node.arguments.each_with_object([]) do |arg_node, lines| - lines << outer_lines(arg_node) - inner_lines << inner_lines(arg_node) if arg_node.multiline? - end - line_nums.flatten.uniq - inner_lines.flatten - outer_lines(node) + empty_range_for_starting_point(node.loc.end.begin, &block) if node.loc.end end - def inner_lines(node) - [node.first_line + 1, node.last_line - 1] - end + def empty_range_for_starting_point(start) + range = range_with_surrounding_space(start, whitespace: true, side: :left) + return unless range.last_line - range.first_line > 1 - def outer_lines(node) - [node.first_line - 1, node.last_line + 1] + yield range.source_buffer.line_range(range.last_line - 1).adjust(end_pos: 1) end end end diff --git a/lib/rubocop/cop/layout/empty_lines_around_class_body.rb b/lib/rubocop/cop/layout/empty_lines_around_class_body.rb index f99c9fdc0707..c684267f71a9 100644 --- a/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +++ b/lib/rubocop/cop/layout/empty_lines_around_class_body.rb @@ -71,7 +71,7 @@ class EmptyLinesAroundClassBody < Base KIND = 'class' def on_class(node) - first_line = node.parent_class.first_line if node.parent_class + first_line = node.parent_class.last_line if node.parent_class check(node, node.body, adjusted_first_line: first_line) end diff --git a/lib/rubocop/cop/style/map_to_hash.rb b/lib/rubocop/cop/style/map_to_hash.rb index 1cee7a511456..f65f5a64203b 100644 --- a/lib/rubocop/cop/style/map_to_hash.rb +++ b/lib/rubocop/cop/style/map_to_hash.rb @@ -56,12 +56,10 @@ def self.autocorrect_incompatible_with def on_send(node) return unless (to_h_node, map_node = map_to_h(node)) + return if to_h_node.block_literal? message = format(MSG, method: map_node.loc.selector.source, dot: to_h_node.loc.dot.source) add_offense(map_node.loc.selector, message: message) do |corrector| - # If the `to_h` call already has a block, do not autocorrect. - next if to_h_node.block_literal? - autocorrect(corrector, to_h_node, map_node) end end diff --git a/lib/rubocop/cop/style/map_to_set.rb b/lib/rubocop/cop/style/map_to_set.rb index e332daf1a8e4..b961b595f800 100644 --- a/lib/rubocop/cop/style/map_to_set.rb +++ b/lib/rubocop/cop/style/map_to_set.rb @@ -40,12 +40,10 @@ class MapToSet < Base def on_send(node) return unless (to_set_node, map_node = map_to_set?(node)) + return if to_set_node.block_literal? message = format(MSG, method: map_node.loc.selector.source) add_offense(map_node.loc.selector, message: message) do |corrector| - # If the `to_set` call already has a block, do not autocorrect. - next if to_set_node.block_literal? - autocorrect(corrector, to_set_node, map_node) end end diff --git a/lib/rubocop/cop/style/redundant_parentheses.rb b/lib/rubocop/cop/style/redundant_parentheses.rb index cc73a77bca92..c70cab503b0c 100644 --- a/lib/rubocop/cop/style/redundant_parentheses.rb +++ b/lib/rubocop/cop/style/redundant_parentheses.rb @@ -149,7 +149,7 @@ def check(begin_node) return offense(begin_node, message) end - check_send(begin_node, node) if node.call_type? + check_send(begin_node, node) if call_node?(node) end # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity @@ -219,7 +219,13 @@ def allow_in_multiline_conditions? !!config.for_enabled_cop('Style/ParenthesesAroundCondition')['AllowInMultilineConditions'] end + def call_node?(node) + node.call_type? || (node.any_block_type? && !node.lambda_or_proc?) + end + def check_send(begin_node, node) + node = node.send_node if node.any_block_type? + return check_unary(begin_node, node) if node.unary_operation? return unless method_call_with_redundant_parentheses?(node) diff --git a/lib/rubocop/cop/style/safe_navigation.rb b/lib/rubocop/cop/style/safe_navigation.rb index a7e8db6ed9d2..ffe39e0f3fbc 100644 --- a/lib/rubocop/cop/style/safe_navigation.rb +++ b/lib/rubocop/cop/style/safe_navigation.rb @@ -259,6 +259,8 @@ def extract_if_body(node) end def dotless_operator_call?(method_call) + method_call = method_call.parent while method_call.parent.send_type? + return false if method_call.loc.dot method_call.method?(:[]) || method_call.method?(:[]=) || method_call.operator_method? diff --git a/lib/rubocop/cop/variable_force.rb b/lib/rubocop/cop/variable_force.rb index 29f49a9b81bb..8af505effa71 100644 --- a/lib/rubocop/cop/variable_force.rb +++ b/lib/rubocop/cop/variable_force.rb @@ -71,6 +71,8 @@ def assignment? end end + BRANCH_NODES = %i[if case case_match rescue].freeze + def variable_table @variable_table ||= VariableTable.new(self) end @@ -353,15 +355,17 @@ def descendant_reference(node) end end - def reference_assignments(loop_assignments, node) + 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 loop_assignments.first.node.each_ancestor(:if, :rescue, :case, :case_match).any? - loop_assignments.each { |assignment| assignment.reference!(node) } + 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!(node) + loop_assignments.last&.reference!(loop_node) end end diff --git a/lib/rubocop/result_cache.rb b/lib/rubocop/result_cache.rb index 1075cde66443..89c53ae72ae2 100644 --- a/lib/rubocop/result_cache.rb +++ b/lib/rubocop/result_cache.rb @@ -198,20 +198,22 @@ def digest(path) end def rubocop_extra_features - lib_root = File.join(File.dirname(__FILE__), '..') - exe_root = File.join(lib_root, '..', 'exe') + @rubocop_extra_features ||= begin + lib_root = File.join(File.dirname(__FILE__), '..') + exe_root = File.join(lib_root, '..', 'exe') - # Make sure to use an absolute path to prevent errors on Windows - # when traversing the relative paths with symlinks. - exe_root = File.absolute_path(exe_root) + # Make sure to use an absolute path to prevent errors on Windows + # when traversing the relative paths with symlinks. + exe_root = File.absolute_path(exe_root) - # These are all the files we have `require`d plus everything in the - # exe directory. A change to any of them could affect the cop output - # so we include them in the cache hash. - source_files = $LOADED_FEATURES + Find.find(exe_root).to_a - source_files -= ResultCache.rubocop_required_features # Rely on gem versions + # These are all the files we have `require`d plus everything in the + # exe directory. A change to any of them could affect the cop output + # so we include them in the cache hash. + source_files = $LOADED_FEATURES + Find.find(exe_root).to_a + source_files -= ResultCache.rubocop_required_features # Rely on gem versions - source_files + source_files + end end # Return a hash of the options given at invocation, minus the ones that have diff --git a/lib/rubocop/version.rb b/lib/rubocop/version.rb index 672b3a6056cb..ebcd211474b1 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.1' + STRING = '1.79.2' MSG = '%s (using %s, ' \ 'rubocop-ast %s, ' \ diff --git a/relnotes/v1.79.2.md b/relnotes/v1.79.2.md new file mode 100644 index 000000000000..a8abd92d4255 --- /dev/null +++ b/relnotes/v1.79.2.md @@ -0,0 +1,20 @@ +### Bug fixes + +* [#11664](https://github.com/rubocop/rubocop/issues/11664): Cache wasn't getting used when using parallelization. ([@jvlara][]) +* [#14411](https://github.com/rubocop/rubocop/issues/14411): Fix false negatives for `Layout/EmptyLinesAroundClassBody` when a class body starts with a blank line and defines a multiline superclass. ([@koic][]) +* [#14413](https://github.com/rubocop/rubocop/issues/14413): Fix a false positive for `Layout/EmptyLinesAroundArguments` with multiline strings that contain only whitespace. ([@earlopain][]) +* [#14408](https://github.com/rubocop/rubocop/pull/14408): Fix false-positive for `Layout/EmptyLinesAfterModuleInclusion` when inclusion is called with modifier. ([@r7kamura][]) +* [#14402](https://github.com/rubocop/rubocop/issues/14402): Fix false positives for `Lint/UselessAssignment` when duplicate assignments appear in `if` branch inside a loop and the variable is used outside `while` loop. ([@koic][]) +* [#14416](https://github.com/rubocop/rubocop/issues/14416): Fix false positives for `Style/MapToHash` when using `to_h` with block argument. ([@koic][]) +* [#14418](https://github.com/rubocop/rubocop/pull/14418): Fix false positives for `Style/MapToSet` when using `to_set` with block argument. ([@koic][]) +* [#14420](https://github.com/rubocop/rubocop/issues/14420): Fix false positives for `Style/SafeNavigation` when ternary expression with operator method call with method chain. ([@koic][]) + +### Changes + +* [#14407](https://github.com/rubocop/rubocop/pull/14407): Register offense for parentheses around method calls with blocks in `Style/RedundantParentheses`. ([@lovro-bikic][]) + +[@jvlara]: https://github.com/jvlara +[@koic]: https://github.com/koic +[@earlopain]: https://github.com/earlopain +[@r7kamura]: https://github.com/r7kamura +[@lovro-bikic]: https://github.com/lovro-bikic 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 d68bc754b8ab..85d15ab85137 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 @@ -241,4 +241,14 @@ module Bar end RUBY end + + it 'does not register an offense when module inclusion is called with modifier' do + expect_no_offenses(<<~RUBY) + class Foo + include Bar + include Baz if condition + include Qux + end + RUBY + end end diff --git a/spec/rubocop/cop/layout/empty_lines_around_arguments_spec.rb b/spec/rubocop/cop/layout/empty_lines_around_arguments_spec.rb index d79c30b8d276..793281542cea 100644 --- a/spec/rubocop/cop/layout/empty_lines_around_arguments_spec.rb +++ b/spec/rubocop/cop/layout/empty_lines_around_arguments_spec.rb @@ -300,6 +300,14 @@ def anything; end RUBY end + it 'ignores a multiline string with only whitespace on one line and []-style method call after' do + expect_no_offenses(<<~RUBY) + format('%d + + ', 1)[0] + RUBY + end + context 'with one argument' do it 'ignores empty lines inside of method arguments' do expect_no_offenses(<<~RUBY) diff --git a/spec/rubocop/cop/layout/empty_lines_around_class_body_spec.rb b/spec/rubocop/cop/layout/empty_lines_around_class_body_spec.rb index 26d654434a23..d9a188d760cb 100644 --- a/spec/rubocop/cop/layout/empty_lines_around_class_body_spec.rb +++ b/spec/rubocop/cop/layout/empty_lines_around_class_body_spec.rb @@ -72,6 +72,28 @@ class << self end RUBY end + + it 'registers an offense when a class body starts with a blank line and defines a multiline superclass' do + expect_offense(<<~RUBY) + class SomeClass < Struct.new( + :attr, + keyword_init: true + ) + + ^{} #{extra_begin} + do_something + end + RUBY + + expect_correction(<<~RUBY) + class SomeClass < Struct.new( + :attr, + keyword_init: true + ) + do_something + end + RUBY + end end context 'when EnforcedStyle is empty_lines' do diff --git a/spec/rubocop/cop/lint/useless_assignment_spec.rb b/spec/rubocop/cop/lint/useless_assignment_spec.rb index e1c3c08f4b81..df3a04328a23 100644 --- a/spec/rubocop/cop/lint/useless_assignment_spec.rb +++ b/spec/rubocop/cop/lint/useless_assignment_spec.rb @@ -2363,6 +2363,21 @@ def some_method(environment) RUBY end end + + context 'while loop with parenthesized body' do + it 'registers an offense' do + expect_offense(<<~RUBY) + while + ( + foo = 1 + ^^^ Useless assignment to variable - `foo`. + foo = 1 + ) + p foo + end + RUBY + end + end end context 'when duplicate assignments in `if` branch inside a loop' do @@ -2381,6 +2396,25 @@ def some_method(environment) end end + context 'when duplicate assignments appear in `if` branch 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) + var = false + while loop_cond + if var + var = false + foo + else + var = true + bar + 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/style/map_to_hash_spec.rb b/spec/rubocop/cop/style/map_to_hash_spec.rb index 60330ac97eca..1782f588c3bc 100644 --- a/spec/rubocop/cop/style/map_to_hash_spec.rb +++ b/spec/rubocop/cop/style/map_to_hash_spec.rb @@ -195,14 +195,27 @@ end end - context "`#{method}.to_h` with a block on `to_h`" do - it 'registers an offense but does not correct' do - expect_offense(<<~RUBY, method: method) + context "`#{method}` followed by `to_h` with a block passed to `to_h`" do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + foo.#{method} { |x| x * 2 }.to_h { |x| [x.to_s, x] } + RUBY + end + end + + context "`#{method}` followed by `to_h` with a numbered block passed to `to_h`", :ruby27 do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) foo.#{method} { |x| x * 2 }.to_h { |x| [x.to_s, x] } - ^{method} Pass a block to `to_h` instead of calling `#{method}.to_h`. RUBY + end + end - expect_no_corrections + context "`#{method}` followed by `to_h` with an `it` block passed to `to_h`", :ruby34 do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + foo.#{method} { |x| x * 2 }.to_h { [it.to_s, it] } + RUBY end end diff --git a/spec/rubocop/cop/style/map_to_set_spec.rb b/spec/rubocop/cop/style/map_to_set_spec.rb index 6ca6d61a38fc..768650be6ff9 100644 --- a/spec/rubocop/cop/style/map_to_set_spec.rb +++ b/spec/rubocop/cop/style/map_to_set_spec.rb @@ -116,14 +116,27 @@ end end - context "`#{method}.to_set` with a block on `to_set`" do - it 'registers an offense but does not correct' do - expect_offense(<<~RUBY, method: method) + context "`#{method}` followed by `to_set` with a block passed to `to_set`" do + it 'does not register an offense but does not correct' do + expect_no_offenses(<<~RUBY) + foo.#{method} { |x| x * 2 }.to_set { |x| [x.to_s, x] } + RUBY + end + end + + context "`#{method}` followed by `to_set` with a numbered block passed to `to_set`", :ruby27 do + it 'does not register an offense but does not correct' do + expect_no_offenses(<<~RUBY) foo.#{method} { |x| x * 2 }.to_set { |x| [x.to_s, x] } - ^{method} Pass a block to `to_set` instead of calling `#{method}.to_set`. RUBY + end + end - expect_no_corrections + context "`#{method}` followed by `to_set` with an `it` block passed to `to_set`", :ruby34 do + it 'does not register an offense but does not correct' do + expect_no_offenses(<<~RUBY) + foo.#{method} { |x| x * 2 }.to_set { |x| [x.to_s, x] } + RUBY end end diff --git a/spec/rubocop/cop/style/redundant_parentheses_spec.rb b/spec/rubocop/cop/style/redundant_parentheses_spec.rb index c0b2116eb242..f44067f8f630 100644 --- a/spec/rubocop/cop/style/redundant_parentheses_spec.rb +++ b/spec/rubocop/cop/style/redundant_parentheses_spec.rb @@ -683,6 +683,29 @@ RUBY end + it 'registers a multiline expression around block wrapped in parens with a chained method' do + expect_offense(<<~RUBY) + ( + ^ Don't use parentheses around a method call. + x.select { |item| item.foo } + ).map(&:bar) + RUBY + + expect_correction(<<~RUBY) + x.select { |item| item.foo }.map(&:bar) + RUBY + end + + it_behaves_like 'redundant', '(x.select { |item| item })', 'x.select { |item| item }', 'a method call' + + context 'when Ruby 2.7', :ruby27 do + it_behaves_like 'redundant', '(x.select { _1 })', 'x.select { _1 }', 'a method call' + end + + context 'when Ruby 3.4', :ruby34 do + it_behaves_like 'redundant', '(x.select { it })', 'x.select { it }', 'a method call' + end + it_behaves_like 'plausible', '(-2)**2' it_behaves_like 'plausible', '(-2.1)**2' diff --git a/spec/rubocop/cop/style/safe_navigation_spec.rb b/spec/rubocop/cop/style/safe_navigation_spec.rb index b632866e1179..062345e97649 100644 --- a/spec/rubocop/cop/style/safe_navigation_spec.rb +++ b/spec/rubocop/cop/style/safe_navigation_spec.rb @@ -818,6 +818,12 @@ def foobar RUBY end + it 'allows ternary expression with indexed assignment call chain without dot' do + expect_no_offenses(<<~RUBY) + %{variable}.nil? ? nil : %{variable}.foo[index] + RUBY + end + it 'allows ternary expression with double colon method call' do expect_no_offenses(<<~RUBY) #{variable} ? #{variable}::foo : nil @@ -830,6 +836,12 @@ def foobar RUBY end + it 'allows ternary expression with operator method call chain without dot' do + expect_no_offenses(<<~RUBY) + %{variable}.nil? ? nil : %{variable}.foo * 42 + RUBY + end + it 'registers an offense for ternary expressions in a method argument' do expect_offense(<<~RUBY, variable: variable) puts(%{variable} ? %{variable}.bar : nil) @@ -879,17 +891,6 @@ def foobar RUBY end - it 'registers an offense for ternary expression with operator method call with method chain' do - expect_offense(<<~RUBY, variable: variable) - %{variable}.nil? ? nil : %{variable}.foo * 42 - ^{variable}^^^^^^^^^^^^^^^{variable}^^^^^^^^^ Use safe navigation (`&.`) instead [...] - RUBY - - expect_correction(<<~RUBY) - #{variable}&.foo * 42 - RUBY - end - it 'registers an offense for ternary expressions in a collection assignment' do expect_offense(<<~RUBY, variable: variable) results[0] = %{variable} ? %{variable}.bar : nil