erb_lint 0.1.3 → 0.5.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/lib/erb_lint/all.rb +2 -0
- data/lib/erb_lint/cache.rb +88 -0
- data/lib/erb_lint/cached_offense.rb +58 -0
- data/lib/erb_lint/cli.rb +90 -9
- data/lib/erb_lint/corrector.rb +1 -1
- data/lib/erb_lint/linter.rb +27 -1
- data/lib/erb_lint/linters/comment_syntax.rb +52 -0
- data/lib/erb_lint/linters/hard_coded_string.rb +3 -2
- data/lib/erb_lint/linters/no_unused_disable.rb +45 -0
- data/lib/erb_lint/linters/rubocop.rb +13 -26
- data/lib/erb_lint/linters/rubocop_text.rb +1 -0
- data/lib/erb_lint/offense.rb +27 -0
- data/lib/erb_lint/reporters/json_reporter.rb +4 -4
- data/lib/erb_lint/reporters/junit_reporter.rb +112 -0
- data/lib/erb_lint/runner.rb +36 -3
- data/lib/erb_lint/runner_config.rb +2 -1
- data/lib/erb_lint/utils/inline_configs.rb +15 -0
- data/lib/erb_lint/version.rb +1 -1
- metadata +14 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98ba7a87f348e584502a9e4810f9311329c04aeddf34af0b05a37702648b495e
|
4
|
+
data.tar.gz: 8728892a4c09fcbdbddb60316b911bf73ce099cc04e9ac10534b76f8aa6708a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d01f3c07ddebb9482ee744c69df6e799b4c0cb06bd24ffd7939f3d86e612a4f1bb2d5b4040c8505d7c8f8e9cceaecaabcca7dc03cab6ea5f23e73f10e098345
|
7
|
+
data.tar.gz: '080398f6522352b669bf21ea1fbdd8850b7b6b8dcef60493d8349fbfeb98eceeefc77c15442cad339612e83c5056d5498a286046c3a3749a73428661c962b695'
|
data/lib/erb_lint/all.rb
CHANGED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
class Cache
|
5
|
+
CACHE_DIRECTORY = ".erb-lint-cache"
|
6
|
+
|
7
|
+
def initialize(config, cache_dir = nil)
|
8
|
+
@config = config
|
9
|
+
@cache_dir = cache_dir || CACHE_DIRECTORY
|
10
|
+
@hits = []
|
11
|
+
@new_results = []
|
12
|
+
puts "Cache mode is on"
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(filename, file_content)
|
16
|
+
file_checksum = checksum(filename, file_content)
|
17
|
+
begin
|
18
|
+
cache_file_contents_as_offenses = JSON.parse(
|
19
|
+
File.read(File.join(@cache_dir, file_checksum))
|
20
|
+
).map do |offense_hash|
|
21
|
+
ERBLint::CachedOffense.new(offense_hash)
|
22
|
+
end
|
23
|
+
rescue Errno::ENOENT
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
@hits.push(file_checksum)
|
27
|
+
cache_file_contents_as_offenses
|
28
|
+
end
|
29
|
+
|
30
|
+
def set(filename, file_content, offenses_as_json)
|
31
|
+
file_checksum = checksum(filename, file_content)
|
32
|
+
@new_results.push(file_checksum)
|
33
|
+
|
34
|
+
FileUtils.mkdir_p(@cache_dir)
|
35
|
+
|
36
|
+
File.open(File.join(@cache_dir, file_checksum), "wb") do |f|
|
37
|
+
f.write(offenses_as_json)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def close
|
42
|
+
prune_cache
|
43
|
+
end
|
44
|
+
|
45
|
+
def prune_cache
|
46
|
+
if hits.empty?
|
47
|
+
puts "Cache being created for the first time, skipping prune"
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
cache_files = Dir.new(@cache_dir).children
|
52
|
+
cache_files.each do |cache_file|
|
53
|
+
next if hits.include?(cache_file) || new_results.include?(cache_file)
|
54
|
+
|
55
|
+
File.delete(File.join(@cache_dir, cache_file))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def cache_dir_exists?
|
60
|
+
File.directory?(@cache_dir)
|
61
|
+
end
|
62
|
+
|
63
|
+
def clear
|
64
|
+
return unless cache_dir_exists?
|
65
|
+
|
66
|
+
puts "Clearing cache by deleting cache directory"
|
67
|
+
FileUtils.rm_r(@cache_dir)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :config, :hits, :new_results
|
73
|
+
|
74
|
+
def checksum(filename, file_content)
|
75
|
+
digester = Digest::SHA1.new
|
76
|
+
mode = File.stat(filename).mode
|
77
|
+
|
78
|
+
digester.update(
|
79
|
+
"#{mode}#{config.to_hash}#{ERBLint::VERSION}#{file_content}"
|
80
|
+
)
|
81
|
+
digester.hexdigest
|
82
|
+
rescue Errno::ENOENT
|
83
|
+
# Spurious files that come and go should not cause a crash, at least not
|
84
|
+
# here.
|
85
|
+
"_"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
# A Cached version of an Offense with only essential information represented as strings
|
5
|
+
class CachedOffense
|
6
|
+
attr_reader(
|
7
|
+
:message,
|
8
|
+
:line_number,
|
9
|
+
:severity,
|
10
|
+
:column,
|
11
|
+
:simple_name,
|
12
|
+
:last_line,
|
13
|
+
:last_column,
|
14
|
+
:length,
|
15
|
+
)
|
16
|
+
|
17
|
+
def initialize(params)
|
18
|
+
params = params.transform_keys(&:to_sym)
|
19
|
+
|
20
|
+
@message = params[:message]
|
21
|
+
@line_number = params[:line_number]
|
22
|
+
@severity = params[:severity]&.to_sym
|
23
|
+
@column = params[:column]
|
24
|
+
@simple_name = params[:simple_name]
|
25
|
+
@last_line = params[:last_line]
|
26
|
+
@last_column = params[:last_column]
|
27
|
+
@length = params[:length]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.new_from_offense(offense)
|
31
|
+
new(
|
32
|
+
{
|
33
|
+
message: offense.message,
|
34
|
+
line_number: offense.line_number,
|
35
|
+
severity: offense.severity,
|
36
|
+
column: offense.column,
|
37
|
+
simple_name: offense.simple_name,
|
38
|
+
last_line: offense.last_line,
|
39
|
+
last_column: offense.last_column,
|
40
|
+
length: offense.length,
|
41
|
+
}
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_h
|
46
|
+
{
|
47
|
+
message: message,
|
48
|
+
line_number: line_number,
|
49
|
+
severity: severity,
|
50
|
+
column: column,
|
51
|
+
simple_name: simple_name,
|
52
|
+
last_line: last_line,
|
53
|
+
last_column: last_column,
|
54
|
+
length: length,
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/erb_lint/cli.rb
CHANGED
@@ -30,12 +30,33 @@ module ERBLint
|
|
30
30
|
def run(args = ARGV)
|
31
31
|
dupped_args = args.dup
|
32
32
|
load_options(dupped_args)
|
33
|
+
|
34
|
+
if cache? && autocorrect?
|
35
|
+
failure!("cannot run autocorrect mode with cache")
|
36
|
+
end
|
37
|
+
|
33
38
|
@files = @options[:stdin] || dupped_args
|
34
39
|
|
35
40
|
load_config
|
36
41
|
|
42
|
+
cache_dir = @options[:cache_dir]
|
43
|
+
@cache = Cache.new(@config, cache_dir) if cache? || clear_cache?
|
44
|
+
|
45
|
+
if clear_cache?
|
46
|
+
if cache.cache_dir_exists?
|
47
|
+
cache.clear
|
48
|
+
success!("cache directory cleared")
|
49
|
+
else
|
50
|
+
failure!("cache directory doesn't exist, skipping deletion.")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
37
54
|
if !@files.empty? && lint_files.empty?
|
38
|
-
|
55
|
+
if allow_no_files?
|
56
|
+
success!("no files found...\n")
|
57
|
+
else
|
58
|
+
failure!("no files found...\n")
|
59
|
+
end
|
39
60
|
elsif lint_files.empty?
|
40
61
|
failure!("no files found or given, specify files or config...\n#{option_parser}")
|
41
62
|
end
|
@@ -48,6 +69,7 @@ module ERBLint
|
|
48
69
|
|
49
70
|
@options[:format] ||= :multiline
|
50
71
|
@options[:fail_level] ||= severity_level_for_name(:refactor)
|
72
|
+
@options[:disable_inline_configs] ||= false
|
51
73
|
@stats.files = lint_files.size
|
52
74
|
@stats.linters = enabled_linter_classes.size
|
53
75
|
@stats.autocorrectable_linters = enabled_linter_classes.count(&:support_autocorrect?)
|
@@ -55,13 +77,13 @@ module ERBLint
|
|
55
77
|
reporter = Reporter.create_reporter(@options[:format], @stats, autocorrect?)
|
56
78
|
reporter.preview
|
57
79
|
|
58
|
-
runner = ERBLint::Runner.new(file_loader, @config)
|
80
|
+
runner = ERBLint::Runner.new(file_loader, @config, @options[:disable_inline_configs])
|
59
81
|
file_content = nil
|
60
82
|
|
61
83
|
lint_files.each do |filename|
|
62
84
|
runner.clear_offenses
|
63
85
|
begin
|
64
|
-
file_content =
|
86
|
+
file_content = run_on_file(runner, filename)
|
65
87
|
rescue => e
|
66
88
|
@stats.exceptions += 1
|
67
89
|
puts "Exception occurred when processing: #{relative_filename(filename)}"
|
@@ -73,6 +95,8 @@ module ERBLint
|
|
73
95
|
end
|
74
96
|
end
|
75
97
|
|
98
|
+
cache&.close
|
99
|
+
|
76
100
|
reporter.show
|
77
101
|
|
78
102
|
if stdin? && autocorrect?
|
@@ -95,13 +119,43 @@ module ERBLint
|
|
95
119
|
|
96
120
|
private
|
97
121
|
|
122
|
+
attr_reader :cache, :config
|
123
|
+
|
124
|
+
def run_on_file(runner, filename)
|
125
|
+
file_content = read_content(filename)
|
126
|
+
|
127
|
+
if cache? && !autocorrect?
|
128
|
+
run_using_cache(runner, filename, file_content)
|
129
|
+
else
|
130
|
+
file_content = run_with_corrections(runner, filename, file_content)
|
131
|
+
end
|
132
|
+
|
133
|
+
log_offense_stats(runner, filename)
|
134
|
+
file_content
|
135
|
+
end
|
136
|
+
|
137
|
+
def run_using_cache(runner, filename, file_content)
|
138
|
+
if (cache_result_offenses = cache.get(filename, file_content))
|
139
|
+
runner.restore_offenses(cache_result_offenses)
|
140
|
+
else
|
141
|
+
run_with_corrections(runner, filename, file_content)
|
142
|
+
cache.set(filename, file_content, runner.offenses.map(&:to_cached_offense_hash).to_json)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
98
146
|
def autocorrect?
|
99
147
|
@options[:autocorrect]
|
100
148
|
end
|
101
149
|
|
102
|
-
def
|
103
|
-
|
150
|
+
def cache?
|
151
|
+
@options[:cache]
|
152
|
+
end
|
104
153
|
|
154
|
+
def clear_cache?
|
155
|
+
@options[:clear_cache]
|
156
|
+
end
|
157
|
+
|
158
|
+
def run_with_corrections(runner, filename, file_content)
|
105
159
|
7.times do
|
106
160
|
processed_source = ERBLint::ProcessedSource.new(filename, file_content)
|
107
161
|
runner.run(processed_source)
|
@@ -123,6 +177,11 @@ module ERBLint
|
|
123
177
|
file_content = corrector.corrected_content
|
124
178
|
runner.clear_offenses
|
125
179
|
end
|
180
|
+
|
181
|
+
file_content
|
182
|
+
end
|
183
|
+
|
184
|
+
def log_offense_stats(runner, filename)
|
126
185
|
offenses_filename = relative_filename(filename)
|
127
186
|
offenses = runner.offenses || []
|
128
187
|
|
@@ -134,8 +193,6 @@ module ERBLint
|
|
134
193
|
|
135
194
|
@stats.processed_files[offenses_filename] ||= []
|
136
195
|
@stats.processed_files[offenses_filename] |= offenses
|
137
|
-
|
138
|
-
file_content
|
139
196
|
end
|
140
197
|
|
141
198
|
def read_content(filename)
|
@@ -165,7 +222,7 @@ module ERBLint
|
|
165
222
|
rescue Psych::SyntaxError => e
|
166
223
|
failure!("error parsing config: #{e.message}")
|
167
224
|
ensure
|
168
|
-
@config
|
225
|
+
@config&.merge!(runner_config_override)
|
169
226
|
end
|
170
227
|
|
171
228
|
def file_loader
|
@@ -262,7 +319,7 @@ module ERBLint
|
|
262
319
|
end
|
263
320
|
end
|
264
321
|
|
265
|
-
opts.on("--format FORMAT", format_options_help) do |format|
|
322
|
+
opts.on("-f", "--format FORMAT", format_options_help) do |format|
|
266
323
|
unless Reporter.available_format?(format)
|
267
324
|
error_message = invalid_format_error_message(format)
|
268
325
|
failure!(error_message)
|
@@ -279,6 +336,18 @@ module ERBLint
|
|
279
336
|
@options[:enabled_linters] = known_linter_names
|
280
337
|
end
|
281
338
|
|
339
|
+
opts.on("--cache", "Enable caching") do |config|
|
340
|
+
@options[:cache] = config
|
341
|
+
end
|
342
|
+
|
343
|
+
opts.on("--cache-dir DIR", "Set the cache directory") do |dir|
|
344
|
+
@options[:cache_dir] = dir
|
345
|
+
end
|
346
|
+
|
347
|
+
opts.on("--clear-cache", "Clear cache") do |config|
|
348
|
+
@options[:clear_cache] = config
|
349
|
+
end
|
350
|
+
|
282
351
|
opts.on("--enable-linters LINTER[,LINTER,...]", Array,
|
283
352
|
"Only use specified linter", "Known linters are: #{known_linter_names.join(", ")}") do |linters|
|
284
353
|
linters.each do |linter|
|
@@ -302,6 +371,14 @@ module ERBLint
|
|
302
371
|
@options[:autocorrect] = config
|
303
372
|
end
|
304
373
|
|
374
|
+
opts.on("--allow-no-files", "When no matching files found, exit successfully (default: false)") do |config|
|
375
|
+
@options[:allow_no_files] = config
|
376
|
+
end
|
377
|
+
|
378
|
+
opts.on("--disable-inline-configs", "Report all offenses while ignoring inline disable comments") do
|
379
|
+
@options[:disable_inline_configs] = true
|
380
|
+
end
|
381
|
+
|
305
382
|
opts.on(
|
306
383
|
"-sFILE",
|
307
384
|
"--stdin FILE",
|
@@ -333,5 +410,9 @@ module ERBLint
|
|
333
410
|
def stdin?
|
334
411
|
@options[:stdin].present?
|
335
412
|
end
|
413
|
+
|
414
|
+
def allow_no_files?
|
415
|
+
@options[:allow_no_files]
|
416
|
+
end
|
336
417
|
end
|
337
418
|
end
|
data/lib/erb_lint/corrector.rb
CHANGED
@@ -12,7 +12,7 @@ module ERBLint
|
|
12
12
|
|
13
13
|
def corrections
|
14
14
|
@corrections ||= @offenses.map do |offense|
|
15
|
-
offense.linter.autocorrect(@processed_source, offense)
|
15
|
+
offense.linter.autocorrect(@processed_source, offense) if offense.linter.class.support_autocorrect?
|
16
16
|
end.compact
|
17
17
|
end
|
18
18
|
|
data/lib/erb_lint/linter.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "erb_lint/utils/inline_configs"
|
4
|
+
|
3
5
|
module ERBLint
|
4
6
|
# Defines common functionality available to all linters.
|
5
7
|
class Linter
|
@@ -30,7 +32,7 @@ module ERBLint
|
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
33
|
-
attr_reader :offenses
|
35
|
+
attr_reader :offenses, :config
|
34
36
|
|
35
37
|
# Must be implemented by the concrete inheriting class.
|
36
38
|
def initialize(file_loader, config)
|
@@ -53,6 +55,13 @@ module ERBLint
|
|
53
55
|
raise NotImplementedError, "must implement ##{__method__}"
|
54
56
|
end
|
55
57
|
|
58
|
+
def run_and_update_offense_status(processed_source, enable_inline_configs = true)
|
59
|
+
run(processed_source)
|
60
|
+
if @offenses.any? && enable_inline_configs
|
61
|
+
update_offense_status(processed_source)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
56
65
|
def add_offense(source_range, message, context = nil, severity = nil)
|
57
66
|
@offenses << Offense.new(self, source_range, message, context, severity)
|
58
67
|
end
|
@@ -60,5 +69,22 @@ module ERBLint
|
|
60
69
|
def clear_offenses
|
61
70
|
@offenses = []
|
62
71
|
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def update_offense_status(processed_source)
|
76
|
+
@offenses.each do |offense|
|
77
|
+
offense_line_range = offense.source_range.line_range
|
78
|
+
offense_lines = source_for_line_range(processed_source, offense_line_range)
|
79
|
+
|
80
|
+
if Utils::InlineConfigs.rule_disable_comment_for_lines?(self.class.simple_name, offense_lines)
|
81
|
+
offense.disabled = true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def source_for_line_range(processed_source, line_range)
|
87
|
+
processed_source.source_buffer.source_lines[line_range.first - 1..line_range.last - 1].join
|
88
|
+
end
|
63
89
|
end
|
64
90
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Linters
|
5
|
+
# Detects comment syntax that isn't valid ERB.
|
6
|
+
class CommentSyntax < Linter
|
7
|
+
include LinterRegistry
|
8
|
+
|
9
|
+
def initialize(file_loader, config)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(processed_source)
|
14
|
+
file_content = processed_source.file_content
|
15
|
+
return if file_content.empty?
|
16
|
+
|
17
|
+
processed_source.ast.descendants(:erb).each do |erb_node|
|
18
|
+
indicator_node, _, code_node, _ = *erb_node
|
19
|
+
next if code_node.nil?
|
20
|
+
|
21
|
+
indicator_node_str = indicator_node&.deconstruct&.last
|
22
|
+
next if indicator_node_str == "#"
|
23
|
+
|
24
|
+
code_node_str = code_node.deconstruct.last
|
25
|
+
next unless code_node_str.start_with?(" #")
|
26
|
+
|
27
|
+
range = find_range(erb_node, code_node_str)
|
28
|
+
source_range = processed_source.to_source_range(range)
|
29
|
+
|
30
|
+
correct_erb_tag = indicator_node_str == "=" ? "<%#=" : "<%#"
|
31
|
+
|
32
|
+
add_offense(
|
33
|
+
source_range,
|
34
|
+
<<~EOF.chomp
|
35
|
+
Bad ERB comment syntax. Should be #{correct_erb_tag} without a space between.
|
36
|
+
Leaving a space between ERB tags and the Ruby comment character can cause parser errors.
|
37
|
+
EOF
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_range(node, str)
|
43
|
+
match = node.loc.source.match(Regexp.new(Regexp.quote(str.strip)))
|
44
|
+
return unless match
|
45
|
+
|
46
|
+
range_begin = match.begin(0) + node.loc.begin_pos
|
47
|
+
range_end = match.end(0) + node.loc.begin_pos
|
48
|
+
(range_begin...range_end)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -17,7 +17,7 @@ module ERBLint
|
|
17
17
|
ALLOWED_CORRECTORS = ["I18nCorrector", "RuboCop::Corrector::I18n::HardCodedString"]
|
18
18
|
|
19
19
|
NON_TEXT_TAGS = Set.new(["script", "style", "xmp", "iframe", "noembed", "noframes", "listing"])
|
20
|
-
|
20
|
+
NO_TRANSLATION_NEEDED = Set.new([
|
21
21
|
" ",
|
22
22
|
"&",
|
23
23
|
"<",
|
@@ -40,6 +40,7 @@ module ERBLint
|
|
40
40
|
" ",
|
41
41
|
" ",
|
42
42
|
" ",
|
43
|
+
"×",
|
43
44
|
])
|
44
45
|
|
45
46
|
class ConfigSchema < LinterConfig
|
@@ -96,7 +97,7 @@ module ERBLint
|
|
96
97
|
|
97
98
|
def check_string?(str)
|
98
99
|
string = str.gsub(/\s*/, "")
|
99
|
-
string.length > 1 && !
|
100
|
+
string.length > 1 && !NO_TRANSLATION_NEEDED.include?(string)
|
100
101
|
end
|
101
102
|
|
102
103
|
def load_corrector
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb_lint/utils/inline_configs"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
# Checks for unused disable comments.
|
8
|
+
class NoUnusedDisable < Linter
|
9
|
+
include LinterRegistry
|
10
|
+
|
11
|
+
def run(processed_source, offenses)
|
12
|
+
disabled_rules_and_line_number = {}
|
13
|
+
|
14
|
+
processed_source.source_buffer.source_lines.each_with_index do |line, index|
|
15
|
+
rule_disables = Utils::InlineConfigs.disabled_rules(line)
|
16
|
+
next unless rule_disables
|
17
|
+
|
18
|
+
rule_disables.split(",").each do |rule|
|
19
|
+
disabled_rules_and_line_number[rule.strip] =
|
20
|
+
(disabled_rules_and_line_number[rule.strip] ||= []).push(index + 1)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
offenses.each do |offense|
|
25
|
+
rule_name = offense.linter.class.simple_name
|
26
|
+
line_numbers = disabled_rules_and_line_number[rule_name]
|
27
|
+
next unless line_numbers
|
28
|
+
|
29
|
+
line_numbers.reject do |line_number|
|
30
|
+
if (offense.source_range.line_span.first..offense.source_range.line_span.last).include?(line_number)
|
31
|
+
disabled_rules_and_line_number[rule_name].delete(line_number)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
disabled_rules_and_line_number.each do |rule, line_numbers|
|
37
|
+
line_numbers.each do |line_number|
|
38
|
+
add_offense(processed_source.source_buffer.line_range(line_number),
|
39
|
+
"Unused erblint:disable comment for #{rule}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -13,6 +13,7 @@ module ERBLint
|
|
13
13
|
class ConfigSchema < LinterConfig
|
14
14
|
property :only, accepts: array_of?(String)
|
15
15
|
property :rubocop_config, accepts: Hash, default: -> { {} }
|
16
|
+
property :config_file_path, accepts: String
|
16
17
|
end
|
17
18
|
|
18
19
|
self.config_schema = ConfigSchema
|
@@ -24,7 +25,8 @@ module ERBLint
|
|
24
25
|
def initialize(file_loader, config)
|
25
26
|
super
|
26
27
|
@only_cops = @config.only
|
27
|
-
custom_config =
|
28
|
+
custom_config = config_from_path(@config.config_file_path) if @config.config_file_path
|
29
|
+
custom_config ||= config_from_hash(@config.rubocop_config)
|
28
30
|
@rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, "")
|
29
31
|
end
|
30
32
|
|
@@ -129,11 +131,17 @@ module ERBLint
|
|
129
131
|
end
|
130
132
|
|
131
133
|
def rubocop_processed_source(content, filename)
|
132
|
-
::RuboCop::ProcessedSource.new(
|
134
|
+
source = ::RuboCop::ProcessedSource.new(
|
133
135
|
content,
|
134
136
|
@rubocop_config.target_ruby_version,
|
135
137
|
filename
|
136
138
|
)
|
139
|
+
if ::RuboCop::Version::STRING.to_f >= 1.38
|
140
|
+
registry = RuboCop::Cop::Registry.global
|
141
|
+
source.registry = registry
|
142
|
+
source.config = @rubocop_config
|
143
|
+
end
|
144
|
+
source
|
137
145
|
end
|
138
146
|
|
139
147
|
def cop_classes
|
@@ -158,34 +166,13 @@ module ERBLint
|
|
158
166
|
end
|
159
167
|
|
160
168
|
def config_from_hash(hash)
|
161
|
-
inherit_from = hash&.delete("inherit_from")
|
162
|
-
resolve_inheritance(hash, inherit_from)
|
163
|
-
|
164
169
|
tempfile_from(".erblint-rubocop", hash.to_yaml) do |tempfile|
|
165
|
-
|
170
|
+
config_from_path(tempfile.path)
|
166
171
|
end
|
167
172
|
end
|
168
173
|
|
169
|
-
def
|
170
|
-
|
171
|
-
.reverse_each do |base_config|
|
172
|
-
base_config.each do |k, v|
|
173
|
-
hash[k] = hash.key?(k) ? ::RuboCop::ConfigLoader.merge(v, hash[k]) : v if v.is_a?(Hash)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def base_configs(inherit_from)
|
179
|
-
regex = URI::DEFAULT_PARSER.make_regexp(["http", "https"])
|
180
|
-
configs = Array(inherit_from).compact.map do |base_name|
|
181
|
-
if base_name =~ /\A#{regex}\z/
|
182
|
-
::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
|
183
|
-
else
|
184
|
-
config_from_hash(@file_loader.yaml(base_name))
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
configs.compact
|
174
|
+
def config_from_path(path)
|
175
|
+
::RuboCop::ConfigLoader.load_file(path)
|
189
176
|
end
|
190
177
|
|
191
178
|
def add_offense(rubocop_offense, offense_range, correction, offset, bound_range)
|
data/lib/erb_lint/offense.rb
CHANGED
@@ -15,6 +15,11 @@ module ERBLint
|
|
15
15
|
@message = message
|
16
16
|
@context = context
|
17
17
|
@severity = severity
|
18
|
+
@disabled = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_cached_offense_hash
|
22
|
+
ERBLint::CachedOffense.new_from_offense(self).to_h
|
18
23
|
end
|
19
24
|
|
20
25
|
def inspect
|
@@ -40,8 +45,30 @@ module ERBLint
|
|
40
45
|
line_range.begin
|
41
46
|
end
|
42
47
|
|
48
|
+
attr_writer :disabled
|
49
|
+
|
50
|
+
def disabled?
|
51
|
+
@disabled
|
52
|
+
end
|
53
|
+
|
43
54
|
def column
|
44
55
|
source_range.column
|
45
56
|
end
|
57
|
+
|
58
|
+
def simple_name
|
59
|
+
linter.class.simple_name
|
60
|
+
end
|
61
|
+
|
62
|
+
def last_line
|
63
|
+
source_range.last_line
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_column
|
67
|
+
source_range.last_column
|
68
|
+
end
|
69
|
+
|
70
|
+
def length
|
71
|
+
source_range.length
|
72
|
+
end
|
46
73
|
end
|
47
74
|
end
|
@@ -56,14 +56,14 @@ module ERBLint
|
|
56
56
|
|
57
57
|
def format_offense(offense)
|
58
58
|
{
|
59
|
-
linter: offense.
|
59
|
+
linter: offense.simple_name,
|
60
60
|
message: offense.message.to_s,
|
61
61
|
location: {
|
62
62
|
start_line: offense.line_number,
|
63
63
|
start_column: offense.column,
|
64
|
-
last_line: offense.
|
65
|
-
last_column: offense.
|
66
|
-
length: offense.
|
64
|
+
last_line: offense.last_line,
|
65
|
+
last_column: offense.last_column,
|
66
|
+
length: offense.length,
|
67
67
|
},
|
68
68
|
}
|
69
69
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rexml/document"
|
4
|
+
require "rexml/formatters/pretty"
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
module Reporters
|
8
|
+
class JunitReporter < Reporter
|
9
|
+
def preview; end
|
10
|
+
|
11
|
+
def show
|
12
|
+
xml = create_junit_xml
|
13
|
+
formatted_xml_string = StringIO.new
|
14
|
+
REXML::Formatters::Pretty.new.write(xml, formatted_xml_string)
|
15
|
+
puts formatted_xml_string.string
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
CONTEXT = {
|
21
|
+
prologue_quote: :quote,
|
22
|
+
attribute_quote: :quote,
|
23
|
+
}
|
24
|
+
|
25
|
+
def create_junit_xml
|
26
|
+
# create prologue
|
27
|
+
xml = REXML::Document.new(nil, CONTEXT)
|
28
|
+
xml << REXML::XMLDecl.new("1.0", "UTF-8")
|
29
|
+
|
30
|
+
xml.add_element(create_testsuite_element)
|
31
|
+
|
32
|
+
xml
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_testsuite_element
|
36
|
+
tests = stats.processed_files.size
|
37
|
+
failures = stats.found
|
38
|
+
testsuite_element = REXML::Element.new("testsuite", nil, CONTEXT)
|
39
|
+
testsuite_element.add_attribute("name", "erblint")
|
40
|
+
testsuite_element.add_attribute("tests", tests.to_s)
|
41
|
+
testsuite_element.add_attribute("failures", failures.to_s)
|
42
|
+
|
43
|
+
testsuite_element.add_element(create_properties)
|
44
|
+
|
45
|
+
processed_files.each do |filename, offenses|
|
46
|
+
if offenses.empty?
|
47
|
+
testcase_element = REXML::Element.new("testcase", nil, CONTEXT)
|
48
|
+
testcase_element.add_attribute("name", filename.to_s)
|
49
|
+
testcase_element.add_attribute("file", filename.to_s)
|
50
|
+
|
51
|
+
testsuite_element.add_element(testcase_element)
|
52
|
+
end
|
53
|
+
|
54
|
+
offenses.each do |offense|
|
55
|
+
testsuite_element.add_element(create_testcase(filename, offense))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
testsuite_element
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_properties
|
63
|
+
properties_element = REXML::Element.new("properties", nil, CONTEXT)
|
64
|
+
|
65
|
+
[
|
66
|
+
["erb_lint_version", ERBLint::VERSION],
|
67
|
+
["ruby_engine", RUBY_ENGINE],
|
68
|
+
["ruby_version", RUBY_VERSION],
|
69
|
+
["ruby_patchlevel", RUBY_PATCHLEVEL.to_s],
|
70
|
+
["ruby_platform", RUBY_PLATFORM],
|
71
|
+
].each do |property_attribute|
|
72
|
+
properties_element.add_element(create_property(*property_attribute))
|
73
|
+
end
|
74
|
+
|
75
|
+
properties_element
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_property(name, value)
|
79
|
+
property_element = REXML::Element.new("property")
|
80
|
+
property_element.add_attribute("name", name)
|
81
|
+
property_element.add_attribute("value", value)
|
82
|
+
|
83
|
+
property_element
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_testcase(filename, offense)
|
87
|
+
testcase_element = REXML::Element.new("testcase", nil, CONTEXT)
|
88
|
+
testcase_element.add_attribute("name", filename.to_s)
|
89
|
+
testcase_element.add_attribute("file", filename.to_s)
|
90
|
+
testcase_element.add_attribute("lineno", offense.line_number.to_s)
|
91
|
+
|
92
|
+
testcase_element.add_element(create_failure(filename, offense))
|
93
|
+
|
94
|
+
testcase_element
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_failure(filename, offense)
|
98
|
+
message = offense.message
|
99
|
+
type = offense.simple_name
|
100
|
+
|
101
|
+
failure_element = REXML::Element.new("failure", nil, CONTEXT)
|
102
|
+
failure_element.add_attribute("message", "#{type}: #{message}")
|
103
|
+
failure_element.add_attribute("type", type.to_s)
|
104
|
+
|
105
|
+
cdata_element = REXML::CData.new("#{type}: #{message} at #{filename}:#{offense.line_number}:#{offense.column}")
|
106
|
+
failure_element.add_text(cdata_element)
|
107
|
+
|
108
|
+
failure_element
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/erb_lint/runner.rb
CHANGED
@@ -5,15 +5,19 @@ module ERBLint
|
|
5
5
|
class Runner
|
6
6
|
attr_reader :offenses
|
7
7
|
|
8
|
-
def initialize(file_loader, config)
|
8
|
+
def initialize(file_loader, config, disable_inline_configs = false)
|
9
9
|
@file_loader = file_loader
|
10
10
|
@config = config || RunnerConfig.default
|
11
11
|
raise ArgumentError, "expect `config` to be a RunnerConfig instance" unless @config.is_a?(RunnerConfig)
|
12
12
|
|
13
|
-
linter_classes = LinterRegistry.linters.select
|
13
|
+
linter_classes = LinterRegistry.linters.select do |klass|
|
14
|
+
@config.for_linter(klass).enabled? && klass != ERBLint::Linters::NoUnusedDisable
|
15
|
+
end
|
14
16
|
@linters = linter_classes.map do |linter_class|
|
15
17
|
linter_class.new(@file_loader, @config.for_linter(linter_class))
|
16
18
|
end
|
19
|
+
@no_unused_disable = nil
|
20
|
+
@disable_inline_configs = disable_inline_configs
|
17
21
|
@offenses = []
|
18
22
|
end
|
19
23
|
|
@@ -21,14 +25,43 @@ module ERBLint
|
|
21
25
|
@linters
|
22
26
|
.reject { |linter| linter.excludes_file?(processed_source.filename) }
|
23
27
|
.each do |linter|
|
24
|
-
linter.
|
28
|
+
linter.run_and_update_offense_status(processed_source, enable_inline_configs?)
|
25
29
|
@offenses.concat(linter.offenses)
|
26
30
|
end
|
31
|
+
report_unused_disable(processed_source)
|
32
|
+
@offenses = @offenses.reject(&:disabled?)
|
27
33
|
end
|
28
34
|
|
29
35
|
def clear_offenses
|
30
36
|
@offenses = []
|
31
37
|
@linters.each(&:clear_offenses)
|
38
|
+
@no_unused_disable&.clear_offenses
|
39
|
+
end
|
40
|
+
|
41
|
+
def restore_offenses(offenses)
|
42
|
+
@offenses.concat(offenses)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def enable_inline_configs?
|
48
|
+
!@disable_inline_configs
|
49
|
+
end
|
50
|
+
|
51
|
+
def no_unused_disable_enabled?
|
52
|
+
LinterRegistry.linters.include?(ERBLint::Linters::NoUnusedDisable) &&
|
53
|
+
@config.for_linter(ERBLint::Linters::NoUnusedDisable).enabled?
|
54
|
+
end
|
55
|
+
|
56
|
+
def report_unused_disable(processed_source)
|
57
|
+
if no_unused_disable_enabled? && enable_inline_configs?
|
58
|
+
@no_unused_disable = ERBLint::Linters::NoUnusedDisable.new(
|
59
|
+
@file_loader,
|
60
|
+
@config.for_linter(ERBLint::Linters::NoUnusedDisable)
|
61
|
+
)
|
62
|
+
@no_unused_disable.run(processed_source, @offenses)
|
63
|
+
@offenses.concat(@no_unused_disable.offenses)
|
64
|
+
end
|
32
65
|
end
|
33
66
|
end
|
34
67
|
end
|
@@ -63,6 +63,7 @@ module ERBLint
|
|
63
63
|
SpaceInHtmlTag: { enabled: default_enabled },
|
64
64
|
TrailingWhitespace: { enabled: default_enabled },
|
65
65
|
RequireInputAutocomplete: { enabled: default_enabled },
|
66
|
+
CommentSyntax: { enabled: default_enabled },
|
66
67
|
},
|
67
68
|
)
|
68
69
|
end
|
@@ -82,7 +83,7 @@ module ERBLint
|
|
82
83
|
def config_hash_for_linter(klass_name)
|
83
84
|
config_hash = linters_config[klass_name] || {}
|
84
85
|
config_hash["exclude"] ||= []
|
85
|
-
config_hash["exclude"].concat(global_exclude) if config_hash["exclude"].is_a?(Array)
|
86
|
+
config_hash["exclude"].concat(global_exclude).uniq! if config_hash["exclude"].is_a?(Array)
|
86
87
|
config_hash
|
87
88
|
end
|
88
89
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Utils
|
5
|
+
class InlineConfigs
|
6
|
+
def self.rule_disable_comment_for_lines?(rule, lines)
|
7
|
+
lines.match?(/# erblint:disable (?<rules>.*#{rule}).*/)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.disabled_rules(line)
|
11
|
+
line.match(/# erblint:disable (?<rules>.*) %>/)&.named_captures&.fetch("rules")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/erb_lint/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erb_lint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Chan
|
8
|
+
- Shopify Developers
|
8
9
|
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2023-08-25 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: activesupport
|
@@ -26,32 +27,18 @@ dependencies:
|
|
26
27
|
version: '0'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: better_html
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.0.7
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 1.0.7
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: html_tokenizer
|
43
30
|
requirement: !ruby/object:Gem::Requirement
|
44
31
|
requirements:
|
45
32
|
- - ">="
|
46
33
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
34
|
+
version: 2.0.1
|
48
35
|
type: :runtime
|
49
36
|
prerelease: false
|
50
37
|
version_requirements: !ruby/object:Gem::Requirement
|
51
38
|
requirements:
|
52
39
|
- - ">="
|
53
40
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
41
|
+
version: 2.0.1
|
55
42
|
- !ruby/object:Gem::Dependency
|
56
43
|
name: parser
|
57
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -152,7 +139,7 @@ dependencies:
|
|
152
139
|
version: '0'
|
153
140
|
description: ERB Linter tool.
|
154
141
|
email:
|
155
|
-
-
|
142
|
+
- ruby@shopify.com
|
156
143
|
executables:
|
157
144
|
- erblint
|
158
145
|
extensions: []
|
@@ -161,6 +148,8 @@ files:
|
|
161
148
|
- exe/erblint
|
162
149
|
- lib/erb_lint.rb
|
163
150
|
- lib/erb_lint/all.rb
|
151
|
+
- lib/erb_lint/cache.rb
|
152
|
+
- lib/erb_lint/cached_offense.rb
|
164
153
|
- lib/erb_lint/cli.rb
|
165
154
|
- lib/erb_lint/corrector.rb
|
166
155
|
- lib/erb_lint/file_loader.rb
|
@@ -169,12 +158,14 @@ files:
|
|
169
158
|
- lib/erb_lint/linter_registry.rb
|
170
159
|
- lib/erb_lint/linters/allowed_script_type.rb
|
171
160
|
- lib/erb_lint/linters/closing_erb_tag_indent.rb
|
161
|
+
- lib/erb_lint/linters/comment_syntax.rb
|
172
162
|
- lib/erb_lint/linters/deprecated_classes.rb
|
173
163
|
- lib/erb_lint/linters/erb_safety.rb
|
174
164
|
- lib/erb_lint/linters/extra_newline.rb
|
175
165
|
- lib/erb_lint/linters/final_newline.rb
|
176
166
|
- lib/erb_lint/linters/hard_coded_string.rb
|
177
167
|
- lib/erb_lint/linters/no_javascript_tag_helper.rb
|
168
|
+
- lib/erb_lint/linters/no_unused_disable.rb
|
178
169
|
- lib/erb_lint/linters/parser_errors.rb
|
179
170
|
- lib/erb_lint/linters/partial_instance_variable.rb
|
180
171
|
- lib/erb_lint/linters/require_input_autocomplete.rb
|
@@ -192,12 +183,14 @@ files:
|
|
192
183
|
- lib/erb_lint/reporter.rb
|
193
184
|
- lib/erb_lint/reporters/compact_reporter.rb
|
194
185
|
- lib/erb_lint/reporters/json_reporter.rb
|
186
|
+
- lib/erb_lint/reporters/junit_reporter.rb
|
195
187
|
- lib/erb_lint/reporters/multiline_reporter.rb
|
196
188
|
- lib/erb_lint/runner.rb
|
197
189
|
- lib/erb_lint/runner_config.rb
|
198
190
|
- lib/erb_lint/runner_config_resolver.rb
|
199
191
|
- lib/erb_lint/stats.rb
|
200
192
|
- lib/erb_lint/utils/block_map.rb
|
193
|
+
- lib/erb_lint/utils/inline_configs.rb
|
201
194
|
- lib/erb_lint/utils/offset_corrector.rb
|
202
195
|
- lib/erb_lint/utils/ruby_to_erb.rb
|
203
196
|
- lib/erb_lint/utils/severity_levels.rb
|
@@ -215,14 +208,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
215
208
|
requirements:
|
216
209
|
- - ">="
|
217
210
|
- !ruby/object:Gem::Version
|
218
|
-
version: 2.
|
211
|
+
version: 2.7.0
|
219
212
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
220
213
|
requirements:
|
221
214
|
- - ">="
|
222
215
|
- !ruby/object:Gem::Version
|
223
216
|
version: '0'
|
224
217
|
requirements: []
|
225
|
-
rubygems_version: 3.
|
218
|
+
rubygems_version: 3.4.18
|
226
219
|
signing_key:
|
227
220
|
specification_version: 4
|
228
221
|
summary: ERB lint tool
|