Class: HamlLint::RubyExtractor

Inherits:
Object
  • Object
show all
Includes:
HamlVisitor
Defined in:
lib/haml_lint/ruby_extractor.rb

Overview

Utility class for extracting Ruby script from a HAML file that can then be linted with a Ruby linter (i.e. is “legal” Ruby). The goal is to turn this:

- if signed_in?(viewer)
  %span Stuff
  = link_to 'Sign Out', sign_out_path
- else
  .some-class{ class: my_method }= my_method
  = link_to 'Sign In', sign_in_path

into this:

if signed_in?(viewer)
  link_to 'Sign Out', sign_out_path
else
  { class: my_method }
  my_method
  link_to 'Sign In', 
end

The translation won't be perfect, and won't make any real sense, but the relationship between variable declarations/uses and the flow control graph will remain intact.

Defined Under Namespace

Classes: RubySource

Instance Method Summary collapse

Methods included from HamlVisitor

#visit, #visit_children

Instance Method Details

#after_visit_tag(node) ⇒ Object


90
91
92
93
# File 'lib/haml_lint/ruby_extractor.rb', line 90

def after_visit_tag(node)
  # We add a dummy puts statement for closing tag.
  add_dummy_puts(node, "#{node.tag_name}/")
end

#extract(document) ⇒ HamlLint::RubyExtractor::RubySource

Extracts Ruby code from Sexp representing a Slim document.

Parameters:

Returns:


42
43
44
45
# File 'lib/haml_lint/ruby_extractor.rb', line 42

def extract(document)
  visit(document.tree)
  RubySource.new(@source_lines.join("\n"), @source_map)
end

#visit_filter(node) ⇒ Object


127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/haml_lint/ruby_extractor.rb', line 127

def visit_filter(node)
  if node.filter_type == 'ruby'
    node.text.split("\n").each_with_index do |line, index|
      add_line(line, node.line + index + 1, false)
    end
  else
    add_dummy_puts(node, ":#{node.filter_type}")
    HamlLint::Utils.extract_interpolated_values(node.text) do |interpolated_code, line|
      add_line(interpolated_code, node.line + line)
    end
  end
end

#visit_haml_comment(node) ⇒ Object


113
114
115
116
117
118
119
120
121
# File 'lib/haml_lint/ruby_extractor.rb', line 113

def visit_haml_comment(node)
  # We want to preseve leading whitespace if it exists, but include leading
  # whitespace if it doesn't exist so that RuboCop's LeadingCommentSpace
  # doesn't complain
  comment = node.text
                .gsub(/\n(\S)/, "\n# \\1")
                .gsub(/\n(\s)/, "\n#\\1")
  add_line("##{comment}", node)
end

#visit_plain(node) ⇒ Object


57
58
59
60
61
62
# File 'lib/haml_lint/ruby_extractor.rb', line 57

def visit_plain(node)
  # Don't output the text, as we don't want to have to deal with any RuboCop
  # cops regarding StringQuotes or AsciiComments, and it's not important to
  # overall document anyway.
  add_dummy_puts(node)
end

#visit_root(_node) ⇒ Object


47
48
49
50
51
52
53
54
55
# File 'lib/haml_lint/ruby_extractor.rb', line 47

def visit_root(_node)
  @source_lines = []
  @source_map = {}
  @line_count = 0
  @indent_level = 0
  @output_count = 0

  yield # Collect lines of code from children
end

#visit_script(node) ⇒ Object


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/haml_lint/ruby_extractor.rb', line 95

def visit_script(node)
  code = node.text
  add_line(code.strip, node)

  start_block = anonymous_block?(code) || start_block_keyword?(code)

  if start_block
    @indent_level += 1
  end

  yield # Continue extracting code from children

  if start_block
    @indent_level -= 1
    add_line('end', node)
  end
end

#visit_silent_script(node, &block) ⇒ Object


123
124
125
# File 'lib/haml_lint/ruby_extractor.rb', line 123

def visit_silent_script(node, &block)
  visit_script(node, &block)
end

#visit_tag(node) ⇒ Object


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
# File 'lib/haml_lint/ruby_extractor.rb', line 64

def visit_tag(node)
  additional_attributes = node.dynamic_attributes_sources

  # Include dummy references to code executed in attributes list
  # (this forces a "use" of a variable to prevent "assigned but unused
  # variable" lints)
  additional_attributes.each do |attributes_code|
    # Normalize by removing excess whitespace to avoid format lints
    attributes_code = attributes_code.gsub(/\s*\n\s*/, ' ').strip

    # Attributes can either be a method call or a literal hash, so wrap it
    # in a method call itself in order to avoid having to differentiate the
    # two.
    add_line("{}.merge(#{attributes_code.strip})", node)
  end

  check_tag_static_hash_source(node)

  # We add a dummy puts statement to represent the tag name being output.
  # This prevents some erroneous RuboCop warnings.
  add_dummy_puts(node, node.tag_name)

  code = node.script.strip
  add_line(code, node) unless code.empty?
end