haml_lint 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/bin/haml-lint +7 -0
  3. data/config/default.yml +91 -0
  4. data/lib/haml_lint/cli.rb +122 -0
  5. data/lib/haml_lint/configuration.rb +97 -0
  6. data/lib/haml_lint/configuration_loader.rb +68 -0
  7. data/lib/haml_lint/constants.rb +8 -0
  8. data/lib/haml_lint/exceptions.rb +15 -0
  9. data/lib/haml_lint/file_finder.rb +69 -0
  10. data/lib/haml_lint/haml_visitor.rb +36 -0
  11. data/lib/haml_lint/lint.rb +25 -0
  12. data/lib/haml_lint/linter/alt_text.rb +12 -0
  13. data/lib/haml_lint/linter/class_attribute_with_static_value.rb +51 -0
  14. data/lib/haml_lint/linter/classes_before_ids.rb +26 -0
  15. data/lib/haml_lint/linter/consecutive_comments.rb +20 -0
  16. data/lib/haml_lint/linter/consecutive_silent_scripts.rb +23 -0
  17. data/lib/haml_lint/linter/empty_script.rb +12 -0
  18. data/lib/haml_lint/linter/html_attributes.rb +14 -0
  19. data/lib/haml_lint/linter/implicit_div.rb +20 -0
  20. data/lib/haml_lint/linter/leading_comment_space.rb +14 -0
  21. data/lib/haml_lint/linter/line_length.rb +19 -0
  22. data/lib/haml_lint/linter/multiline_pipe.rb +43 -0
  23. data/lib/haml_lint/linter/multiline_script.rb +43 -0
  24. data/lib/haml_lint/linter/object_reference_attributes.rb +14 -0
  25. data/lib/haml_lint/linter/rubocop.rb +76 -0
  26. data/lib/haml_lint/linter/ruby_comments.rb +18 -0
  27. data/lib/haml_lint/linter/space_before_script.rb +52 -0
  28. data/lib/haml_lint/linter/space_inside_hash_attributes.rb +32 -0
  29. data/lib/haml_lint/linter/tag_name.rb +13 -0
  30. data/lib/haml_lint/linter/trailing_whitespace.rb +16 -0
  31. data/lib/haml_lint/linter/unnecessary_interpolation.rb +29 -0
  32. data/lib/haml_lint/linter/unnecessary_string_output.rb +39 -0
  33. data/lib/haml_lint/linter.rb +156 -0
  34. data/lib/haml_lint/linter_registry.rb +26 -0
  35. data/lib/haml_lint/logger.rb +107 -0
  36. data/lib/haml_lint/node_transformer.rb +28 -0
  37. data/lib/haml_lint/options.rb +89 -0
  38. data/lib/haml_lint/parser.rb +87 -0
  39. data/lib/haml_lint/rake_task.rb +107 -0
  40. data/lib/haml_lint/report.rb +16 -0
  41. data/lib/haml_lint/reporter/default_reporter.rb +39 -0
  42. data/lib/haml_lint/reporter/json_reporter.rb +44 -0
  43. data/lib/haml_lint/reporter.rb +36 -0
  44. data/lib/haml_lint/ruby_parser.rb +29 -0
  45. data/lib/haml_lint/runner.rb +76 -0
  46. data/lib/haml_lint/script_extractor.rb +181 -0
  47. data/lib/haml_lint/tree/comment_node.rb +5 -0
  48. data/lib/haml_lint/tree/doctype_node.rb +5 -0
  49. data/lib/haml_lint/tree/filter_node.rb +9 -0
  50. data/lib/haml_lint/tree/haml_comment_node.rb +18 -0
  51. data/lib/haml_lint/tree/node.rb +98 -0
  52. data/lib/haml_lint/tree/plain_node.rb +5 -0
  53. data/lib/haml_lint/tree/root_node.rb +5 -0
  54. data/lib/haml_lint/tree/script_node.rb +11 -0
  55. data/lib/haml_lint/tree/silent_script_node.rb +12 -0
  56. data/lib/haml_lint/tree/tag_node.rb +221 -0
  57. data/lib/haml_lint/utils.rb +58 -0
  58. data/lib/haml_lint/version.rb +4 -0
  59. data/lib/haml_lint.rb +36 -0
  60. metadata +175 -0
