haml_lint 0.44.0 → 0.46.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|