erb_lint 0.0.37 → 0.1.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.rb +0 -23
- data/lib/erb_lint/all.rb +26 -0
- data/lib/erb_lint/cli.rb +58 -9
- data/lib/erb_lint/linter.rb +2 -2
- data/lib/erb_lint/linters/hard_coded_string.rb +21 -1
- data/lib/erb_lint/linters/partial_instance_variable.rb +23 -0
- data/lib/erb_lint/linters/require_input_autocomplete.rb +123 -0
- data/lib/erb_lint/linters/require_script_nonce.rb +92 -0
- data/lib/erb_lint/linters/rubocop.rb +1 -1
- data/lib/erb_lint/offense.rb +7 -4
- data/lib/erb_lint/reporters/compact_reporter.rb +8 -2
- data/lib/erb_lint/reporters/json_reporter.rb +72 -0
- data/lib/erb_lint/runner_config.rb +1 -0
- data/lib/erb_lint/stats.rb +4 -1
- data/lib/erb_lint/utils/severity_levels.rb +16 -0
- data/lib/erb_lint/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c54cf59770c0719d8cae0376d27a685ba2159d0e8884c00a472b0217d1ca2e6
|
4
|
+
data.tar.gz: c644e23a805c04e02daf4218f8548a167a90cc8d0b2bf0fd73acbbdbbaec237c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '07718e5eed2bfb8246a529615c3950645c0d51e376475c6cea7eb60bedfc17af42c4f24f3aba7e8184e842087027b3c0e033afb6c1d01eceb5b84e7c0da8bb74'
|
7
|
+
data.tar.gz: 6bda3b3ad2b03a0812a6bb796c0f6f2d03545fcdc44a6130dc7e3b2135d523e8decb395e3fd7599d9121045c840585de3c4ced6c9c8fb642d1865d263e978dad
|
data/lib/erb_lint.rb
CHANGED
@@ -1,26 +1,3 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rubocop'
|
4
|
-
|
5
|
-
require 'erb_lint/corrector'
|
6
|
-
require 'erb_lint/file_loader'
|
7
|
-
require 'erb_lint/linter_config'
|
8
|
-
require 'erb_lint/linter_registry'
|
9
|
-
require 'erb_lint/linter'
|
10
|
-
require 'erb_lint/offense'
|
11
|
-
require 'erb_lint/processed_source'
|
12
|
-
require 'erb_lint/runner_config'
|
13
|
-
require 'erb_lint/runner'
|
14
3
|
require 'erb_lint/version'
|
15
|
-
require 'erb_lint/stats'
|
16
|
-
require 'erb_lint/reporter'
|
17
|
-
|
18
|
-
# Load linters
|
19
|
-
Dir[File.expand_path('erb_lint/linters/**/*.rb', File.dirname(__FILE__))].each do |file|
|
20
|
-
require file
|
21
|
-
end
|
22
|
-
|
23
|
-
# Load reporters
|
24
|
-
Dir[File.expand_path('erb_lint/reporters/**/*.rb', File.dirname(__FILE__))].each do |file|
|
25
|
-
require file
|
26
|
-
end
|
data/lib/erb_lint/all.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
require 'erb_lint'
|
6
|
+
require 'erb_lint/corrector'
|
7
|
+
require 'erb_lint/file_loader'
|
8
|
+
require 'erb_lint/linter_config'
|
9
|
+
require 'erb_lint/linter_registry'
|
10
|
+
require 'erb_lint/linter'
|
11
|
+
require 'erb_lint/offense'
|
12
|
+
require 'erb_lint/processed_source'
|
13
|
+
require 'erb_lint/runner_config'
|
14
|
+
require 'erb_lint/runner'
|
15
|
+
require 'erb_lint/stats'
|
16
|
+
require 'erb_lint/reporter'
|
17
|
+
|
18
|
+
# Load linters
|
19
|
+
Dir[File.expand_path('linters/**/*.rb', __dir__)].each do |file|
|
20
|
+
require file
|
21
|
+
end
|
22
|
+
|
23
|
+
# Load reporters
|
24
|
+
Dir[File.expand_path('reporters/**/*.rb', __dir__)].each do |file|
|
25
|
+
require file
|
26
|
+
end
|
data/lib/erb_lint/cli.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'erb_lint'
|
3
|
+
require 'erb_lint/all'
|
4
4
|
require 'active_support'
|
5
5
|
require 'active_support/inflector'
|
6
6
|
require 'optparse'
|
7
7
|
require 'psych'
|
8
8
|
require 'yaml'
|
9
9
|
require 'rainbow'
|
10
|
+
require 'erb_lint/utils/severity_levels'
|
10
11
|
|
11
12
|
module ERBLint
|
12
13
|
class CLI
|
14
|
+
include Utils::SeverityLevels
|
15
|
+
|
13
16
|
DEFAULT_CONFIG_FILENAME = '.erb-lint.yml'
|
14
17
|
DEFAULT_LINT_ALL_GLOB = "**/*.html{+*,}.erb"
|
15
18
|
|
@@ -27,7 +30,7 @@ module ERBLint
|
|
27
30
|
def run(args = ARGV)
|
28
31
|
dupped_args = args.dup
|
29
32
|
load_options(dupped_args)
|
30
|
-
@files = dupped_args
|
33
|
+
@files = @options[:stdin] || dupped_args
|
31
34
|
|
32
35
|
load_config
|
33
36
|
|
@@ -44,6 +47,7 @@ module ERBLint
|
|
44
47
|
end
|
45
48
|
|
46
49
|
@options[:format] ||= :multiline
|
50
|
+
@options[:fail_level] ||= severity_level_for_name(:refactor)
|
47
51
|
@stats.files = lint_files.size
|
48
52
|
@stats.linters = enabled_linter_classes.size
|
49
53
|
|
@@ -51,14 +55,15 @@ module ERBLint
|
|
51
55
|
reporter.preview
|
52
56
|
|
53
57
|
runner = ERBLint::Runner.new(file_loader, @config)
|
58
|
+
file_content = nil
|
54
59
|
|
55
60
|
lint_files.each do |filename|
|
56
61
|
runner.clear_offenses
|
57
62
|
begin
|
58
|
-
run_with_corrections(runner, filename)
|
63
|
+
file_content = run_with_corrections(runner, filename)
|
59
64
|
rescue => e
|
60
65
|
@stats.exceptions += 1
|
61
|
-
puts "Exception
|
66
|
+
puts "Exception occurred when processing: #{relative_filename(filename)}"
|
62
67
|
puts "If this file cannot be processed by erb-lint, "\
|
63
68
|
"you can exclude it in your configuration file."
|
64
69
|
puts e.message
|
@@ -69,6 +74,12 @@ module ERBLint
|
|
69
74
|
|
70
75
|
reporter.show
|
71
76
|
|
77
|
+
if stdin? && autocorrect?
|
78
|
+
# When running from stdin, we only lint a single file
|
79
|
+
puts "================ #{lint_files.first} ==================\n"
|
80
|
+
puts file_content
|
81
|
+
end
|
82
|
+
|
72
83
|
@stats.found == 0 && @stats.exceptions == 0
|
73
84
|
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, ExitWithFailure => e
|
74
85
|
warn(Rainbow(e.message).red)
|
@@ -88,7 +99,7 @@ module ERBLint
|
|
88
99
|
end
|
89
100
|
|
90
101
|
def run_with_corrections(runner, filename)
|
91
|
-
file_content =
|
102
|
+
file_content = read_content(filename)
|
92
103
|
|
93
104
|
7.times do
|
94
105
|
processed_source = ERBLint::ProcessedSource.new(filename, file_content)
|
@@ -101,8 +112,11 @@ module ERBLint
|
|
101
112
|
|
102
113
|
@stats.corrected += corrector.corrections.size
|
103
114
|
|
104
|
-
|
105
|
-
|
115
|
+
# Don't overwrite the file if the input comes from stdin
|
116
|
+
unless stdin?
|
117
|
+
File.open(filename, "wb") do |file|
|
118
|
+
file.write(corrector.corrected_content)
|
119
|
+
end
|
106
120
|
end
|
107
121
|
|
108
122
|
file_content = corrector.corrected_content
|
@@ -111,9 +125,22 @@ module ERBLint
|
|
111
125
|
offenses_filename = relative_filename(filename)
|
112
126
|
offenses = runner.offenses || []
|
113
127
|
|
114
|
-
@stats.found
|
128
|
+
@stats.ignored, @stats.found = offenses.partition do |offense|
|
129
|
+
severity_level_for_name(offense.severity) < @options[:fail_level]
|
130
|
+
end.map(&:size)
|
131
|
+
.zip([@stats.ignored, @stats.found])
|
132
|
+
.map(&:sum)
|
133
|
+
|
115
134
|
@stats.processed_files[offenses_filename] ||= []
|
116
135
|
@stats.processed_files[offenses_filename] |= offenses
|
136
|
+
|
137
|
+
file_content
|
138
|
+
end
|
139
|
+
|
140
|
+
def read_content(filename)
|
141
|
+
return File.read(filename, encoding: Encoding::UTF_8) unless stdin?
|
142
|
+
|
143
|
+
$stdin.binmode.read.force_encoding(Encoding::UTF_8)
|
117
144
|
end
|
118
145
|
|
119
146
|
def correct(processed_source, offenses)
|
@@ -169,7 +196,8 @@ module ERBLint
|
|
169
196
|
|
170
197
|
def excluded?(filename)
|
171
198
|
@config.global_exclude.any? do |path|
|
172
|
-
File.
|
199
|
+
expanded_path = File.expand_path(path, Dir.pwd)
|
200
|
+
File.fnmatch?(expanded_path, filename)
|
173
201
|
end
|
174
202
|
end
|
175
203
|
|
@@ -264,10 +292,27 @@ module ERBLint
|
|
264
292
|
@options[:enabled_linters] = linters
|
265
293
|
end
|
266
294
|
|
295
|
+
opts.on("--fail-level SEVERITY", "Minimum severity for exit with error code") do |level|
|
296
|
+
parsed_severity = SEVERITY_CODE_TABLE[level.upcase.to_sym] || (SEVERITY_NAMES & [level.downcase]).first
|
297
|
+
|
298
|
+
if parsed_severity.nil?
|
299
|
+
failure!("#{level}: not a valid failure level (#{SEVERITY_NAMES.join(', ')})")
|
300
|
+
end
|
301
|
+
@options[:fail_level] = severity_level_for_name(parsed_severity)
|
302
|
+
end
|
303
|
+
|
267
304
|
opts.on("-a", "--autocorrect", "Correct offenses automatically if possible (default: false)") do |config|
|
268
305
|
@options[:autocorrect] = config
|
269
306
|
end
|
270
307
|
|
308
|
+
opts.on(
|
309
|
+
"-sFILE",
|
310
|
+
"--stdin FILE",
|
311
|
+
"Pipe source from STDIN. Takes the path to be used to check which rules to apply."
|
312
|
+
) do |file|
|
313
|
+
@options[:stdin] = [file]
|
314
|
+
end
|
315
|
+
|
271
316
|
opts.on_tail("-h", "--help", "Show this message") do
|
272
317
|
success!(opts)
|
273
318
|
end
|
@@ -287,5 +332,9 @@ module ERBLint
|
|
287
332
|
formats = Reporter.available_formats.map { |format| " - #{format}\n" }
|
288
333
|
"#{given_format}: is not a valid format. Available formats:\n#{formats.join}"
|
289
334
|
end
|
335
|
+
|
336
|
+
def stdin?
|
337
|
+
@options[:stdin].present?
|
338
|
+
end
|
290
339
|
end
|
291
340
|
end
|
data/lib/erb_lint/linter.rb
CHANGED
@@ -53,8 +53,8 @@ module ERBLint
|
|
53
53
|
raise NotImplementedError, "must implement ##{__method__}"
|
54
54
|
end
|
55
55
|
|
56
|
-
def add_offense(source_range, message, context = nil)
|
57
|
-
@offenses << Offense.new(self, source_range, message, context)
|
56
|
+
def add_offense(source_range, message, context = nil, severity = nil)
|
57
|
+
@offenses << Offense.new(self, source_range, message, context, severity)
|
58
58
|
end
|
59
59
|
|
60
60
|
def clear_offenses
|
@@ -19,7 +19,27 @@ module ERBLint
|
|
19
19
|
)
|
20
20
|
|
21
21
|
NON_TEXT_TAGS = Set.new(%w(script style xmp iframe noembed noframes listing))
|
22
|
-
BLACK_LISTED_TEXT = Set.new(%w(
|
22
|
+
BLACK_LISTED_TEXT = Set.new(%w(
|
23
|
+
|
24
|
+
&
|
25
|
+
<
|
26
|
+
>
|
27
|
+
"
|
28
|
+
©
|
29
|
+
®
|
30
|
+
™
|
31
|
+
…
|
32
|
+
—
|
33
|
+
•
|
34
|
+
“
|
35
|
+
”
|
36
|
+
‘
|
37
|
+
’
|
38
|
+
←
|
39
|
+
→
|
40
|
+
↓
|
41
|
+
↑
|
42
|
+
))
|
23
43
|
|
24
44
|
class ConfigSchema < LinterConfig
|
25
45
|
property :corrector, accepts: Hash, required: false, default: -> { {} }
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Linters
|
5
|
+
# Checks for instance variables in partials.
|
6
|
+
class PartialInstanceVariable < Linter
|
7
|
+
include LinterRegistry
|
8
|
+
|
9
|
+
def run(processed_source)
|
10
|
+
instance_variable_regex = /\s@\w+/
|
11
|
+
return unless processed_source.filename.match?(/.*_.*.erb\z/) &&
|
12
|
+
processed_source.file_content.match?(instance_variable_regex)
|
13
|
+
|
14
|
+
add_offense(
|
15
|
+
processed_source.to_source_range(
|
16
|
+
processed_source.file_content =~ instance_variable_regex..processed_source.file_content.size
|
17
|
+
),
|
18
|
+
"Instance variable detected in partial."
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'better_html'
|
4
|
+
require 'better_html/tree/tag'
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
module Linters
|
8
|
+
class RequireInputAutocomplete < Linter
|
9
|
+
include LinterRegistry
|
10
|
+
|
11
|
+
HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE = [
|
12
|
+
"color",
|
13
|
+
"date",
|
14
|
+
"datetime-local",
|
15
|
+
"email",
|
16
|
+
"hidden",
|
17
|
+
"month",
|
18
|
+
"number",
|
19
|
+
"password",
|
20
|
+
"range",
|
21
|
+
"search",
|
22
|
+
"tel",
|
23
|
+
"text",
|
24
|
+
"time",
|
25
|
+
"url",
|
26
|
+
"week",
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
FORM_HELPERS_REQUIRING_AUTOCOMPLETE = [
|
30
|
+
:date_field_tag,
|
31
|
+
:color_field_tag,
|
32
|
+
:email_field_tag,
|
33
|
+
:text_field_tag,
|
34
|
+
:utf8_enforcer_tag,
|
35
|
+
:month_field_tag,
|
36
|
+
:hidden_field_tag,
|
37
|
+
:number_field_tag,
|
38
|
+
:password_field_tag,
|
39
|
+
:search_field_tag,
|
40
|
+
:telephone_field_tag,
|
41
|
+
:time_field_tag,
|
42
|
+
:url_field_tag,
|
43
|
+
:week_field_tag,
|
44
|
+
].freeze
|
45
|
+
|
46
|
+
def run(processed_source)
|
47
|
+
parser = processed_source.parser
|
48
|
+
|
49
|
+
find_html_input_tags(parser)
|
50
|
+
find_rails_helper_input_tags(parser)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def find_html_input_tags(parser)
|
56
|
+
parser.nodes_with_type(:tag).each do |tag_node|
|
57
|
+
tag = BetterHtml::Tree::Tag.from_node(tag_node)
|
58
|
+
|
59
|
+
autocomplete_attribute = tag.attributes['autocomplete']
|
60
|
+
type_attribute = tag.attributes['type']
|
61
|
+
|
62
|
+
next if !html_input_tag?(tag) || autocomplete_present?(autocomplete_attribute)
|
63
|
+
next unless html_type_requires_autocomplete_attribute?(type_attribute)
|
64
|
+
|
65
|
+
add_offense(
|
66
|
+
tag_node.to_a[1].loc,
|
67
|
+
"Input tag is missing an autocomplete attribute. If no "\
|
68
|
+
"autocomplete behaviour is desired, use the value `off` or `nope`.",
|
69
|
+
[autocomplete_attribute]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def autocomplete_present?(autocomplete_attribute)
|
75
|
+
autocomplete_attribute.present? && autocomplete_attribute.value_node.present?
|
76
|
+
end
|
77
|
+
|
78
|
+
def html_input_tag?(tag)
|
79
|
+
!tag.closing? && tag.name == 'input'
|
80
|
+
end
|
81
|
+
|
82
|
+
def html_type_requires_autocomplete_attribute?(type_attribute)
|
83
|
+
type_present = type_attribute.present? && type_attribute.value_node.present?
|
84
|
+
type_present && HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE.include?(type_attribute.value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_rails_helper_input_tags(parser)
|
88
|
+
parser.ast.descendants(:erb).each do |erb_node|
|
89
|
+
indicator_node, _, code_node, _ = *erb_node
|
90
|
+
source = code_node.loc.source
|
91
|
+
ruby_node = extract_ruby_node(source)
|
92
|
+
send_node = ruby_node&.descendants(:send)&.first
|
93
|
+
|
94
|
+
next if code_comment?(indicator_node) ||
|
95
|
+
!ruby_node ||
|
96
|
+
!input_helper?(send_node) ||
|
97
|
+
source.include?("autocomplete")
|
98
|
+
|
99
|
+
add_offense(
|
100
|
+
erb_node.loc,
|
101
|
+
"Input field helper is missing an autocomplete attribute. If no "\
|
102
|
+
"autocomplete behaviour is desired, use the value `off` or `nope`.",
|
103
|
+
[erb_node, send_node]
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def input_helper?(send_node)
|
109
|
+
FORM_HELPERS_REQUIRING_AUTOCOMPLETE.include?(send_node&.method_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def code_comment?(indicator_node)
|
113
|
+
indicator_node&.loc&.source == '#'
|
114
|
+
end
|
115
|
+
|
116
|
+
def extract_ruby_node(source)
|
117
|
+
BetterHtml::TestHelper::RubyNode.parse(source)
|
118
|
+
rescue ::Parser::SyntaxError
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'better_html'
|
4
|
+
require 'better_html/tree/tag'
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
module Linters
|
8
|
+
# Allow inline script tags in ERB that have a nonce attribute.
|
9
|
+
# This only validates inline <script> tags, as well as rails helpers like javascript_tag.
|
10
|
+
class RequireScriptNonce < Linter
|
11
|
+
include LinterRegistry
|
12
|
+
|
13
|
+
def run(processed_source)
|
14
|
+
parser = processed_source.parser
|
15
|
+
|
16
|
+
find_html_script_tags(parser)
|
17
|
+
find_rails_helper_script_tags(parser)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def find_html_script_tags(parser)
|
23
|
+
parser.nodes_with_type(:tag).each do |tag_node|
|
24
|
+
tag = BetterHtml::Tree::Tag.from_node(tag_node)
|
25
|
+
nonce_attribute = tag.attributes['nonce']
|
26
|
+
|
27
|
+
next if !html_javascript_tag?(tag) || nonce_present?(nonce_attribute)
|
28
|
+
|
29
|
+
add_offense(
|
30
|
+
tag_node.to_a[1].loc,
|
31
|
+
"Missing a nonce attribute. Use request.content_security_policy_nonce",
|
32
|
+
[nonce_attribute]
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def nonce_present?(nonce_attribute)
|
38
|
+
nonce_attribute.present? && nonce_attribute.value_node.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
def html_javascript_tag?(tag)
|
42
|
+
!tag.closing? &&
|
43
|
+
(tag.name == 'script' && !html_javascript_type_attribute?(tag))
|
44
|
+
end
|
45
|
+
|
46
|
+
def html_javascript_type_attribute?(tag)
|
47
|
+
type_attribute = tag.attributes['type']
|
48
|
+
|
49
|
+
type_attribute &&
|
50
|
+
type_attribute.value_node.present? &&
|
51
|
+
type_attribute.value_node.to_a[1] != 'text/javascript' &&
|
52
|
+
type_attribute.value_node.to_a[1] != 'application/javascript'
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_rails_helper_script_tags(parser)
|
56
|
+
parser.ast.descendants(:erb).each do |erb_node|
|
57
|
+
indicator_node, _, code_node, _ = *erb_node
|
58
|
+
source = code_node.loc.source
|
59
|
+
ruby_node = extract_ruby_node(source)
|
60
|
+
send_node = ruby_node&.descendants(:send)&.first
|
61
|
+
|
62
|
+
next if code_comment?(indicator_node) ||
|
63
|
+
!ruby_node ||
|
64
|
+
!tag_helper?(send_node) ||
|
65
|
+
source.include?("nonce")
|
66
|
+
|
67
|
+
add_offense(
|
68
|
+
erb_node.loc,
|
69
|
+
"Missing a nonce attribute. Use nonce: true",
|
70
|
+
[erb_node, send_node]
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def tag_helper?(send_node)
|
76
|
+
send_node&.method_name?(:javascript_tag) ||
|
77
|
+
send_node&.method_name?(:javascript_include_tag) ||
|
78
|
+
send_node&.method_name?(:javascript_pack_tag)
|
79
|
+
end
|
80
|
+
|
81
|
+
def code_comment?(indicator_node)
|
82
|
+
indicator_node&.loc&.source == '#'
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_ruby_node(source)
|
86
|
+
BetterHtml::TestHelper::RubyNode.parse(source)
|
87
|
+
rescue ::Parser::SyntaxError
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -191,7 +191,7 @@ module ERBLint
|
|
191
191
|
{ rubocop_correction: correction, offset: offset, bound_range: bound_range }
|
192
192
|
end
|
193
193
|
|
194
|
-
super(offense_range, rubocop_offense.message.strip, context)
|
194
|
+
super(offense_range, rubocop_offense.message.strip, context, rubocop_offense.severity.name)
|
195
195
|
end
|
196
196
|
end
|
197
197
|
end
|
data/lib/erb_lint/offense.rb
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
module ERBLint
|
4
4
|
# Defines common functionality available to all linters.
|
5
5
|
class Offense
|
6
|
-
attr_reader :linter, :source_range, :message, :context
|
6
|
+
attr_reader :linter, :source_range, :message, :context, :severity
|
7
7
|
|
8
|
-
def initialize(linter, source_range, message, context = nil)
|
8
|
+
def initialize(linter, source_range, message, context = nil, severity = nil)
|
9
9
|
unless source_range.is_a?(Parser::Source::Range)
|
10
10
|
raise ArgumentError, "expected Parser::Source::Range for arg 2"
|
11
11
|
end
|
@@ -13,19 +13,22 @@ module ERBLint
|
|
13
13
|
@source_range = source_range
|
14
14
|
@message = message
|
15
15
|
@context = context
|
16
|
+
@severity = severity
|
16
17
|
end
|
17
18
|
|
18
19
|
def inspect
|
19
20
|
"#<#{self.class.name} linter=#{linter.class.name} "\
|
20
21
|
"source_range=#{source_range.begin_pos}...#{source_range.end_pos} "\
|
21
|
-
"message=#{message}>"
|
22
|
+
"message=#{message}> "\
|
23
|
+
"severity=#{severity}"
|
22
24
|
end
|
23
25
|
|
24
26
|
def ==(other)
|
25
27
|
other.class <= ERBLint::Offense &&
|
26
28
|
other.linter == linter &&
|
27
29
|
other.source_range == source_range &&
|
28
|
-
other.message == message
|
30
|
+
other.message == message &&
|
31
|
+
other.severity == severity
|
29
32
|
end
|
30
33
|
|
31
34
|
def line_range
|
@@ -35,8 +35,14 @@ module ERBLint
|
|
35
35
|
def summary
|
36
36
|
if stats.corrected > 0
|
37
37
|
report_corrected_offenses
|
38
|
-
elsif stats.found > 0
|
39
|
-
|
38
|
+
elsif stats.ignored > 0 || stats.found > 0
|
39
|
+
if stats.ignored > 0
|
40
|
+
warn(Rainbow("#{stats.ignored} error(s) were ignored in ERB files").yellow)
|
41
|
+
end
|
42
|
+
|
43
|
+
if stats.found > 0
|
44
|
+
warn(Rainbow("#{stats.found} error(s) were found in ERB files").red)
|
45
|
+
end
|
40
46
|
else
|
41
47
|
puts Rainbow("No errors were found in ERB files").green
|
42
48
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Reporters
|
7
|
+
class JsonReporter < Reporter
|
8
|
+
def preview; end
|
9
|
+
|
10
|
+
def show
|
11
|
+
puts formatted_data
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def formatted_data
|
17
|
+
{
|
18
|
+
metadata: metadata,
|
19
|
+
files: formatted_files,
|
20
|
+
summary: summary,
|
21
|
+
}.to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
def metadata
|
25
|
+
{
|
26
|
+
erb_lint_version: ERBLint::VERSION,
|
27
|
+
ruby_engine: RUBY_ENGINE,
|
28
|
+
ruby_version: RUBY_VERSION,
|
29
|
+
ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
|
30
|
+
ruby_platform: RUBY_PLATFORM,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def summary
|
35
|
+
{
|
36
|
+
offenses: stats.found,
|
37
|
+
inspected_files: stats.processed_files.size,
|
38
|
+
corrected: stats.corrected,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def formatted_files
|
43
|
+
processed_files.map do |filename, offenses|
|
44
|
+
{
|
45
|
+
path: filename,
|
46
|
+
offenses: formatted_offenses(offenses),
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def formatted_offenses(offenses)
|
52
|
+
offenses.map do |offense|
|
53
|
+
format_offense(offense)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_offense(offense)
|
58
|
+
{
|
59
|
+
linter: offense.linter.class.simple_name,
|
60
|
+
message: offense.message.to_s,
|
61
|
+
location: {
|
62
|
+
start_line: offense.line_number,
|
63
|
+
start_column: offense.column,
|
64
|
+
last_line: offense.source_range.last_line,
|
65
|
+
last_column: offense.source_range.last_column,
|
66
|
+
length: offense.source_range.length,
|
67
|
+
},
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/erb_lint/stats.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ERBLint
|
3
3
|
class Stats
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :ignored,
|
5
|
+
:found,
|
5
6
|
:corrected,
|
6
7
|
:exceptions,
|
7
8
|
:linters,
|
@@ -9,6 +10,7 @@ module ERBLint
|
|
9
10
|
:processed_files
|
10
11
|
|
11
12
|
def initialize(
|
13
|
+
ignored: 0,
|
12
14
|
found: 0,
|
13
15
|
corrected: 0,
|
14
16
|
exceptions: 0,
|
@@ -16,6 +18,7 @@ module ERBLint
|
|
16
18
|
files: 0,
|
17
19
|
processed_files: {}
|
18
20
|
)
|
21
|
+
@ignored = ignored
|
19
22
|
@found = found
|
20
23
|
@corrected = corrected
|
21
24
|
@exceptions = exceptions
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Utils
|
5
|
+
module SeverityLevels
|
6
|
+
SEVERITY_NAMES = %i[info refactor convention warning error fatal].freeze
|
7
|
+
|
8
|
+
SEVERITY_CODE_TABLE = { I: :info, R: :refactor, C: :convention,
|
9
|
+
W: :warning, E: :error, F: :fatal }.freeze
|
10
|
+
|
11
|
+
def severity_level_for_name(name)
|
12
|
+
SEVERITY_NAMES.index(name || :error) + 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/erb_lint/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erb_lint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Chan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: better_html
|
@@ -160,6 +160,7 @@ extra_rdoc_files: []
|
|
160
160
|
files:
|
161
161
|
- exe/erblint
|
162
162
|
- lib/erb_lint.rb
|
163
|
+
- lib/erb_lint/all.rb
|
163
164
|
- lib/erb_lint/cli.rb
|
164
165
|
- lib/erb_lint/corrector.rb
|
165
166
|
- lib/erb_lint/file_loader.rb
|
@@ -175,6 +176,9 @@ files:
|
|
175
176
|
- lib/erb_lint/linters/hard_coded_string.rb
|
176
177
|
- lib/erb_lint/linters/no_javascript_tag_helper.rb
|
177
178
|
- lib/erb_lint/linters/parser_errors.rb
|
179
|
+
- lib/erb_lint/linters/partial_instance_variable.rb
|
180
|
+
- lib/erb_lint/linters/require_input_autocomplete.rb
|
181
|
+
- lib/erb_lint/linters/require_script_nonce.rb
|
178
182
|
- lib/erb_lint/linters/right_trim.rb
|
179
183
|
- lib/erb_lint/linters/rubocop.rb
|
180
184
|
- lib/erb_lint/linters/rubocop_text.rb
|
@@ -187,6 +191,7 @@ files:
|
|
187
191
|
- lib/erb_lint/processed_source.rb
|
188
192
|
- lib/erb_lint/reporter.rb
|
189
193
|
- lib/erb_lint/reporters/compact_reporter.rb
|
194
|
+
- lib/erb_lint/reporters/json_reporter.rb
|
190
195
|
- lib/erb_lint/reporters/multiline_reporter.rb
|
191
196
|
- lib/erb_lint/runner.rb
|
192
197
|
- lib/erb_lint/runner_config.rb
|
@@ -195,6 +200,7 @@ files:
|
|
195
200
|
- lib/erb_lint/utils/block_map.rb
|
196
201
|
- lib/erb_lint/utils/offset_corrector.rb
|
197
202
|
- lib/erb_lint/utils/ruby_to_erb.rb
|
203
|
+
- lib/erb_lint/utils/severity_levels.rb
|
198
204
|
- lib/erb_lint/version.rb
|
199
205
|
homepage: https://github.com/Shopify/erb-lint
|
200
206
|
licenses:
|
@@ -216,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
216
222
|
- !ruby/object:Gem::Version
|
217
223
|
version: '0'
|
218
224
|
requirements: []
|
219
|
-
rubygems_version: 3.
|
225
|
+
rubygems_version: 3.2.20
|
220
226
|
signing_key:
|
221
227
|
specification_version: 4
|
222
228
|
summary: ERB lint tool
|