haml_lint 0.40.0 → 0.51.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 +9 -27
- data/config/forced_rubocop_config.yml +180 -0
- data/lib/haml_lint/adapter/haml_4.rb +20 -0
- data/lib/haml_lint/adapter/haml_5.rb +11 -0
- data/lib/haml_lint/adapter/haml_6.rb +59 -0
- data/lib/haml_lint/adapter.rb +2 -0
- data/lib/haml_lint/cli.rb +8 -3
- data/lib/haml_lint/configuration_loader.rb +49 -13
- 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/repeated_id.rb +2 -1
- data/lib/haml_lint/linter/rubocop.rb +353 -59
- data/lib/haml_lint/linter/space_before_script.rb +8 -10
- data/lib/haml_lint/linter/trailing_empty_lines.rb +22 -0
- 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 +60 -10
- 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 +24 -0
- data/lib/haml_lint/ruby_extraction/base_chunk.rb +114 -0
- data/lib/haml_lint/ruby_extraction/chunk_extractor.rb +672 -0
- data/lib/haml_lint/ruby_extraction/coordinator.rb +181 -0
- data/lib/haml_lint/ruby_extraction/haml_comment_chunk.rb +34 -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 +251 -0
- data/lib/haml_lint/ruby_extraction/tag_attributes_chunk.rb +63 -0
- data/lib/haml_lint/ruby_extraction/tag_script_chunk.rb +30 -0
- data/lib/haml_lint/ruby_parser.rb +11 -1
- 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 +158 -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/script_node.rb +7 -1
- data/lib/haml_lint/tree/silent_script_node.rb +16 -1
- data/lib/haml_lint/tree/tag_node.rb +5 -9
- data/lib/haml_lint/utils.rb +135 -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 +29 -15
- data/lib/haml_lint/ruby_extractor.rb +0 -222
data/lib/haml_lint/utils.rb
CHANGED
@@ -4,7 +4,7 @@ require 'pathname'
|
|
4
4
|
|
5
5
|
module HamlLint
|
6
6
|
# A miscellaneous set of utility functions.
|
7
|
-
module Utils
|
7
|
+
module Utils # rubocop:disable Metrics/ModuleLength
|
8
8
|
module_function
|
9
9
|
|
10
10
|
# Returns whether a glob pattern (or any of a list of patterns) matches the
|
@@ -52,12 +52,21 @@ module HamlLint
|
|
52
52
|
# the text.
|
53
53
|
# @yieldparam interpolated_code [String] code that was interpolated
|
54
54
|
# @yieldparam line [Integer] line number code appears on in text
|
55
|
-
def extract_interpolated_values(text)
|
55
|
+
def extract_interpolated_values(text) # rubocop:disable Metrics/AbcSize
|
56
56
|
dumped_text = text.dump
|
57
|
-
|
57
|
+
|
58
|
+
# Basically, match pairs of '\' and '\ followed by the letter 'n'
|
59
|
+
quoted_regex_s = "(#{Regexp.quote('\\\\')}|#{Regexp.quote('\\n')})"
|
60
|
+
newline_positions = extract_substring_positions(dumped_text, quoted_regex_s)
|
61
|
+
|
62
|
+
# Filter the matches to only keep those ending in 'n'.
|
63
|
+
# This way, escaped \n will not be considered
|
64
|
+
newline_positions.select! do |pos|
|
65
|
+
dumped_text[pos - 1] == 'n'
|
66
|
+
end
|
58
67
|
|
59
68
|
Haml::Util.handle_interpolation(dumped_text) do |scan|
|
60
|
-
line = (newline_positions.find_index { |marker| scan.
|
69
|
+
line = (newline_positions.find_index { |marker| scan.charpos <= marker } ||
|
61
70
|
newline_positions.size) + 1
|
62
71
|
|
63
72
|
escape_count = (scan[2].size - 1) / 2
|
@@ -70,6 +79,41 @@ module HamlLint
|
|
70
79
|
end
|
71
80
|
end
|
72
81
|
|
82
|
+
def handle_interpolation_with_indexes(text)
|
83
|
+
newline_indexes = extract_substring_positions(text, "\n")
|
84
|
+
|
85
|
+
handle_interpolation_with_newline(text) do |scan|
|
86
|
+
line_index = newline_indexes.find_index { |index| scan.charpos <= index }
|
87
|
+
line_index ||= newline_indexes.size
|
88
|
+
|
89
|
+
line_start_char_index = if line_index == 0
|
90
|
+
0
|
91
|
+
else
|
92
|
+
newline_indexes[line_index - 1]
|
93
|
+
end
|
94
|
+
|
95
|
+
char_index = scan.charpos - line_start_char_index
|
96
|
+
|
97
|
+
yield scan, line_index, char_index
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if Gem::Version.new(Haml::VERSION) >= Gem::Version.new('5')
|
102
|
+
# Same as Haml::Util.handle_interpolation, but enables multiline mode on the regex
|
103
|
+
def handle_interpolation_with_newline(str)
|
104
|
+
scan = StringScanner.new(str)
|
105
|
+
yield scan while scan.scan(/(.*?)(\\*)#([{@$])/m)
|
106
|
+
scan.rest
|
107
|
+
end
|
108
|
+
else
|
109
|
+
# Same as Haml::Util.handle_interpolation, but enables multiline mode on the regex
|
110
|
+
def handle_interpolation_with_newline(str)
|
111
|
+
scan = StringScanner.new(str)
|
112
|
+
yield scan while scan.scan(/(.*?)(\\*)\#\{/m)
|
113
|
+
scan.rest
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
73
117
|
# Returns indexes of all occurrences of a substring within a string.
|
74
118
|
#
|
75
119
|
# Note, this will not return overlaping substrings, so searching for "aa"
|
@@ -81,7 +125,7 @@ module HamlLint
|
|
81
125
|
def extract_substring_positions(text, substr)
|
82
126
|
positions = []
|
83
127
|
scanner = StringScanner.new(text)
|
84
|
-
positions << scanner.
|
128
|
+
positions << scanner.charpos while scanner.scan(/(.*?)#{substr}/)
|
85
129
|
positions
|
86
130
|
end
|
87
131
|
|
@@ -136,6 +180,22 @@ module HamlLint
|
|
136
180
|
count
|
137
181
|
end
|
138
182
|
|
183
|
+
# Process ERB, providing some values for for versions to it
|
184
|
+
#
|
185
|
+
# @param content [String] the (usually yaml) content to process
|
186
|
+
# @return [String]
|
187
|
+
def process_erb(content)
|
188
|
+
# Variables for use in the ERB's post-processing
|
189
|
+
rubocop_version = HamlLint::VersionComparer.for_rubocop
|
190
|
+
|
191
|
+
ERB.new(content).result(binding)
|
192
|
+
end
|
193
|
+
|
194
|
+
def insert_after_indentation(code, insert)
|
195
|
+
index = code.index(/\S/)
|
196
|
+
"#{code[0...index]}#{insert}#{code[index..]}"
|
197
|
+
end
|
198
|
+
|
139
199
|
# Calls a block of code with a modified set of environment variables,
|
140
200
|
# restoring them once the code has executed.
|
141
201
|
#
|
@@ -151,5 +211,75 @@ module HamlLint
|
|
151
211
|
ensure
|
152
212
|
old_env.each { |var, value| ENV[var.to_s] = value }
|
153
213
|
end
|
214
|
+
|
215
|
+
def indent(string, nb_indent)
|
216
|
+
if nb_indent < 0
|
217
|
+
string.gsub(/^ {1,#{-nb_indent}}/, '')
|
218
|
+
else
|
219
|
+
string.gsub(/^/, ' ' * nb_indent)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def map_subset!(array, range, &block)
|
224
|
+
subset = array[range]
|
225
|
+
return if subset.nil? || subset.empty?
|
226
|
+
|
227
|
+
array[range] = subset.map(&block)
|
228
|
+
end
|
229
|
+
|
230
|
+
def map_after_first!(array, &block)
|
231
|
+
map_subset!(array, 1..-1, &block)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Returns true if line is only whitespace.
|
235
|
+
# Note, this is not like blank? is rails. For nil, this returns false.
|
236
|
+
def is_blank_line?(line)
|
237
|
+
line && line.index(/\S/).nil?
|
238
|
+
end
|
239
|
+
|
240
|
+
def check_error_when_compiling_haml(haml_string)
|
241
|
+
begin
|
242
|
+
ruby_code = ::HamlLint::Adapter.detect_class.new(haml_string).precompile
|
243
|
+
rescue StandardError => e
|
244
|
+
return e
|
245
|
+
end
|
246
|
+
eval("BEGIN {return nil}; #{ruby_code}", binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
|
247
|
+
# The eval will return nil
|
248
|
+
rescue ::SyntaxError
|
249
|
+
$!
|
250
|
+
end
|
251
|
+
|
252
|
+
# Overrides the global stdin, stdout and stderr while within the block, to
|
253
|
+
# push a string in stdin, and capture both stdout and stderr which are returned.
|
254
|
+
#
|
255
|
+
# @param stdin_str [String] the string to push in as stdin
|
256
|
+
# @param _block [Block] the block to perform with the overridden std streams
|
257
|
+
# @return [String, String]
|
258
|
+
def with_captured_streams(stdin_str, &_block)
|
259
|
+
original_stdin = $stdin
|
260
|
+
# The dup is needed so that stdin_data isn't altered (encoding-wise at least)
|
261
|
+
$stdin = StringIO.new(stdin_str.dup)
|
262
|
+
begin
|
263
|
+
original_stdout = $stdout
|
264
|
+
$stdout = StringIO.new
|
265
|
+
begin
|
266
|
+
original_stderr = $stderr
|
267
|
+
$stderr = StringIO.new
|
268
|
+
yield
|
269
|
+
[$stdout.string, $stderr.string]
|
270
|
+
ensure
|
271
|
+
$stderr = original_stderr
|
272
|
+
end
|
273
|
+
ensure
|
274
|
+
$stdout = original_stdout
|
275
|
+
end
|
276
|
+
ensure
|
277
|
+
$stdin = original_stdin
|
278
|
+
end
|
279
|
+
|
280
|
+
def regexp_for_parts(parts, join_regexp)
|
281
|
+
regexp_code = parts.map { |c| Regexp.quote(c) }.join(join_regexp)
|
282
|
+
Regexp.new(regexp_code)
|
283
|
+
end
|
154
284
|
end
|
155
285
|
end
|
data/lib/haml_lint/version.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HamlLint
|
4
|
+
# A simple wrapper around Gem::Version to allow comparison with String instances
|
5
|
+
# This makes code shorter in some places
|
6
|
+
class VersionComparer
|
7
|
+
def initialize(version)
|
8
|
+
@version = Gem::Version.new(version)
|
9
|
+
end
|
10
|
+
|
11
|
+
include Comparable
|
12
|
+
def <=>(other)
|
13
|
+
@version <=> Gem::Version.new(other)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Shortcut to create a version comparer for the current RuboCop's version
|
17
|
+
def self.for_rubocop
|
18
|
+
new(RuboCop::Version::STRING)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.for_haml
|
22
|
+
new(Haml::VERSION)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/haml_lint.rb
CHANGED
@@ -21,8 +21,14 @@ require 'haml_lint/file_finder'
|
|
21
21
|
require 'haml_lint/runner'
|
22
22
|
require 'haml_lint/utils'
|
23
23
|
require 'haml_lint/version'
|
24
|
+
require 'haml_lint/version_comparer'
|
24
25
|
require 'haml_lint/severity'
|
25
26
|
|
27
|
+
# Lead all extensions to external source code
|
28
|
+
Dir[File.expand_path('haml_lint/extensions/*.rb', File.dirname(__FILE__))].sort.each do |file|
|
29
|
+
require file
|
30
|
+
end
|
31
|
+
|
26
32
|
# Load all parse tree node classes
|
27
33
|
require 'haml_lint/tree/node'
|
28
34
|
require 'haml_lint/node_transformer'
|
@@ -39,3 +45,9 @@ end
|
|
39
45
|
Dir[File.expand_path('haml_lint/reporter/*.rb', File.dirname(__FILE__))].sort.each do |file|
|
40
46
|
require file
|
41
47
|
end
|
48
|
+
|
49
|
+
# Load all the chunks for RubyExtraction
|
50
|
+
require 'haml_lint/ruby_extraction/base_chunk'
|
51
|
+
Dir[File.expand_path('haml_lint/ruby_extraction/*.rb', File.dirname(__FILE__))].sort.each do |file|
|
52
|
+
require file
|
53
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: haml_lint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.51.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane da Silva
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: haml
|
@@ -17,9 +17,6 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '4.0'
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '5.3'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,9 +24,6 @@ dependencies:
|
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '4.0'
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '5.3'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: parallel
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,14 +58,14 @@ dependencies:
|
|
64
58
|
requirements:
|
65
59
|
- - ">="
|
66
60
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
61
|
+
version: '1.0'
|
68
62
|
type: :runtime
|
69
63
|
prerelease: false
|
70
64
|
version_requirements: !ruby/object:Gem::Requirement
|
71
65
|
requirements:
|
72
66
|
- - ">="
|
73
67
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
68
|
+
version: '1.0'
|
75
69
|
- !ruby/object:Gem::Dependency
|
76
70
|
name: sysexits
|
77
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,10 +90,12 @@ extra_rdoc_files: []
|
|
96
90
|
files:
|
97
91
|
- bin/haml-lint
|
98
92
|
- config/default.yml
|
93
|
+
- config/forced_rubocop_config.yml
|
99
94
|
- lib/haml_lint.rb
|
100
95
|
- lib/haml_lint/adapter.rb
|
101
96
|
- lib/haml_lint/adapter/haml_4.rb
|
102
97
|
- lib/haml_lint/adapter/haml_5.rb
|
98
|
+
- lib/haml_lint/adapter/haml_6.rb
|
103
99
|
- lib/haml_lint/cli.rb
|
104
100
|
- lib/haml_lint/comment_configuration.rb
|
105
101
|
- lib/haml_lint/configuration.rb
|
@@ -108,6 +104,7 @@ files:
|
|
108
104
|
- lib/haml_lint/directive.rb
|
109
105
|
- lib/haml_lint/document.rb
|
110
106
|
- lib/haml_lint/exceptions.rb
|
107
|
+
- lib/haml_lint/extensions/haml_util_unescape_interpolation_tracking.rb
|
111
108
|
- lib/haml_lint/file_finder.rb
|
112
109
|
- lib/haml_lint/haml_visitor.rb
|
113
110
|
- lib/haml_lint/lint.rb
|
@@ -131,6 +128,7 @@ files:
|
|
131
128
|
- lib/haml_lint/linter/line_length.rb
|
132
129
|
- lib/haml_lint/linter/multiline_pipe.rb
|
133
130
|
- lib/haml_lint/linter/multiline_script.rb
|
131
|
+
- lib/haml_lint/linter/no_placeholders.rb
|
134
132
|
- lib/haml_lint/linter/object_reference_attributes.rb
|
135
133
|
- lib/haml_lint/linter/repeated_id.rb
|
136
134
|
- lib/haml_lint/linter/rubocop.rb
|
@@ -139,6 +137,7 @@ files:
|
|
139
137
|
- lib/haml_lint/linter/space_inside_hash_attributes.rb
|
140
138
|
- lib/haml_lint/linter/syntax.rb
|
141
139
|
- lib/haml_lint/linter/tag_name.rb
|
140
|
+
- lib/haml_lint/linter/trailing_empty_lines.rb
|
142
141
|
- lib/haml_lint/linter/trailing_whitespace.rb
|
143
142
|
- lib/haml_lint/linter/unnecessary_interpolation.rb
|
144
143
|
- lib/haml_lint/linter/unnecessary_string_output.rb
|
@@ -161,7 +160,20 @@ files:
|
|
161
160
|
- lib/haml_lint/reporter/offense_count_reporter.rb
|
162
161
|
- lib/haml_lint/reporter/progress_reporter.rb
|
163
162
|
- lib/haml_lint/reporter/utils.rb
|
164
|
-
- lib/haml_lint/
|
163
|
+
- lib/haml_lint/ruby_extraction/ad_hoc_chunk.rb
|
164
|
+
- lib/haml_lint/ruby_extraction/base_chunk.rb
|
165
|
+
- lib/haml_lint/ruby_extraction/chunk_extractor.rb
|
166
|
+
- lib/haml_lint/ruby_extraction/coordinator.rb
|
167
|
+
- lib/haml_lint/ruby_extraction/haml_comment_chunk.rb
|
168
|
+
- lib/haml_lint/ruby_extraction/implicit_end_chunk.rb
|
169
|
+
- lib/haml_lint/ruby_extraction/interpolation_chunk.rb
|
170
|
+
- lib/haml_lint/ruby_extraction/non_ruby_filter_chunk.rb
|
171
|
+
- lib/haml_lint/ruby_extraction/placeholder_marker_chunk.rb
|
172
|
+
- lib/haml_lint/ruby_extraction/ruby_filter_chunk.rb
|
173
|
+
- lib/haml_lint/ruby_extraction/ruby_source.rb
|
174
|
+
- lib/haml_lint/ruby_extraction/script_chunk.rb
|
175
|
+
- lib/haml_lint/ruby_extraction/tag_attributes_chunk.rb
|
176
|
+
- lib/haml_lint/ruby_extraction/tag_script_chunk.rb
|
165
177
|
- lib/haml_lint/ruby_parser.rb
|
166
178
|
- lib/haml_lint/runner.rb
|
167
179
|
- lib/haml_lint/severity.rb
|
@@ -169,6 +181,7 @@ files:
|
|
169
181
|
- lib/haml_lint/spec/matchers/report_lint.rb
|
170
182
|
- lib/haml_lint/spec/normalize_indent.rb
|
171
183
|
- lib/haml_lint/spec/shared_linter_context.rb
|
184
|
+
- lib/haml_lint/spec/shared_rubocop_autocorrect_context.rb
|
172
185
|
- lib/haml_lint/tree/comment_node.rb
|
173
186
|
- lib/haml_lint/tree/doctype_node.rb
|
174
187
|
- lib/haml_lint/tree/filter_node.rb
|
@@ -182,11 +195,12 @@ files:
|
|
182
195
|
- lib/haml_lint/tree/tag_node.rb
|
183
196
|
- lib/haml_lint/utils.rb
|
184
197
|
- lib/haml_lint/version.rb
|
198
|
+
- lib/haml_lint/version_comparer.rb
|
185
199
|
homepage: https://github.com/sds/haml-lint
|
186
200
|
licenses:
|
187
201
|
- MIT
|
188
202
|
metadata: {}
|
189
|
-
post_install_message:
|
203
|
+
post_install_message:
|
190
204
|
rdoc_options: []
|
191
205
|
require_paths:
|
192
206
|
- lib
|
@@ -194,7 +208,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
194
208
|
requirements:
|
195
209
|
- - ">="
|
196
210
|
- !ruby/object:Gem::Version
|
197
|
-
version: 2.
|
211
|
+
version: 2.7.0
|
198
212
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
213
|
requirements:
|
200
214
|
- - ">="
|
@@ -202,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
202
216
|
version: '0'
|
203
217
|
requirements: []
|
204
218
|
rubygems_version: 3.1.6
|
205
|
-
signing_key:
|
219
|
+
signing_key:
|
206
220
|
specification_version: 4
|
207
221
|
summary: HAML lint tool
|
208
222
|
test_files: []
|
@@ -1,222 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module HamlLint
|
4
|
-
# Utility class for extracting Ruby script from a HAML file that can then be
|
5
|
-
# linted with a Ruby linter (i.e. is "legal" Ruby). The goal is to turn this:
|
6
|
-
#
|
7
|
-
# - if signed_in?(viewer)
|
8
|
-
# %span Stuff
|
9
|
-
# = link_to 'Sign Out', sign_out_path
|
10
|
-
# - else
|
11
|
-
# .some-class{ class: my_method }= my_method
|
12
|
-
# = link_to 'Sign In', sign_in_path
|
13
|
-
#
|
14
|
-
# into this:
|
15
|
-
#
|
16
|
-
# if signed_in?(viewer)
|
17
|
-
# link_to 'Sign Out', sign_out_path
|
18
|
-
# else
|
19
|
-
# { class: my_method }
|
20
|
-
# my_method
|
21
|
-
# link_to 'Sign In', sign_in_path
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# The translation won't be perfect, and won't make any real sense, but the
|
25
|
-
# relationship between variable declarations/uses and the flow control graph
|
26
|
-
# will remain intact.
|
27
|
-
class RubyExtractor # rubocop:disable Metrics/ClassLength
|
28
|
-
include HamlVisitor
|
29
|
-
|
30
|
-
# Stores the extracted source and a map of lines of generated source to the
|
31
|
-
# original source that created them.
|
32
|
-
#
|
33
|
-
# @attr_reader source [String] generated source code
|
34
|
-
# @attr_reader source_map [Hash] map of line numbers from generated source
|
35
|
-
# to original source line number
|
36
|
-
RubySource = Struct.new(:source, :source_map)
|
37
|
-
|
38
|
-
# Extracts Ruby code from Sexp representing a Slim document.
|
39
|
-
#
|
40
|
-
# @param document [HamlLint::Document]
|
41
|
-
# @return [HamlLint::RubyExtractor::RubySource]
|
42
|
-
def extract(document)
|
43
|
-
visit(document.tree)
|
44
|
-
RubySource.new(@source_lines.join("\n"), @source_map)
|
45
|
-
end
|
46
|
-
|
47
|
-
def visit_root(_node)
|
48
|
-
@source_lines = []
|
49
|
-
@source_map = {}
|
50
|
-
@line_count = 0
|
51
|
-
@indent_level = 0
|
52
|
-
@output_count = 0
|
53
|
-
|
54
|
-
yield # Collect lines of code from children
|
55
|
-
end
|
56
|
-
|
57
|
-
def visit_plain(node)
|
58
|
-
# Don't output the text, as we don't want to have to deal with any RuboCop
|
59
|
-
# cops regarding StringQuotes or AsciiComments, and it's not important to
|
60
|
-
# overall document anyway.
|
61
|
-
add_dummy_puts(node)
|
62
|
-
end
|
63
|
-
|
64
|
-
def visit_tag(node)
|
65
|
-
additional_attributes = node.dynamic_attributes_sources
|
66
|
-
|
67
|
-
# Include dummy references to code executed in attributes list
|
68
|
-
# (this forces a "use" of a variable to prevent "assigned but unused
|
69
|
-
# variable" lints)
|
70
|
-
additional_attributes.each do |attributes_code|
|
71
|
-
# Normalize by removing excess whitespace to avoid format lints
|
72
|
-
attributes_code = attributes_code.gsub(/\s*\n\s*/, ' ').strip
|
73
|
-
|
74
|
-
# Attributes can either be a method call or a literal hash, so wrap it
|
75
|
-
# in a method call itself in order to avoid having to differentiate the
|
76
|
-
# two.
|
77
|
-
add_line("{}.merge(#{attributes_code.strip})", node)
|
78
|
-
end
|
79
|
-
|
80
|
-
check_tag_static_hash_source(node)
|
81
|
-
|
82
|
-
# We add a dummy puts statement to represent the tag name being output.
|
83
|
-
# This prevents some erroneous RuboCop warnings.
|
84
|
-
add_dummy_puts(node, node.tag_name)
|
85
|
-
|
86
|
-
code = node.script.strip
|
87
|
-
add_line(code, node) unless code.empty?
|
88
|
-
end
|
89
|
-
|
90
|
-
def after_visit_tag(node)
|
91
|
-
# We add a dummy puts statement for closing tag.
|
92
|
-
add_dummy_puts(node, "#{node.tag_name}/")
|
93
|
-
end
|
94
|
-
|
95
|
-
def visit_script(node)
|
96
|
-
code = node.text
|
97
|
-
add_line(code.strip, node)
|
98
|
-
|
99
|
-
start_block = anonymous_block?(code) || start_block_keyword?(code)
|
100
|
-
|
101
|
-
if start_block
|
102
|
-
@indent_level += 1
|
103
|
-
end
|
104
|
-
|
105
|
-
yield # Continue extracting code from children
|
106
|
-
|
107
|
-
if start_block
|
108
|
-
@indent_level -= 1
|
109
|
-
add_line('end', node)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def visit_haml_comment(node)
|
114
|
-
# We want to preseve leading whitespace if it exists, but include leading
|
115
|
-
# whitespace if it doesn't exist so that RuboCop's LeadingCommentSpace
|
116
|
-
# doesn't complain
|
117
|
-
comment = node.text
|
118
|
-
.gsub(/\n(\S)/, "\n# \\1")
|
119
|
-
.gsub(/\n(\s)/, "\n#\\1")
|
120
|
-
add_line("##{comment}", node)
|
121
|
-
end
|
122
|
-
|
123
|
-
def visit_silent_script(node, &block)
|
124
|
-
visit_script(node, &block)
|
125
|
-
end
|
126
|
-
|
127
|
-
def visit_filter(node)
|
128
|
-
if node.filter_type == 'ruby'
|
129
|
-
node.text.split("\n").each_with_index do |line, index|
|
130
|
-
add_line(line, node.line + index + 1, false)
|
131
|
-
end
|
132
|
-
else
|
133
|
-
add_dummy_puts(node, ":#{node.filter_type}")
|
134
|
-
HamlLint::Utils.extract_interpolated_values(node.text) do |interpolated_code, line|
|
135
|
-
add_line(interpolated_code, node.line + line)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
def check_tag_static_hash_source(node)
|
143
|
-
# Haml::Parser converts hashrocket-style hash attributes of strings and symbols
|
144
|
-
# to static attributes, and excludes them from the dynamic attribute sources:
|
145
|
-
# https://github.com/haml/haml/blob/08f97ec4dc8f59fe3d7f6ab8f8807f86f2a15b68/lib/haml/parser.rb#L400-L404
|
146
|
-
# https://github.com/haml/haml/blob/08f97ec4dc8f59fe3d7f6ab8f8807f86f2a15b68/lib/haml/parser.rb#L540-L554
|
147
|
-
# Here, we add the hash source back in so it can be inspected by rubocop.
|
148
|
-
if node.hash_attributes? && node.dynamic_attributes_sources.empty?
|
149
|
-
normalized_attr_source = node.dynamic_attributes_source[:hash].gsub(/\s*\n\s*/, ' ')
|
150
|
-
|
151
|
-
add_line(normalized_attr_source, node)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# Adds a dummy method call with a unique name so we don't get
|
156
|
-
# Style/IdenticalConditionalBranches RuboCop warnings
|
157
|
-
def add_dummy_puts(node, annotation = nil)
|
158
|
-
annotation = " # #{annotation}" if annotation
|
159
|
-
add_line("_haml_lint_puts_#{@output_count}#{annotation}", node)
|
160
|
-
@output_count += 1
|
161
|
-
end
|
162
|
-
|
163
|
-
def add_line(code, node_or_line, discard_blanks = true)
|
164
|
-
return if code.empty? && discard_blanks
|
165
|
-
|
166
|
-
indent_level = @indent_level
|
167
|
-
|
168
|
-
if node_or_line.respond_to?(:line)
|
169
|
-
# Since mid-block keywords are children of the corresponding start block
|
170
|
-
# keyword, we need to reduce their indentation level by 1. However, we
|
171
|
-
# don't do this unless this is an actual tag node (a raw line number
|
172
|
-
# means this came from a `:ruby` filter).
|
173
|
-
indent_level -= 1 if mid_block_keyword?(code)
|
174
|
-
end
|
175
|
-
|
176
|
-
indent = (' ' * 2 * indent_level)
|
177
|
-
|
178
|
-
@source_lines << indent_code(code, indent)
|
179
|
-
|
180
|
-
original_line =
|
181
|
-
node_or_line.respond_to?(:line) ? node_or_line.line : node_or_line
|
182
|
-
|
183
|
-
# For interpolated code in filters that spans multiple lines, the
|
184
|
-
# resulting code will span multiple lines, so we need to create a
|
185
|
-
# mapping for each line.
|
186
|
-
(code.count("\n") + 1).times do
|
187
|
-
@line_count += 1
|
188
|
-
@source_map[@line_count] = original_line
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def indent_code(code, indent)
|
193
|
-
codes = code.split("\n")
|
194
|
-
codes.map { |c| indent + c }.join("\n")
|
195
|
-
end
|
196
|
-
|
197
|
-
def anonymous_block?(text)
|
198
|
-
text =~ /\bdo\s*(\|\s*[^\|]*\s*\|)?(\s*#.*)?\z/
|
199
|
-
end
|
200
|
-
|
201
|
-
START_BLOCK_KEYWORDS = %w[if unless case begin for until while].freeze
|
202
|
-
def start_block_keyword?(text)
|
203
|
-
START_BLOCK_KEYWORDS.include?(block_keyword(text))
|
204
|
-
end
|
205
|
-
|
206
|
-
MID_BLOCK_KEYWORDS = %w[else elsif when rescue ensure].freeze
|
207
|
-
def mid_block_keyword?(text)
|
208
|
-
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
209
|
-
end
|
210
|
-
|
211
|
-
LOOP_KEYWORDS = %w[for until while].freeze
|
212
|
-
def block_keyword(text)
|
213
|
-
# Need to handle 'for'/'while' since regex stolen from HAML parser doesn't
|
214
|
-
if keyword = text[/\A\s*([^\s]+)\s+/, 1]
|
215
|
-
return keyword if LOOP_KEYWORDS.include?(keyword)
|
216
|
-
end
|
217
|
-
|
218
|
-
return unless keyword = text.scan(Haml::Parser::BLOCK_KEYWORD_REGEX)[0]
|
219
|
-
keyword[0] || keyword[1]
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|