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
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
module HamlLint::RubyExtraction
|
|
5
5
|
# Extracts "chunks" of the haml file into instances of subclasses of HamlLint::RubyExtraction::BaseChunk.
|
|
6
6
|
#
|
|
7
|
-
# This is the first step of generating Ruby code from a
|
|
7
|
+
# This is the first step of generating Ruby code from a Haml file to then be processed by RuboCop.
|
|
8
8
|
# See HamlLint::RubyExtraction::BaseChunk for more details.
|
|
9
9
|
class ChunkExtractor
|
|
10
10
|
include HamlLint::HamlVisitor
|
|
11
11
|
|
|
12
|
-
attr_reader :script_output_prefix
|
|
12
|
+
attr_reader :script_output_prefix, :autocorrect
|
|
13
13
|
|
|
14
14
|
HAML_PARSER_INSTANCE = if Haml::VERSION >= '5.0.0'
|
|
15
15
|
::Haml::Parser.new({})
|
|
@@ -17,9 +17,14 @@ module HamlLint::RubyExtraction
|
|
|
17
17
|
::Haml::Parser.new('', {})
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# Haml strips newlines when handling multi-line statements (using pipes or trailing comma)
|
|
21
|
+
# We don't. So the regex must be fixed to correctly detect the start of the string.
|
|
22
|
+
BLOCK_KEYWORD_REGEX = Regexp.new(Haml::Parser::BLOCK_KEYWORD_REGEX.source.sub('^', '\A'))
|
|
23
|
+
|
|
24
|
+
def initialize(document, script_output_prefix:, autocorrect: nil)
|
|
21
25
|
@document = document
|
|
22
26
|
@script_output_prefix = script_output_prefix
|
|
27
|
+
@autocorrect = autocorrect
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
def extract
|
|
@@ -119,7 +124,7 @@ module HamlLint::RubyExtraction
|
|
|
119
124
|
def visit_script(node, &block)
|
|
120
125
|
raw_first_line = @original_haml_lines[node.line - 1]
|
|
121
126
|
|
|
122
|
-
# ==, !, !==, &, &== means interpolation (was needed before
|
|
127
|
+
# ==, !, !==, &, &== means interpolation (was needed before Haml 2.2... it's still supported)
|
|
123
128
|
# =, !=, &= mean actual ruby code is coming
|
|
124
129
|
# Anything else is interpolation
|
|
125
130
|
# The regex lists the case for Ruby Code. The 3 cases and making sure they are not followed by another = sign
|
|
@@ -164,20 +169,20 @@ module HamlLint::RubyExtraction
|
|
|
164
169
|
prev_chunk.node == node.parent
|
|
165
170
|
# When an outputting script is nested under another outputting script,
|
|
166
171
|
# we want to block them from being merged together by rubocop, because
|
|
167
|
-
# this doesn't make sense in
|
|
172
|
+
# this doesn't make sense in Haml.
|
|
168
173
|
# Example:
|
|
169
174
|
# = if this_is_short
|
|
170
175
|
# = this_is_short_too
|
|
171
176
|
# Could become (after RuboCop):
|
|
172
177
|
# HL.out = (HL.out = this_is_short_too if this_is_short)
|
|
173
|
-
# Or in (broken)
|
|
178
|
+
# Or in (broken) Haml style:
|
|
174
179
|
# = this_is_short_too = if this_is_short
|
|
175
180
|
# By forcing this to start a chunk, there will be extra placeholders which
|
|
176
181
|
# blocks rubocop from merging the lines.
|
|
177
182
|
must_start_chunk = true
|
|
178
183
|
elsif script_prefix != '='
|
|
179
184
|
# In the few cases where &= and != are used to start the script,
|
|
180
|
-
# We need to remember and put it back in the final
|
|
185
|
+
# We need to remember and put it back in the final Haml. Fusing scripts together
|
|
181
186
|
# would make that basically impossible. Instead, a script has a "first_output_prefix"
|
|
182
187
|
# field for this specific case
|
|
183
188
|
must_start_chunk = true
|
|
@@ -247,31 +252,29 @@ module HamlLint::RubyExtraction
|
|
|
247
252
|
def visit_tag(node)
|
|
248
253
|
indent = @original_haml_lines[node.line - 1].index(/\S/)
|
|
249
254
|
|
|
255
|
+
@ruby_chunks << PlaceholderMarkerChunk.new(node, 'tag', indent: indent)
|
|
256
|
+
|
|
257
|
+
current_line_index = visit_tag_attributes(node, indent: indent)
|
|
258
|
+
visit_tag_script(node, line_index: current_line_index, indent: indent)
|
|
259
|
+
|
|
250
260
|
# We don't want to use a block because assignments in a block are local to that block,
|
|
251
261
|
# so the semantics of the extracted ruby would be different from the one generated by
|
|
252
262
|
# Haml. Those differences can make some cops, such as UselessAssignment, have false
|
|
253
263
|
# positives
|
|
254
264
|
code = 'begin'
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
indent += 2
|
|
265
|
+
begin_chunk = AdHocChunk.new(node, [' ' * indent + code])
|
|
266
|
+
@ruby_chunks << begin_chunk
|
|
258
267
|
|
|
259
|
-
|
|
260
|
-
@ruby_chunks << tag_chunk
|
|
261
|
-
|
|
262
|
-
current_line_index = visit_tag_attributes(node, indent: indent)
|
|
263
|
-
visit_tag_script(node, line_index: current_line_index, indent: indent)
|
|
268
|
+
indent += 2
|
|
264
269
|
|
|
265
270
|
yield
|
|
266
271
|
|
|
267
272
|
indent -= 2
|
|
268
273
|
|
|
269
|
-
if @ruby_chunks.last.equal?(
|
|
274
|
+
if @ruby_chunks.last.equal?(begin_chunk)
|
|
270
275
|
# So there is nothing going "in" the tag, remove the wrapping "begin" and replace the PlaceholderMarkerChunk
|
|
271
276
|
# by one less indented
|
|
272
277
|
@ruby_chunks.pop
|
|
273
|
-
@ruby_chunks.pop
|
|
274
|
-
@ruby_chunks << PlaceholderMarkerChunk.new(node, 'tag', indent: indent)
|
|
275
278
|
else
|
|
276
279
|
@ruby_chunks << AdHocChunk.new(node,
|
|
277
280
|
[' ' * indent + 'ensure', ' ' * indent + ' HL.noop', ' ' * indent + 'end'],
|
|
@@ -297,7 +300,13 @@ module HamlLint::RubyExtraction
|
|
|
297
300
|
end
|
|
298
301
|
|
|
299
302
|
if attributes_code&.start_with?('{')
|
|
300
|
-
#
|
|
303
|
+
# HTML-style (parens) attributes, e.g. %div(foo=foo), arrive as a synthesized hash string
|
|
304
|
+
# like '{"foo" => foo,}'. That text isn't present verbatim in the Haml, so we can't map
|
|
305
|
+
# RuboCop corrections back and intentionally don't autocorrect it. We still extract the
|
|
306
|
+
# attribute *values* as a non-correctable chunk so RuboCop sees variables used here;
|
|
307
|
+
# otherwise they look unused (false Lint/UselessAssignment, plus unsafe autocorrect that
|
|
308
|
+
# removes the assignment). See issue #648.
|
|
309
|
+
add_html_attributes_value_usage(node, attributes_code, indent: indent)
|
|
301
310
|
attributes_code = nil
|
|
302
311
|
end
|
|
303
312
|
|
|
@@ -342,6 +351,22 @@ module HamlLint::RubyExtraction
|
|
|
342
351
|
final_line_index
|
|
343
352
|
end
|
|
344
353
|
|
|
354
|
+
# For HTML-style (parens) attributes, adds the attribute value expressions as a
|
|
355
|
+
# non-autocorrectable chunk purely so RuboCop counts the variables/methods used there.
|
|
356
|
+
def add_html_attributes_value_usage(node, attributes_code, indent:)
|
|
357
|
+
ast = parse_ruby(attributes_code)
|
|
358
|
+
return unless ast&.type == :hash
|
|
359
|
+
|
|
360
|
+
value_sources = ast.children.filter_map do |pair|
|
|
361
|
+
next unless pair.type == :pair
|
|
362
|
+
pair.children[1]&.source
|
|
363
|
+
end
|
|
364
|
+
return if value_sources.empty?
|
|
365
|
+
|
|
366
|
+
lines = value_sources.map { |value| "#{' ' * indent}#{script_output_prefix}#{value}" }
|
|
367
|
+
@ruby_chunks << AdHocChunk.new(node, lines)
|
|
368
|
+
end
|
|
369
|
+
|
|
345
370
|
# Visiting the script besides tag. The part to the right of the equal sign of
|
|
346
371
|
# lines looking like ` %div= foo(bar)`
|
|
347
372
|
def visit_tag_script(node, line_index:, indent:)
|
|
@@ -381,7 +406,7 @@ module HamlLint::RubyExtraction
|
|
|
381
406
|
end
|
|
382
407
|
end
|
|
383
408
|
|
|
384
|
-
# Visiting a
|
|
409
|
+
# Visiting a Haml filter. Lines looking like ` :javascript` and the following lines
|
|
385
410
|
# that are nested.
|
|
386
411
|
def visit_filter(node)
|
|
387
412
|
# For unknown reasons, haml doesn't escape interpolations in filters.
|
|
@@ -389,10 +414,12 @@ module HamlLint::RubyExtraction
|
|
|
389
414
|
filter_name_indent = @original_haml_lines[node.line - 1].index(/\S/)
|
|
390
415
|
if node.filter_type == 'ruby'
|
|
391
416
|
# The indentation in node.text is normalized, so that at least one line
|
|
392
|
-
# is indented by 0.
|
|
393
|
-
|
|
417
|
+
# is indented by 0. Split("\n", -1) + pop keeps trailing blank lines (plain split drops them),
|
|
418
|
+
# which the end marker needs so it doesn't land right after the last code line.
|
|
419
|
+
lines = node.text.split("\n", -1)
|
|
420
|
+
lines.pop
|
|
394
421
|
lines.map! do |line|
|
|
395
|
-
if line
|
|
422
|
+
if !/\S/.match?(line)
|
|
396
423
|
# whitespace or empty
|
|
397
424
|
''
|
|
398
425
|
else
|
|
@@ -494,14 +521,14 @@ module HamlLint::RubyExtraction
|
|
|
494
521
|
# The first and last lines may not be the complete lines from the Haml, only the Ruby parts
|
|
495
522
|
# and the indentation between the first and last list.
|
|
496
523
|
|
|
497
|
-
#
|
|
524
|
+
# Haml transforms the ruby code in many ways as it parses a document. Often removing lines and/or
|
|
498
525
|
# indentation. This is quite annoying for us since we want the exact layout of the code to analyze it.
|
|
499
526
|
#
|
|
500
527
|
# This function receives the code as haml provides it and the line where it starts. It returns
|
|
501
528
|
# the actual code as it is in the haml file, keeping breaks and indentation for the following lines.
|
|
502
529
|
# In addition, the start position of the code in the first line.
|
|
503
530
|
#
|
|
504
|
-
# The rules for handling multiline code in
|
|
531
|
+
# The rules for handling multiline code in Haml are as follow:
|
|
505
532
|
# * if the line being processed ends with a space and a pipe, then append to the line (without
|
|
506
533
|
# newlines) every following lines that also end with a space and a pipe. This means the last line of
|
|
507
534
|
# the "block" also needs a pipe at the end.
|
|
@@ -548,7 +575,7 @@ module HamlLint::RubyExtraction
|
|
|
548
575
|
joined_lines = lines.join("\n")
|
|
549
576
|
|
|
550
577
|
if haml_processed_ruby_code.include?("\n")
|
|
551
|
-
haml_processed_ruby_code = haml_processed_ruby_code.
|
|
578
|
+
haml_processed_ruby_code = haml_processed_ruby_code.tr("\n", ' ')
|
|
552
579
|
end
|
|
553
580
|
|
|
554
581
|
haml_processed_ruby_code.split(/[, ]/)
|
|
@@ -600,9 +627,12 @@ module HamlLint::RubyExtraction
|
|
|
600
627
|
return [char_index, [haml_processed_ruby_code]]
|
|
601
628
|
end
|
|
602
629
|
|
|
603
|
-
|
|
630
|
+
# The +1 is for the closing brace, which we need
|
|
631
|
+
min_non_white_chars_to_add = haml_processed_ruby_code.scan(/\S/).size + 1
|
|
604
632
|
|
|
605
|
-
regexp = HamlLint::Utils.regexp_for_parts(
|
|
633
|
+
regexp = HamlLint::Utils.regexp_for_parts(
|
|
634
|
+
haml_processed_ruby_code.split(/\s+/), '\\s+', prefix: '\s*', suffix: '\s*(?=[)}])'
|
|
635
|
+
)
|
|
606
636
|
|
|
607
637
|
joined_lines = first_line.rstrip
|
|
608
638
|
process_multiline!(joined_lines)
|
|
@@ -624,7 +654,7 @@ module HamlLint::RubyExtraction
|
|
|
624
654
|
|
|
625
655
|
first_line_offset = match.begin(0)
|
|
626
656
|
raw_ruby = match[0]
|
|
627
|
-
ruby_lines = raw_ruby.split("\n")
|
|
657
|
+
ruby_lines = raw_ruby.split("\n", -1)
|
|
628
658
|
|
|
629
659
|
[first_line_offset, ruby_lines]
|
|
630
660
|
end
|
|
@@ -632,8 +662,19 @@ module HamlLint::RubyExtraction
|
|
|
632
662
|
def wrap_lines(lines, wrap_depth)
|
|
633
663
|
lines = lines.dup
|
|
634
664
|
wrapping_prefix = 'W' * (wrap_depth - 1) + '('
|
|
665
|
+
|
|
666
|
+
# Strip leading/trailing space only when linting (not autocorrecting) to avoid SpaceInsideParens violations.
|
|
667
|
+
# Preserve spaces for fully nested attribute hashes that need no adjustment.
|
|
668
|
+
# Preserve spaces during autocorrect, so RuboCop can fix them and map corrections back correctly.
|
|
669
|
+
unless lines[0] =~ /^\s*$/ || autocorrect
|
|
670
|
+
leading_spaces = lines[0][/^\s*/].length
|
|
671
|
+
lines = lines.map { |line| line.gsub(/^\s{,#{leading_spaces}}/, '') }
|
|
672
|
+
lines[-1] = lines[-1].gsub(/\s+\z/, '')
|
|
673
|
+
end
|
|
674
|
+
|
|
635
675
|
lines[0] = wrapping_prefix + lines[0]
|
|
636
676
|
lines[-1] = lines[-1] + ')'
|
|
677
|
+
|
|
637
678
|
lines
|
|
638
679
|
end
|
|
639
680
|
|
|
@@ -673,7 +714,7 @@ module HamlLint::RubyExtraction
|
|
|
673
714
|
|
|
674
715
|
def self.anonymous_block?(code)
|
|
675
716
|
# Don't start with a comment and end with a `do`
|
|
676
|
-
#
|
|
717
|
+
# Definitely not perfect for the comment handling, but otherwise a more advanced parsing system is needed.
|
|
677
718
|
# Move the comment to its own line if it's annoying.
|
|
678
719
|
code !~ /\A\s*#/ &&
|
|
679
720
|
code =~ /\bdo\s*(\|[^|]*\|\s*)?(#.*)?\z/
|
|
@@ -686,12 +727,12 @@ module HamlLint::RubyExtraction
|
|
|
686
727
|
|
|
687
728
|
LOOP_KEYWORDS = %w[for until while].freeze
|
|
688
729
|
def self.block_keyword(code)
|
|
689
|
-
# Need to handle 'for'/'while' since regex stolen from
|
|
730
|
+
# Need to handle 'for'/'while' since regex stolen from Haml parser doesn't
|
|
690
731
|
if (keyword = code[/\A\s*([^\s]+)\s+/, 1]) && LOOP_KEYWORDS.include?(keyword)
|
|
691
732
|
return keyword
|
|
692
733
|
end
|
|
693
734
|
|
|
694
|
-
return unless keyword = code.scan(
|
|
735
|
+
return unless keyword = code.scan(BLOCK_KEYWORD_REGEX)[0]
|
|
695
736
|
keyword[0] || keyword[1]
|
|
696
737
|
end
|
|
697
738
|
end
|
|
@@ -9,7 +9,7 @@ module HamlLint::RubyExtraction
|
|
|
9
9
|
# * Preprocess the chunks to cleanup/fuse some of them.
|
|
10
10
|
# * Generates the extracted ruby code from the Chunks.
|
|
11
11
|
# * Handles the markers (see below)
|
|
12
|
-
# * Use the chunks to transfer corrections from corrected Ruby code back to
|
|
12
|
+
# * Use the chunks to transfer corrections from corrected Ruby code back to Haml
|
|
13
13
|
#
|
|
14
14
|
# The generated Ruby code uses markers to wrap around the Ruby code from the chunks.
|
|
15
15
|
# Those markers look like function calls, like: `haml_lint_marker_1`, so are valid ruby.
|
|
@@ -22,14 +22,18 @@ module HamlLint::RubyExtraction
|
|
|
22
22
|
# @return [String] The prefix used for markers in the Ruby code
|
|
23
23
|
attr_reader :marker_prefix
|
|
24
24
|
|
|
25
|
-
# @return [Array<String>] The ruby lines after extraction from
|
|
25
|
+
# @return [Array<String>] The ruby lines after extraction from Haml (before RuboCop)
|
|
26
26
|
attr_reader :assembled_ruby_lines
|
|
27
27
|
|
|
28
28
|
# @return [Array<String>] The ruby lines after correction by RuboCop
|
|
29
29
|
attr_reader :corrected_ruby_lines
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
# @return [Symbol, nil] The autocorrect mode (:safe, :all, or nil)
|
|
32
|
+
attr_reader :autocorrect
|
|
33
|
+
|
|
34
|
+
def initialize(document, autocorrect:)
|
|
32
35
|
@document = document
|
|
36
|
+
@autocorrect = autocorrect
|
|
33
37
|
@ruby_chunks = nil
|
|
34
38
|
@assembled_ruby_lines = nil
|
|
35
39
|
@corrected_ruby_lines = nil
|
|
@@ -46,7 +50,8 @@ module HamlLint::RubyExtraction
|
|
|
46
50
|
pick_a_script_output_prefix
|
|
47
51
|
|
|
48
52
|
@ruby_chunks = HamlLint::RubyExtraction::ChunkExtractor.new(@document,
|
|
49
|
-
script_output_prefix: @script_output_prefix
|
|
53
|
+
script_output_prefix: @script_output_prefix,
|
|
54
|
+
autocorrect: autocorrect).extract
|
|
50
55
|
preprocess_chunks
|
|
51
56
|
|
|
52
57
|
@assembled_ruby_lines = []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HamlLint::RubyExtraction
|
|
4
|
-
#
|
|
4
|
+
# Haml adds a `end` when code gets outdented. We need to add that to the Ruby too, this
|
|
5
5
|
# is the chunk for it.
|
|
6
6
|
# However:
|
|
7
7
|
# * we can't apply fixes to it, so there are no markers
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HamlLint::RubyExtraction
|
|
4
|
-
# Chunk for dealing with every
|
|
4
|
+
# Chunk for dealing with every Haml filter other than `:ruby`
|
|
5
5
|
# The generated Ruby for these is just a HEREDOC, so interpolation is corrected at
|
|
6
6
|
# the same time by RuboCop.
|
|
7
7
|
class NonRubyFilterChunk < BaseChunk
|
|
@@ -14,7 +14,7 @@ module HamlLint::RubyExtraction
|
|
|
14
14
|
to_content_lines = to_ruby_lines[1...-1]
|
|
15
15
|
|
|
16
16
|
to_haml_lines = to_content_lines.map do |line|
|
|
17
|
-
if line
|
|
17
|
+
if !/\S/.match?(line)
|
|
18
18
|
# whitespace or empty
|
|
19
19
|
''
|
|
20
20
|
else
|
|
@@ -5,7 +5,7 @@ module HamlLint::RubyExtraction
|
|
|
5
5
|
# transfer the corrections it receives to the indentation of the associated lines.
|
|
6
6
|
#
|
|
7
7
|
# Also used so that Rubocop doesn't think that there is nothing in `if` and other such structures,
|
|
8
|
-
# so that it does corrections that make sense for the
|
|
8
|
+
# so that it does corrections that make sense for the Haml.
|
|
9
9
|
class PlaceholderMarkerChunk < BaseChunk
|
|
10
10
|
def initialize(node, marker_name, indent:, nb_lines: 1, **kwargs)
|
|
11
11
|
@marker_name = marker_name
|
|
@@ -19,7 +19,7 @@ module HamlLint::RubyExtraction
|
|
|
19
19
|
haml_lines[@haml_line_index - 1] = HamlLint::Utils.indent(haml_lines[@haml_line_index - 1], delta_indent)
|
|
20
20
|
|
|
21
21
|
to_haml_lines = to_ruby_lines.map do |line|
|
|
22
|
-
if line
|
|
22
|
+
if !/\S/.match?(line)
|
|
23
23
|
# whitespace or empty
|
|
24
24
|
''
|
|
25
25
|
else
|
|
@@ -18,7 +18,7 @@ module HamlLint::RubyExtraction
|
|
|
18
18
|
attr_reader :must_start_chunk
|
|
19
19
|
|
|
20
20
|
# @return [Array<Integer>] Line indexes to ignore when building the source_map. For examples,
|
|
21
|
-
# implicit `end` are on their own line in the Ruby file, but in the
|
|
21
|
+
# implicit `end` are on their own line in the Ruby file, but in the Haml, they are absent.
|
|
22
22
|
attr_reader :skip_line_indexes_in_source_map
|
|
23
23
|
|
|
24
24
|
# @return [HamlLint::RubyExtraction::BaseChunk] The previous chunk can affect how
|
|
@@ -113,7 +113,7 @@ module HamlLint::RubyExtraction
|
|
|
113
113
|
line_start_indexes_that_need_pipes = []
|
|
114
114
|
haml_output_prefix = first_output_haml_prefix
|
|
115
115
|
to_haml_lines = to_ruby_lines.map.with_index do |line, i| # rubocop:disable Metrics/BlockLength
|
|
116
|
-
if line
|
|
116
|
+
if !/\S/.match?(line)
|
|
117
117
|
# whitespace or empty lines, we don't want any indentation
|
|
118
118
|
''
|
|
119
119
|
elsif statement_start_line_indexes.include?(i)
|
data/lib/haml_lint/runner.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'parallel'
|
|
4
4
|
|
|
5
|
+
require_relative 'source'
|
|
6
|
+
|
|
5
7
|
module HamlLint
|
|
6
8
|
# Responsible for running the applicable linters against the desired files.
|
|
7
9
|
class Runner
|
|
@@ -20,12 +22,14 @@ module HamlLint
|
|
|
20
22
|
# @return [HamlLint::Report] a summary of all lints found
|
|
21
23
|
def run(options = {})
|
|
22
24
|
@config = load_applicable_config(options)
|
|
23
|
-
@
|
|
24
|
-
@
|
|
25
|
+
@sources = extract_applicable_sources(config, options)
|
|
26
|
+
@options = options
|
|
27
|
+
@linter_selector = build_linter_selector
|
|
25
28
|
@fail_fast = options.fetch(:fail_fast, false)
|
|
26
29
|
@cache = {}
|
|
27
30
|
@autocorrect = options[:autocorrect]
|
|
28
31
|
@autocorrect_only = options[:autocorrect_only]
|
|
32
|
+
@autocorrect_stdout = options[:stdin] && options[:stderr]
|
|
29
33
|
|
|
30
34
|
report(options)
|
|
31
35
|
end
|
|
@@ -48,16 +52,41 @@ module HamlLint
|
|
|
48
52
|
# @return [true, false]
|
|
49
53
|
alias fail_fast? fail_fast
|
|
50
54
|
|
|
51
|
-
# The list of
|
|
55
|
+
# The list of sources to lint during this run.
|
|
52
56
|
#
|
|
53
|
-
# @return [Array<
|
|
54
|
-
attr_reader :
|
|
57
|
+
# @return [Array<HamlLint::Source>]
|
|
58
|
+
attr_reader :sources
|
|
55
59
|
|
|
56
60
|
# The selector for which linters to run during this run.
|
|
57
61
|
#
|
|
58
62
|
# @return [HamlLint::LinterSelector]
|
|
59
63
|
attr_reader :linter_selector
|
|
60
64
|
|
|
65
|
+
PARALLEL_LINTER_SELECTOR_THREAD_KEY = :haml_lint_runner_parallel_linter_selectors
|
|
66
|
+
|
|
67
|
+
# Returns a fresh selector for this run.
|
|
68
|
+
#
|
|
69
|
+
# LinterSelector memoizes linter instances, and linters mutate instance
|
|
70
|
+
# state while processing a document. JRuby runs Parallel.map in threads, so
|
|
71
|
+
# parallel jobs must not share a selector.
|
|
72
|
+
#
|
|
73
|
+
# @return [HamlLint::LinterSelector]
|
|
74
|
+
def build_linter_selector
|
|
75
|
+
HamlLint::LinterSelector.new(config, @options)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns a selector scoped to the current parallel worker.
|
|
79
|
+
#
|
|
80
|
+
# MRI runs Parallel.map in forked processes, so each worker can safely reuse
|
|
81
|
+
# its own linter instances. JRuby runs Parallel.map in threads, so this must
|
|
82
|
+
# be isolated per thread to avoid sharing mutable linter state.
|
|
83
|
+
#
|
|
84
|
+
# @return [HamlLint::LinterSelector]
|
|
85
|
+
def parallel_linter_selector
|
|
86
|
+
selectors = Thread.current[PARALLEL_LINTER_SELECTOR_THREAD_KEY] ||= {}.compare_by_identity
|
|
87
|
+
selectors[self] ||= build_linter_selector
|
|
88
|
+
end
|
|
89
|
+
|
|
61
90
|
# Returns the {HamlLint::Configuration} that should be used given the
|
|
62
91
|
# specified options.
|
|
63
92
|
#
|
|
@@ -79,18 +108,20 @@ module HamlLint
|
|
|
79
108
|
# Runs all provided linters using the specified config against the given
|
|
80
109
|
# file.
|
|
81
110
|
#
|
|
82
|
-
# @param
|
|
111
|
+
# @param source [HamlLint::Source] source to lint
|
|
83
112
|
# @param linter_selector [HamlLint::LinterSelector]
|
|
84
113
|
# @param config [HamlLint::Configuration]
|
|
85
|
-
def collect_lints(
|
|
114
|
+
def collect_lints(source, linter_selector, config)
|
|
86
115
|
begin
|
|
87
|
-
document = HamlLint::Document.new
|
|
116
|
+
document = HamlLint::Document.new source.contents, file: source.path,
|
|
117
|
+
config: config,
|
|
118
|
+
write_to_stdout: @autocorrect_stdout
|
|
88
119
|
rescue HamlLint::Exceptions::ParseError => e
|
|
89
|
-
return [HamlLint::Lint.new(HamlLint::Linter::Syntax.new(config),
|
|
120
|
+
return [HamlLint::Lint.new(HamlLint::Linter::Syntax.new(config), source.path,
|
|
90
121
|
e.line, e.to_s, :error)]
|
|
91
122
|
end
|
|
92
123
|
|
|
93
|
-
linters = linter_selector.linters_for_file(
|
|
124
|
+
linters = linter_selector.linters_for_file(source.path)
|
|
94
125
|
lint_arrays = []
|
|
95
126
|
|
|
96
127
|
if @autocorrect
|
|
@@ -116,6 +147,9 @@ module HamlLint
|
|
|
116
147
|
lint_arrays = []
|
|
117
148
|
|
|
118
149
|
autocorrecting_linters = linters.select(&:supports_autocorrect?)
|
|
150
|
+
.each_with_index
|
|
151
|
+
.sort_by { |linter, index| [linter.class.autocorrect_priority, index] }
|
|
152
|
+
.map(&:first)
|
|
119
153
|
lint_arrays << autocorrecting_linters.map do |linter|
|
|
120
154
|
linter.run(document, autocorrect: @autocorrect)
|
|
121
155
|
end
|
|
@@ -125,40 +159,46 @@ module HamlLint
|
|
|
125
159
|
lint_arrays
|
|
126
160
|
end
|
|
127
161
|
|
|
128
|
-
# Returns the list of
|
|
162
|
+
# Returns the list of sources that should be linted given the specified
|
|
129
163
|
# configuration and options.
|
|
130
164
|
#
|
|
131
165
|
# @param config [HamlLint::Configuration]
|
|
132
166
|
# @param options [Hash]
|
|
133
|
-
# @return [Array<
|
|
134
|
-
def
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
167
|
+
# @return [Array<HamlLint::Source>]
|
|
168
|
+
def extract_applicable_sources(config, options)
|
|
169
|
+
if options[:stdin]
|
|
170
|
+
[HamlLint::Source.new(io: $stdin, path: options[:stdin])]
|
|
171
|
+
else
|
|
172
|
+
included_patterns = options[:files]
|
|
173
|
+
excluded_patterns = config['exclude']
|
|
174
|
+
excluded_patterns += options.fetch(:excluded_files, [])
|
|
138
175
|
|
|
139
|
-
|
|
176
|
+
HamlLint::FileFinder.new(config).find(included_patterns, excluded_patterns).map do |file_path|
|
|
177
|
+
HamlLint::Source.new path: file_path
|
|
178
|
+
end
|
|
179
|
+
end
|
|
140
180
|
end
|
|
141
181
|
|
|
142
|
-
# Process the
|
|
182
|
+
# Process the sources and add them to the given report.
|
|
143
183
|
#
|
|
144
184
|
# @param report [HamlLint::Report]
|
|
145
185
|
# @return [void]
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
186
|
+
def process_sources(report)
|
|
187
|
+
sources.each do |source|
|
|
188
|
+
process_source(source, report)
|
|
149
189
|
break if report.failed? && fail_fast?
|
|
150
190
|
end
|
|
151
191
|
end
|
|
152
192
|
|
|
153
193
|
# Process a file and add it to the given report.
|
|
154
194
|
#
|
|
155
|
-
# @param
|
|
195
|
+
# @param source [HamlLint::Source] the source to process
|
|
156
196
|
# @param report [HamlLint::Report]
|
|
157
197
|
# @return [void]
|
|
158
|
-
def
|
|
159
|
-
lints = @cache[
|
|
198
|
+
def process_source(source, report)
|
|
199
|
+
lints = @cache[source.path] || collect_lints(source, linter_selector, config)
|
|
160
200
|
lints.each { |lint| report.add_lint(lint) }
|
|
161
|
-
report.finish_file(
|
|
201
|
+
report.finish_file(source.path, lints)
|
|
162
202
|
end
|
|
163
203
|
|
|
164
204
|
# Generates a report based on the given options.
|
|
@@ -168,9 +208,9 @@ module HamlLint
|
|
|
168
208
|
# @return [HamlLint::Report]
|
|
169
209
|
def report(options)
|
|
170
210
|
report = HamlLint::Report.new(reporter: options[:reporter], fail_level: options[:fail_level])
|
|
171
|
-
report.start(
|
|
211
|
+
report.start(sources.map(&:path))
|
|
172
212
|
warm_cache if options[:parallel]
|
|
173
|
-
|
|
213
|
+
process_sources(report)
|
|
174
214
|
report
|
|
175
215
|
end
|
|
176
216
|
|
|
@@ -178,9 +218,9 @@ module HamlLint
|
|
|
178
218
|
#
|
|
179
219
|
# @return [void]
|
|
180
220
|
def warm_cache
|
|
181
|
-
results = Parallel.map(
|
|
182
|
-
lints = collect_lints(
|
|
183
|
-
[
|
|
221
|
+
results = Parallel.map(sources) do |source|
|
|
222
|
+
lints = collect_lints(source, parallel_linter_selector, config)
|
|
223
|
+
[source.path, lints]
|
|
184
224
|
end
|
|
185
225
|
@cache = results.to_h
|
|
186
226
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HamlLint
|
|
4
|
+
# Wrapper class representing a single target for HamlLint::Runner to run against, comprised of a file path to
|
|
5
|
+
# eventually read from, and an optional IO argument to override with.
|
|
6
|
+
class Source
|
|
7
|
+
# @return [String] File path associated with the given IO object.
|
|
8
|
+
attr_reader :path
|
|
9
|
+
|
|
10
|
+
# Wraps an optional IO object and file path to a source object.
|
|
11
|
+
#
|
|
12
|
+
# @param [String] path
|
|
13
|
+
# @param [IO] io
|
|
14
|
+
def initialize(path: nil, io: nil)
|
|
15
|
+
@path = path
|
|
16
|
+
@file_path = File.expand_path(path) if path
|
|
17
|
+
@io = io
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [String] Contents of the given IO object.
|
|
21
|
+
def contents
|
|
22
|
+
@contents ||= @io&.read || File.read(@file_path)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -11,10 +11,11 @@ module HamlLint
|
|
|
11
11
|
expected_message = options[:message]
|
|
12
12
|
expected_severity = options[:severity]
|
|
13
13
|
expected_corrected = options[:corrected]
|
|
14
|
+
expected_correctable = options[:correctable]
|
|
14
15
|
|
|
15
16
|
match do |linter|
|
|
16
17
|
has_lints?(linter, expected_line, count, expected_message, expected_severity,
|
|
17
|
-
expected_corrected)
|
|
18
|
+
expected_corrected, expected_correctable)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
failure_message do |linter|
|
|
@@ -65,15 +66,16 @@ module HamlLint
|
|
|
65
66
|
(expected_severity ? " with severity '#{expected_severity}'" : '')
|
|
66
67
|
end
|
|
67
68
|
|
|
68
|
-
def has_lints?(linter, expected_line, count, expected_message, expected_severity, # rubocop:disable Metrics/ParameterLists
|
|
69
|
-
expected_corrected)
|
|
69
|
+
def has_lints?(linter, expected_line, count, expected_message, expected_severity, # rubocop:disable Metrics/ParameterLists,Naming
|
|
70
|
+
expected_corrected, expected_correctable)
|
|
70
71
|
if expected_line
|
|
71
72
|
has_expected_line_lints?(linter,
|
|
72
73
|
expected_line,
|
|
73
74
|
count,
|
|
74
75
|
expected_message,
|
|
75
76
|
expected_severity,
|
|
76
|
-
expected_corrected
|
|
77
|
+
expected_corrected,
|
|
78
|
+
expected_correctable)
|
|
77
79
|
elsif count
|
|
78
80
|
linter.lints.count == count
|
|
79
81
|
elsif expected_message
|
|
@@ -83,12 +85,13 @@ module HamlLint
|
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
87
|
|
|
86
|
-
def has_expected_line_lints?(linter, # rubocop:disable Metrics/ParameterLists
|
|
88
|
+
def has_expected_line_lints?(linter, # rubocop:disable Metrics/ParameterLists,Naming
|
|
87
89
|
expected_line,
|
|
88
90
|
count,
|
|
89
91
|
expected_message,
|
|
90
92
|
expected_severity,
|
|
91
|
-
expected_corrected
|
|
93
|
+
expected_corrected,
|
|
94
|
+
expected_correctable)
|
|
92
95
|
if count
|
|
93
96
|
multiple_lints_match_line?(linter, expected_line, count)
|
|
94
97
|
elsif expected_message
|
|
@@ -97,6 +100,8 @@ module HamlLint
|
|
|
97
100
|
lint_on_line_matches_severity?(linter, expected_line, expected_severity)
|
|
98
101
|
elsif !expected_corrected.nil?
|
|
99
102
|
lint_on_line_matches_corrected?(linter, expected_line, expected_corrected)
|
|
103
|
+
elsif !expected_correctable.nil?
|
|
104
|
+
lint_on_line_matches_correctable?(linter, expected_line, expected_correctable)
|
|
100
105
|
else
|
|
101
106
|
lint_lines(linter).include?(expected_line)
|
|
102
107
|
end
|
|
@@ -126,6 +131,12 @@ module HamlLint
|
|
|
126
131
|
.any? { |lint| lint.line == expected_line && lint.corrected == expected_corrected }
|
|
127
132
|
end
|
|
128
133
|
|
|
134
|
+
def lint_on_line_matches_correctable?(linter, expected_line, expected_correctable)
|
|
135
|
+
linter
|
|
136
|
+
.lints
|
|
137
|
+
.any? { |lint| lint.line == expected_line && lint.correctable == expected_correctable }
|
|
138
|
+
end
|
|
139
|
+
|
|
129
140
|
def lint_messages_match?(linter, expected_message)
|
|
130
141
|
# Using === to support regex to match anywhere in the string
|
|
131
142
|
lint_messages(linter).all? { |message| expected_message === message } # rubocop:disable Style/CaseEquality
|