Skip to content

Enable Style/FrozenStringLiteralComment - #39

Merged
dduugg merged 1 commit into
mainfrom
enable-frozen-string-literal-comment
Sep 26, 2026
Merged

dduugg merged 1 commit into
mainfrom
enable-frozen-string-literal-comment

Conversation

@dduugg

@dduugg dduugg commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Why

With # frozen_string_literal: true, an accidental in-place mutation of a string literal raises FrozenError right where it happens. Without it, the mutation silently changes shared state. The pragma also lets Ruby deduplicate literals, so the same literal is not allocated again on every evaluation. Ruby 3.4+ already warns about mutating chilled literals, and this change settles the question for this gem ahead of that default flipping.

Config change

.rubocop.yml: removed the block that disabled the cop, together with its comment ("Disabling for now until it's clearer why we want this"):

Style/FrozenStringLiteralComment:
  Enabled: false

The cop now runs with its default EnforcedStyle: always. No Exclude entries were needed.

Files that got the pragma

The pragma was added to 8 files: Gemfile, Rakefile, packs-specification.gemspec, lib/packs-specification.rb, lib/packs/pack.rb, lib/packs/specification.rb, lib/packs/specification/configuration.rb and lib/packs/rspec/support.rb. bin/*, spec/* and lib/packs/rspec/fixture_helper.rb already had it.

  • In files with a Sorbet sigil, # typed: stays on line 1 and the pragma goes on line 2, the same layout fixture_helper.rb already uses. I confirmed Ruby honors the pragma on line 2 on 3.3, 3.4 and 4.0.
  • There are no exclusions. sorbet/rbi/todo.rbi is generated, has no string literals, and rubocop doesn't inspect it, so it was left alone.

Runtime fixes

None were needed. No spec or dynamic check found any code that mutates a literal that is now frozen.

The only code change besides the pragmas:

  • lib/packs-specification.rb:14: dropped the now-redundant .freeze from PACKAGE_FILE = T.let('package.yml', String), which the pragma makes Style/RedundantFreeze. The constant is still frozen (checked Packs::PACKAGE_FILE.frozen? on 3.3, 3.4 and 4.0).

One visible behavior change, left in on purpose:

  • lib/packs/specification/configuration.rb:30: DEFAULT_PACK_PATHS.dup is a shallow copy. So when there is no packs.yml, or it has no pack_paths key, Configuration.fetch.pack_paths now returns frozen strings.
  • They are the same objects as the elements of DEFAULT_PACK_PATHS, and that was already true on main. So on main, a caller that mutated one would have silently corrupted the shared default. Now that raises FrozenError instead.
  • No spec covers mutating these strings. I checked every consumer I could find (packs, packs-rails, rubocop-packs, pack_stats, query_packwerk, danger-packwerk, visualize_packs), and none of them mutate them. packs only passes them to File.fnmatch, Dir.glob and interpolation.
  • Paths read from packs.yml still come back unfrozen, as before.

Verification

  • Specs:
    • Baseline on main (Ruby 4.0.5): 28 examples, 0 failures.
    • After the change: 28/0 on Ruby 4.0.5, 27/0 on 3.3.11 and 27/0 on 3.4.11. One example only runs on Ruby >= 4. The 3.x runs also exercise the custom Pack#inspect.
    • All of these runs used RUBYOPT=-W:deprecated and printed no chilled-string warnings.
    • I also ran the whole suite under RUBYOPT=--enable-frozen-string-literal with a TracePoint(:raise) hook that records every FrozenError, including rescued ones. It recorded none.
  • rubocop: the cop and Style/RedundantFreeze are clean. The full run reports the same 6 pre-existing offenses in 3 files as main (Naming/PredicatePrefix on is_gem? and related directive cops, Style/AccessModifierDeclarations, Layout/EmptyLinesAfterModuleInclusion, Style/RegexpLiteral). None of them come from this change, and CI doesn't run rubocop.
  • srb tc: "No errors!"
  • Static sweep: I read every inspected file for mutating calls (<<, concat, bang methods, []=, force_encoding, replace, buffer arguments, StringIO) and traced every string literal to where it is used, including library calls (Pathname, Dir.mktmpdir, YAML, sorbet-runtime T::Struct, RubyGems setters, Bundler DSL). Nothing mutates a literal.
  • Dynamic checks:
    • Line, branch and method coverage showed which paths the specs miss: skip_chdir_to_tmpdir, delete_app_file, the default write_file content, and for_file given a frozen String or a Pathname. I ran each of those directly.
    • I compared the frozenness of every public return value between main and this branch on 3.3, 3.4 and 4.0. The only difference is the pack_paths change described above.
    • Smoke tests: gem build, rake build, rake -T, Gem::Specification.load + validate, bin/rubocop --version, and piped bin/console.
    • Downstream spec suites run against this branch's lib: packs (119), packs-rails (7), rubocop-packs (90), pack_stats (19), query_packwerk (60), danger-packwerk (67) and visualize_packs (181). All passed with no failures.
  • Fresh Eyes (local pre-push review against origin/main): 0 blockers, 0 major, 0 minor, 0 info.

Remove the .rubocop.yml entry that disabled Style/FrozenStringLiteralComment
so the cop runs with its default EnforcedStyle (always).

Add "# frozen_string_literal: true" to the 8 files the cop flagged: Gemfile,
Rakefile, packs-specification.gemspec, lib/packs-specification.rb,
lib/packs/pack.rb, lib/packs/rspec/support.rb, lib/packs/specification.rb and
lib/packs/specification/configuration.rb. In files with a Sorbet sigil the
pragma goes on the line after "# typed:", matching
lib/packs/rspec/fixture_helper.rb.

Drop the now-redundant .freeze on Packs::PACKAGE_FILE (Style/RedundantFreeze).
The constant is still frozen via the pragma.

No mutation fixes were needed. Specs pass on Ruby 3.3, 3.4 and 4.0 with
-W:deprecated and produce no chilled-string warnings. A static sweep of lib/,
bin/, spec/, the Gemfile, the Rakefile and the gemspec found no in-place
mutation of string literals.
@dduugg
dduugg requested a review from a team as a code owner September 26, 2026 16:41
@dduugg
dduugg merged commit dc7c04e into main Sep 26, 2026
8 checks passed
@dduugg
dduugg deleted the enable-frozen-string-literal-comment branch September 26, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant