haml_lint 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/haml-lint +7 -0
- data/config/default.yml +91 -0
- data/lib/haml_lint/cli.rb +122 -0
- data/lib/haml_lint/configuration.rb +97 -0
- data/lib/haml_lint/configuration_loader.rb +68 -0
- data/lib/haml_lint/constants.rb +8 -0
- data/lib/haml_lint/exceptions.rb +15 -0
- data/lib/haml_lint/file_finder.rb +69 -0
- data/lib/haml_lint/haml_visitor.rb +36 -0
- data/lib/haml_lint/lint.rb +25 -0
- data/lib/haml_lint/linter/alt_text.rb +12 -0
- data/lib/haml_lint/linter/class_attribute_with_static_value.rb +51 -0
- data/lib/haml_lint/linter/classes_before_ids.rb +26 -0
- data/lib/haml_lint/linter/consecutive_comments.rb +20 -0
- data/lib/haml_lint/linter/consecutive_silent_scripts.rb +23 -0
- data/lib/haml_lint/linter/empty_script.rb +12 -0
- data/lib/haml_lint/linter/html_attributes.rb +14 -0
- data/lib/haml_lint/linter/implicit_div.rb +20 -0
- data/lib/haml_lint/linter/leading_comment_space.rb +14 -0
- data/lib/haml_lint/linter/line_length.rb +19 -0
- data/lib/haml_lint/linter/multiline_pipe.rb +43 -0
- data/lib/haml_lint/linter/multiline_script.rb +43 -0
- data/lib/haml_lint/linter/object_reference_attributes.rb +14 -0
- data/lib/haml_lint/linter/rubocop.rb +76 -0
- data/lib/haml_lint/linter/ruby_comments.rb +18 -0
- data/lib/haml_lint/linter/space_before_script.rb +52 -0
- data/lib/haml_lint/linter/space_inside_hash_attributes.rb +32 -0
- data/lib/haml_lint/linter/tag_name.rb +13 -0
- data/lib/haml_lint/linter/trailing_whitespace.rb +16 -0
- data/lib/haml_lint/linter/unnecessary_interpolation.rb +29 -0
- data/lib/haml_lint/linter/unnecessary_string_output.rb +39 -0
- data/lib/haml_lint/linter.rb +156 -0
- data/lib/haml_lint/linter_registry.rb +26 -0
- data/lib/haml_lint/logger.rb +107 -0
- data/lib/haml_lint/node_transformer.rb +28 -0
- data/lib/haml_lint/options.rb +89 -0
- data/lib/haml_lint/parser.rb +87 -0
- data/lib/haml_lint/rake_task.rb +107 -0
- data/lib/haml_lint/report.rb +16 -0
- data/lib/haml_lint/reporter/default_reporter.rb +39 -0
- data/lib/haml_lint/reporter/json_reporter.rb +44 -0
- data/lib/haml_lint/reporter.rb +36 -0
- data/lib/haml_lint/ruby_parser.rb +29 -0
- data/lib/haml_lint/runner.rb +76 -0
- data/lib/haml_lint/script_extractor.rb +181 -0
- data/lib/haml_lint/tree/comment_node.rb +5 -0
- data/lib/haml_lint/tree/doctype_node.rb +5 -0
- data/lib/haml_lint/tree/filter_node.rb +9 -0
- data/lib/haml_lint/tree/haml_comment_node.rb +18 -0
- data/lib/haml_lint/tree/node.rb +98 -0
- data/lib/haml_lint/tree/plain_node.rb +5 -0
- data/lib/haml_lint/tree/root_node.rb +5 -0
- data/lib/haml_lint/tree/script_node.rb +11 -0
- data/lib/haml_lint/tree/silent_script_node.rb +12 -0
- data/lib/haml_lint/tree/tag_node.rb +221 -0
- data/lib/haml_lint/utils.rb +58 -0
- data/lib/haml_lint/version.rb +4 -0
- data/lib/haml_lint.rb +36 -0
- 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,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,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
|
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
|