Module: Ryac::Pipeline::RenamePatcher

Included in:
AttrDeclShorten, ConstantAliaser, MethodRenamer, VariableRenamer
Defined in:
lib/ryac/pipeline/rename_patcher.rb,
sig/ryac/pipeline/rename_patcher.rbs

Overview

Shared infrastructure for L2-L5 rename pipeline stages. Each stage includes this module and implements its own collect_patches.

Defined Under Namespace

Modules: _NodeWithNameLoc

Instance Method Summary collapse

Instance Method Details

#patch_assoc(node, patches, rename_map, keyword_arg: false) ⇒ Object

The return value is only ever compared against :skip_children; a record-alias union here sends typeprof's solver into unbounded re-propagation during self-hosting, so it stays untyped.

Parameters:

  • node (Prism::AssocNode)
  • patches (Array[patch_entry])
  • rename_map (Hash[location_key, String])
  • keyword_arg: (Boolean) (defaults to: false)

Returns:

  • (Object)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'sig/ryac/pipeline/rename_patcher.rbs', line 34

def patch_assoc(node, patches, rename_map, keyword_arg: false)
  key_node = node.key
  val_node = node.value

  return unless key_node.is_a?(Prism::SymbolNode)
  return unless key_node.value.to_s.match?(/\A[a-zA-Z_][a-zA-Z0-9_]*\z/)

  original_key = key_node.value.to_s
  renamed_key = rename_map[prism_location_key(key_node)]

  if val_node.is_a?(Prism::ImplicitNode)
    inner = val_node.value
    if inner.is_a?(Prism::LocalVariableReadNode)
      mangled_var = rename_map[prism_location_key(inner)] || inner.name.to_s
      key_name = keyword_arg ? (renamed_key || original_key) : original_key
      if key_name == mangled_var
        if key_name != original_key
          patches << { start: key_node.location.start_offset, end: val_node.location.end_offset,
                       replacement: "#{key_name}:" }
        end
      else
        sep = mangled_var.start_with?(':') ? ': ' : ':'
        patches << { start: key_node.location.start_offset, end: val_node.location.end_offset,
                     replacement: "#{key_name}#{sep}#{mangled_var}" }
      end
    else
      if keyword_arg && renamed_key
        patches << { start: key_node.location.start_offset, end: val_node.location.end_offset,
                     replacement: "#{renamed_key}:" }
      end
    end
    return :skip_children
  end

  key_name = renamed_key || original_key

  if val_node.is_a?(Prism::LocalVariableReadNode)
    mangled_var = rename_map[prism_location_key(val_node)] || val_node.name.to_s
    if key_name == mangled_var
      patches << { start: key_node.location.start_offset, end: val_node.location.end_offset,
                   replacement: "#{key_name}:" }
      return :skip_children
    end
  end

  if renamed_key
    loc = key_node.location
    suffix = loc.slice.end_with?(':') ? ':' : ''
    patches << { start: loc.start_offset, end: loc.end_offset, replacement: "#{renamed_key}#{suffix}" }
  end
end

#patch_block_param_node(block, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • block (Prism::Node, nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


152
153
154
155
# File 'lib/ryac/pipeline/rename_patcher.rb', line 152

def patch_block_param_node(block, param_names, patches)
  return unless block.is_a?(Prism::BlockParameterNode) && block.name
  patch_named_param(block.name, block.name_loc, param_names, patches)
end

#patch_block_params(node, patches, block_param_names_map) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::CallNode)
  • patches (Array[patch_entry])
  • block_param_names_map (Hash[location_key, Hash[Symbol, String]])


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/ryac/pipeline/rename_patcher.rb', line 157

def patch_block_params(node, patches, block_param_names_map)
  return unless node.block.is_a?(Prism::BlockNode)
  block = node.block
  return unless block.parameters.is_a?(Prism::BlockParametersNode)

  key = prism_location_key(node)
  param_map = block_param_names_map[key]
  return unless param_map

  mangled_values = param_map.values
  block_params = block.parameters.parameters
  has_non_required = block_params && (
    block_params.optionals&.any? ||
    block_params.rest ||
    block_params.posts&.any? ||
    block_params.keywords&.any? ||
    block_params.keyword_rest ||
    block_params.block
  )
  if mangled_values.any? { |v| v.match?(/^_\d+$/) } && !has_non_required
    bp_loc = block.parameters.location
    patches << { start: bp_loc.start_offset, end: bp_loc.end_offset, replacement: '' }
    return
  end

  block_params = block.parameters.parameters
  return unless block_params

  patch_required_params(block_params.requireds, param_map, patches)
  patch_optional_params(block_params.optionals, param_map, patches)
  patch_rest_param(block_params.rest, param_map, patches)
  patch_post_params(block_params.posts, param_map, patches)
  patch_keyword_params(block_params.keywords, param_map, patches)
  patch_keyword_rest(block_params.keyword_rest, param_map, patches)
  patch_block_param_node(block_params.block, param_map, patches)
