Class: Ryac::KeywordRenameMapping

Inherits:
Object
  • Object
show all
Includes:
UnionFind, UnionFind[method_key]
Defined in:
lib/ryac/analysis/keyword/rename_mapping.rb,
sig/ryac/analysis/keyword/rename_mapping.rbs

Instance Method Summary collapse

Methods included from UnionFind

#uf_add, #uf_init, #uf_remove, #uf_root

Constructor Details

#initialize ⇒ KeywordRenameMapping

Returns a new instance of KeywordRenameMapping.



7
8
9
10
11
12
13
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 7

def initialize
  uf_init
  @methods = {}
  @node_short_names = {}
  @keyword_maps = {}
  @frozen = false
end

Instance Method Details

#add_keyword_call(method_key, keyword_sym, symbol_node, val_node) ⇒ void

This method returns an undefined value.

Parameters:

  • method_key (method_key)
  • keyword_sym (Symbol)
  • symbol_node (Prism::SymbolNode)
  • val_node (Prism::Node)


20
21
22
23
24
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 20

def add_keyword_call(method_key, keyword_sym, symbol_node, val_node)
  init_method(method_key)
  @methods[method_key][:call_entries][keyword_sym] ||= []
  @methods[method_key][:call_entries][keyword_sym] << { symbol_node: symbol_node, val_node: val_node }
end

#add_keyword_def(method_key, keyword_sym) ⇒ void

This method returns an undefined value.

Parameters:

  • method_key (method_key)
  • keyword_sym (Symbol)


15
16
17
18
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 15

def add_keyword_def(method_key, keyword_sym)
  init_method(method_key)
  @methods[method_key][:keywords] << keyword_sym
end

#assign_short_names ⇒ void

This method returns an undefined value.



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
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 50

def assign_short_names
  groups = Hash.new { |h, k| h[k] = [] } #: Hash[method_key, Array[method_key]]
  @methods.each_key { |key| groups[uf_root(key)] << key }

  groups.each do |_root, keys|
    next if keys.any? { |k| @methods[k][:excluded] }

    all_keywords = keys.flat_map { |k| @methods[k][:keywords].to_a }.uniq
    total_call_entries = keys.sum { |k| @methods[k][:call_entries].values.sum(&:size) }
    next if total_call_entries == 0

    generator = NameGenerator.new
    keyword_map = {} #: Hash[Symbol, String]
    occurrences_by_sym = Hash.new(0) #: Hash[Symbol, Integer]
    keys.each do |k|
      info = @methods[k]
      info[:keywords].each { |sym| occurrences_by_sym[sym] += 1 }
      info[:call_entries].each { |sym, entries| occurrences_by_sym[sym] += entries.size }
    end
    # Sorted by total bytes at stake — the same greedy order every other
    # rename family uses, so the shortest names go where they pay most.
    all_keywords.sort_by { |sym| -(sym.to_s.size * occurrences_by_sym[sym]) }.each do |sym|
      next if sym.to_s.size <= NameGenerator::KEPT_NAME_MAX

      short = generator.next_name
      savings = (sym.to_s.size - short.size) * occurrences_by_sym[sym]
      next unless savings > MethodRenameMapping::MIN_GROUP_SAVINGS

      keyword_map[sym] = short
    end

    next if keyword_map.empty?

    @keyword_maps[_root] = keyword_map

    keys.each do |key|
      @methods[key][:call_entries].each do |sym, entries|
        short = keyword_map[sym]
        next unless short
        entries.each { |e| @node_short_names[AstUtils.location_key(e[:symbol_node])] = short }
      end
    end
  end

  @frozen = true
end

#build_variable_hints {|arg0| ... } ⇒ Hash[scope_id, Hash[Symbol, String]]

A call passing a local as a keyword value — f(code: c) — pays nothing when the local is named like the keyword, because the shorthand form f(code:) applies. These hints suggest that name to the local's scope.

