diff --git a/CHANGELOG.md b/CHANGELOG.md index 46db90c86522..75e5f8f227e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5366,3 +5366,4 @@ [@ohbarye]: https://github.com/ohbarye [@magneland]: https://github.com/magneland [@k-karen]: https://github.com/k-karen +[@uplus]: https://github.com/uplus diff --git a/changelog/new_add_new_cop_lintorassignmenttoconstant.md b/changelog/new_add_new_cop_lintorassignmenttoconstant.md new file mode 100644 index 000000000000..15cd930c6d9c --- /dev/null +++ b/changelog/new_add_new_cop_lintorassignmenttoconstant.md @@ -0,0 +1 @@ +* [#9363](https://github.com/rubocop-hq/rubocop/pull/9363): Add new cop `Lint/OrAssignmentToConstant`. ([@uplus][]) diff --git a/config/default.yml b/config/default.yml index 5f56c66b55ae..f54a327725b8 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1811,6 +1811,12 @@ Lint/NumberedParameterAssignment: Enabled: pending VersionAdded: '<>' +Lint/OrAssignmentToConstant: + Description: 'Checks unintended or-assignment to constant.' + Enabled: pending + Safe: false + VersionAdded: '<>' + Lint/OrderedMagicComments: Description: 'Checks the proper ordering of magic comments and whether a magic comment is not placed before a shebang.' Enabled: true diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 75ef2e0476a2..407f37b4e58a 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -313,6 +313,7 @@ require_relative 'rubocop/cop/lint/non_local_exit_from_iterator' require_relative 'rubocop/cop/lint/number_conversion' require_relative 'rubocop/cop/lint/numbered_parameter_assignment' +require_relative 'rubocop/cop/lint/or_assignment_to_constant' require_relative 'rubocop/cop/lint/ordered_magic_comments' require_relative 'rubocop/cop/lint/out_of_range_regexp_ref' require_relative 'rubocop/cop/lint/parentheses_as_grouped_expression' diff --git a/lib/rubocop/cop/lint/or_assignment_to_constant.rb b/lib/rubocop/cop/lint/or_assignment_to_constant.rb new file mode 100644 index 000000000000..3681ae867062 --- /dev/null +++ b/lib/rubocop/cop/lint/or_assignment_to_constant.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Lint + # This cop checks for unintended or-assignment to a constant. + # + # Constants should always be assigned in the same location. And its value + # should always be the same. If constants are assigned in multiple + # locations, the result may vary depending on the order of `require`. + # + # Also, if you already have such an implementation, auto-correction may + # change the result. + # + # @example + # + # # bad + # CONST ||= 1 + # + # # good + # CONST = 1 + # + class OrAssignmentToConstant < Base + extend AutoCorrector + + MSG = 'Avoid using or-assignment with constants.' + + def on_or_asgn(node) + lhs, _rhs = *node + return unless lhs&.casgn_type? + + add_offense(node.loc.operator) do |corrector| + corrector.replace(node.loc.operator, '=') + end + end + end + end + end +end diff --git a/spec/rubocop/cop/lint/or_assignment_to_constant_spec.rb b/spec/rubocop/cop/lint/or_assignment_to_constant_spec.rb new file mode 100644 index 000000000000..cd15d6a10ef1 --- /dev/null +++ b/spec/rubocop/cop/lint/or_assignment_to_constant_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Lint::OrAssignmentToConstant, :config do + subject(:cop) { described_class.new(config) } + + it 'registers an offense with or-assignment to a constant' do + expect_offense(<<~RUBY) + CONST ||= 1 + ^^^ Avoid using or-assignment with constants. + RUBY + + expect_correction(<<~RUBY) + CONST = 1 + RUBY + end + + it 'does not register an offense with plain assignment to a constant' do + expect_no_offenses(<<~RUBY) + CONST = 1 + RUBY + end + + [ + ['a local variable', 'var'], + ['an instance variable', '@var'], + ['a class variable', '@@var'], + ['a global variable', '$var'], + ['an attribute', 'self.var'] + ].each do |type, var| + it "does not register an offense with or-assignment to #{type}" do + expect_no_offenses(<<~RUBY) + #{var} ||= 1 + RUBY + end + end +end