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).
When the Recursive option is enabled, mutable literals nested inside
arrays and hashes are also frozen, so an offense on the outermost
unfrozen literal will autocorrect every nested mutable literal as well.
When the outer literal already has .freeze appended, the cop descends
into it and reports each outermost unfrozen literal underneath. The
option is disabled by default to preserve existing behavior; opt in to
get strict nested freezing.
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.
|
Constant Summary
-
MSG =
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 144'Freeze mutable objects assigned to constants.'
::RuboCop::Cop::Base - Inherited
EMPTY_OFFENSES, RESTRICT_ON_SEND
::RuboCop::Cop::FrozenStringLiteral - Included
FROZEN_STRING_LITERAL_ENABLED, FROZEN_STRING_LITERAL_TYPES_RUBY27
::RuboCop::Cop::ConfigurableEnforcedStyle - Included
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 |
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
- #recursive? ⇒ Boolean readonly private
::RuboCop::Cop::ConfigurableEnforcedStyle - Included
::RuboCop::Cop::Base - Inherited
::RuboCop::Cop::AutocorrectLogic - Included
Instance Method Summary
- #on_casgn(node)
- #autocorrect(corrector, node) private
- #correct_splat_expansion(corrector, expr, splat_value) private
- #explicitly_frozen_literal?(node) ⇒ Boolean private
- #freezable_nested_literal?(node) ⇒ Boolean private
-
#freeze_nested_literals(corrector, node)
private
Recursively freezes every nested mutable literal inside an array or hash literal.
- #frozen_regexp_or_range_literals?(node) ⇒ Boolean private
- #immutable_literal?(node) ⇒ Boolean private
- #literal_check(value) private
-
#literal_children(node)
private
Returns the child literals of an array or hash node that may themselves need freezing.
- #mutable_literal?(value) ⇒ Boolean private
- #mutable_nodes(value, &block) private
- #mutable_or_unfrozen_range?(value) ⇒ Boolean private
- #on_assignment(value) private
-
#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.
- #range_enclosed_in_parentheses?(node) private
- #requires_parentheses?(node) ⇒ Boolean private
- #shareable_constant_value?(node) ⇒ Boolean private
- #splat_value(node) private
- #strict_check(value) private
::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 |
| #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, #matches_absolute_include_pattern?, #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 Attribute Details
#recursive? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 273
def recursive? cop_config.fetch('Recursive', false) end
Instance Method Details
#autocorrect(corrector, node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 209
def autocorrect(corrector, node) expr = node.source_range splat_value = splat_value(node) if splat_value correct_splat_expansion(corrector, expr, splat_value) corrector.insert_after(expr, '.freeze') return end if node.array_type? && !node.bracketed? corrector.wrap(expr, '[', ']') elsif requires_parentheses?(node) corrector.wrap(expr, '(', ')') end corrector.insert_after(expr, '.freeze') freeze_nested_literals(corrector, node) if recursive? end
#correct_splat_expansion(corrector, expr, splat_value) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 301
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
#explicitly_frozen_literal?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 267
def explicitly_frozen_literal?(node) return false unless node.send_type? && node.method?(:freeze) node.receiver && mutable_literal?(node.receiver) end
#freezable_nested_literal?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 243
def freezable_nested_literal?(node) return false if frozen_string_literal?(node) return false if shareable_constant_value?(node) mutable_literal?(node) end
#freeze_nested_literals(corrector, node) (private)
Recursively freezes every nested mutable literal inside an array or hash literal. Already-frozen subtrees are not re-frozen, but their children are still inspected for unfrozen literals deeper down.
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 233
def freeze_nested_literals(corrector, node) literal_children(node).each do |child| if explicitly_frozen_literal?(child) freeze_nested_literals(corrector, child.receiver) elsif freezable_nested_literal?(child) autocorrect(corrector, child) end end end
#frozen_regexp_or_range_literals?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 293
def frozen_regexp_or_range_literals?(node) target_ruby_version >= 3.0 && node.type?(:regexp, :range) end
#immutable_literal?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 283
def immutable_literal?(node) frozen_regexp_or_range_literals?(node) || node.immutable_literal? end
#literal_check(value) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 196
def literal_check(value) return unless mutable_or_unfrozen_range?(value) return if frozen_string_literal?(value) return if shareable_constant_value?(value) true end
#literal_children(node) (private)
Returns the child literals of an array or hash node that may
themselves need freezing. For hashes, both keys and values are
included. Percent-literal arrays (e.g. %w(a b)) are skipped because
.freeze cannot be appended to their contents.
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 254
def literal_children(node) case node.type when :array return [] if node.percent_literal? node.children when :hash node.children.flat_map { |child| child.pair_type? ? child.children : [] } else [] end end
#mutable_literal?(value) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 277
def mutable_literal?(value) return false if frozen_regexp_or_range_literals?(value) value.mutable_literal? end
#mutable_nodes(value, &block) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 173
def mutable_nodes(value, &block) if recursive? && explicitly_frozen_literal?(value) literal_children(value.receiver).flat_map { |c| mutable_nodes(c, &block) } else node_offending = yield(value) if node_offending [value] else [] end end end
#mutable_or_unfrozen_range?(value) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 204
def mutable_or_unfrozen_range?(value) mutable_literal?(value) || (target_ruby_version <= 2.7 && range_enclosed_in_parentheses?(value)) end
#on_assignment(value) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 159
def on_assignment(value) nodes = mutable_nodes(value) do |node| if style == :strict strict_check(node) else literal_check(node) end end nodes.each do |node| add_offense(node) { |corrector| autocorrect(corrector, node) } end end
#on_casgn(node)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 146
def on_casgn(node) if node.expression.nil? # This is only the case for `CONST += ...` or similar 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.
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 317
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 334
def_node_matcher :range_enclosed_in_parentheses?, <<~PATTERN (begin (range _ _)) PATTERN
#requires_parentheses?(node) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/mutable_constant.rb', line 297
def requires_parentheses?(node) node.range_type? || (node.send_type? && node.loc.dot.nil?) end
#splat_value(node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 310
def_node_matcher :splat_value, <<~PATTERN (array (splat $_)) PATTERN
#strict_check(value) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/mutable_constant.rb', line 187
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) true end