The scope containing the call site is the caller's concern: pass a block mapping a call-argument node to its scope id.

Yields:

Yield Parameters:

  • arg0 (Prism::Node)

Yield Returns:

  • (scope_id, nil)

Returns:

  • (Hash[scope_id, Hash[Symbol, String]])


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 128

def build_variable_hints
  hints = {} #: scope_mapping_table

  @methods.each do |method_key, info|
    next if info[:excluded]
    root = uf_root(method_key)
    keyword_map = @keyword_maps[root] || {}

    info[:call_entries].each do |keyword_sym, entries|
      # Use renamed name if available; for already-short keywords,
      # use original name to preserve idempotency across re-minification passes
      final_name = keyword_map[keyword_sym]
      final_name ||= keyword_sym.to_s if keyword_sym.to_s.size <= NameGenerator::KEPT_NAME_MAX
      next unless final_name

      entries.each do |entry|
        val_node = entry[:val_node]
        next unless val_node.is_a?(Prism::LocalVariableReadNode)

        scope_id = yield(val_node)
        next unless scope_id

        hints[scope_id] ||= {}
        hints[scope_id][val_node.name] ||= final_name
      end
    end
  end

  hints
end

#def_node_mapping(def_node_registry) ⇒ Hash[location_key, Hash[Symbol, String]]

Keyed by the def's location, the coordinate the scope analysis works in.

Parameters:

  • def_node_registry (Hash[method_key, Array[Prism::DefNode]])

Returns:

  • (Hash[location_key, Hash[Symbol, String]])


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 102

def def_node_mapping(def_node_registry)
  result = {} #: Hash[location_key, Hash[Symbol, String]]
  def_node_registry.each do |method_key, def_nodes|
    root = uf_root(method_key)
    keyword_map = @keyword_maps[root]
    next unless keyword_map

    next if @methods[method_key]&.[](:excluded)

    def_nodes.each do |def_node|
      mapping = {} #: Hash[Symbol, String]
      keyword_map.each do |sym, short|
        mapping[sym] = short
      end
      result[AstUtils.location_key(def_node)] = mapping unless mapping.empty?
    end
  end
  result
end

#each_method_key {|arg0| ... } ⇒ void

This method returns an undefined value.

Yields:

Yield Parameters:

  • arg0 (method_key)

Yield Returns:

  • (void)


46
47
48
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 46

def each_method_key(&block)
  @methods.each_key(&block)
end

#exclude_method(method_key) ⇒ void

This method returns an undefined value.

Parameters:

  • method_key (method_key)


26
27
28
29
30
31
32
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 26

def exclude_method(method_key)
  init_method(method_key)
  root = uf_root(method_key)
  @methods.each_key do |k|
    @methods[k][:excluded] = true if uf_root(k) == root
  end
end

#exclude_methods_by_mid(mids) ⇒ void

This method returns an undefined value.

Parameters:

  • mids (Enumerable[Symbol])


34
35
36
37
38
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 34

def exclude_methods_by_mid(mids)
  @methods.keys.each do |key|
    exclude_method(key) if mids.include?(key[2])
  end
end

#init_method(method_key) ⇒ void

This method returns an undefined value.

Parameters:

  • method_key (method_key)


161
162
163
164
165
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 161

def init_method(method_key)
  return if @methods.key?(method_key)
  @methods[method_key] = { keywords: Set.new, call_entries: {}, excluded: false }
  uf_add(method_key)
end

#merge_groups(key1, key2) ⇒ void

This method returns an undefined value.

Parameters:

  • key1 (method_key)
  • key2 (method_key)


40
41
42
43
44
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 40

def merge_groups(key1, key2)
  init_method(key1)
  init_method(key2)
  super
end

#node_mapping ⇒ Hash[location_key, String]

Returns:

  • (Hash[location_key, String])


97
98
99
# File 'lib/ryac/analysis/keyword/rename_mapping.rb', line 97

def node_mapping
  @node_short_names.dup
end