haml_lint 0.40.0 → 0.51.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 +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
|