123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::MutableConstant

Relationships & Source Files
Namespace Children
Modules:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::AutoCorrector, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/style/mutable_constant.rb

Overview

Checks whether some constant value isn’t a mutable literal (e.g. array or hash).

Strict mode can be used to freeze all constants, rather than just literals. Strict mode is considered an experimental feature. It has not been updated with an exhaustive list of all methods that will produce frozen objects so there is a decent chance of getting some false positives. Luckily, there is no harm in freezing an already frozen object.

From Ruby 3.0, this cop honours the magic comment 'shareable_constant_value'. When this magic comment is set to any acceptable value other than none, it will suppress the offenses raised by this cop. It enforces frozen state.

Note
Regexp and Range literals are frozen objects since Ruby 3.0.
Note
From Ruby 3.0, interpolated strings are not frozen when # frozen-string-literal: true is used, so this cop enforces explicit freezing for such strings.
Note
From Ruby 3.0, this cop allows explicit freezing of constants when the shareable_constant_value directive is used.

Examples:

EnforcedStyle: literals (default)

# bad
CONST = [1, 2, 3]

# good
CONST = [1, 2, 3].freeze

# good
CONST = <<~TESTING.freeze
  This is a heredoc
TESTING

# good
CONST = Something.new

EnforcedStyle: strict

# bad
CONST = Something.new

# bad
CONST = Struct.new do
  def foo
    puts 1
  end
end

# good
CONST = Something.new.freeze

# good
CONST = Struct.new do
  def foo
    puts 1
  end
end.freeze
# Magic comment - shareable_constant_value: literal

# bad
CONST = [1, 2, 3]

# good
# shareable_constant_value: literal
CONST = [1, 2, 3]

Cop Safety Information:

  • This cop’s autocorrection is unsafe since any mutations on objects that are made frozen will change from being accepted to raising FrozenError, and will need to be manually refactored.

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::FrozenStringLiteral - Included

FROZEN_STRING_LITERAL, FROZEN_STRING_LITERAL_ENABLED, FROZEN_STRING_LITERAL_TYPES_RUBY27

::RuboCop::Cop::ConfigurableEnforcedStyle - Included

SYMBOL_TO_STRING_CACHE

Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns a url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::ConfigurableEnforcedStyle - Included

::RuboCop::Cop::FrozenStringLiteral - Included

ShareableConstantValue - Included

#magic_comment_in_scope

Identifies the most recent magic comment with valid shareable constant values that’s in scope for this node.

#processed_source_till_node, #recent_shareable_value?, #shareable_constant_value_enabled?

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?,
#target_gem_version

Returns a gems locked versions (i.e.

#target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Instance Method Details

#autocorrect(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 168

def autocorrect(corrector, node)
  expr = node.source_range

  splat_value = splat_value(node)
  if splat_value
    correct_splat_expansion(corrector, expr, splat_value)
  elsif node.array_type? && !node.bracketed?
    corrector.wrap(expr, '[', ']')
  elsif requires_parentheses?(node)
    corrector.wrap(expr, '(', ')')
  end

  corrector.insert_after(expr, '.freeze')
end

#check(value) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 157

def check(value)
  range_enclosed_in_parentheses = range_enclosed_in_parentheses?(value)
  return unless mutable_literal?(value) ||
                (target_ruby_version <= 2.7 && range_enclosed_in_parentheses)

  return if frozen_string_literal?(value)
  return if shareable_constant_value?(value)

  add_offense(value) { |corrector| autocorrect(corrector, value) }
end

#correct_splat_expansion(corrector, expr, splat_value) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 207

def correct_splat_expansion(corrector, expr, splat_value)
  if range_enclosed_in_parentheses?(splat_value)
    corrector.replace(expr, "#{splat_value.source}.to_a")
  else
    corrector.replace(expr, "(#{splat_value.source}).to_a")
  end
end

#frozen_regexp_or_range_literals?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 199

def frozen_regexp_or_range_literals?(node)
  target_ruby_version >= 3.0 && (node.regexp_type? || node.range_type?)
end

#immutable_literal?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 189

def immutable_literal?(node)
  frozen_regexp_or_range_literals?(node) || node.immutable_literal?
end

#mutable_literal?(value) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 183

def mutable_literal?(value)
  return false if frozen_regexp_or_range_literals?(value)

  value.mutable_literal?
end

#on_assignment(value) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 140

def on_assignment(value)
  if style == :strict
    strict_check(value)
  else
    check(value)
  end
end

#on_casgn(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 127

def on_casgn(node)
  if node.expression.nil? # This is only the case for `CONST += ...` or similarg66
    parent = node.parent
    return unless parent.or_asgn_type? # We only care about `CONST ||= ...`

    on_assignment(parent.children.last)
  else
    on_assignment(node.expression)
  end
end

#operation_produces_immutable_object?(node) (private)

Some of these patterns may not actually return an immutable object, but we want to consider them immutable for this cop.

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 223

def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
  {
    (const _ _)
    (send (const {nil? cbase} :Struct) :new ...)
    (block (send (const {nil? cbase} :Struct) :new ...) ...)
    (send _ :freeze)
    (send {float int} {:+ :- :* :** :/ :% :<<} _)
    (send _ {:+ :- :* :** :/ :%} {float int})
    (send _ {:== :=== :!= :<= :>= :< :>} _)
    (send (const {nil? cbase} :ENV) :[] _)
    (or (send (const {nil? cbase} :ENV) :[] _) _)
    (send _ {:count :length :size} ...)
    (block (send _ {:count :length :size} ...) ...)
  }
PATTERN

#range_enclosed_in_parentheses?(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 240

def_node_matcher :range_enclosed_in_parentheses?, <<~PATTERN
  (begin ({irange erange} _ _))
PATTERN

#requires_parentheses?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 203

def requires_parentheses?(node)
  node.range_type? || (node.send_type? && node.loc.dot.nil?)
end

#shareable_constant_value?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 193

def shareable_constant_value?(node)
  return false if target_ruby_version < 3.0

  recent_shareable_value? node
end

#splat_value(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 216

def_node_matcher :splat_value, <<~PATTERN
  (array (splat $_))
PATTERN

#strict_check(value) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 148

def strict_check(value)
  return if immutable_literal?(value)
  return if operation_produces_immutable_object?(value)
  return if frozen_string_literal?(value)
  return if shareable_constant_value?(value)

  add_offense(value) { |corrector| autocorrect(corrector, value) }
end