haml_lint 0.44.0 → 0.46.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/bin/haml-lint +1 -1
- data/config/default.yml +6 -28
- data/config/forced_rubocop_config.yml +156 -0
- data/lib/haml_lint/adapter/haml_4.rb +18 -0
- data/lib/haml_lint/adapter/haml_5.rb +11 -0
- data/lib/haml_lint/adapter/haml_6.rb +11 -0
- data/lib/haml_lint/cli.rb +8 -3
- data/lib/haml_lint/configuration_loader.rb +13 -12
- data/lib/haml_lint/document.rb +89 -8
- data/lib/haml_lint/exceptions.rb +6 -0
- data/lib/haml_lint/extensions/haml_util_unescape_interpolation_tracking.rb +35 -0
- data/lib/haml_lint/file_finder.rb +2 -2
- data/lib/haml_lint/lint.rb +10 -1
- data/lib/haml_lint/linter/final_newline.rb +4 -3
- data/lib/haml_lint/linter/implicit_div.rb +1 -1
- data/lib/haml_lint/linter/indentation.rb +3 -3
- data/lib/haml_lint/linter/no_placeholders.rb +18 -0
- data/lib/haml_lint/linter/rubocop.rb +351 -59
- data/lib/haml_lint/linter/space_before_script.rb +8 -10
- data/lib/haml_lint/linter/unnecessary_string_output.rb +1 -1
- data/lib/haml_lint/linter/view_length.rb +1 -1
- data/lib/haml_lint/linter.rb +56 -9
- data/lib/haml_lint/linter_registry.rb +3 -5
- data/lib/haml_lint/logger.rb +2 -2
- data/lib/haml_lint/options.rb +26 -2
- data/lib/haml_lint/rake_task.rb +2 -2
- data/lib/haml_lint/reporter/hash_reporter.rb +1 -3
- data/lib/haml_lint/reporter/offense_count_reporter.rb +1 -1
- data/lib/haml_lint/reporter/utils.rb +33 -4
- data/lib/haml_lint/ruby_extraction/ad_hoc_chunk.rb +20 -0
- data/lib/haml_lint/ruby_extraction/base_chunk.rb +113 -0
- data/lib/haml_lint/ruby_extraction/chunk_extractor.rb +504 -0
- data/lib/haml_lint/ruby_extraction/coordinator.rb +181 -0
- data/lib/haml_lint/ruby_extraction/haml_comment_chunk.rb +54 -0
- data/lib/haml_lint/ruby_extraction/implicit_end_chunk.rb +17 -0
- data/lib/haml_lint/ruby_extraction/interpolation_chunk.rb +26 -0
- data/lib/haml_lint/ruby_extraction/non_ruby_filter_chunk.rb +32 -0
- data/lib/haml_lint/ruby_extraction/placeholder_marker_chunk.rb +40 -0
- data/lib/haml_lint/ruby_extraction/ruby_filter_chunk.rb +33 -0
- data/lib/haml_lint/ruby_extraction/ruby_source.rb +5 -0
- data/lib/haml_lint/ruby_extraction/script_chunk.rb +132 -0
- data/lib/haml_lint/ruby_extraction/tag_attributes_chunk.rb +39 -0
- data/lib/haml_lint/ruby_extraction/tag_script_chunk.rb +30 -0
- data/lib/haml_lint/ruby_extractor.rb +11 -10
- data/lib/haml_lint/runner.rb +35 -3
- data/lib/haml_lint/spec/matchers/report_lint.rb +22 -7
- data/lib/haml_lint/spec/normalize_indent.rb +2 -2
- data/lib/haml_lint/spec/shared_linter_context.rb +9 -1
- data/lib/haml_lint/spec/shared_rubocop_autocorrect_context.rb +143 -0
- data/lib/haml_lint/spec.rb +1 -0
- data/lib/haml_lint/tree/filter_node.rb +10 -0
- data/lib/haml_lint/tree/node.rb +13 -4
- data/lib/haml_lint/tree/tag_node.rb +5 -9
- data/lib/haml_lint/utils.rb +130 -5
- data/lib/haml_lint/version.rb +1 -1
- data/lib/haml_lint/version_comparer.rb +25 -0
- data/lib/haml_lint.rb +12 -0
- metadata +25 -6
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# Chunk for haml comments. Lines like ` -# Some commenting!`.
|
5
|
+
# Only deals with indentation while correcting, but can also be fused to a ScriptChunk.
|
6
|
+
class HamlCommentChunk < BaseChunk
|
7
|
+
def fuse(following_chunk)
|
8
|
+
return unless following_chunk.is_a?(HamlCommentChunk)
|
9
|
+
|
10
|
+
# We only merge consecutive comments
|
11
|
+
# The main reason to want to at least merge those is
|
12
|
+
# so that an empty comment doesn't get removed by rubocop by mistake
|
13
|
+
return if @haml_line_index + 1 != following_chunk.haml_line_index
|
14
|
+
|
15
|
+
HamlCommentChunk.new(node, @ruby_lines + following_chunk.ruby_lines, end_marker_indent: end_marker_indent)
|
16
|
+
end
|
17
|
+
|
18
|
+
def fuse_script_chunk(following_chunk)
|
19
|
+
return if following_chunk.end_marker_indent.nil?
|
20
|
+
return if following_chunk.must_start_chunk
|
21
|
+
|
22
|
+
nb_blank_lines_between = following_chunk.haml_line_index - haml_line_index - nb_haml_lines
|
23
|
+
blank_lines = nb_blank_lines_between > 0 ? [''] * nb_blank_lines_between : []
|
24
|
+
new_lines = @ruby_lines + blank_lines + following_chunk.ruby_lines
|
25
|
+
|
26
|
+
source_map_skips = @skip_line_indexes_in_source_map
|
27
|
+
source_map_skips.concat(following_chunk.skip_line_indexes_in_source_map
|
28
|
+
.map { |i| i + @ruby_lines.size })
|
29
|
+
|
30
|
+
ScriptChunk.new(node,
|
31
|
+
new_lines,
|
32
|
+
haml_line_index: haml_line_index,
|
33
|
+
skip_line_indexes_in_source_map: source_map_skips,
|
34
|
+
end_marker_indent: following_chunk.end_marker_indent,
|
35
|
+
previous_chunk: previous_chunk)
|
36
|
+
end
|
37
|
+
|
38
|
+
def transfer_correction_logic(_coordinator, to_ruby_lines, haml_lines)
|
39
|
+
if to_ruby_lines.empty?
|
40
|
+
haml_lines.slice!(@haml_line_index..haml_end_line_index)
|
41
|
+
return
|
42
|
+
end
|
43
|
+
delta_indent = min_indent_of(to_ruby_lines) - min_indent_of(@ruby_lines)
|
44
|
+
|
45
|
+
HamlLint::Utils.map_subset!(haml_lines, @haml_line_index..haml_end_line_index) do |l|
|
46
|
+
HamlLint::Utils.indent(l, delta_indent)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def min_indent_of(lines)
|
51
|
+
lines.map { |l| l.index(/\S/) }.compact.min
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# HAML adds a `end` when code gets outdented. We need to add that to the Ruby too, this
|
5
|
+
# is the chunk for it.
|
6
|
+
# However:
|
7
|
+
# * we can't apply fixes to it, so there are no markers
|
8
|
+
# * this is a distinct class so that a ScriptChunk can fuse this ImplicitEnd into itself,
|
9
|
+
# So that we can generate bigger chunks of uninterrupted Ruby.
|
10
|
+
class ImplicitEndChunk < BaseChunk
|
11
|
+
def wrap_in_markers
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def transfer_correction(coordinator, all_corrected_ruby_lines, haml_lines); end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# Deals with interpolation within a plain text, filter, etc.
|
5
|
+
# Can only handling single line interpolation, so will be skipped if it takes
|
6
|
+
# more than one line or if the correction takes more than one line.
|
7
|
+
#
|
8
|
+
# Stores the char index to know where in the line to do the replacements.
|
9
|
+
class InterpolationChunk < BaseChunk
|
10
|
+
def initialize(*args, start_char_index:, **kwargs)
|
11
|
+
super(*args, **kwargs)
|
12
|
+
@start_char_index = start_char_index
|
13
|
+
end
|
14
|
+
|
15
|
+
def transfer_correction_logic(coordinator, to_ruby_lines, haml_lines)
|
16
|
+
return if @ruby_lines.size != 1
|
17
|
+
return if to_ruby_lines.size != 1
|
18
|
+
|
19
|
+
from_ruby_line = @ruby_lines.first.partition(coordinator.script_output_prefix).last
|
20
|
+
to_ruby_line = to_ruby_lines.first.partition(coordinator.script_output_prefix).last
|
21
|
+
|
22
|
+
haml_line = haml_lines[@haml_line_index]
|
23
|
+
haml_line[@start_char_index...(@start_char_index + from_ruby_line.size)] = to_ruby_line
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# Chunk for dealing with every HAML filter other than `:ruby`
|
5
|
+
# The generated Ruby for these is just a HEREDOC, so interpolation is corrected at
|
6
|
+
# the same time by RuboCop.
|
7
|
+
class NonRubyFilterChunk < BaseChunk
|
8
|
+
def transfer_correction_logic(_coordinator, to_ruby_lines, haml_lines)
|
9
|
+
delta_indent = to_ruby_lines.first.index(/\S/) - @ruby_lines.first.index(/\S/)
|
10
|
+
|
11
|
+
haml_lines[@haml_line_index] = HamlLint::Utils.indent(haml_lines[@haml_line_index], delta_indent)
|
12
|
+
|
13
|
+
# Ignoring the starting <<~HAML_LINT_FILTER and ending end
|
14
|
+
to_content_lines = to_ruby_lines[1...-1]
|
15
|
+
|
16
|
+
to_haml_lines = to_content_lines.map do |line|
|
17
|
+
if line !~ /\S/
|
18
|
+
# whitespace or empty
|
19
|
+
''
|
20
|
+
else
|
21
|
+
line
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
haml_lines[(@haml_line_index + 1)..haml_end_line_index] = to_haml_lines
|
26
|
+
end
|
27
|
+
|
28
|
+
def skip_line_indexes_in_source_map
|
29
|
+
[@ruby_lines.size - 1]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# This chunk just adds a marker (with a custom name) to the generated Ruby and only attempts to
|
5
|
+
# transfer the corrections it receives to the indentation of the associated lines.
|
6
|
+
#
|
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 HAML.
|
9
|
+
class PlaceholderMarkerChunk < BaseChunk
|
10
|
+
def initialize(node, marker_name, indent:, nb_lines: 1, **kwargs)
|
11
|
+
@marker_name = marker_name
|
12
|
+
@indent = indent
|
13
|
+
@nb_lines = nb_lines
|
14
|
+
super(node, nil, **kwargs.merge(end_marker_indent: @indent))
|
15
|
+
end
|
16
|
+
|
17
|
+
def full_assemble(coordinator)
|
18
|
+
@start_marker_line_number = coordinator.add_marker(@indent, name: @marker_name,
|
19
|
+
haml_line_index: haml_line_index)
|
20
|
+
end
|
21
|
+
|
22
|
+
def transfer_correction(coordinator, all_corrected_ruby_lines, haml_lines)
|
23
|
+
marker_index = coordinator.find_line_index_of_marker_in_corrections(@start_marker_line_number,
|
24
|
+
name: @marker_name)
|
25
|
+
new_indent = all_corrected_ruby_lines[marker_index].index(/\S/)
|
26
|
+
return if new_indent == @indent
|
27
|
+
(haml_line_index..haml_end_line_index).each do |i|
|
28
|
+
haml_lines[i] = HamlLint::Utils.indent(haml_lines[i], new_indent - @indent)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def haml_end_line_index
|
33
|
+
haml_line_index + @nb_lines - 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def end_marker_indent
|
37
|
+
@indent
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# Chunk for dealing with `:ruby` filter.
|
5
|
+
class RubyFilterChunk < BaseChunk
|
6
|
+
attr_reader :start_marker_indent
|
7
|
+
|
8
|
+
def initialize(*args, start_marker_indent:, **kwargs)
|
9
|
+
super(*args, **kwargs)
|
10
|
+
@start_marker_indent = start_marker_indent
|
11
|
+
end
|
12
|
+
|
13
|
+
def transfer_correction_logic(coordinator, to_ruby_lines, haml_lines)
|
14
|
+
marker_index = coordinator.find_line_index_of_marker_in_corrections(@start_marker_line_number)
|
15
|
+
|
16
|
+
new_name_indent = coordinator.corrected_ruby_lines[marker_index].index(/\S/)
|
17
|
+
|
18
|
+
delta_indent = new_name_indent - @start_marker_indent
|
19
|
+
haml_lines[@haml_line_index - 1] = HamlLint::Utils.indent(haml_lines[@haml_line_index - 1], delta_indent)
|
20
|
+
|
21
|
+
to_haml_lines = to_ruby_lines.map do |line|
|
22
|
+
if line !~ /\S/
|
23
|
+
# whitespace or empty
|
24
|
+
''
|
25
|
+
else
|
26
|
+
" #{line}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
haml_lines[@haml_line_index..haml_end_line_index] = to_haml_lines
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# Chunk for handling outputting and silent scripts, so ` = foo` and ` - bar`
|
5
|
+
# Does NOT handle a script beside a tag (ex: `%div= spam`)
|
6
|
+
class ScriptChunk < BaseChunk
|
7
|
+
MID_BLOCK_KEYWORDS = %w[else elsif when rescue ensure].freeze
|
8
|
+
|
9
|
+
# @return [Boolean] true if this ScriptChunk must be at the beginning of a chunk.
|
10
|
+
# This blocks this ScriptChunk from being fused to a ScriptChunk that is before it.
|
11
|
+
# Needed to handle some patterns of outputting script.
|
12
|
+
attr_reader :must_start_chunk
|
13
|
+
|
14
|
+
# @return [Array<Integer>] Line indexes to ignore when building the source_map. For examples,
|
15
|
+
# implicit `end` are on their own line in the Ruby file, but in the HAML, they are absent.
|
16
|
+
attr_reader :skip_line_indexes_in_source_map
|
17
|
+
|
18
|
+
# @return [HamlLint::RubyExtraction::BaseChunk] The previous chunk can affect how
|
19
|
+
# our starting marker must be indented.
|
20
|
+
attr_reader :previous_chunk
|
21
|
+
|
22
|
+
def initialize(*args, previous_chunk:, must_start_chunk: false,
|
23
|
+
skip_line_indexes_in_source_map: [], **kwargs)
|
24
|
+
super(*args, **kwargs)
|
25
|
+
@must_start_chunk = must_start_chunk
|
26
|
+
@skip_line_indexes_in_source_map = skip_line_indexes_in_source_map
|
27
|
+
@previous_chunk = previous_chunk
|
28
|
+
end
|
29
|
+
|
30
|
+
def fuse(following_chunk)
|
31
|
+
case following_chunk
|
32
|
+
when ScriptChunk
|
33
|
+
fuse_script_chunk(following_chunk)
|
34
|
+
when ImplicitEndChunk
|
35
|
+
fuse_implicit_end(following_chunk)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def fuse_script_chunk(following_chunk)
|
40
|
+
return if following_chunk.end_marker_indent.nil?
|
41
|
+
return if following_chunk.must_start_chunk
|
42
|
+
|
43
|
+
nb_blank_lines_between = following_chunk.haml_line_index - haml_line_index - nb_haml_lines
|
44
|
+
blank_lines = nb_blank_lines_between > 0 ? [''] * nb_blank_lines_between : []
|
45
|
+
new_lines = @ruby_lines + blank_lines + following_chunk.ruby_lines
|
46
|
+
|
47
|
+
source_map_skips = @skip_line_indexes_in_source_map
|
48
|
+
source_map_skips.concat(following_chunk.skip_line_indexes_in_source_map
|
49
|
+
.map { |i| i + @ruby_lines.size })
|
50
|
+
|
51
|
+
ScriptChunk.new(node,
|
52
|
+
new_lines,
|
53
|
+
haml_line_index: haml_line_index,
|
54
|
+
skip_line_indexes_in_source_map: source_map_skips,
|
55
|
+
end_marker_indent: following_chunk.end_marker_indent,
|
56
|
+
previous_chunk: previous_chunk)
|
57
|
+
end
|
58
|
+
|
59
|
+
def fuse_implicit_end(following_chunk)
|
60
|
+
new_lines = @ruby_lines.dup
|
61
|
+
last_non_empty_line_index = new_lines.rindex { |line| line =~ /\S/ }
|
62
|
+
|
63
|
+
# There is only one line in ImplicitEndChunk
|
64
|
+
new_end_index = last_non_empty_line_index + 1
|
65
|
+
new_lines.insert(new_end_index, following_chunk.ruby_lines.first)
|
66
|
+
source_map_skips = @skip_line_indexes_in_source_map + [new_end_index]
|
67
|
+
|
68
|
+
ScriptChunk.new(node,
|
69
|
+
new_lines,
|
70
|
+
haml_line_index: haml_line_index,
|
71
|
+
skip_line_indexes_in_source_map: source_map_skips,
|
72
|
+
end_marker_indent: following_chunk.end_marker_indent,
|
73
|
+
previous_chunk: previous_chunk)
|
74
|
+
end
|
75
|
+
|
76
|
+
def start_marker_indent
|
77
|
+
default_indent = super
|
78
|
+
default_indent += 2 if MID_BLOCK_KEYWORDS.include?(ChunkExtractor.block_keyword(ruby_lines.first))
|
79
|
+
[default_indent, previous_chunk&.end_marker_indent || previous_chunk&.start_marker_indent].compact.max
|
80
|
+
end
|
81
|
+
|
82
|
+
def transfer_correction_logic(coordinator, to_ruby_lines, haml_lines) # rubocop:disable Metrics
|
83
|
+
to_ruby_lines.reject! { |l| l.strip == 'end' }
|
84
|
+
|
85
|
+
output_comment_prefix = ' ' + coordinator.script_output_prefix.rstrip
|
86
|
+
to_ruby_lines.map! do |line|
|
87
|
+
if line.lstrip.start_with?('#' + output_comment_prefix)
|
88
|
+
line = line.dup
|
89
|
+
comment_index = line.index('#')
|
90
|
+
removal_start_index = comment_index + 1
|
91
|
+
removal_end_index = removal_start_index + output_comment_prefix.size
|
92
|
+
line[removal_start_index...removal_end_index] = ''
|
93
|
+
# It will be removed again below, but will know its suposed to be a =
|
94
|
+
line.insert(comment_index, coordinator.script_output_prefix)
|
95
|
+
end
|
96
|
+
line
|
97
|
+
end
|
98
|
+
|
99
|
+
continued_line_indent_delta = 2
|
100
|
+
|
101
|
+
to_haml_lines = to_ruby_lines.map.with_index do |line, i|
|
102
|
+
if line !~ /\S/
|
103
|
+
# whitespace or empty lines, we don't want any indentation
|
104
|
+
''
|
105
|
+
elsif line_starts_script?(to_ruby_lines, i)
|
106
|
+
code_start = line.index(/\S/)
|
107
|
+
if line[code_start..].start_with?(coordinator.script_output_prefix)
|
108
|
+
line = line.sub(coordinator.script_output_prefix, '')
|
109
|
+
continued_line_indent_delta = 2 - coordinator.script_output_prefix.size
|
110
|
+
"#{line[0...code_start]}= #{line[code_start..]}"
|
111
|
+
else
|
112
|
+
continued_line_indent_delta = 2
|
113
|
+
"#{line[0...code_start]}- #{line[code_start..]}"
|
114
|
+
end
|
115
|
+
else
|
116
|
+
HamlLint::Utils.indent(line, continued_line_indent_delta)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
haml_lines[@haml_line_index..haml_end_line_index] = to_haml_lines
|
121
|
+
end
|
122
|
+
|
123
|
+
def unfinished_script_line?(lines, line_index)
|
124
|
+
!!lines[line_index][/,[ \t]*\z/]
|
125
|
+
end
|
126
|
+
|
127
|
+
def line_starts_script?(lines, line_index)
|
128
|
+
return true if line_index == 0
|
129
|
+
!unfinished_script_line?(lines, line_index - 1)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# Chunk for handling the a tag attributes, such as `%div{style: 'yes_please'}`
|
5
|
+
class TagAttributesChunk < BaseChunk
|
6
|
+
def initialize(*args, indent_to_remove:, **kwargs)
|
7
|
+
super(*args, **kwargs)
|
8
|
+
@indent_to_remove = indent_to_remove
|
9
|
+
end
|
10
|
+
|
11
|
+
def transfer_correction_logic(_coordinator, to_ruby_lines, haml_lines)
|
12
|
+
affected_haml_lines = haml_lines[@haml_line_index..haml_end_line_index]
|
13
|
+
|
14
|
+
affected_haml = affected_haml_lines.join("\n")
|
15
|
+
|
16
|
+
from_ruby = unwrap(@ruby_lines).join("\n")
|
17
|
+
to_ruby = unwrap(to_ruby_lines).join("\n")
|
18
|
+
|
19
|
+
affected_start_index = affected_haml.index(from_ruby)
|
20
|
+
affected_end_index = affected_start_index + from_ruby.size
|
21
|
+
affected_haml[affected_start_index...affected_end_index] = to_ruby
|
22
|
+
|
23
|
+
haml_lines[@haml_line_index..haml_end_line_index] = affected_haml.split("\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
def unwrap(lines)
|
27
|
+
lines = lines.dup
|
28
|
+
lines[0] = lines[0].sub(/^\s*/, '').sub(/W+\(/, '')
|
29
|
+
lines[-1] = lines[-1].sub(/\)\s*\Z/, '')
|
30
|
+
|
31
|
+
if @indent_to_remove
|
32
|
+
HamlLint::Utils.map_after_first!(lines) do |line|
|
33
|
+
line.sub(/^ {1,#{@indent_to_remove}}/, '')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
lines
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint::RubyExtraction
|
4
|
+
# Chunk for handling outputting scripts after a tag, such as `%div= spam`
|
5
|
+
class TagScriptChunk < BaseChunk
|
6
|
+
def transfer_correction_logic(coordinator, to_ruby_lines, haml_lines) # rubocop:disable Metrics/AbcSize
|
7
|
+
# TODO: add checks that we have commas at the end of each line except the last one
|
8
|
+
|
9
|
+
from_ruby_line = @ruby_lines.first
|
10
|
+
to_ruby_line = to_ruby_lines.first
|
11
|
+
|
12
|
+
to_line_indent = to_ruby_line.index(/\S/)
|
13
|
+
|
14
|
+
from_ruby_line = from_ruby_line.sub(coordinator.script_output_prefix, '').sub(/^\s+/, '')
|
15
|
+
to_ruby_line = to_ruby_line.sub(coordinator.script_output_prefix, '').sub(/^\s+/, '')
|
16
|
+
|
17
|
+
affected_start_index = haml_lines[@haml_line_index].rindex(from_ruby_line)
|
18
|
+
|
19
|
+
haml_lines[@haml_line_index][affected_start_index..-1] = to_ruby_line
|
20
|
+
|
21
|
+
indent_delta = affected_start_index - coordinator.script_output_prefix.size - to_line_indent
|
22
|
+
|
23
|
+
HamlLint::Utils.map_after_first!(to_ruby_lines) do |line|
|
24
|
+
HamlLint::Utils.indent(line, indent_delta)
|
25
|
+
end
|
26
|
+
|
27
|
+
haml_lines[(@haml_line_index + 1)..haml_end_line_index] = to_ruby_lines[1..]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -24,7 +24,7 @@ module HamlLint
|
|
24
24
|
# The translation won't be perfect, and won't make any real sense, but the
|
25
25
|
# relationship between variable declarations/uses and the flow control graph
|
26
26
|
# will remain intact.
|
27
|
-
class RubyExtractor
|
27
|
+
class RubyExtractor
|
28
28
|
include HamlVisitor
|
29
29
|
|
30
30
|
# Stores the extracted source and a map of lines of generated source to the
|
@@ -73,8 +73,9 @@ module HamlLint
|
|
73
73
|
|
74
74
|
# Attributes can either be a method call or a literal hash, so wrap it
|
75
75
|
# in a method call itself in order to avoid having to differentiate the
|
76
|
-
# two.
|
77
|
-
|
76
|
+
# two. Use the tag name for the method to differentiate different tag types
|
77
|
+
# for RuboCop and prevent erroneous warnings.
|
78
|
+
add_line("#{node.tag_name}(#{attributes_code})", node)
|
78
79
|
end
|
79
80
|
|
80
81
|
check_tag_static_hash_source(node)
|
@@ -128,7 +129,7 @@ module HamlLint
|
|
128
129
|
def visit_filter(node)
|
129
130
|
if node.filter_type == 'ruby'
|
130
131
|
node.text.split("\n").each_with_index do |line, index|
|
131
|
-
add_line(line, node.line + index + 1, false)
|
132
|
+
add_line(line, node.line + index + 1, discard_blanks: false)
|
132
133
|
end
|
133
134
|
else
|
134
135
|
add_dummy_puts(node, ":#{node.filter_type}")
|
@@ -161,17 +162,17 @@ module HamlLint
|
|
161
162
|
@output_count += 1
|
162
163
|
end
|
163
164
|
|
164
|
-
def add_line(code, node_or_line, discard_blanks
|
165
|
+
def add_line(code, node_or_line, discard_blanks: true)
|
165
166
|
return if code.empty? && discard_blanks
|
166
167
|
|
167
168
|
indent_level = @indent_level
|
168
169
|
|
169
|
-
if node_or_line.respond_to?(:line)
|
170
|
+
if node_or_line.respond_to?(:line) && mid_block_keyword?(code)
|
170
171
|
# Since mid-block keywords are children of the corresponding start block
|
171
172
|
# keyword, we need to reduce their indentation level by 1. However, we
|
172
173
|
# don't do this unless this is an actual tag node (a raw line number
|
173
174
|
# means this came from a `:ruby` filter).
|
174
|
-
indent_level -= 1
|
175
|
+
indent_level -= 1
|
175
176
|
end
|
176
177
|
|
177
178
|
indent = (' ' * 2 * indent_level)
|
@@ -196,7 +197,7 @@ module HamlLint
|
|
196
197
|
end
|
197
198
|
|
198
199
|
def anonymous_block?(text)
|
199
|
-
text =~ /\bdo\s*(\|\s*[
|
200
|
+
text =~ /\bdo\s*(\|\s*[^|]*\s*\|)?(\s*#.*)?\z/
|
200
201
|
end
|
201
202
|
|
202
203
|
START_BLOCK_KEYWORDS = %w[if unless case begin for until while].freeze
|
@@ -212,8 +213,8 @@ module HamlLint
|
|
212
213
|
LOOP_KEYWORDS = %w[for until while].freeze
|
213
214
|
def block_keyword(text)
|
214
215
|
# Need to handle 'for'/'while' since regex stolen from HAML parser doesn't
|
215
|
-
if keyword = text[/\A\s*([^\s]+)\s+/, 1]
|
216
|
-
return keyword
|
216
|
+
if (keyword = text[/\A\s*([^\s]+)\s+/, 1]) && LOOP_KEYWORDS.include?(keyword)
|
217
|
+
return keyword
|
217
218
|
end
|
218
219
|
|
219
220
|
return unless keyword = text.scan(Haml::Parser::BLOCK_KEYWORD_REGEX)[0]
|
data/lib/haml_lint/runner.rb
CHANGED
@@ -24,6 +24,8 @@ module HamlLint
|
|
24
24
|
@linter_selector = HamlLint::LinterSelector.new(config, options)
|
25
25
|
@fail_fast = options.fetch(:fail_fast, false)
|
26
26
|
@cache = {}
|
27
|
+
@autocorrect = options[:autocorrect]
|
28
|
+
@autocorrect_only = options[:autocorrect_only]
|
27
29
|
|
28
30
|
report(options)
|
29
31
|
end
|
@@ -88,9 +90,39 @@ module HamlLint
|
|
88
90
|
e.line, e.to_s, :error)]
|
89
91
|
end
|
90
92
|
|
91
|
-
linter_selector.linters_for_file(file)
|
92
|
-
|
93
|
-
|
93
|
+
linters = linter_selector.linters_for_file(file)
|
94
|
+
lint_arrays = []
|
95
|
+
|
96
|
+
if @autocorrect
|
97
|
+
lint_arrays << autocorrect_document(document, linters)
|
98
|
+
end
|
99
|
+
|
100
|
+
unless @autocorrect_only
|
101
|
+
lint_arrays << linters.map do |linter|
|
102
|
+
linter.run(document)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
lint_arrays.flatten
|
106
|
+
end
|
107
|
+
|
108
|
+
# Out of the provided linters, runs those that support autocorrect
|
109
|
+
# against the specified document.
|
110
|
+
# Updates the document and returns the lints that were corrected.
|
111
|
+
#
|
112
|
+
# @param document [HamlLint::Document]
|
113
|
+
# @param linter_selector [HamlLint::LinterSelector]
|
114
|
+
# @return [Array<HamlLint::Lint>]
|
115
|
+
def autocorrect_document(document, linters)
|
116
|
+
lint_arrays = []
|
117
|
+
|
118
|
+
autocorrecting_linters = linters.select(&:supports_autocorrect?)
|
119
|
+
lint_arrays << autocorrecting_linters.map do |linter|
|
120
|
+
linter.run(document, autocorrect: @autocorrect)
|
121
|
+
end
|
122
|
+
|
123
|
+
document.write_to_disk!
|
124
|
+
|
125
|
+
lint_arrays
|
94
126
|
end
|
95
127
|
|
96
128
|
# Returns the list of files that should be linted given the specified
|
@@ -10,9 +10,11 @@ module HamlLint
|
|
10
10
|
expected_line = options[:line]
|
11
11
|
expected_message = options[:message]
|
12
12
|
expected_severity = options[:severity]
|
13
|
+
expected_corrected = options[:corrected]
|
13
14
|
|
14
15
|
match do |linter|
|
15
|
-
has_lints?(linter, expected_line, count, expected_message, expected_severity
|
16
|
+
has_lints?(linter, expected_line, count, expected_message, expected_severity,
|
17
|
+
expected_corrected)
|
16
18
|
end
|
17
19
|
|
18
20
|
failure_message do |linter|
|
@@ -63,13 +65,15 @@ module HamlLint
|
|
63
65
|
(expected_severity ? " with severity '#{expected_severity}'" : '')
|
64
66
|
end
|
65
67
|
|
66
|
-
def has_lints?(linter, expected_line, count, expected_message, expected_severity
|
68
|
+
def has_lints?(linter, expected_line, count, expected_message, expected_severity, # rubocop:disable Metrics/ParameterLists
|
69
|
+
expected_corrected)
|
67
70
|
if expected_line
|
68
71
|
has_expected_line_lints?(linter,
|
69
72
|
expected_line,
|
70
73
|
count,
|
71
74
|
expected_message,
|
72
|
-
expected_severity
|
75
|
+
expected_severity,
|
76
|
+
expected_corrected)
|
73
77
|
elsif count
|
74
78
|
linter.lints.count == count
|
75
79
|
elsif expected_message
|
@@ -79,17 +83,20 @@ module HamlLint
|
|
79
83
|
end
|
80
84
|
end
|
81
85
|
|
82
|
-
def has_expected_line_lints?(linter,
|
86
|
+
def has_expected_line_lints?(linter, # rubocop:disable Metrics/ParameterLists
|
83
87
|
expected_line,
|
84
88
|
count,
|
85
89
|
expected_message,
|
86
|
-
expected_severity
|
90
|
+
expected_severity,
|
91
|
+
expected_corrected)
|
87
92
|
if count
|
88
93
|
multiple_lints_match_line?(linter, expected_line, count)
|
89
94
|
elsif expected_message
|
90
95
|
lint_on_line_matches_message?(linter, expected_line, expected_message)
|
91
96
|
elsif expected_severity
|
92
97
|
lint_on_line_matches_severity?(linter, expected_line, expected_severity)
|
98
|
+
elsif !expected_corrected.nil?
|
99
|
+
lint_on_line_matches_corrected?(linter, expected_line, expected_corrected)
|
93
100
|
else
|
94
101
|
lint_lines(linter).include?(expected_line)
|
95
102
|
end
|
@@ -101,9 +108,10 @@ module HamlLint
|
|
101
108
|
end
|
102
109
|
|
103
110
|
def lint_on_line_matches_message?(linter, expected_line, expected_message)
|
111
|
+
# Using === to support regex to match anywhere in the string
|
104
112
|
linter
|
105
113
|
.lints
|
106
|
-
.any? { |lint| lint.line == expected_line && lint.message
|
114
|
+
.any? { |lint| lint.line == expected_line && expected_message === lint.message } # rubocop:disable Style/CaseEquality
|
107
115
|
end
|
108
116
|
|
109
117
|
def lint_on_line_matches_severity?(linter, expected_line, expected_severity)
|
@@ -112,8 +120,15 @@ module HamlLint
|
|
112
120
|
.any? { |lint| lint.line == expected_line && lint.severity == expected_severity }
|
113
121
|
end
|
114
122
|
|
123
|
+
def lint_on_line_matches_corrected?(linter, expected_line, expected_corrected)
|
124
|
+
linter
|
125
|
+
.lints
|
126
|
+
.any? { |lint| lint.line == expected_line && lint.corrected == expected_corrected }
|
127
|
+
end
|
128
|
+
|
115
129
|
def lint_messages_match?(linter, expected_message)
|
116
|
-
|
130
|
+
# Using === to support regex to match anywhere in the string
|
131
|
+
lint_messages(linter).all? { |message| expected_message === message } # rubocop:disable Style/CaseEquality
|
117
132
|
end
|
118
133
|
|
119
134
|
def lint_lines(linter)
|
@@ -6,8 +6,8 @@ module HamlLint
|
|
6
6
|
# for writing code without having the leading indentation count.
|
7
7
|
module IndentNormalizer
|
8
8
|
def normalize_indent(code)
|
9
|
-
leading_indent = code[
|
10
|
-
code.
|
9
|
+
leading_indent = code[/([ \t]*)/, 1]
|
10
|
+
code.gsub(/^#{leading_indent}/, '')
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
@@ -14,13 +14,21 @@ module HamlLint
|
|
14
14
|
}
|
15
15
|
end
|
16
16
|
|
17
|
+
let(:autocorrect) { nil }
|
18
|
+
|
17
19
|
let(:config) { options[:config].for_linter(described_class) }
|
18
20
|
|
19
21
|
let(:document) { HamlLint::Document.new(normalize_indent(haml), options) }
|
20
22
|
|
23
|
+
# :run_or_raise, :run, or nil to not auto-call something
|
24
|
+
let(:run_method_to_use) { :run_or_raise }
|
25
|
+
|
21
26
|
subject { described_class.new(config) }
|
22
27
|
|
23
|
-
before
|
28
|
+
before do
|
29
|
+
next unless run_method_to_use
|
30
|
+
subject.send(run_method_to_use, document, autocorrect: autocorrect)
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
end
|