Class: RubyUIConverter::LocalsDetector

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_ui_converter/locals_detector.rb

Overview

Heuristically detects the locals a partial expects, so the generated component can declare keyword arguments and private readers.

This is intentionally conservative and best-effort: it favors a few clearly detectable cases (local_assigns) plus bare identifiers that look like locals. Anything it misses can be added by hand to the generated component.

Constant Summary collapse

KEYWORDS =
%w[
  if elsif else end unless case when in while until for do begin rescue
  ensure return yield self nil true false and or not then break next redo
  retry def class module super defined lambda proc new raise puts print p
  require require_relative attr_reader attr_accessor attr_writer
].to_set

Instance Method Summary collapse

Constructor Details

#initialize(tree) ⇒ LocalsDetector

Returns a new instance of LocalsDetector.



20
21
22
# File 'lib/ruby_ui_converter/locals_detector.rb', line 20

def initialize(tree)
  @tree = tree
end

Instance Method Details

#ivarsObject

Instance variables a top-level view reads from its controller (‘@products`), so the generated component can take them as keyword arguments. Excludes any ivar assigned within the template and class variables (`@@foo`). Strings are stripped first, like #locals, to avoid `@` inside literals (`“x@y.com”`).



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/ruby_ui_converter/locals_detector.rb', line 70

def ivars
  codes = []
  collect(@tree.children, codes)

  found = Set.new
  assigned = Set.new

  codes.each do |code|
    without_strings = code.gsub(/"[^"]*"|'[^']*'/, " ")
    without_strings.scan(/(?<!@)@([a-zA-Z_]\w*)\s*=(?!=)/) { assigned << Regexp.last_match(1) }
    without_strings.scan(/(?<!@)@([a-zA-Z_]\w*)/) { found << Regexp.last_match(1) }
  end

  (found - assigned).to_a.sort
end

#localsObject



24
25
26
27
28
29
30
31
32
33
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
# File 'lib/ruby_ui_converter/locals_detector.rb', line 24

def locals
  codes = []
  collect(@tree.children, codes)

  found = Set.new
  assigned = Set.new
  block_params = Set.new

  codes.each do |code|
    code.scan(/\|([^|]*)\|/) do
      Regexp.last_match(1).split(",").each do |param|
        block_params << param.strip.sub(/\A\*+/, "").sub(/:.*\z/, "").strip
      end
    end
    code.scan(/([a-z_]\w*)\s*=(?!=)/) { assigned << Regexp.last_match(1) }
    code.scan(/local_assigns\[:(\w+)\]/) { found << Regexp.last_match(1) }
    code.scan(/local_assigns\.fetch\(:(\w+)/) { found << Regexp.last_match(1) }
  end

  codes.each do |code|
    without_strings = code.gsub(/"[^"]*"|'[^']*'/, " ")
    without_strings.scan(/(?<![.\w:@$])([a-z_]\w*)/) do
      name = Regexp.last_match(1)
      after = Regexp.last_match.post_match

      next if after =~ /\A\s*\(/          # method call
      next if after =~ /\A\s*=(?!=)/      # assignment target
      next if after =~ /\A:(?!:)/         # keyword/hash key
      next if KEYWORDS.include?(name)
      next if RailsHelpers::HTML_HELPERS.include?(name)
      next if RailsHelpers::KNOWN_HELPERS.include?(name)
      next if block_params.include?(name)
      next if assigned.include?(name)

      found << name
    end
  end

  found.delete("local_assigns")
  found.to_a.sort
end