end

#patch_def_params(node, patches, analysis, param_names_key) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::DefNode)
  • patches (Array[patch_entry])
  • analysis (AnalysisResult)
  • param_names_key (Symbol)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ryac/pipeline/rename_patcher.rb', line 48

def patch_def_params(node, patches, analysis, param_names_key)
  return unless node.parameters

  syntax_key = AstUtils.line_col_key(node)
  data = analysis.syntax_data[syntax_key]
  return unless data

  # any? rather than empty?: `empty?` on a receiver TypeProf can prove
  # is a Hash gets rewritten to `=={}` — but only when it can prove it,
  # and whether it can differs between the original and the minified
  # text. Self-hosting then never reaches a fixed point. `any?` has no
  # such rewrite.
  param_names = data[param_names_key] || {} #: Hash[Symbol, String]
  return unless param_names.any?

  params = node.parameters
  patch_required_params(params.requireds, param_names, patches)
  patch_optional_params(params.optionals, param_names, patches)
  patch_rest_param(params.rest, param_names, patches)
  patch_post_params(params.posts, param_names, patches)
  patch_keyword_params(params.keywords, param_names, patches)
  patch_keyword_rest(params.keyword_rest, param_names, patches)
  patch_block_param_node(params.block, param_names, patches)
end

#patch_for_node(node, patches, analysis, mangled_key) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::ForNode)
  • patches (Array[patch_entry])
  • analysis (AnalysisResult)
  • mangled_key (Symbol)


194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/ryac/pipeline/rename_patcher.rb', line 194

def patch_for_node(node, patches, analysis, mangled_key)
  syntax_key = AstUtils.line_col_key(node)
  data = analysis.syntax_data[syntax_key]
  return unless data

  target_name = data[mangled_key] #: String?
  return unless target_name

  idx = node.index
  return unless idx.is_a?(Prism::LocalVariableTargetNode)
  patches << { start: idx.location.start_offset, end: idx.location.end_offset, replacement: target_name }
end

#patch_keyword_hash(node, patches, rename_map) {|arg0| ... } ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::KeywordHashNode)
  • patches (Array[patch_entry])
  • rename_map (Hash[location_key, String])

Yields:

Yield Parameters:

  • arg0 (Prism::Node)

Yield Returns:

  • (Object)


259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/ryac/pipeline/rename_patcher.rb', line 259

def patch_keyword_hash(node, patches, rename_map, &block)
  node.elements.each do |elem|
    if elem.is_a?(Prism::AssocNode)
      result = patch_assoc(elem, patches, rename_map, keyword_arg: true)
      if result != :skip_children
        elem.compact_child_nodes.each { |child| walk_prism(child, &block) }
      end
    else
      walk_prism(elem, &block)
    end
  end
end

#patch_keyword_params(keywords, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • keywords (Array[Prism::RequiredKeywordParameterNode | Prism::OptionalKeywordParameterNode], nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


138
139
140
141
142
143
144
145
# File 'lib/ryac/pipeline/rename_patcher.rb', line 138

def patch_keyword_params(keywords, param_names, patches)
  return unless keywords
  keywords.each do |p|
    short = param_names[p.name]
    next unless short && short != p.name.to_s
    patches << { start: p.name_loc.start_offset, end: p.name_loc.end_offset, replacement: "#{short}:" }
  end
end

#patch_keyword_rest(keyword_rest, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • keyword_rest (Prism::Node, nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


147
148
149
150
# File 'lib/ryac/pipeline/rename_patcher.rb', line 147

def patch_keyword_rest(keyword_rest, param_names, patches)
  return unless keyword_rest.is_a?(Prism::KeywordRestParameterNode) && keyword_rest.name
  patch_named_param(keyword_rest.name, keyword_rest.name_loc, param_names, patches)
end

#patch_multi_target_params(node, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::MultiTargetNode)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/ryac/pipeline/rename_patcher.rb', line 86

def patch_multi_target_params(node, param_names, patches)
  node.lefts.each do |p|
    if p.is_a?(Prism::RequiredParameterNode)
      short = param_names[p.name]
      next unless short && short != p.name.to_s
      patches << { start: p.location.start_offset, end: p.location.end_offset, replacement: short }
    elsif p.is_a?(Prism::MultiTargetNode)
      patch_multi_target_params(p, param_names, patches)
    end
  end
  if node.rest.is_a?(Prism::SplatNode) &&
     node.rest.expression.is_a?(Prism::RequiredParameterNode)
    p = node.rest.expression
    short = param_names[p.name]
    if short && short != p.name.to_s
      patches << { start: p.location.start_offset, end: p.location.end_offset, replacement: short }
    end
  end
end

