Add an EnforcedStyle to Style/MutableConstant that forbids .freeze on literals
Before submitting
- I have searched the existing issues and this hasn't been requested yet.
Cop name
Style/MutableConstant
Is your feature request related to a problem?
Style/MutableConstant can only ask for more freezing (literals, strict, Recursive). There is no way to enforce the opposite convention.
Some codebases treat the constant name itself as the contract. Ruby already warns on reassignment (warning: already initialized constant FOO) and in-place mutation of a constant is a bug in those codebases whether or not .freeze is present. For them .freeze on a literal is ceremony with no runtime effect they rely on, in the same category as trailing commas, semicolons or parentheses around arguments: a matter of taste that RuboCop lets each project pick a side on and then enforces consistently.
Describe the solution you'd like
A third EnforcedStyle value for Style/MutableConstant, tentatively unfrozen_literals:
- Registers an offense for
.freezecalled on a mutable literal (array, hash, string, heredoc) assigned to a constant and autocorrects by removing it. - Leaves
.freezeon non-literal values alone (Something.new.freeze,OTHER_CONST.freeze), since that cannot be expressed any other way. - Honours
Recursive: trueby also reporting nested frozen literals. - Does not change the default style.
This mirrors how other Style cops already offer both directions of a preference, for example Style/FrozenStringLiteralComment with always and never or Style/TrailingCommaInArrayLiteral with comma and no_comma.
Code examples
# EnforcedStyle: unfrozen_literals
# bad
CONST = [1, 2, 3].freeze
CONST = 'str'.freeze
CONST = { a: 1 }.freeze
# good
CONST = [1, 2, 3]
CONST = 'str'
CONST = { a: 1 }
# good - freezing a non-literal value is still allowed
CONST = Something.new.freezeDescribe alternatives you've considered
Enabled: false, which stops the cop from asking for.freezebut cannot enforce the no-freeze convention or autocorrect toward it..freezekeeps creeping back through habit and has to be caught in code review by hand.- A custom cop, which every such project has to write and maintain separately.
Style/RedundantFreeze does not cover this. It removes .freeze from objects that are immutable anyway (integers, symbols, regexps). .freeze on an array, hash or string literal is not redundant in that sense, so nothing flags it.
Additional context
Edit: I have fast-forwarded and opened the PR - https://github.com/rubocop/rubocop/pull/15727.
I have a branch with the implementation, specs, a CLI spec proving it converges with Style/RedundantFreeze in one -A pass. I run it against an 8800-file Rails monolith (161 offenses in 88 files, all autocorrected, every changed line differing only by the removed .freeze). Happy to open the PR if the idea is welcome and open to a different name for the style.
Source: rubocop/rubocop