diff --git a/changelog/new_add_rails_top_level_hash_with_indifferent_access.md b/changelog/new_add_rails_top_level_hash_with_indifferent_access.md new file mode 100644 index 0000000000..93d6d48dd2 --- /dev/null +++ b/changelog/new_add_rails_top_level_hash_with_indifferent_access.md @@ -0,0 +1 @@ +* [#752](https://github.com/rubocop/rubocop-rails/pull/752): Add `Rails/TopLevelHashWithIndifferentAccess` cop. ([@r7kamura][]) diff --git a/config/default.yml b/config/default.yml index b693a86b67..6150915d27 100644 --- a/config/default.yml +++ b/config/default.yml @@ -958,6 +958,12 @@ Rails/ToSWithArgument: Safe: false VersionAdded: '<>' +Rails/TopLevelHashWithIndifferentAccess: + Description: 'Identifies top-level `HashWithIndifferentAccess`.' + Reference: 'https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#top-level-hashwithindifferentaccess-is-soft-deprecated' + Enabled: pending + VersionAdded: '<>' + Rails/TransactionExitStatement: Description: 'Avoid the usage of `return`, `break` and `throw` in transaction blocks.' Enabled: pending diff --git a/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb b/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb new file mode 100644 index 0000000000..2d5ca04db6 --- /dev/null +++ b/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # Identifies top-level `HashWithIndifferentAccess`. + # This has been soft-deprecated since Rails 5.1. + # + # @example + # # bad + # HashWithIndifferentAccess.new(foo: 'bar') + # + # # good + # ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') + # + class TopLevelHashWithIndifferentAccess < Base + extend AutoCorrector + extend TargetRailsVersion + + minimum_target_rails_version 5.1 + + MSG = 'Avoid top-level `HashWithIndifferentAccess`.' + + # @!method top_level_hash_with_indifferent_access?(node) + # @param [RuboCop::AST::ConstNode] node + # @return [Boolean] + def_node_matcher :top_level_hash_with_indifferent_access?, <<~PATTERN + (const {nil? cbase} :HashWithIndifferentAccess) + PATTERN + + # @param [RuboCop::AST::ConstNode] node + def on_const(node) + return unless top_level_hash_with_indifferent_access?(node) + + add_offense(node) do |corrector| + autocorrect(corrector, node) + end + end + + private + + def autocorrect(corrector, node) + corrector.insert_before(node.location.name, 'ActiveSupport::') + end + end + end + end +end diff --git a/lib/rubocop/cop/rails_cops.rb b/lib/rubocop/cop/rails_cops.rb index bfaedc72ee..14dcb13b30 100644 --- a/lib/rubocop/cop/rails_cops.rb +++ b/lib/rubocop/cop/rails_cops.rb @@ -114,6 +114,7 @@ require_relative 'rails/time_zone_assignment' require_relative 'rails/to_formatted_s' require_relative 'rails/to_s_with_argument' +require_relative 'rails/top_level_hash_with_indifferent_access' require_relative 'rails/transaction_exit_statement' require_relative 'rails/uniq_before_pluck' require_relative 'rails/unique_validation_without_index' diff --git a/spec/rubocop/cop/rails/top_level_hash_with_indifferent_access_spec.rb b/spec/rubocop/cop/rails/top_level_hash_with_indifferent_access_spec.rb new file mode 100644 index 0000000000..d335a7d68a --- /dev/null +++ b/spec/rubocop/cop/rails/top_level_hash_with_indifferent_access_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Rails::TopLevelHashWithIndifferentAccess, :config, :rails51 do + context 'with top-level HashWithIndifferentAccess' do + it 'registers and corrects an offense' do + expect_offense(<<~RUBY) + HashWithIndifferentAccess.new(foo: 'bar') + ^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid top-level `HashWithIndifferentAccess`. + RUBY + + expect_correction(<<~RUBY) + ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') + RUBY + end + end + + context 'with top-level ::HashWithIndifferentAccess' do + it 'registers and corrects an offense' do + expect_offense(<<~RUBY) + ::HashWithIndifferentAccess.new(foo: 'bar') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid top-level `HashWithIndifferentAccess`. + RUBY + + expect_correction(<<~RUBY) + ::ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') + RUBY + end + end + + context 'with ActiveSupport::HashWithIndifferentAccess' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar') + RUBY + end + end + + context 'with ActiveSupport::HashWithIndifferentAccess on Rails 5.0', :rails50 do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + HashWithIndifferentAccess.new(foo: 'bar') + RUBY + end + end +end