@@ -0,0 +1,181 @@
1
+ module HamlLint
2
+ # Utility class for extracting Ruby script from a HAML file that can then be
3
+ # linted with a Ruby linter (i.e. is "legal" Ruby). The goal is to turn this:
4
+ #
5
+ # - if signed_in?(viewer)
6
+ # %span Stuff
7
+ # = link_to 'Sign Out', sign_out_path
8
+ # - else
9
+ # .some-class{ class: my_method }= my_method
10
+ # = link_to 'Sign In', sign_in_path
11
+ #
12
+ # into this:
13
+ #
14
+ # if signed_in?(viewer)
15
+ # link_to 'Sign Out', sign_out_path
16
+ # else
17
+ # { class: my_method }
18
+ # my_method
19
+ # link_to 'Sign In', sign_in_path
20
+ # end
21
+ #
22
+ class ScriptExtractor
23
+ include HamlVisitor
24
+
25
+ attr_reader :source, :source_map
26
+
27
+ def initialize(parser)
28
+ @parser = parser
29
+ end
30
+
31
+ def extract
32
+ visit(@parser.tree)
33
+ @source = @code.join("\n")
34
+ end
35
+
36
+ def visit_root(_node)
37
+ @code = []
38
+ @total_lines = 0
39
+ @source_map = {}
40
+ @indent_level = 0
41
+
42
+ yield # Collect lines of code from children
43
+ end
44
+
45
+ def visit_plain(node)
46
+ # Don't output the text, as we don't want to have to deal with any RuboCop
47
+ # cops regarding StringQuotes or AsciiComments, and it's not important to
48
+ # overall document anyway.
49
+ add_line('puts', node)
50
+ end
51
+
52
+ def visit_tag(node)
53
+ additional_attributes = node.dynamic_attributes_sources
54
+
55
+ # Include dummy references to code executed in attributes list
56
+ # (this forces a "use" of a variable to prevent "assigned but unused
57
+ # variable" lints)
58
+ additional_attributes.each do |attributes_code|
59
+ # Normalize by removing excess whitespace to avoid format lints
60
+ attributes_code = attributes_code.gsub(/\s*\n\s*/, ' ').strip
61
+
62
+ # Attributes can either be a method call or a literal hash, so wrap it
63
+ # in a method call itself in order to avoid having to differentiate the
64
+ # two.
65
+ add_line("{}.merge(#{attributes_code.strip})", node)
66
+ end
67
+
68
+ check_tag_static_hash_source(node)
69
+
70
+ # We add a dummy puts statement to represent the tag name being output.
71
+ # This prevents some erroneous RuboCop warnings.
72
+ add_line("puts # #{node.tag_name}", node)
73
+
74
+ code = node.script.strip
75
+ add_line(code, node) unless code.empty?
76
+ end
77
+
78
+ def visit_script(node)
79
+ code = node.text
80
+ add_line(code.strip, node)
81
+
82
+ start_block = anonymous_block?(code) || start_block_keyword?(code)
83
+
84
+ if start_block
85
+ @indent_level += 1
86
+ end
87
+
88
+ yield # Continue extracting code from children
89
+
90
+ if start_block
91
+ @indent_level -= 1
92
+ add_line('end', node)
93
+ end
94
+ end
95
+
96
+ def visit_silent_script(node, &block)
97
+ visit_script(node, &block)
98
+ end
99
+
100
+ def visit_filter(node)
101
+ if node.filter_type == 'ruby'
102
+ node.text.split("\n").each_with_index do |line, index|
103
+ add_line(line, node.line + index + 1)
104
+ end
105
+ else
106
+ add_line('puts', node)
107
+ HamlLint::Utils.extract_interpolated_values(node.text) do |interpolated_code|
108
+ add_line(interpolated_code, node)
109
+ end
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def check_tag_static_hash_source(node)
116
+ # Haml::Parser converts hashrocket-style hash attributes of strings and symbols
117
+ # to static attributes, and excludes them from the dynamic attribute sources:
118
+ # https://github.com/haml/haml/blob/08f97ec4dc8f59fe3d7f6ab8f8807f86f2a15b68/lib/haml/parser.rb#L400-L404
119
+ # https://github.com/haml/haml/blob/08f97ec4dc8f59fe3d7f6ab8f8807f86f2a15b68/lib/haml/parser.rb#L540-L554
120
+ # Here, we add the hash source back in so it can be inspected by rubocop.
121
+ if node.hash_attributes? && node.dynamic_attributes_sources.empty?
122
+ normalized_attr_source = node.dynamic_attributes_source[:hash].gsub(/\s*\n\s*/, ' ')
123
+
124
+ add_line(normalized_attr_source, node)
125
+ end
126
+ end
127
+
128
+ def add_line(code, node_or_line)
129
+ return if code.empty?
130
+
131
+ indent_level = @indent_level
132
+
133
+ if node_or_line.respond_to?(:line)
134
+ # Since mid-block keywords are children of the corresponding start block
135
+ # keyword, we need to reduce their indentation level by 1. However, we
136
+ # don't do this unless this is an actual tag node (a raw line number
137
+ # means this came from a `:ruby` filter).
138
+ indent_level -= 1 if mid_block_keyword?(code)
139
+ end
140
+
141
+ indent = (' ' * 2 * indent_level)
142
+
143
+ @code << indent + code
144
+
145
+ original_line =
146
+ node_or_line.respond_to?(:line) ? node_or_line.line : node_or_line
147
+
148
+ # For interpolated code in filters that spans multiple lines, the
149
+ # resulting code will span multiple lines, so we need to create a
150
+ # mapping for each line.
151
+ (code.count("\n") + 1).times do
152
+ @total_lines += 1
153
+ @source_map[@total_lines] = original_line
154
+ end
155
+ end
156
+
157
+ def anonymous_block?(text)
158
+ text =~ /\bdo\s*(\|\s*[^\|]*\s*\|)?\z/
159
+ end
160
+
161
+ START_BLOCK_KEYWORDS = %w[if unless case begin for until while]
162
+ def start_block_keyword?(text)
163
+ START_BLOCK_KEYWORDS.include?(block_keyword(text))
164
+ end
165
+
166
+ MID_BLOCK_KEYWORDS = %w[else elsif when rescue ensure]
167
+ def mid_block_keyword?(text)
168
+ MID_BLOCK_KEYWORDS.include?(block_keyword(text))
169
+ end
170
+
171
+ def block_keyword(text)
172
+ # Need to handle 'for'/'while' since regex stolen from HAML parser doesn't
173
+ if keyword = text[/\A\s*([^\s]+)\s+/, 1]
174
+ return keyword if %w[for until while].include?(keyword)
175
+ end
176
+
177
+ return unless keyword = text.scan(Haml::Parser::BLOCK_KEYWORD_REGEX)[0]
178
+ keyword[0] || keyword[1]
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,5 @@
1
+ module HamlLint::Tree
2
+ # Represents a visible XHTML comment in a HAML document.
3
+ class CommentNode < Node
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module HamlLint::Tree
2
+ # Represents a doctype definition for a HAML document.
3
+ class DoctypeNode < Node
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module HamlLint::Tree
2
+ # Represents a filter node which contains arbitrary code.
3
+ class FilterNode < Node
4
+ # The type of code contained in this filter.
5
+ def filter_type
6
+ @value[:name]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module HamlLint::Tree
2
+ # Represents a HAML comment node.
3
+ class HamlCommentNode < Node
4
+ # Returns the full text content of this comment, including newlines if a
5
+ # single comment spans multiple lines.
6
+ #
7
+ # @return [String]
8
+ def text
9
+ content = source_code
10
+ indent = content[/^ */]
11
+
12
+ content.gsub(/^#{indent}/, '')
13
+ .gsub(/^-#/, '')
14
+ .gsub(/^ /, '')
15
+ .rstrip
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,98 @@
1
+ module HamlLint::Tree
2
+ # Decorator class that provides a convenient set of helpers for HAML's
3
+ # {Haml::Parser::ParseNode} struct.
4
+ #
5
+ # The goal is to abstract away the details of the underlying struct and
6
+ # provide a cleaner and more uniform interface for getting information about a
7
+ # node, as there are a number of weird/special cases in the struct returned by
8
+ # the HAML parser.
9
+ #
10
+ # @abstract
11
+ class Node
12
+ attr_accessor :children, :parent
13
+ attr_reader :line, :type
14
+
15
+ # Creates a node wrapping the given {Haml::Parser::ParseNode} struct.
16
+ #
17
+ # @param parser [HamlLint::Parser] parser that created this node
18
+ # @param parse_node [Haml::Parser::ParseNode] parse node created by HAML's parser
19
+ def initialize(parser, parse_node)
20
+ # TODO: Change signature to take source code object, not parser
21
+ @line = parse_node.line
22
+ @parser = parser
23
+ @value = parse_node.value
24
+ @type = parse_node.type
25
+ end
26
+
27
+ # Returns the first node found under the subtree which matches the given
28
+ # block.
29
+ #
30
+ # Returns nil if no node matching the given block was found.
31
+ #
32
+ # @return [HamlLint::Tree::Node,nil]
33
+ def find(&block)
34
+ return self if block.call(self)
35
+
36
+ children.each do |child|
37
+ if result = child.find(&block)
38
+ return result
39
+ end
40
+ end
41
+
42
+ nil # Otherwise no matching node was found
43
+ end
44
+
45
+ # Source code of all lines this node spans (excluding children).
46
+ #
47
+ # @return [String]
48
+ def source_code
49
+ next_node_line =
50
+ if next_node
51
+ next_node.line - 1
52
+ else
53
+ @parser.lines.count + 1
54
+ end
55
+
56
+ @parser.lines[@line - 1...next_node_line]
57
+ .join("\n")
58
+ .gsub(/^\s*\z/m, '') # Remove blank lines at the end
59
+ end
60
+
61
+ def inspect
62
+ "#<#{self.class.name}>"
63
+ end
64
+
65
+ # Returns the node that follows this node, whether it be a sibling or an
66
+ # ancestor's child, but not a child of this node.
67
+ #
68
+ # If you are also willing to return the child, call {#next_node}.
69
+ #
70
+ # Returns nil if there is no successor.
71
+ #
72
+ # @return [HamlLint::Tree::Node,nil]
73
+ def successor
74
+ siblings = parent ? parent.children : [self]
75
+
76
+ next_sibling = siblings[siblings.index(self) + 1] if siblings.count > 1
77
+ return next_sibling if next_sibling
78
+
79
+ parent.successor if parent
80
+ end
81
+
82
+ # Returns the next node that appears after this node in the document.
83
+ #
84
+ # Returns nil if there is no next node.
85
+ #
86
+ # @return [HamlLint::Tree::Node,nil]
87
+ def next_node
88
+ children.first || successor
89
+ end
90
+
91
+ # Returns the text content of this node.
92
+ #
93
+ # @return [String]
94
+ def text
95
+ @value[:text].to_s
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ module HamlLint::Tree
2
+ # Represents a node that contains plain text.
3
+ class PlainNode < Node
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module HamlLint::Tree
2
+ # Represents the root node of a HAML document that contains all other nodes.
3
+ class RootNode < Node
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module HamlLint::Tree
2
+ # Represents a node which produces output based on Ruby code.
3
+ class ScriptNode < Node
4
+ # Returns the source for the script following the `-` marker.
5
+ #
6
+ # @return [String]
7
+ def script
8
+ @value[:text]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module HamlLint::Tree
2
+ # Represents a HAML silent script node (`- some_expression`) which executes
3
+ # code without producing output.
4
+ class SilentScriptNode < Node
5
+ # Returns the source for the script following the `-` marker.
6
+ #
7
+ # @return [String]
8
+ def script
9
+ @value[:text]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,221 @@
1
+ module HamlLint::Tree
2
+ # Represents a tag node in a HAML document.
3
+ class TagNode < Node
4
+ # Computed set of attribute hashes code.
5
+ #
6
+ # This is a combination of all dynamically calculated attributes from the
7
+ # different attribute setting syntaxes (`{...}`/`(...)`), converted into
8
+ # Ruby code.
9
+ #
10
+ # @return [Array<String>]
11
+ def dynamic_attributes_sources
12
+ @value[:attributes_hashes]
13
+ end
14
+
15
+ # Returns whether this tag contains executable script (e.g. is followed by a
16
+ # `=`).
17
+ #
18
+ # @return [true,false]
19
+ def contains_script?
20
+ @value[:parse] && !@value[:value].strip.empty?
21
+ end
22
+
23
+ # Returns whether this tag has a specified attribute.
24
+ #
25
+ # @return [true,false]
26
+ def has_hash_attribute?(attribute)
27
+ hash_attributes? && existing_attributes.include?(attribute)
28
+ end
29
+
30
+ # List of classes statically defined for this tag.
31
+ #
32
+ # @example For `%tag.button.button-info{ class: status }`, this returns:
33
+ # ['button', 'button-info']
34
+ #
35
+ # @return [Array<String>] list of statically defined classes with leading
36
+ # dot removed
37
+ def static_classes
38
+ @static_classes ||=
39
+ begin
40
+ static_attributes_source.scan(/\.([-:\w]+)/)
41
+ end
42
+ end
43
+
44
+ # List of ids statically defined for this tag.
45
+ #
46
+ # @example For `%tag.button#start-button{ id: special_id }`, this returns:
47
+ # ['start-button']
48
+ #
49
+ # @return [Array<String>] list of statically defined ids with leading `#`
50
+ # removed
51
+ def static_ids
52
+ @static_ids ||=
53
+ begin
54
+ static_attributes_source.scan(/#([-:\w]+)/)
55
+ end
56
+ end
57
+
58
+ # Static element attributes defined after the tag name.
59
+ #
60
+ # @example For `%tag.button#start-button`, this returns:
61
+ # '.button#start-button'
62
+ #
63
+ # @return [String]
64
+ def static_attributes_source
65
+ attributes_source[:static] || ''
66
+ end
67
+
68
+ # Returns the source code for the dynamic attributes defined in `{...}`,
69
+ # `(...)`, or `[...]` after a tag name.
70
+ #
71
+ # @example For `%tag.class{ id: 'hello' }(lang=en)`, this returns:
72
+ # { :hash => " id: 'hello' ", :html => "lang=en" }
73
+ #
74
+ # @return [Hash]
75
+ def dynamic_attributes_source
76
+ @dynamic_attributes_source ||=
77
+ attributes_source.reject { |key| key == :static }
78
+ end
79
+
80
+ # Returns the source code for the static and dynamic attributes
81
+ # of a tag.
82
+ #
83
+ # @example For `%tag.class{ id: 'hello' }(lang=en)`, this returns:
84
+ # { :static => '.class', :hash => " id: 'hello' ", :html => "lang=en" }
85
+ #
86
+ # @return [Hash]
87
+ def attributes_source
88
+ @attr_source ||=
89
+ begin
90
+ _explicit_tag, static_attrs, rest = source_code
91
+ .scan(/\A\s*(%[-:\w]+)?([-:\w\.\#]*)(.*)/m)[0]
92
+
93
+ attr_types = {
94
+ '{' => [:hash, %w[{ }]],
95
+ '(' => [:html, %w[( )]],
96
+ '[' => [:object_ref, %w[[ ]]],
97
+ }
98
+
99
+ attr_source = { static: static_attrs }
100
+ while rest
101
+ type, chars = attr_types[rest[0]]
102
+ break unless type # Not an attribute opening character, so we're done
103
+
104
+ # Can't define multiple of the same attribute type (e.g. two {...})
105
+ break if attr_source[type]
106
+
107
+ attr_source[type], rest = Haml::Util.balance(rest, *chars)
108
+ end
109
+
110
+ attr_source
111
+ end
112
+ end
113
+
114
+ # Whether this tag node has a set of hash attributes defined via the
115
+ # curly brace syntax (e.g. `%tag{ lang: 'en' }`).
116
+ #
117
+ # @return [true,false]
118
+ def hash_attributes?
119
+ !dynamic_attributes_source[:hash].nil?
120
+ end
121
+
122
+ # Attributes defined after the tag name in Ruby hash brackets (`{}`).
123
+ #
124
+ # @example For `%tag.class{ lang: 'en' }`, this returns:
125
+ # " lang: 'en' "
126
+ #
127
+ # @return [String] source without the surrounding curly braces
128
+ def hash_attributes_source
129
+ dynamic_attributes_source[:hash]
130
+ end
131
+
132
+ # Whether this tag node has a set of HTML attributes defined via the
133
+ # parentheses syntax (e.g. `%tag(lang=en)`).
134
+ #
135
+ # @return [true,false]
136
+ def html_attributes?
137
+ !dynamic_attributes_source[:html].nil?
138
+ end
139
+
140
+ # Attributes defined after the tag name in parentheses (`()`).
141
+ #
142
+ # @example For `%tag.class(lang=en)`, this returns:
143
+ # "lang=en"
144
+ #
145
+ # @return [String] source without the surrounding parentheses
146
+ def html_attributes_source
147
+ dynamic_attributes_source[:html] || ''
148
+ end
149
+
150
+ # Name of the HTML tag.
151
+ #
152
+ # @return [String]
153
+ def tag_name
154
+ @value[:name]
155
+ end
156
+
157
+ # Whether this tag node has a set of square brackets (e.g. `%tag[...]`)
158
+ # following it that indicates its class and ID will be to the value of the
159
+ # given object's {#to_key} or {#id} method (in that order).
160
+ #
161
+ # @return [true,false]
162
+ def object_reference?
163
+ @value[:object_ref].to_s != 'nil'
164
+ end
165
+
166
+ # Source code for the contents of the node's object reference.
167
+ #
168
+ # @see http://haml.info/docs/yardoc/file.REFERENCE.html#object_reference_
169
+ # @return [String,nil] string source of object reference or `nil` if it has
170
+ # not been defined
171
+ def object_reference_source
172
+ (@value[:object_ref] if object_reference?) || ''
173
+ end
174
+
175
+ # Whether this node had a `<` after it signifying that outer whitespace
176
+ # should be removed.
177
+ #
178
+ # @return [true,false]
179
+ def remove_inner_whitespace?
180
+ @value[:nuke_inner_whitespace]
181
+ end
182
+
183
+ # Whether this node had a `>` after it signifying that outer whitespace
184
+ # should be removed.
185
+ #
186
+ # @return [true,false]
187
+ def remove_outer_whitespace?
188
+ @value[:nuke_inner_whitespace]
189
+ end
190
+
191
+ # Returns the script source that will be evaluated to produce this tag's
192
+ # inner content, if any.
193
+ #
194
+ # @return [String]
195
+ def script
196
+ (@value[:value] if @value[:parse]) || ''
197
+ end
198
+
199
+ # Returns the static inner content for this tag.
200
+ #
201
+ # If this tag contains dynamic content of any kind, this will still return
202
+ # an empty string, and you'll have to use {#script} to obtain the source.
203
+ #
204
+ # @return [String]
205
+ def text
206
+ (@value[:value] if @value[:parse]) || ''
207
+ end
208
+
209
+ private
210
+
211
+ def parsed_attributes
212
+ HamlLint::RubyParser.new.parse(hash_attributes_source)
213
+ end
214
+
215
+ def existing_attributes
216
+ parsed_attributes.children.collect do |parsed_attribute|
217
+ parsed_attribute.children.first.to_a.first
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,58 @@
1
+ module HamlLint
2
+ # A miscellaneous set of utility functions.
3
+ module Utils
4
+ module_function
5
+
6
+ # Yields interpolated values within a block of filter text.
7
+ def extract_interpolated_values(filter_text)
8
+ Haml::Util.handle_interpolation(filter_text.dump) do |scan|
9
+ escape_count = (scan[2].size - 1) / 2
10
+ return unless escape_count.even? # rubocop:disable Lint/NonLocalExitFromIterator
11
+
12
+ dumped_interpolated_str = Haml::Util.balance(scan, '{', '}', 1)[0][0...-1]
13
+
14
+ # Hacky way to turn a dumped string back into a regular string
15
+ yield eval('"' + dumped_interpolated_str + '"') # rubocop:disable Eval
16
+ end
17
+ end
18
+
19
+ # Converts a string containing underscores/hyphens/spaces into CamelCase.
20
+ def camel_case(str)
21
+ str.split(/_|-| /).map { |part| part.sub(/^\w/) { |c| c.upcase } }.join
22
+ end
23
+
24
+ # Find all consecutive nodes satisfying the given {Proc} of a minimum size
25
+ # and yield each group.
26
+ #
27
+ # @param items [Array]
28
+ # @param min_size [Fixnum] minimum number of consecutive items before
29
+ # yielding
30
+ # @param satisfies [Proc] function that takes an item and returns true/false
31
+ def find_consecutive(items, min_size, satisfies)
32
+ current = -1
33
+
34
+ while (current += 1) < items.count
35
+ next unless satisfies[items[current]]
36
+
37
+ count = count_consecutive(items, current, satisfies)
38
+ next unless count >= min_size
39
+
40
+ # Yield the chunk of consecutive items
41
+ yield items[current...(current + count)]
42
+
43
+ current += count # Skip this patch of consecutive items to find more
44
+ end
45
+ end
46
+
47
+ # Count the number of consecutive items satisfying the given {Proc}.
48
+ #
49
+ # @param items [Array]
50
+ # @param offset [Fixnum] index to start searching
51
+ # @param satisfies [Proc] function to evaluate item with
52
+ def count_consecutive(items, offset, satisfies)
53
+ count = 1
54
+ count += 1 while (offset + count < items.count) && satisfies[items[offset + count]]
55
+ count
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,4 @@
1
+ # Defines the gem version.
2
+ module HamlLint
3
+ VERSION = '0.13.0'
4
+ end
data/lib/haml_lint.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'haml_lint/constants'
2
+ require 'haml_lint/exceptions'
3
+ require 'haml_lint/configuration'
4
+ require 'haml_lint/configuration_loader'
5
+ require 'haml_lint/parser'
6
+ require 'haml_lint/haml_visitor'
7
+ require 'haml_lint/lint'
8
+ require 'haml_lint/linter_registry'
9
+ require 'haml_lint/ruby_parser'
10
+ require 'haml_lint/linter'
11
+ require 'haml_lint/logger'
12
+ require 'haml_lint/reporter'
13
+ require 'haml_lint/report'
14
+ require 'haml_lint/file_finder'
15
+ require 'haml_lint/runner'
16
+ require 'haml_lint/utils'
17
+ require 'haml_lint/version'
18
+
19
+ require 'haml'
20
+
21
+ # Load all parse tree node classes
22
+ require 'haml_lint/tree/node'
23
+ require 'haml_lint/node_transformer'
24
+ Dir[File.expand_path('haml_lint/tree/*.rb', File.dirname(__FILE__))].each do |file|
25
+ require file
26
+ end
27
+
28
+ # Load all linters
29
+ Dir[File.expand_path('haml_lint/linter/*.rb', File.dirname(__FILE__))].each do |file|
30
+ require file
31
+ end
32
+
33
+ # Load all reporters
34
+ Dir[File.expand_path('haml_lint/reporter/*.rb', File.dirname(__FILE__))].each do |file|
35
+ require file
36
+ end