Module: Audition::Rewriters::MagicComments
- Defined in:
- lib/audition/rewriters.rb
Overview
-- magic comments ----------------------------------------------
Defined Under Namespace
Classes: ConstantValues
Constant Summary collapse
- SCV_LINE =
"# shareable_constant_value: literal\n"- FSL_LINE =
"# frozen_string_literal: true\n"
Class Method Summary collapse
- .comment_plan(file, line) ⇒ Object
- .constant_collector(file) ⇒ Object
-
.deep_literal?(node, classifier) ⇒ Boolean
Bare literals only:
[...].freezeis a method call, and under the magic comment Ruby raises for it at assignment because the shallowly-frozen value is unshareable (verified on 4.0 with jwt's NAMED_CURVES). - .mutates_own_constant?(file, assigned) ⇒ Boolean
-
.plan(file, findings) ⇒ Object
shareable_constant_value: literalraises at load for non-literal constant values (verified on 4.0), so it is only planned when every constant assignment in the file is a literal all the way down: an array literal holding a local or a call (Racc-generated parser tables are the canonical case) becomes an unshareable value that the magic comment rejects at load time.
Class Method Details
.comment_plan(file, line) ⇒ Object
137 138 139 140 141 142 143 144 145 146 |
# File 'lib/audition/rewriters.rb', line 137 def self.comment_plan(file, line) offset = file.magic_insertion_offset { edit: Autofix.new(start_offset: offset, end_offset: offset, replacement: line, safety: :unsafe), covers: ["mutable-constants"] } end |
.constant_collector(file) ⇒ Object
148 149 150 151 152 |
# File 'lib/audition/rewriters.rb', line 148 def self.constant_collector(file) collector = ConstantValues.new collector.visit(file.root) collector end |
.deep_literal?(node, classifier) ⇒ Boolean
Bare literals only: [...].freeze is a method call, and
under the magic comment Ruby raises for it at assignment
because the shallowly-frozen value is unshareable
(verified on 4.0 with jwt's NAMED_CURVES).
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/audition/rewriters.rb', line 117 def self.deep_literal?(node, classifier) case node when Prism::ArrayNode node.elements.all? do |element| deep_literal?(element, classifier) end when Prism::HashNode, Prism::KeywordHashNode node.elements.all? do |element| element.is_a?(Prism::AssocNode) && deep_literal?(element.key, classifier) && deep_literal?(element.value, classifier) end when Prism::CallNode false else %i[shareable mutable_string] .include?(classifier.classify(node)) end end |
.mutates_own_constant?(file, assigned) ⇒ Boolean
103 104 105 106 107 108 109 110 111 |
# File 'lib/audition/rewriters.rb', line 103 def self.mutates_own_constant?(file, assigned) mutated = file.mutated_constants assigned.any? do |name| = name.split("::").last mutated.any? do |m| m == name || m.split("::").last == end end end |
.plan(file, findings) ⇒ Object
shareable_constant_value: literal raises at load for
non-literal constant values (verified on 4.0), so it is
only planned when every constant assignment in the file is
a literal all the way down: an array literal holding a
local or a call (Racc-generated parser tables are the
canonical case) becomes an unshareable value that the
magic comment rejects at load time. Otherwise, if the
flagged values are all strings, frozen_string_literal
covers them.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/audition/rewriters.rb', line 67 def self.plan(file, findings) return nil if file.shareable_constants? return nil if findings.none? { |f| f.check == "mutable-constants" } classifier = Static::LiteralClassifier.new( frozen_string_literal: file.frozen_string_literal? ) collector = constant_collector(file) # A constant assigned here and mutated here is a # deliberate accumulator (sinatra's PARAMS_CONFIG); # freezing the file's constants would raise at the # mutation site. return nil if mutates_own_constant?(file, collector.names) values = collector.values kinds = values.map { |v| classifier.classify(v) } flagged = kinds.reject do |k| %i[shareable unknown].include?(k) end scv_ok = values.any? && values.all? { |value| deep_literal?(value, classifier) } # Both branches demand something to fix: with no constant # values (a lone constant-mutation finding) or nothing # flagged, a comment would freeze unrelated code while # fixing nothing. An explicit `frozen_string_literal: # false` is an author opt-out and is never overridden. if scv_ok comment_plan(file, SCV_LINE) elsif flagged.any? && file.magic_comment("frozen_string_literal").nil? && flagged.all? { |k| k == :mutable_string } comment_plan(file, FSL_LINE) end end |