#patch_named_param(name, name_loc, param_names, patches) ⇒ void

This method returns an undefined value.

Shared tail of the rest/**rest/&block patchers: Prism types their name_loc as nullable, but a named parameter always carries one.

Parameters:

  • name (Symbol)
  • name_loc (Prism::Location, nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


117
118
119
120
121
# File 'lib/ryac/pipeline/rename_patcher.rb', line 117

def patch_named_param(name, name_loc, param_names, patches)
  short = param_names[name]
  return unless name_loc && short && short != name.to_s
  patches << { start: name_loc.start_offset, end: name_loc.end_offset, replacement: short }
end

#patch_optional_params(optionals, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • optionals (Array[Prism::OptionalParameterNode], nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


106
107
108
109
110
111
112
113
# File 'lib/ryac/pipeline/rename_patcher.rb', line 106

def patch_optional_params(optionals, param_names, patches)
  return unless optionals
  optionals.each do |p|
    short = param_names[p.name]
    next unless short && short != p.name.to_s
    patches << { start: p.name_loc.start_offset, end: p.name_loc.end_offset, replacement: short }
  end
end

#patch_post_params(posts, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • posts (Array[Prism::Node], nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


128
129
130
131
132
133
134
135
136
# File 'lib/ryac/pipeline/rename_patcher.rb', line 128

def patch_post_params(posts, param_names, patches)
  return unless posts
  posts.each do |p|
    next unless p.is_a?(Prism::RequiredParameterNode)
    short = param_names[p.name]
    next unless short && short != p.name.to_s
    patches << { start: p.location.start_offset, end: p.location.end_offset, replacement: short }
  end
end

#patch_required_params(requireds, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • requireds (Array[Prism::Node], nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/ryac/pipeline/rename_patcher.rb', line 73

def patch_required_params(requireds, param_names, patches)
  return unless requireds
  requireds.each do |p|
    if p.is_a?(Prism::MultiTargetNode)
      patch_multi_target_params(p, param_names, patches)
    elsif p.is_a?(Prism::RequiredParameterNode)
      short = param_names[p.name]
      next unless short && short != p.name.to_s
      patches << { start: p.location.start_offset, end: p.location.end_offset, replacement: short }
    end
  end
end

#patch_rest_param(rest, param_names, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • rest (Prism::Node, nil)
  • param_names (Hash[Symbol, String])
  • patches (Array[patch_entry])


123
124
125
126
# File 'lib/ryac/pipeline/rename_patcher.rb', line 123

def patch_rest_param(rest, param_names, patches)
  return unless rest.is_a?(Prism::RestParameterNode) && rest.name
  patch_named_param(rest.name, rest.name_loc, param_names, patches)
end

#patch_variable(node, patches, rename_map) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::Node)
  • patches (Array[patch_entry])
  • rename_map (Hash[location_key, String])


30
31
32
33
34
35
36
37
# File 'lib/ryac/pipeline/rename_patcher.rb', line 30

def patch_variable(node, patches, rename_map)
  key = prism_location_key(node)
  short = rename_map[key]
  return unless short

  loc = node.location
  patches << { start: loc.start_offset, end: loc.end_offset, replacement: short }
end

#patch_variable_name_only(node, patches, rename_map) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::Node & _NodeWithNameLoc)
  • patches (Array[patch_entry])
  • rename_map (Hash[location_key, String])


39
40
41
42
43
44
45
46
# File 'lib/ryac/pipeline/rename_patcher.rb', line 39

def patch_variable_name_only(node, patches, rename_map)
  key = prism_location_key(node)
  short = rename_map[key]
  return unless short

  name_loc = node.name_loc
  patches << { start: name_loc.start_offset, end: name_loc.end_offset, replacement: short }
end

#prism_location_key(node) ⇒ location_key

Same key the analysis stored the rename under. Kept as its own name because the patchers only ever pass Prism nodes, but it must not grow a second implementation — the two sides sharing one definition is the point.

Parameters:

  • node (Prism::Node)

Returns:

  • (location_key)


26
27
28
# File 'lib/ryac/pipeline/rename_patcher.rb', line 26

def prism_location_key(node)
  AstUtils.location_key(node)
end

#walk_prism(node) {|arg0| ... } ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::Node, nil)

Yields:

Yield Parameters:

  • arg0 (Prism::Node)

Yield Returns:

  • (Object)


10
11
12
13
14
15
16
17
18
19
20
# File 'lib/ryac/pipeline/rename_patcher.rb', line 10

def walk_prism(node, &block)
  return unless node
  result = yield node
  return if result == :skip_children

  children = node.compact_child_nodes
  if node.is_a?(Prism::MatchWriteNode)
    children = children.reject { |c| c.is_a?(Prism::LocalVariableTargetNode) }
  end
  children.each { |child| walk_prism(child, &block) }
end