diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 17d3c7f671..d0a1e8b93e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: sed -e "/gem 'rubocop', github: 'rubocop\/rubocop'/d" \ -e "/gem 'rubocop-rspec',/d" -i Gemfile cat << EOF > Gemfile.local - gem 'rubocop', '1.48.1' # Specify the oldest supported RuboCop version + gem 'rubocop', '1.72.1' # Specify the oldest supported RuboCop version EOF - name: set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.rubocop.yml b/.rubocop.yml index ab330ce48d..e111ec6c5f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,9 +1,12 @@ # This is the configuration used to check the rubocop source code. inherit_from: .rubocop_todo.yml -require: - - rubocop/cop/internal_affairs + +plugins: + - rubocop-internal_affairs - rubocop-performance + +require: - rubocop-rspec AllCops: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 88cad19a6c..719e5d053f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2024-12-13 20:34:48 UTC using RuboCop version 1.69.2. +# on 2025-01-22 15:33:31 UTC using RuboCop version 1.70.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -28,12 +28,16 @@ InternalAffairs/NumblockHandler: - 'lib/rubocop/cop/performance/redundant_equality_comparison_block.rb' - 'lib/rubocop/cop/performance/sum.rb' +# Offense count: 18 +InternalAffairs/OnSendWithoutOnCSend: + Enabled: false + # Offense count: 4 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 159 -# Offense count: 13 +# Offense count: 14 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Max: 14 @@ -53,11 +57,11 @@ RSpec/ContextWording: - 'spec/rubocop/cop/performance/string_replacement_spec.rb' - 'spec/rubocop/cop/performance/times_map_spec.rb' -# Offense count: 388 +# Offense count: 390 # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 24 -# Offense count: 312 +# Offense count: 314 RSpec/MultipleExpectations: Max: 3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 886b3e795c..1d8bbc8dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,17 @@ ## master (unreleased) +## 1.24.0 (2025-02-16) + +### New features + +* [#490](https://github.com/rubocop/rubocop-performance/pull/490): Pluginfy RuboCop Performance. ([@koic][]) +* [#462](https://github.com/rubocop/rubocop-performance/pull/462): Add new `Performance/ZipWithoutBlock` cop that checks patterns like `.map { |id| [id] }` or `.map { [_1] }` and can replace them with `.zip`. ([@corsonknowles][]) + +### Bug fixes + +* [#484](https://github.com/rubocop/rubocop-performance/pull/484): Fix `Performance/CaseWhenSplat` cop error on `when` node without body. ([@viralpraxis][]) + ## 1.23.1 (2025-01-04) ### Bug fixes @@ -569,3 +580,4 @@ [@earlopain]: https://github.com/earlopain [@parkerfinch]: https://github.com/parkerfinch [@viralpraxis]: https://github.com/viralpraxis +[@corsonknowles]: https://github.com/corsonknowles diff --git a/README.md b/README.md index 8058c780ef..5d0f13a7fb 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ ways to do this: Put this into your `.rubocop.yml`. ```yaml -require: rubocop-performance +plugins: rubocop-performance ``` Alternatively, use the following array notation when specifying multiple extensions. ```yaml -require: +plugins: - rubocop-other-extension - rubocop-performance ``` @@ -44,10 +44,13 @@ require: Now you can run `rubocop` and it will automatically load the RuboCop Performance cops together with the standard cops. +> [!NOTE] +> The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. + ### Command line ```sh -$ rubocop --require rubocop-performance +$ rubocop --plugin rubocop-performance ``` ### Rake task @@ -56,7 +59,7 @@ $ rubocop --require rubocop-performance require 'rubocop/rake_task' RuboCop::RakeTask.new do |task| - task.requires << 'rubocop-performance' + task.plugins << 'rubocop-performance' end ``` diff --git a/config/default.yml b/config/default.yml index 437f1ed440..606acb83df 100644 --- a/config/default.yml +++ b/config/default.yml @@ -381,3 +381,9 @@ Performance/UriDefaultParser: Description: 'Use `URI::DEFAULT_PARSER` instead of `URI::Parser.new`.' Enabled: true VersionAdded: '0.50' + +Performance/ZipWithoutBlock: + Description: 'Checks for `map { |id| [id] }` and suggests replacing it with `zip`.' + Enabled: pending + Safe: false + VersionAdded: '1.24' diff --git a/docs/antora.yml b/docs/antora.yml index 0ee5485e3f..3ee5f7e808 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -2,6 +2,6 @@ name: rubocop-performance title: RuboCop Performance # We always provide version without patch here (e.g. 1.1), # as patch versions should not appear in the docs. -version: '1.23' +version: '1.24' nav: - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 7e91ba8416..d319e7fd96 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -63,5 +63,6 @@ Performance cops optimization analysis for your projects. * xref:cops_performance.adoc#performancetimesmap[Performance/TimesMap] * xref:cops_performance.adoc#performanceunfreezestring[Performance/UnfreezeString] * xref:cops_performance.adoc#performanceuridefaultparser[Performance/UriDefaultParser] +* xref:cops_performance.adoc#performancezipwithoutblock[Performance/ZipWithoutBlock] // END_COP_LIST diff --git a/docs/modules/ROOT/pages/cops_performance.adoc b/docs/modules/ROOT/pages/cops_performance.adoc index 5c78a3f0e1..bae90e109e 100644 --- a/docs/modules/ROOT/pages/cops_performance.adoc +++ b/docs/modules/ROOT/pages/cops_performance.adoc @@ -2598,3 +2598,38 @@ URI::Parser.new # good URI::DEFAULT_PARSER ---- + +[#performancezipwithoutblock] +== Performance/ZipWithoutBlock + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| No +| Always (Unsafe) +| 1.24 +| - +|=== + +Checks for `map { |id| [id] }` and suggests replacing it with `zip`. + +[#safety-performancezipwithoutblock] +=== Safety + +This cop is unsafe for novel definitions of `map` and `collect` +on non-Enumerable objects that do not respond to `zip`. +To make your object enumerable, define an `each` method +as described in https://ruby-doc.org/core/Enumerable.html + +[#examples-performancezipwithoutblock] +=== Examples + +[source,ruby] +---- +# bad +[1, 2, 3].map { |id| [id] } + +# good +[1, 2, 3].zip +---- diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc index 3632335c8c..3711cae813 100644 --- a/docs/modules/ROOT/pages/usage.adoc +++ b/docs/modules/ROOT/pages/usage.adoc @@ -9,17 +9,19 @@ Put this into your `.rubocop.yml`. [source,yaml] ---- -require: rubocop-performance +plugins: rubocop-performance ---- Now you can run `rubocop` and it will automatically load the RuboCop Performance cops together with the standard cops. +NOTE: The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`. + == Command line [source,sh] ---- -$ rubocop --require rubocop-performance +$ rubocop --plugin rubocop-performance ---- == Rake task @@ -27,6 +29,6 @@ $ rubocop --require rubocop-performance [source,ruby] ---- RuboCop::RakeTask.new do |task| - task.requires << 'rubocop-performance' + task.plugins << 'rubocop-performance' end ---- diff --git a/lib/rubocop-performance.rb b/lib/rubocop-performance.rb index 37a7ceb49c..9a32f00c97 100644 --- a/lib/rubocop-performance.rb +++ b/lib/rubocop-performance.rb @@ -4,10 +4,7 @@ require_relative 'rubocop/performance' require_relative 'rubocop/performance/version' -require_relative 'rubocop/performance/inject' - -RuboCop::Performance::Inject.defaults! - +require_relative 'rubocop/performance/plugin' require_relative 'rubocop/cop/performance_cops' RuboCop::Cop::Lint::UnusedMethodArgument.singleton_class.prepend( diff --git a/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb b/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb index 3658ab3e79..78daaada56 100644 --- a/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +++ b/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb @@ -44,8 +44,8 @@ class ArraySemiInfiniteRangeSlice < Base def_node_matcher :endless_range?, <<~PATTERN { - ({irange erange} nil? (int positive?)) - ({irange erange} (int positive?) nil?) + (range nil? (int positive?)) + (range (int positive?) nil?) } PATTERN diff --git a/lib/rubocop/cop/performance/case_when_splat.rb b/lib/rubocop/cop/performance/case_when_splat.rb index 0fc30bd660..40c9a565dd 100644 --- a/lib/rubocop/cop/performance/case_when_splat.rb +++ b/lib/rubocop/cop/performance/case_when_splat.rb @@ -58,6 +58,7 @@ module Performance class CaseWhenSplat < Base include Alignment include RangeHelp + include CommentsHelp extend AutoCorrector MSG = 'Reordering `when` conditions with a splat to the end of the `when` branches can improve performance.' @@ -116,11 +117,18 @@ def reorder_condition(corrector, when_node) def reordering_correction(when_node) new_condition = replacement(when_node.conditions) - if same_line?(when_node, when_node.body) - new_condition_with_then(when_node, new_condition) - else - new_branch_without_then(when_node, new_condition) - end + condition = + if same_line?(when_node, when_node.body) + new_condition_with_then(when_node, new_condition) + else + new_branch_without_then(when_node, new_condition) + end + + condition_comments = comments_in_range(when_node).map do |comment_node| + "#{indent_for(comment_node)}#{comment_node.source}" + end.join("\n") + + "#{condition}#{condition_comments}" end def when_branch_range(when_node) @@ -134,7 +142,13 @@ def new_condition_with_then(node, new_condition) end def new_branch_without_then(node, new_condition) - "\n#{indent_for(node)}when #{new_condition}\n#{indent_for(node.body)}#{node.body.source}" + new_branch = "\n#{indent_for(node)}when #{new_condition}\n" + + if node.body + "#{new_branch}#{indent_for(node.body)}#{node.body.source}" + else + new_branch + end end def indent_for(node) diff --git a/lib/rubocop/cop/performance/chain_array_allocation.rb b/lib/rubocop/cop/performance/chain_array_allocation.rb index d422013c59..cda0153729 100644 --- a/lib/rubocop/cop/performance/chain_array_allocation.rb +++ b/lib/rubocop/cop/performance/chain_array_allocation.rb @@ -45,6 +45,8 @@ class ChainArrayAllocation < Base RETURNS_NEW_ARRAY = (ALWAYS_RETURNS_NEW_ARRAY + RETURNS_NEW_ARRAY_WHEN_NO_BLOCK).freeze + RESTRICT_ON_SEND = RETURNS_NEW_ARRAY + MSG = 'Use unchained `%s` and `%s!` ' \ '(followed by `return array` if required) instead of chaining ' \ '`%s...%s`.' @@ -52,7 +54,7 @@ class ChainArrayAllocation < Base def_node_matcher :chain_array_allocation?, <<~PATTERN (send { (send _ $%RETURN_NEW_ARRAY_WHEN_ARGS {int lvar ivar cvar gvar send}) - ({block numblock} (send _ $%ALWAYS_RETURNS_NEW_ARRAY) ...) + (any_block (send _ $%ALWAYS_RETURNS_NEW_ARRAY) ...) (send _ $%RETURNS_NEW_ARRAY ...) } $%HAS_MUTATION_ALTERNATIVE ...) PATTERN @@ -73,7 +75,7 @@ def on_send(node) def enumerable_select_method?(node) # NOTE: `QueryMethods#select` in Rails accepts positional arguments, whereas `Enumerable#select` does not. # This difference can be utilized to reduce the knowledge requirements related to `select`. - (node.block_type? || node.numblock_type?) && node.send_node.arguments.empty? + node.any_block_type? && node.send_node.arguments.empty? end end end diff --git a/lib/rubocop/cop/performance/collection_literal_in_loop.rb b/lib/rubocop/cop/performance/collection_literal_in_loop.rb index 937c5e9ea9..f75847a6b1 100644 --- a/lib/rubocop/cop/performance/collection_literal_in_loop.rb +++ b/lib/rubocop/cop/performance/collection_literal_in_loop.rb @@ -65,6 +65,8 @@ class CollectionLiteralInLoop < Base HASH_METHODS = (ENUMERABLE_METHOD_NAMES | NONMUTATING_HASH_METHODS).to_set.freeze + RESTRICT_ON_SEND = ARRAY_METHODS + HASH_METHODS + def_node_matcher :kernel_loop?, <<~PATTERN (block (send {nil? (const nil? :Kernel)} :loop) diff --git a/lib/rubocop/cop/performance/fixed_size.rb b/lib/rubocop/cop/performance/fixed_size.rb index cf7ef63c9d..4e2e1d826f 100644 --- a/lib/rubocop/cop/performance/fixed_size.rb +++ b/lib/rubocop/cop/performance/fixed_size.rb @@ -75,7 +75,7 @@ def allowed_argument?(arg) end def allowed_parent?(node) - node && (node.casgn_type? || node.block_type?) + node&.type?(:casgn, :block) end def contains_splat?(node) diff --git a/lib/rubocop/cop/performance/range_include.rb b/lib/rubocop/cop/performance/range_include.rb index d56afc2262..90062bf6b7 100644 --- a/lib/rubocop/cop/performance/range_include.rb +++ b/lib/rubocop/cop/performance/range_include.rb @@ -38,7 +38,7 @@ class RangeInclude < Base # (We don't even catch it if the Range is in double parens) def_node_matcher :range_include, <<~PATTERN - (call {irange erange (begin {irange erange})} ${:include? :member?} ...) + (call {range (begin range)} ${:include? :member?} ...) PATTERN def on_send(node) diff --git a/lib/rubocop/cop/performance/redundant_match.rb b/lib/rubocop/cop/performance/redundant_match.rb index d5d2c4cb16..b36d6a2e66 100644 --- a/lib/rubocop/cop/performance/redundant_match.rb +++ b/lib/rubocop/cop/performance/redundant_match.rb @@ -84,7 +84,7 @@ def requires_parentheses_for_call_like?(arg) end def call_like?(arg) - arg.call_type? || arg.yield_type? || arg.super_type? + arg.type?(:call, :yield, :super) end end end diff --git a/lib/rubocop/cop/performance/regexp_match.rb b/lib/rubocop/cop/performance/regexp_match.rb index 51d11d4246..0077728b1d 100644 --- a/lib/rubocop/cop/performance/regexp_match.rb +++ b/lib/rubocop/cop/performance/regexp_match.rb @@ -239,7 +239,7 @@ def scope_body(node) def scope_root(node) node.each_ancestor.find do |ancestor| - ancestor.def_type? || ancestor.defs_type? || ancestor.class_type? || ancestor.module_type? + ancestor.type?(:def, :defs, :class, :module) end end diff --git a/lib/rubocop/cop/performance/times_map.rb b/lib/rubocop/cop/performance/times_map.rb index 7b37959479..07623634da 100644 --- a/lib/rubocop/cop/performance/times_map.rb +++ b/lib/rubocop/cop/performance/times_map.rb @@ -36,6 +36,13 @@ class TimesMap < Base MESSAGE_ONLY_IF = 'only if `%s` is always 0 or more' RESTRICT_ON_SEND = %i[map collect].freeze + def_node_matcher :times_map_call, <<~PATTERN + { + (any_block $(call (call $!nil? :times) {:map :collect}) ...) + $(call (call $!nil? :times) {:map :collect} (block_pass ...)) + } + PATTERN + def on_send(node) check(node) end @@ -62,7 +69,7 @@ def check(node) def handleable_receiver?(node) receiver = node.receiver.receiver - return true if receiver.literal? && (receiver.int_type? || receiver.float_type?) + return true if receiver.literal? && receiver.type?(:int, :float) node.receiver.dot? end @@ -75,13 +82,6 @@ def message(map_or_collect, count) end format(template, count: count.source, map_or_collect: map_or_collect.method_name) end - - def_node_matcher :times_map_call, <<~PATTERN - { - ({block numblock} $(call (call $!nil? :times) {:map :collect}) ...) - $(call (call $!nil? :times) {:map :collect} (block_pass ...)) - } - PATTERN end end end diff --git a/lib/rubocop/cop/performance/zip_without_block.rb b/lib/rubocop/cop/performance/zip_without_block.rb new file mode 100644 index 0000000000..f2815b9912 --- /dev/null +++ b/lib/rubocop/cop/performance/zip_without_block.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Performance + # Checks for `map { |id| [id] }` and suggests replacing it with `zip`. + # + # @safety + # This cop is unsafe for novel definitions of `map` and `collect` + # on non-Enumerable objects that do not respond to `zip`. + # To make your object enumerable, define an `each` method + # as described in https://ruby-doc.org/core/Enumerable.html + # + # @example + # # bad + # [1, 2, 3].map { |id| [id] } + # + # # good + # [1, 2, 3].zip + class ZipWithoutBlock < Base + extend AutoCorrector + + MSG = 'Use `zip` without a block argument instead.' + RESTRICT_ON_SEND = Set.new(%i[map collect]).freeze + + # @!method map_with_array?(node) + def_node_matcher :map_with_array?, <<~PATTERN + { + (block (call !nil? RESTRICT_ON_SEND) (args (arg _)) (array (lvar _))) + (numblock (call !nil? RESTRICT_ON_SEND) 1 (array (lvar _))) + } + PATTERN + + def on_send(node) + return unless map_with_array?(node.parent) + + register_offense(node) + end + alias on_csend on_send + + private + + def register_offense(node) + offense_range = offense_range(node) + add_offense(offense_range) do |corrector| + corrector.replace(offense_range, 'zip') + end + end + + def offense_range(node) + node.loc.selector.join(node.parent.loc.end) + end + end + end + end +end diff --git a/lib/rubocop/cop/performance_cops.rb b/lib/rubocop/cop/performance_cops.rb index e71d4066c7..8a56be5db1 100644 --- a/lib/rubocop/cop/performance_cops.rb +++ b/lib/rubocop/cop/performance_cops.rb @@ -54,3 +54,4 @@ require_relative 'performance/unfreeze_string' require_relative 'performance/uri_default_parser' require_relative 'performance/chain_array_allocation' +require_relative 'performance/zip_without_block' diff --git a/lib/rubocop/performance.rb b/lib/rubocop/performance.rb index 4ed61a2fa0..b1bdf70f56 100644 --- a/lib/rubocop/performance.rb +++ b/lib/rubocop/performance.rb @@ -1,14 +1,7 @@ # frozen_string_literal: true module RuboCop - # RuboCop Performance project namespace + # RuboCop Performance project namespace. module Performance - PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze - CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze - CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze - - private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT) - - ::RuboCop::ConfigObsoletion.files << PROJECT_ROOT.join('config', 'obsoletion.yml') end end diff --git a/lib/rubocop/performance/inject.rb b/lib/rubocop/performance/inject.rb deleted file mode 100644 index e255a881a4..0000000000 --- a/lib/rubocop/performance/inject.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Performance - # Because RuboCop doesn't yet support plugins, we have to monkey patch in a - # bit of our configuration. - module Inject - def self.defaults! - path = CONFIG_DEFAULT.to_s - hash = ConfigLoader.send(:load_yaml_configuration, path) - config = Config.new(hash, path).tap(&:make_excludes_absolute) - puts "configuration from #{path}" if ConfigLoader.debug? - config = ConfigLoader.merge_with_default(config, path) - ConfigLoader.instance_variable_set(:@default_configuration, config) - end - end - end -end diff --git a/lib/rubocop/performance/plugin.rb b/lib/rubocop/performance/plugin.rb new file mode 100644 index 0000000000..2c082d3376 --- /dev/null +++ b/lib/rubocop/performance/plugin.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'lint_roller' + +module RuboCop + module Performance + # A plugin that integrates RuboCop Performance with RuboCop's plugin system. + class Plugin < LintRoller::Plugin + def about + LintRoller::About.new( + name: 'rubocop-performance', + version: Version::STRING, + homepage: 'https://github.com/rubocop/rubocop-performance', + description: 'A collection of RuboCop cops to check for performance optimizations in Ruby code.' + ) + end + + def supported?(context) + context.engine == :rubocop + end + + def rules(_context) + project_root = Pathname.new(__dir__).join('../../..') + + ConfigObsoletion.files << project_root.join('config', 'obsoletion.yml') + + LintRoller::Rules.new(type: :path, config_format: :rubocop, value: project_root.join('config', 'default.yml')) + end + end + end +end diff --git a/lib/rubocop/performance/version.rb b/lib/rubocop/performance/version.rb index 103e69b485..24695abafb 100644 --- a/lib/rubocop/performance/version.rb +++ b/lib/rubocop/performance/version.rb @@ -4,7 +4,7 @@ module RuboCop module Performance # This module holds the RuboCop Performance version information. module Version - STRING = '1.23.1' + STRING = '1.24.0' def self.document_version STRING.match('\d+\.\d+').to_s diff --git a/relnotes/v1.24.0.md b/relnotes/v1.24.0.md new file mode 100644 index 0000000000..2c9a4586c3 --- /dev/null +++ b/relnotes/v1.24.0.md @@ -0,0 +1,12 @@ +### New features + +* [#490](https://github.com/rubocop/rubocop-performance/pull/490): Pluginfy RuboCop Performance. ([@koic][]) +* [#462](https://github.com/rubocop/rubocop-performance/pull/462): Add new `Performance/ZipWithoutBlock` cop that checks patterns like `.map { |id| [id] }` or `.map { [_1] }` and can replace them with `.zip`. ([@corsonknowles][]) + +### Bug fixes + +* [#484](https://github.com/rubocop/rubocop-performance/pull/484): Fix `Performance/CaseWhenSplat` cop error on `when` node without body. ([@viralpraxis][]) + +[@koic]: https://github.com/koic +[@corsonknowles]: https://github.com/corsonknowles +[@viralpraxis]: https://github.com/viralpraxis diff --git a/rubocop-performance.gemspec b/rubocop-performance.gemspec index 4c6e2025f4..e7c2ebd7e3 100644 --- a/rubocop-performance.gemspec +++ b/rubocop-performance.gemspec @@ -27,9 +27,11 @@ Gem::Specification.new do |s| 'source_code_uri' => 'https://github.com/rubocop/rubocop-performance/', 'documentation_uri' => "https://docs.rubocop.org/rubocop-performance/#{RuboCop::Performance::Version.document_version}/", 'bug_tracker_uri' => 'https://github.com/rubocop/rubocop-performance/issues', - 'rubygems_mfa_required' => 'true' + 'rubygems_mfa_required' => 'true', + 'default_lint_roller_plugin' => 'RuboCop::Performance::Plugin' } - s.add_dependency('rubocop', '>= 1.48.1', '< 2.0') - s.add_dependency('rubocop-ast', '>= 1.31.1', '< 2.0') + s.add_dependency('lint_roller', '~> 1.1') + s.add_dependency('rubocop', '>= 1.72.1', '< 2.0') + s.add_dependency('rubocop-ast', '>= 1.38.0', '< 2.0') end diff --git a/spec/rubocop/cop/performance/case_when_splat_spec.rb b/spec/rubocop/cop/performance/case_when_splat_spec.rb index 4b25112474..99961b053f 100644 --- a/spec/rubocop/cop/performance/case_when_splat_spec.rb +++ b/spec/rubocop/cop/performance/case_when_splat_spec.rb @@ -54,6 +54,31 @@ RUBY end + it 'registers an offense for case when with a splat in the first condition with branch without body' do + expect_offense(<<~RUBY) + case foo + when *cond + ^^^^^^^^^^ Reordering `when` conditions with a splat to the end of the `when` branches can improve performance. + # bar + # bar + # bar + when 3 + baz + end + RUBY + + expect_correction(<<~RUBY) + case foo + when 3 + baz + when *cond + # bar + # bar + # bar + end + RUBY + end + it 'registers an offense for case when with a splat without an else' do expect_offense(<<~RUBY) case foo diff --git a/spec/rubocop/cop/performance/sum_spec.rb b/spec/rubocop/cop/performance/sum_spec.rb index fdbf2b76a8..987d69f3bd 100644 --- a/spec/rubocop/cop/performance/sum_spec.rb +++ b/spec/rubocop/cop/performance/sum_spec.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::Performance::Sum, :config do + let(:cop_config) { { 'SafeAutoCorrect' => false, 'OnlySumOrWithInitialValue' => false } } + %i[inject reduce].each do |method| it "registers an offense and corrects when using `array.#{method}(10, :+)`" do expect_offense(<<~RUBY, method: method) diff --git a/spec/rubocop/cop/performance/zip_without_block_spec.rb b/spec/rubocop/cop/performance/zip_without_block_spec.rb new file mode 100644 index 0000000000..f750b4ff06 --- /dev/null +++ b/spec/rubocop/cop/performance/zip_without_block_spec.rb @@ -0,0 +1,389 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Performance::ZipWithoutBlock, :config do + context 'when using map with array literal' do + it 'registers an offense and corrects to use zip with no arguments' do + expect_offense(<<~RUBY) + [1, 2, 3].map { |id| [id] } + ^^^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3].zip + RUBY + end + end + + context 'when using map with a short iterator name' do + it 'registers an offense and corrects to use zip with no arguments' do + expect_offense(<<~RUBY) + [1, 2, 3].map { |e| [e] } + ^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3].zip + RUBY + end + end + + context 'when using map on a range with another iterator name' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + (1..3).map { |x| [x] } + ^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + (1..3).zip + RUBY + end + end + + context 'when using map in a do end block' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + (a..b).map do + ^^^^^^ Use `zip` without a block argument instead. + |m| [m] + end + RUBY + + expect_correction(<<~RUBY) + (a..b).zip + RUBY + end + end + + context 'with a safe operator' do + context 'when using map with array literal' do + it 'registers an offense and corrects to use zip with no arguments' do + expect_offense(<<~RUBY) + [1, 2, 3]&.map { |id| [id] } + ^^^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3]&.zip + RUBY + end + end + + context 'when using map with a short iterator name' do + it 'registers an offense and corrects to use zip with no arguments' do + expect_offense(<<~RUBY) + [1, 2, 3]&.map { |e| [e] } + ^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3]&.zip + RUBY + end + end + + context 'when using map on a range with another iterator name' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + (1..3)&.map { |x| [x] } + ^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + (1..3)&.zip + RUBY + end + end + + context 'when using map in a do end block' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + (a..b)&.map do + ^^^^^^ Use `zip` without a block argument instead. + |m| [m] + end + RUBY + + expect_correction(<<~RUBY) + (a..b)&.zip + RUBY + end + end + end + + context 'when using map in a chain' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + [nil, tuple].flatten.map { |e| [e] }.call + ^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [nil, tuple].flatten.zip.call + RUBY + end + end + + context 'when the map block does not contain an array literal' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |id| id } + RUBY + end + end + + context 'when using map with an array literal containing multiple elements' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |id| [id, id] } + RUBY + end + end + + context 'when using other iterators such as' do + context 'when using collect' do + it 'registers an offense as collect is an alias of map' do + expect_offense(<<~RUBY) + [1, 2, 3].collect { |id| [id] } + ^^^^^^^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3].zip + RUBY + end + + it 'registers an offense for collect with a numblock', :ruby27 do + expect_offense(<<~RUBY) + [1, 2, 3].collect { [_1] } + ^^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3].zip + RUBY + end + end + + context 'when using select with an array literal' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].select { |id| [id] } + RUBY + end + end + + context 'when calling filter_map' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].filter_map {|id| [id]} + RUBY + end + end + + context 'when calling flat_map' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].flat_map {|id| [id]} + RUBY + end + end + end + + context 'when using map with doubly wrapped arrays' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |id| [[id]] } + RUBY + end + end + + context 'when using map with addition' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |id| id + 1 } + RUBY + end + end + + context 'when using map with array addition' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |id| [id] + [id] } + RUBY + end + end + + context 'when using map with indexing into an array' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |id| [id][id] } + RUBY + end + end + + context 'when calling map with no arguments i.e. no parent' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map + RUBY + end + end + + context 'when calling map with an empty block' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map {} + RUBY + end + end + + context 'with related array of array patterns' do + context 'when using [*foo] to dynamically wrap only non-arrays in the list' do + it 'does not register an offense since the map is doing useful work' do + expect_no_offenses(<<~RUBY) + [1, [2], 3].map { |id| [*id] } + RUBY + end + end + + context 'when using Array.wrap the Rails extension of the [*foo] pattern that handles Hashes' do + it 'does not register an offense since the map is doing useful work' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |id| Array.wrap(id) } + RUBY + end + end + + context 'when making an array of arrays using each_with_object' do + it 'does not register an offense since we have not included this pattern yet' do + expect_no_offenses(<<~RUBY) + [1,2,3].each_with_object([]) {|id, object| object << [id]} + RUBY + end + end + end + + context 'with a numblock', :ruby27 do + context 'when using map with a numerical argument' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + [1, 2, 3].map { [_1] } + ^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3].zip + RUBY + end + end + + context 'when using map with a numblock in a chain' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + [1, 2].sum.map { [_1] }.flatten + ^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + [1, 2].sum.zip.flatten + RUBY + end + end + + context 'when using map on a range with a numblock' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + (1..3).map { [_1] } + ^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + (1..3).zip + RUBY + end + end + + context 'when using map in a do end block with a numblock' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + (a..b).map do [_1] end + ^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + (a..b).zip + RUBY + end + end + + context 'when calling filter_map with a numblock' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].filter_map { [_1] } + RUBY + end + end + + context 'when calling map, adding, and wrapping, with a numblock' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { [_1 + 1] } + RUBY + end + end + + context 'when calling double wrapping with a numblock' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { [[_1]] } + RUBY + end + end + end + + context 'when calling map with an unused iterator' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { |e| [1] } + RUBY + end + end + + context 'when calling map with a static block that always returns the same value' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { [id] } + RUBY + end + end + + context 'when calling map with a static array' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + [1, 2, 3].map { [] } + RUBY + end + end + + context 'when map has no receiver' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + map { |id| [id] } + RUBY + end + end + + context 'when map has an indeterminate object as a receiver' do + it 'still registers an offense' do + expect_offense(<<~RUBY) + foo.map { |id| [id] } + ^^^^^^^^^^^^^^^^^ Use `zip` without a block argument instead. + RUBY + + expect_correction(<<~RUBY) + foo.zip + RUBY + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3e67f78a51..cd49bf7ca8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,8 +9,6 @@ end RSpec.configure do |config| - config.include RuboCop::RSpec::ExpectOffense - config.shared_context_metadata_behavior = :apply_to_host_groups config.filter_run_when_matching :focus config.filter_run_excluding broken_on: :prism if ENV['PARSER_ENGINE'] == 'parser_prism' diff --git a/tasks/cops_documentation.rake b/tasks/cops_documentation.rake index 677bbfb940..52e29f1be7 100644 --- a/tasks/cops_documentation.rake +++ b/tasks/cops_documentation.rake @@ -15,7 +15,7 @@ task update_cops_documentation: :yard_for_generate_documentation do # NOTE: Update `<>` version for docs/modules/ROOT/pages/cops_performance.adoc # when running release tasks. - RuboCop::Performance::Inject.defaults! + RuboCop::ConfigLoader.inject_defaults!("#{__dir__}/../config/default.yml") CopsDocumentationGenerator.new(departments: deps).call end pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy