haml_lint 0.53.0 → 0.76.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 +4 -4
- data/config/default.yml +17 -5
- data/config/forced_rubocop_config.yml +25 -13
- data/lib/haml_lint/adapter.rb +5 -4
- data/lib/haml_lint/cli.rb +7 -3
- data/lib/haml_lint/constants.rb +1 -1
- data/lib/haml_lint/directive.rb +2 -2
- data/lib/haml_lint/document.rb +14 -5
- data/lib/haml_lint/exceptions.rb +2 -2
- data/lib/haml_lint/extensions/haml_util_unescape_interpolation_tracking.rb +2 -2
- data/lib/haml_lint/haml_visitor.rb +1 -1
- data/lib/haml_lint/lint.rb +8 -3
- data/lib/haml_lint/linter/alignment_tabs.rb +21 -4
- data/lib/haml_lint/linter/class_attribute_with_static_value.rb +52 -12
- data/lib/haml_lint/linter/classes_before_ids.rb +15 -6
- data/lib/haml_lint/linter/consecutive_comments.rb +20 -1
- data/lib/haml_lint/linter/consecutive_silent_scripts.rb +68 -1
- data/lib/haml_lint/linter/empty_object_reference.rb +16 -1
- data/lib/haml_lint/linter/empty_script.rb +32 -2
- data/lib/haml_lint/linter/final_newline.rb +31 -7
- data/lib/haml_lint/linter/html_attributes.rb +71 -2
- data/lib/haml_lint/linter/id_names.rb +1 -1
- data/lib/haml_lint/linter/implicit_div.rb +14 -1
- data/lib/haml_lint/linter/indentation.rb +2 -2
- data/lib/haml_lint/linter/instance_variables.rb +28 -3
- data/lib/haml_lint/linter/leading_comment_space.rb +14 -2
- data/lib/haml_lint/linter/multiline_pipe.rb +3 -3
- data/lib/haml_lint/linter/multiline_script.rb +65 -5
- data/lib/haml_lint/linter/no_placeholders.rb +2 -2
- data/lib/haml_lint/linter/rubocop.rb +87 -76
- data/lib/haml_lint/linter/ruby_comments.rb +14 -4
- data/lib/haml_lint/linter/space_before_script.rb +37 -4
- data/lib/haml_lint/linter/space_inside_hash_attributes.rb +41 -3
- data/lib/haml_lint/linter/strict_locals.rb +69 -0
- data/lib/haml_lint/linter/tag_name.rb +15 -2
- data/lib/haml_lint/linter/trailing_empty_lines.rb +13 -1
- data/lib/haml_lint/linter/trailing_whitespace.rb +16 -3
- data/lib/haml_lint/linter/unescaped_html.rb +27 -0
- data/lib/haml_lint/linter/unnecessary_interpolation.rb +30 -3
- data/lib/haml_lint/linter/unnecessary_string_output.rb +108 -12
- data/lib/haml_lint/linter.rb +119 -5
- data/lib/haml_lint/node_transformer.rb +2 -2
- data/lib/haml_lint/options.rb +17 -3
- data/lib/haml_lint/rake_task.rb +3 -2
- data/lib/haml_lint/reporter/default_reporter.rb +1 -1
- data/lib/haml_lint/reporter/disabled_config_reporter.rb +29 -8
- data/lib/haml_lint/reporter/github_reporter.rb +50 -0
- data/lib/haml_lint/reporter/hash_reporter.rb +2 -0
- data/lib/haml_lint/reporter/json_reporter.rb +1 -1
- data/lib/haml_lint/reporter/progress_reporter.rb +2 -1
- data/lib/haml_lint/reporter/utils.rb +4 -2
- data/lib/haml_lint/reporter.rb +1 -1
- data/lib/haml_lint/ruby_extraction/base_chunk.rb +4 -4
- data/lib/haml_lint/ruby_extraction/chunk_extractor.rb +73 -32
- data/lib/haml_lint/ruby_extraction/coordinator.rb +9 -4
- data/lib/haml_lint/ruby_extraction/implicit_end_chunk.rb +1 -1
- data/lib/haml_lint/ruby_extraction/non_ruby_filter_chunk.rb +2 -2
- data/lib/haml_lint/ruby_extraction/placeholder_marker_chunk.rb +1 -1
- data/lib/haml_lint/ruby_extraction/ruby_filter_chunk.rb +1 -1
- data/lib/haml_lint/ruby_extraction/script_chunk.rb +2 -2
- data/lib/haml_lint/runner.rb +70 -30
- data/lib/haml_lint/source.rb +25 -0
- data/lib/haml_lint/spec/matchers/report_lint.rb +17 -6
- data/lib/haml_lint/spec/shared_rubocop_autocorrect_context.rb +34 -4
- data/lib/haml_lint/spec.rb +4 -4
- data/lib/haml_lint/tree/comment_node.rb +1 -1
- data/lib/haml_lint/tree/doctype_node.rb +1 -1
- data/lib/haml_lint/tree/filter_node.rb +14 -1
- data/lib/haml_lint/tree/haml_comment_node.rb +9 -2
- data/lib/haml_lint/tree/node.rb +4 -4
- data/lib/haml_lint/tree/root_node.rb +4 -4
- data/lib/haml_lint/tree/script_node.rb +1 -1
- data/lib/haml_lint/tree/silent_script_node.rb +1 -1
- data/lib/haml_lint/tree/tag_node.rb +69 -25
- data/lib/haml_lint/utils.rb +4 -31
- data/lib/haml_lint/version.rb +1 -1
- data/lib/haml_lint.rb +23 -23
- metadata +14 -10
|
@@ -11,32 +11,99 @@ module HamlLint
|
|
|
11
11
|
class Linter::UnnecessaryStringOutput < Linter
|
|
12
12
|
include LinterRegistry
|
|
13
13
|
|
|
14
|
+
supports_autocorrect(true)
|
|
15
|
+
autocorrect_safe(false)
|
|
16
|
+
|
|
14
17
|
MESSAGE = '`= "..."` should be rewritten as `...`'
|
|
15
18
|
|
|
16
19
|
def visit_tag(node)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
return unless tag_has_inline_script?(node) && inline_content_is_string?(node)
|
|
21
|
+
|
|
22
|
+
plain = safe_plain_text(node.script)
|
|
23
|
+
corrected = plain ? correct_tag(node, plain) : false
|
|
24
|
+
record_lint(node, MESSAGE, corrected: corrected)
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
def visit_script(node)
|
|
23
|
-
# Some script nodes created by the
|
|
28
|
+
# Some script nodes created by the Haml parser aren't actually script
|
|
24
29
|
# nodes declared via the `=` marker. Check for it.
|
|
25
|
-
return
|
|
30
|
+
return unless /\A\s*=/.match?(node.source_code)
|
|
31
|
+
return unless plain = safe_plain_text(node.script)
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
record_lint(node, MESSAGE)
|
|
29
|
-
end
|
|
33
|
+
record_lint(node, MESSAGE, corrected: correct_script(node, plain))
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
private
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
# Rewrites `%tag= "..."` as `%tag ...`, dropping the `=` marker and the
|
|
39
|
+
# surrounding quotes.
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean] whether a correction was applied
|
|
42
|
+
def correct_tag(node, plain)
|
|
43
|
+
marker = node.inline_marker_source.rstrip
|
|
44
|
+
# Only unwrap plain escaped output (`= "..."`); leave `!=`, `~`, and the
|
|
45
|
+
# whitespace-removal markers (`<`/`>`) untouched, as they change rendering.
|
|
46
|
+
return false unless /\A=\s*["']/.match?(marker)
|
|
47
|
+
|
|
48
|
+
index = node.line - 1
|
|
49
|
+
line = autocorrected_lines[index]
|
|
50
|
+
return false unless line.rstrip.end_with?(marker)
|
|
51
|
+
|
|
52
|
+
prefix = line.rstrip[0...(line.rstrip.length - marker.length)]
|
|
53
|
+
correct_line(index, "#{prefix} #{plain}")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Rewrites a standalone `= "..."` script as plain text, preserving the
|
|
57
|
+
# node's indentation.
|
|
58
|
+
#
|
|
59
|
+
# @return [Boolean] whether a correction was applied
|
|
60
|
+
def correct_script(node, plain)
|
|
61
|
+
index = node.line - 1
|
|
62
|
+
line = autocorrected_lines[index]
|
|
63
|
+
indentation = line[/\A\s*/]
|
|
64
|
+
correct_line(index, "#{indentation}#{plain}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns the Haml plain-text equivalent of a Ruby string literal script, or
|
|
68
|
+
# +nil+ when the script is not a string literal that can be safely unwrapped
|
|
69
|
+
# (i.e. unwrapping would change the rendered output).
|
|
70
|
+
#
|
|
71
|
+
# @return [String, nil]
|
|
72
|
+
def safe_plain_text(script)
|
|
73
|
+
return if script.include?("\n")
|
|
74
|
+
return unless tree = parse_ruby(script)
|
|
75
|
+
return unless %i[str dstr].include?(tree.type)
|
|
76
|
+
return unless safely_unwrappable?(tree)
|
|
77
|
+
|
|
78
|
+
plain_text_from(tree)
|
|
38
79
|
rescue ::Parser::SyntaxError
|
|
39
80
|
# Gracefully ignore syntax errors, as that's managed by a different linter
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Whether unwrapping the string literal into Haml plain text would preserve
|
|
85
|
+
# the rendered output.
|
|
86
|
+
#
|
|
87
|
+
# @return [Boolean]
|
|
88
|
+
def safely_unwrappable?(tree)
|
|
89
|
+
!starts_with_reserved_character?(tree.children.first) &&
|
|
90
|
+
!contains_escape_sequence?(tree) &&
|
|
91
|
+
!contains_significant_whitespace?(tree)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Reconstructs the Haml plain-text form of a string literal. Literal segments
|
|
95
|
+
# keep their text (with any `#{` escaped so Haml won't interpolate it), while
|
|
96
|
+
# interpolation segments are emitted verbatim in their `#{...}` form.
|
|
97
|
+
#
|
|
98
|
+
# @return [String]
|
|
99
|
+
def plain_text_from(tree)
|
|
100
|
+
string_segments(tree).map do |segment|
|
|
101
|
+
if segment.type == :str
|
|
102
|
+
segment.children.first.gsub('#{') { '\#{' }
|
|
103
|
+
else
|
|
104
|
+
segment.location.expression.source
|
|
105
|
+
end
|
|
106
|
+
end.join
|
|
40
107
|
end
|
|
41
108
|
|
|
42
109
|
# Returns whether a string starts with a character that would otherwise be
|
|
@@ -45,5 +112,34 @@ module HamlLint
|
|
|
45
112
|
string = stringish.respond_to?(:children) ? stringish.children.first : stringish
|
|
46
113
|
string =~ %r{\A\s*[/#-=%~]} if string.is_a?(String)
|
|
47
114
|
end
|
|
115
|
+
|
|
116
|
+
# The ordered segments of a string literal, including any interpolation.
|
|
117
|
+
# A plain `str` node has no interpolation, so it is its own only segment.
|
|
118
|
+
def string_segments(tree)
|
|
119
|
+
tree.type == :dstr ? tree.children : [tree]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns whether any literal portion of the string contains a backslash
|
|
123
|
+
# escape (e.g. `\n`, `\t`, `\u202F`). Such escapes are interpreted inside
|
|
124
|
+
# a Ruby string but would be emitted verbatim as Haml plain text, so the
|
|
125
|
+
# `= "..."` form is not equivalent to the unwrapped plain text.
|
|
126
|
+
def contains_escape_sequence?(tree)
|
|
127
|
+
string_segments(tree).any? do |segment|
|
|
128
|
+
segment.type == :str && segment.location.expression.source.include?('\\')
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Returns whether the string begins or ends with whitespace. Haml strips
|
|
133
|
+
# trailing whitespace from plain text (and leading whitespace denotes
|
|
134
|
+
# indentation), so unwrapping such a string would change the output.
|
|
135
|
+
def contains_significant_whitespace?(tree)
|
|
136
|
+
segments = string_segments(tree)
|
|
137
|
+
bounded_by_whitespace?(segments.first, /\A\s/) ||
|
|
138
|
+
bounded_by_whitespace?(segments.last, /\s\z/)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def bounded_by_whitespace?(segment, pattern)
|
|
142
|
+
segment.type == :str && segment.children.first.to_s.match?(pattern)
|
|
143
|
+
end
|
|
48
144
|
end
|
|
49
145
|
end
|
data/lib/haml_lint/linter.rb
CHANGED
|
@@ -37,7 +37,7 @@ module HamlLint
|
|
|
37
37
|
:error
|
|
38
38
|
)
|
|
39
39
|
rescue StandardError => e
|
|
40
|
-
msg = "Couldn't process the file"
|
|
40
|
+
msg = +"Couldn't process the file"
|
|
41
41
|
if @autocorrect
|
|
42
42
|
# Those lints related to auto-correction were not saved, so don't display them
|
|
43
43
|
@lints = []
|
|
@@ -56,13 +56,14 @@ module HamlLint
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Runs the linter against the given Haml document, raises if the file cannot be processed due to
|
|
59
|
-
# Syntax or
|
|
59
|
+
# Syntax or Haml-Lint internal errors. (For testing purposes)
|
|
60
60
|
#
|
|
61
61
|
# @param document [HamlLint::Document]
|
|
62
62
|
def run_or_raise(document, autocorrect: nil)
|
|
63
63
|
@document = document
|
|
64
64
|
@lints = []
|
|
65
65
|
@autocorrect = autocorrect
|
|
66
|
+
reset_autocorrect_state
|
|
66
67
|
visit(document.tree)
|
|
67
68
|
@lints
|
|
68
69
|
end
|
|
@@ -85,6 +86,30 @@ module HamlLint
|
|
|
85
86
|
self.class.supports_autocorrect?
|
|
86
87
|
end
|
|
87
88
|
|
|
89
|
+
# Returns whether this linter's autocorrect is safe. Defaults to true (safe)
|
|
90
|
+
# unless the linter declares otherwise via `autocorrect_safe(false)`.
|
|
91
|
+
#
|
|
92
|
+
# @return [Boolean]
|
|
93
|
+
def self.autocorrect_safe?
|
|
94
|
+
@autocorrect_safe != false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The autocorrect ordering priority for this linter. During autocorrect,
|
|
98
|
+
# linters with a lower priority run first and higher priority run later,
|
|
99
|
+
# against the same (already mutated) document. Linters with the same priority
|
|
100
|
+
# keep their default (alphabetical) order.
|
|
101
|
+
#
|
|
102
|
+
# Called with an argument (e.g. `autocorrect_priority(1)`) in a linter's
|
|
103
|
+
# top-level scope, it sets the priority; called with no argument it reads it.
|
|
104
|
+
# The default, when never set, is 0.
|
|
105
|
+
#
|
|
106
|
+
# @param value [Integer, nil] the new priority, or nil to read the current one
|
|
107
|
+
# @return [Integer]
|
|
108
|
+
def self.autocorrect_priority(value = nil)
|
|
109
|
+
@autocorrect_priority = value unless value.nil?
|
|
110
|
+
@autocorrect_priority || 0
|
|
111
|
+
end
|
|
112
|
+
|
|
88
113
|
private
|
|
89
114
|
|
|
90
115
|
attr_reader :config, :document
|
|
@@ -97,15 +122,104 @@ module HamlLint
|
|
|
97
122
|
@supports_autocorrect = value
|
|
98
123
|
end
|
|
99
124
|
|
|
125
|
+
# Linters can call autocorrect_safe(false) in their top-level scope to declare that their
|
|
126
|
+
# autocorrect is unsafe, meaning it only runs under `--auto-correct-all` (`:all`).
|
|
127
|
+
# The default, when not called, is safe.
|
|
128
|
+
#
|
|
129
|
+
# @params value [Boolean] The new value for autocorrect_safe
|
|
130
|
+
private_class_method def self.autocorrect_safe(value)
|
|
131
|
+
@autocorrect_safe = value
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Resets the per-run autocorrect bookkeeping. Linter instances are reused
|
|
135
|
+
# across files, so subclasses that accumulate extra autocorrect state during
|
|
136
|
+
# a traversal (e.g. a list of lines to merge or delete) must override this,
|
|
137
|
+
# calling `super`, to clear that state between files. Otherwise corrections
|
|
138
|
+
# from one file leak into the next.
|
|
139
|
+
def reset_autocorrect_state
|
|
140
|
+
@autocorrected_lines = nil
|
|
141
|
+
@autocorrect_changed = false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Whether the linter is currently allowed to apply autocorrections, given the active
|
|
145
|
+
# autocorrect mode and this linter's declared safety:
|
|
146
|
+
#
|
|
147
|
+
# * safe linters correct under both `:safe` and `:all`;
|
|
148
|
+
# * unsafe linters correct only under `:all`;
|
|
149
|
+
# * with no mode (`nil`) nothing is corrected (detection only).
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
def autocorrect?
|
|
153
|
+
case @autocorrect
|
|
154
|
+
when :all
|
|
155
|
+
true
|
|
156
|
+
when :safe
|
|
157
|
+
self.class.autocorrect_safe?
|
|
158
|
+
else
|
|
159
|
+
false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Applies a corrected, full-document source to the document through the single
|
|
164
|
+
# mutation path (`Document#change_source`), but only when the safety gate permits.
|
|
165
|
+
# No-ops otherwise; `change_source` itself also no-ops when the source is unchanged.
|
|
166
|
+
#
|
|
167
|
+
# @param new_source [String] the corrected Haml source
|
|
168
|
+
def apply_autocorrect(new_source)
|
|
169
|
+
return unless autocorrect?
|
|
170
|
+
document.change_source(new_source)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Lazily-initialized working copy of the document's source lines, used to
|
|
174
|
+
# accumulate line-level corrections during a single tree traversal. Editing
|
|
175
|
+
# this copy (rather than calling `apply_autocorrect` per node) avoids
|
|
176
|
+
# reparsing the document mid-walk, which would invalidate the tree being
|
|
177
|
+
# visited.
|
|
178
|
+
#
|
|
179
|
+
# @return [Array<String>]
|
|
180
|
+
def autocorrected_lines
|
|
181
|
+
@autocorrected_lines ||= document.source_lines.dup
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Replaces a single source line (0-indexed) in the working copy, but only
|
|
185
|
+
# when autocorrect is permitted and the new text actually differs.
|
|
186
|
+
#
|
|
187
|
+
# @param index [Integer] the 0-indexed line to replace
|
|
188
|
+
# @param new_text [String] the corrected line
|
|
189
|
+
# @return [Boolean] true if a change was recorded, false otherwise
|
|
190
|
+
def correct_line(index, new_text)
|
|
191
|
+
return false unless autocorrect?
|
|
192
|
+
return false if autocorrected_lines[index] == new_text
|
|
193
|
+
|
|
194
|
+
autocorrected_lines[index] = new_text
|
|
195
|
+
@autocorrect_changed = true
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Flushes any corrections accumulated via `correct_line` through the single
|
|
199
|
+
# mutation path, once, after the whole tree has been visited. Defined on the
|
|
200
|
+
# base so per-node linters need no boilerplate; no-ops for linters that never
|
|
201
|
+
# accumulated a change (including those that apply within `visit_root`).
|
|
202
|
+
#
|
|
203
|
+
# NOTE: a linter that overrides `after_visit_root` must call `super`, or
|
|
204
|
+
# accumulated corrections will not be applied.
|
|
205
|
+
def after_visit_root(_node)
|
|
206
|
+
return unless @autocorrect_changed
|
|
207
|
+
|
|
208
|
+
apply_autocorrect(autocorrected_lines.join("\n"))
|
|
209
|
+
end
|
|
210
|
+
|
|
100
211
|
# Record a lint for reporting back to the user.
|
|
101
212
|
#
|
|
102
213
|
# @param node_or_line [#line] line number or node to extract the line number from
|
|
103
214
|
# @param message [String] error/warning to display to the user
|
|
104
|
-
|
|
215
|
+
# @param corrected [Boolean] whether the lint was fixed by auto-correct
|
|
216
|
+
# @param correctable [Boolean] whether the lint can be fixed by auto-correct;
|
|
217
|
+
# defaults to whether this linter supports auto-correct at all
|
|
218
|
+
def record_lint(node_or_line, message, corrected: false, correctable: supports_autocorrect?)
|
|
105
219
|
line = node_or_line.is_a?(Integer) ? node_or_line : node_or_line.line
|
|
106
220
|
@lints << HamlLint::Lint.new(self, @document.file, line, message,
|
|
107
221
|
config.fetch('severity', :warning).to_sym,
|
|
108
|
-
corrected: corrected)
|
|
222
|
+
corrected: corrected, correctable: correctable)
|
|
109
223
|
end
|
|
110
224
|
|
|
111
225
|
# Parse Ruby code into an abstract syntax tree.
|
|
@@ -231,7 +345,7 @@ module HamlLint
|
|
|
231
345
|
# excess whitespace
|
|
232
346
|
@document.source_lines[(tag_node.line - 1)...(following_node_line(tag_node) - 1)]
|
|
233
347
|
.map do |line|
|
|
234
|
-
line.strip.
|
|
348
|
+
line.strip.delete_suffix('|').rstrip
|
|
235
349
|
end.join(' ')
|
|
236
350
|
end
|
|
237
351
|
end
|
|
@@ -4,7 +4,7 @@ module HamlLint
|
|
|
4
4
|
# Responsible for transforming {Haml::Parser::ParseNode} objects into
|
|
5
5
|
# corresponding {HamlLint::Tree::Node} objects.
|
|
6
6
|
#
|
|
7
|
-
# The parse tree generated by
|
|
7
|
+
# The parse tree generated by Haml has a number of strange cases where certain
|
|
8
8
|
# types of nodes are created that don't necessarily correspond to what one
|
|
9
9
|
# would expect. This class is intended to isolate and handle these cases so
|
|
10
10
|
# that linters don't have to deal with them.
|
|
@@ -16,7 +16,7 @@ module HamlLint
|
|
|
16
16
|
@document = document
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
# Converts the given
|
|
19
|
+
# Converts the given Haml parse node into its corresponding Haml-Lint parse
|
|
20
20
|
# node.
|
|
21
21
|
#
|
|
22
22
|
# @param haml_node [Haml::Parser::ParseNode]
|
data/lib/haml_lint/options.rb
CHANGED
|
@@ -10,7 +10,7 @@ module HamlLint
|
|
|
10
10
|
# @param args [Array<String>] arguments passed via the command line
|
|
11
11
|
# @return [Hash] parsed options
|
|
12
12
|
def parse(args)
|
|
13
|
-
@options =
|
|
13
|
+
@options = default_options
|
|
14
14
|
|
|
15
15
|
OptionParser.new do |parser|
|
|
16
16
|
parser.banner = "Usage: #{APP_NAME} [options] [file1, file2, ...]"
|
|
@@ -32,7 +32,11 @@ module HamlLint
|
|
|
32
32
|
|
|
33
33
|
private
|
|
34
34
|
|
|
35
|
-
def
|
|
35
|
+
def default_options
|
|
36
|
+
{ parallel: true }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add_linter_options(parser) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
36
40
|
parser.on('--auto-gen-config', 'Generate a configuration file acting as a TODO list') do
|
|
37
41
|
@options[:auto_gen_config] = true
|
|
38
42
|
end
|
|
@@ -56,6 +60,10 @@ module HamlLint
|
|
|
56
60
|
@options[:parallel] = true
|
|
57
61
|
end
|
|
58
62
|
|
|
63
|
+
parser.on('--no-parallel', 'Disable parallel linter runs') do
|
|
64
|
+
@options[:parallel] = false
|
|
65
|
+
end
|
|
66
|
+
|
|
59
67
|
parser.on('-a', '--auto-correct', 'Auto-correct offenses (only when it’s safe)') do
|
|
60
68
|
@options[:autocorrect] = :safe
|
|
61
69
|
end
|
|
@@ -70,7 +78,13 @@ module HamlLint
|
|
|
70
78
|
@options[:autocorrect] ||= :safe
|
|
71
79
|
end
|
|
72
80
|
|
|
73
|
-
parser.on('--
|
|
81
|
+
parser.on('-s', '--stdin FILE', 'Pipe source from STDIN, using FILE in ' \
|
|
82
|
+
'offense when combined with --auto-correct and --stdin.') do |file_path|
|
|
83
|
+
@options[:stdin] = file_path
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
parser.on('--stderr', 'Write all output to stderr except for the autocorrected source. ' \
|
|
87
|
+
'This is especially useful when combined with --auto-correct and --stdin') do
|
|
74
88
|
@options[:stderr] = true
|
|
75
89
|
end
|
|
76
90
|
end
|
data/lib/haml_lint/rake_task.rb
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rake'
|
|
4
4
|
require 'rake/tasklib'
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
require_relative 'constants'
|
|
6
7
|
|
|
7
8
|
module HamlLint
|
|
8
9
|
# Rake task interface for haml-lint command line interface.
|
|
@@ -93,7 +94,7 @@ module HamlLint
|
|
|
93
94
|
|
|
94
95
|
task(name, [:files]) do |_task, task_args|
|
|
95
96
|
# Lazy-load so task doesn't affect Rakefile load time
|
|
96
|
-
|
|
97
|
+
require_relative 'cli'
|
|
97
98
|
|
|
98
99
|
run_cli(task_args)
|
|
99
100
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative 'progress_reporter'
|
|
4
4
|
|
|
5
5
|
module HamlLint
|
|
6
6
|
# Outputs a YAML configuration file based on existing violations.
|
|
7
7
|
class Reporter::DisabledConfigReporter < Reporter::ProgressReporter
|
|
8
|
-
|
|
8
|
+
DEFAULT_EXCLUDE_LIMIT = 15
|
|
9
|
+
|
|
10
|
+
HEADING_TEMPLATE =
|
|
9
11
|
['# This configuration was generated by',
|
|
10
|
-
'# `
|
|
11
|
-
|
|
12
|
+
'# `%<command>s`',
|
|
13
|
+
'# on %<timestamp>s using Haml-Lint version %<version>s.',
|
|
12
14
|
'# The point is for the user to remove these configuration records',
|
|
13
15
|
'# one by one as the lints are removed from the code base.',
|
|
14
16
|
'# Note that changes in the inspected code, or installation of new',
|
|
@@ -25,11 +27,12 @@ module HamlLint
|
|
|
25
27
|
# Create the reporter that will display the report and write the config.
|
|
26
28
|
#
|
|
27
29
|
# @param _log [HamlLint::Logger]
|
|
28
|
-
def initialize(log, limit:
|
|
30
|
+
def initialize(log, limit: DEFAULT_EXCLUDE_LIMIT, options: {})
|
|
29
31
|
super(log)
|
|
30
32
|
@linters_with_lints = Hash.new { |hash, key| hash[key] = [] }
|
|
31
33
|
@linters_lint_count = Hash.new(0)
|
|
32
34
|
@exclude_limit = limit
|
|
35
|
+
@options = options
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
# A hash of linters with the files that have that type of lint.
|
|
@@ -81,17 +84,35 @@ module HamlLint
|
|
|
81
84
|
|
|
82
85
|
private
|
|
83
86
|
|
|
87
|
+
# Reconstructs the CLI command used to generate this config.
|
|
88
|
+
#
|
|
89
|
+
# @return [String] the command string
|
|
90
|
+
def command
|
|
91
|
+
cmd = "#{HamlLint::APP_NAME} --auto-gen-config"
|
|
92
|
+
if @options[:auto_gen_exclude_limit]
|
|
93
|
+
cmd += " --auto-gen-exclude-limit #{@options[:auto_gen_exclude_limit]}"
|
|
94
|
+
end
|
|
95
|
+
cmd
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The heading comment for the generated config file.
|
|
99
|
+
#
|
|
100
|
+
# @return [String]
|
|
101
|
+
def heading
|
|
102
|
+
format(HEADING_TEMPLATE, command: command, timestamp: Time.now, version: HamlLint::VERSION)
|
|
103
|
+
end
|
|
104
|
+
|
|
84
105
|
# The contents of the generated configuration file based on captured lint.
|
|
85
106
|
#
|
|
86
107
|
# @return [String] a Yaml-formatted configuration file's contents
|
|
87
108
|
def config_file_contents
|
|
88
109
|
output = []
|
|
89
|
-
output <<
|
|
110
|
+
output << heading
|
|
90
111
|
output << 'linters:' if linters_with_lints.any?
|
|
91
|
-
linters_with_lints.each do |linter, files|
|
|
112
|
+
linters_with_lints.sort.each do |linter, files|
|
|
92
113
|
output << generate_config_for_linter(linter, files)
|
|
93
114
|
end
|
|
94
|
-
output.join("\n\n")
|
|
115
|
+
output.join("\n\n") + "\n"
|
|
95
116
|
end
|
|
96
117
|
|
|
97
118
|
# Constructs the configuration for excluding a linter in some files.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module HamlLint
|
|
4
|
+
# Outputs GitHub workflow commands for GitHub check annotations when run within GitHub actions.
|
|
5
|
+
class Reporter::GithubReporter < Reporter
|
|
6
|
+
# Characters to escape within a command message.
|
|
7
|
+
ESCAPE_MAP = { '%' => '%25', "\n" => '%0A', "\r" => '%0D' }.freeze
|
|
8
|
+
# Property values also escape the `:` and `,` that delimit the command.
|
|
9
|
+
PROPERTY_ESCAPE_MAP = ESCAPE_MAP.merge(':' => '%3A', ',' => '%2C').freeze
|
|
10
|
+
|
|
11
|
+
include Reporter::Utils
|
|
12
|
+
|
|
13
|
+
def added_lint(lint, report)
|
|
14
|
+
severity = lint.severity >= report.fail_level ? 'error' : 'warning'
|
|
15
|
+
print_workflow_command(lint, severity)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def display_report(report)
|
|
19
|
+
print_summary(report)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def print_workflow_command(lint, severity)
|
|
25
|
+
log.log "::#{severity} file=#{github_escape_property(lint.filename)}," \
|
|
26
|
+
"line=#{lint.line},title=#{github_escape_property(annotation_title(lint))}" \
|
|
27
|
+
"::#{github_escape(annotation_message(lint))}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Annotation title, naming the linter that produced the lint when known.
|
|
31
|
+
def annotation_title(lint)
|
|
32
|
+
lint.linter ? "haml-lint #{lint.linter.name}" : 'haml-lint'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Message body, prefixed with the location and linter so it stays useful in the plain log.
|
|
36
|
+
def annotation_message(lint)
|
|
37
|
+
location = "#{lint.filename}:#{lint.line}"
|
|
38
|
+
location += " #{lint.linter.name}:" if lint.linter
|
|
39
|
+
"#{location} #{lint.message}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def github_escape(string)
|
|
43
|
+
string.to_s.gsub(Regexp.union(ESCAPE_MAP.keys), ESCAPE_MAP)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def github_escape_property(string)
|
|
47
|
+
string.to_s.gsub(Regexp.union(PROPERTY_ESCAPE_MAP.keys), PROPERTY_ESCAPE_MAP)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -58,6 +58,8 @@ module HamlLint
|
|
|
58
58
|
def print_message(lint)
|
|
59
59
|
if lint.corrected
|
|
60
60
|
log.success('[Corrected] ', false)
|
|
61
|
+
elsif lint.correctable
|
|
62
|
+
log.info('[Correctable] ', false)
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
if lint.linter
|
|
@@ -96,7 +98,7 @@ module HamlLint
|
|
|
96
98
|
# Prints a summary of the number of lints found in a report.
|
|
97
99
|
#
|
|
98
100
|
# @param report [HamlLint::Report] the report to print
|
|
99
|
-
# @param is_append [Boolean] if this is appending to a line. Will
|
|
101
|
+
# @param is_append [Boolean] if this is appending to a line. Will prefix with ", ".
|
|
100
102
|
# @return [void]
|
|
101
103
|
def print_summary_lints(report, is_append:)
|
|
102
104
|
log.log ', ', false if is_append
|
|
@@ -114,7 +116,7 @@ module HamlLint
|
|
|
114
116
|
# Prints a summary of the number of lints corrected in a report.
|
|
115
117
|
#
|
|
116
118
|
# @param report [HamlLint::Report] the report to print
|
|
117
|
-
# @param is_append [Boolean] if this is appending to a line. Will
|
|
119
|
+
# @param is_append [Boolean] if this is appending to a line. Will prefix with ", ".
|
|
118
120
|
# @return [void]
|
|
119
121
|
def print_summary_corrected_lints(report, is_append:)
|
|
120
122
|
lint_count = report.lints.count(&:corrected)
|
data/lib/haml_lint/reporter.rb
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module HamlLint::RubyExtraction
|
|
4
4
|
# This is the base class for all of the Chunks of HamlLint::RubyExtraction.
|
|
5
|
-
# A Chunk represents a part of the
|
|
5
|
+
# A Chunk represents a part of the Haml file that HamlLint::Linter::RuboCop
|
|
6
6
|
# is processing and will insert some Ruby code in a file passed to RuboCop.
|
|
7
7
|
#
|
|
8
|
-
# There are chunks for most
|
|
8
|
+
# There are chunks for most Haml concepts, even if they don't represent Ruby
|
|
9
9
|
# code. For example, there is a chunk that represents a `%div` tag, which
|
|
10
10
|
# uses a `begin` in the generated Ruby to add indentation for the children
|
|
11
|
-
# of the %div in the Ruby file just like there is in the
|
|
11
|
+
# of the %div in the Ruby file just like there is in the Haml file.
|
|
12
12
|
class BaseChunk
|
|
13
13
|
COMMA_CHANGES_LINES = true
|
|
14
14
|
|
|
@@ -63,7 +63,7 @@ module HamlLint::RubyExtraction
|
|
|
63
63
|
transfer_correction_logic(coordinator, to_ruby_lines, haml_lines)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
# To be
|
|
66
|
+
# To be overridden by subclasses.
|
|
67
67
|
#
|
|
68
68
|
# Logic to transfer the corrections that turned from_ruby_lines into to_ruby_lines.
|
|
69
69
|
#
|