erb_lint 0.0.34 → 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 -14
- data/lib/erb_lint/all.rb +26 -0
- data/lib/erb_lint/cli.rb +87 -40
- data/lib/erb_lint/corrector.rb +14 -3
- data/lib/erb_lint/linter.rb +3 -2
- data/lib/erb_lint/linter_registry.rb +12 -3
- data/lib/erb_lint/linters/hard_coded_string.rb +22 -2
- 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 +63 -32
- data/lib/erb_lint/linters/rubocop_text.rb +2 -2
- data/lib/erb_lint/offense.rb +15 -4
- data/lib/erb_lint/reporter.rb +39 -0
- data/lib/erb_lint/reporters/compact_reporter.rb +66 -0
- data/lib/erb_lint/reporters/json_reporter.rb +72 -0
- data/lib/erb_lint/reporters/multiline_reporter.rb +22 -0
- data/lib/erb_lint/runner.rb +0 -1
- data/lib/erb_lint/runner_config.rb +1 -0
- data/lib/erb_lint/stats.rb +30 -0
- data/lib/erb_lint/utils/severity_levels.rb +16 -0
- data/lib/erb_lint/version.rb +1 -1
- metadata +46 -8
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,17 +1,3 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'erb_lint/corrector'
|
4
|
-
require 'erb_lint/file_loader'
|
5
|
-
require 'erb_lint/linter_config'
|
6
|
-
require 'erb_lint/linter_registry'
|
7
|
-
require 'erb_lint/linter'
|
8
|
-
require 'erb_lint/offense'
|
9
|
-
require 'erb_lint/processed_source'
|
10
|
-
require 'erb_lint/runner_config'
|
11
|
-
require 'erb_lint/runner'
|
12
3
|
require 'erb_lint/version'
|
13
|
-
|
14
|
-
# Load linters
|
15
|
-
Dir[File.expand_path('erb_lint/linters/**/*.rb', File.dirname(__FILE__))].each do |file|
|
16
|
-
require file
|
17
|
-
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,29 +1,24 @@
|
|
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
|
|
16
19
|
class ExitWithFailure < RuntimeError; end
|
17
|
-
class ExitWithSuccess < RuntimeError; end
|
18
20
|
|
19
|
-
class
|
20
|
-
attr_accessor :found, :corrected, :exceptions
|
21
|
-
def initialize
|
22
|
-
@found = 0
|
23
|
-
@corrected = 0
|
24
|
-
@exceptions = 0
|
25
|
-
end
|
26
|
-
end
|
21
|
+
class ExitWithSuccess < RuntimeError; end
|
27
22
|
|
28
23
|
def initialize
|
29
24
|
@options = {}
|
@@ -35,7 +30,7 @@ module ERBLint
|
|
35
30
|
def run(args = ARGV)
|
36
31
|
dupped_args = args.dup
|
37
32
|
load_options(dupped_args)
|
38
|
-
@files = dupped_args
|
33
|
+
@files = @options[:stdin] || dupped_args
|
39
34
|
|
40
35
|
load_config
|
41
36
|
|
@@ -51,19 +46,24 @@ module ERBLint
|
|
51
46
|
failure!('no linter available with current configuration')
|
52
47
|
end
|
53
48
|
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
@options[:format] ||= :multiline
|
50
|
+
@options[:fail_level] ||= severity_level_for_name(:refactor)
|
51
|
+
@stats.files = lint_files.size
|
52
|
+
@stats.linters = enabled_linter_classes.size
|
53
|
+
|
54
|
+
reporter = Reporter.create_reporter(@options[:format], @stats, autocorrect?)
|
55
|
+
reporter.preview
|
57
56
|
|
58
57
|
runner = ERBLint::Runner.new(file_loader, @config)
|
58
|
+
file_content = nil
|
59
59
|
|
60
60
|
lint_files.each do |filename|
|
61
61
|
runner.clear_offenses
|
62
62
|
begin
|
63
|
-
run_with_corrections(runner, filename)
|
63
|
+
file_content = run_with_corrections(runner, filename)
|
64
64
|
rescue => e
|
65
65
|
@stats.exceptions += 1
|
66
|
-
puts "Exception
|
66
|
+
puts "Exception occurred when processing: #{relative_filename(filename)}"
|
67
67
|
puts "If this file cannot be processed by erb-lint, "\
|
68
68
|
"you can exclude it in your configuration file."
|
69
69
|
puts e.message
|
@@ -72,19 +72,12 @@ module ERBLint
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
else
|
82
|
-
puts Rainbow("#{@stats.corrected} error(s) corrected in ERB files").green
|
83
|
-
end
|
84
|
-
elsif @stats.found > 0
|
85
|
-
warn(Rainbow("#{@stats.found} error(s) were found in ERB files").red)
|
86
|
-
else
|
87
|
-
puts Rainbow("No errors were found in ERB files").green
|
75
|
+
reporter.show
|
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
|
88
81
|
end
|
89
82
|
|
90
83
|
@stats.found == 0 && @stats.exceptions == 0
|
@@ -106,7 +99,7 @@ module ERBLint
|
|
106
99
|
end
|
107
100
|
|
108
101
|
def run_with_corrections(runner, filename)
|
109
|
-
file_content =
|
102
|
+
file_content = read_content(filename)
|
110
103
|
|
111
104
|
7.times do
|
112
105
|
processed_source = ERBLint::ProcessedSource.new(filename, file_content)
|
@@ -119,22 +112,35 @@ module ERBLint
|
|
119
112
|
|
120
113
|
@stats.corrected += corrector.corrections.size
|
121
114
|
|
122
|
-
|
123
|
-
|
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
|
124
120
|
end
|
125
121
|
|
126
122
|
file_content = corrector.corrected_content
|
127
123
|
runner.clear_offenses
|
128
124
|
end
|
125
|
+
offenses_filename = relative_filename(filename)
|
126
|
+
offenses = runner.offenses || []
|
129
127
|
|
130
|
-
@stats.found
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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)
|
135
133
|
|
136
|
-
|
137
|
-
|
134
|
+
@stats.processed_files[offenses_filename] ||= []
|
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)
|
138
144
|
end
|
139
145
|
|
140
146
|
def correct(processed_source, offenses)
|
@@ -190,7 +196,8 @@ module ERBLint
|
|
190
196
|
|
191
197
|
def excluded?(filename)
|
192
198
|
@config.global_exclude.any? do |path|
|
193
|
-
File.
|
199
|
+
expanded_path = File.expand_path(path, Dir.pwd)
|
200
|
+
File.fnmatch?(expanded_path, filename)
|
194
201
|
end
|
195
202
|
end
|
196
203
|
|
@@ -258,6 +265,15 @@ module ERBLint
|
|
258
265
|
end
|
259
266
|
end
|
260
267
|
|
268
|
+
opts.on("--format FORMAT", format_options_help) do |format|
|
269
|
+
unless Reporter.available_format?(format)
|
270
|
+
error_message = invalid_format_error_message(format)
|
271
|
+
failure!(error_message)
|
272
|
+
end
|
273
|
+
|
274
|
+
@options[:format] = format
|
275
|
+
end
|
276
|
+
|
261
277
|
opts.on("--lint-all", "Lint all files matching configured glob [default: #{DEFAULT_LINT_ALL_GLOB}]") do |config|
|
262
278
|
@options[:lint_all] = config
|
263
279
|
end
|
@@ -276,10 +292,27 @@ module ERBLint
|
|
276
292
|
@options[:enabled_linters] = linters
|
277
293
|
end
|
278
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
|
+
|
279
304
|
opts.on("-a", "--autocorrect", "Correct offenses automatically if possible (default: false)") do |config|
|
280
305
|
@options[:autocorrect] = config
|
281
306
|
end
|
282
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
|
+
|
283
316
|
opts.on_tail("-h", "--help", "Show this message") do
|
284
317
|
success!(opts)
|
285
318
|
end
|
@@ -289,5 +322,19 @@ module ERBLint
|
|
289
322
|
end
|
290
323
|
end
|
291
324
|
end
|
325
|
+
|
326
|
+
def format_options_help
|
327
|
+
"Report offenses in the given format: "\
|
328
|
+
"(#{Reporter.available_formats.join(', ')}) (default: multiline)"
|
329
|
+
end
|
330
|
+
|
331
|
+
def invalid_format_error_message(given_format)
|
332
|
+
formats = Reporter.available_formats.map { |format| " - #{format}\n" }
|
333
|
+
"#{given_format}: is not a valid format. Available formats:\n#{formats.join}"
|
334
|
+
end
|
335
|
+
|
336
|
+
def stdin?
|
337
|
+
@options[:stdin].present?
|
338
|
+
end
|
292
339
|
end
|
293
340
|
end
|
data/lib/erb_lint/corrector.rb
CHANGED
@@ -17,11 +17,22 @@ module ERBLint
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def corrector
|
20
|
-
|
20
|
+
BASE.new(@processed_source.source_buffer, corrections)
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
corrector
|
23
|
+
if ::RuboCop::Version::STRING.to_f >= 0.87
|
24
|
+
require 'rubocop/cop/legacy/corrector'
|
25
|
+
BASE = ::RuboCop::Cop::Legacy::Corrector
|
26
|
+
|
27
|
+
def diagnostics
|
28
|
+
[]
|
29
|
+
end
|
30
|
+
else
|
31
|
+
BASE = ::RuboCop::Cop::Corrector
|
32
|
+
|
33
|
+
def diagnostics
|
34
|
+
corrector.diagnostics
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
27
38
|
end
|
data/lib/erb_lint/linter.rb
CHANGED
@@ -14,6 +14,7 @@ module ERBLint
|
|
14
14
|
# `ERBLint::Linters::Foo.simple_name` #=> "Foo"
|
15
15
|
# `ERBLint::Linters::Compass::Bar.simple_name` #=> "Compass::Bar"
|
16
16
|
def inherited(linter)
|
17
|
+
super
|
17
18
|
linter.simple_name = if linter.name.start_with?('ERBLint::Linters::')
|
18
19
|
name_parts = linter.name.split('::')
|
19
20
|
name_parts[2..-1].join('::')
|
@@ -52,8 +53,8 @@ module ERBLint
|
|
52
53
|
raise NotImplementedError, "must implement ##{__method__}"
|
53
54
|
end
|
54
55
|
|
55
|
-
def add_offense(source_range, message, context = nil)
|
56
|
-
@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)
|
57
58
|
end
|
58
59
|
|
59
60
|
def clear_offenses
|
@@ -4,19 +4,28 @@ module ERBLint
|
|
4
4
|
# Stores all linters available to the application.
|
5
5
|
module LinterRegistry
|
6
6
|
CUSTOM_LINTERS_DIR = '.erb-linters'
|
7
|
-
@
|
7
|
+
@loaded_linters = []
|
8
8
|
|
9
9
|
class << self
|
10
|
-
|
10
|
+
def clear
|
11
|
+
@linters = nil
|
12
|
+
end
|
11
13
|
|
12
14
|
def included(linter_class)
|
13
|
-
@
|
15
|
+
@loaded_linters << linter_class
|
14
16
|
end
|
15
17
|
|
16
18
|
def find_by_name(name)
|
17
19
|
linters.detect { |linter| linter.simple_name == name }
|
18
20
|
end
|
19
21
|
|
22
|
+
def linters
|
23
|
+
@linters ||= begin
|
24
|
+
load_custom_linters
|
25
|
+
@loaded_linters
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
20
29
|
def load_custom_linters(directory = CUSTOM_LINTERS_DIR)
|
21
30
|
ruby_files = Dir.glob(File.expand_path(File.join(directory, '**', '*.rb')))
|
22
31
|
ruby_files.each { |file| require file }
|
@@ -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: -> { {} }
|
@@ -63,7 +83,7 @@ module ERBLint
|
|
63
83
|
string = offense.source_range.source
|
64
84
|
return unless (klass = load_corrector)
|
65
85
|
return unless string.strip.length > 1
|
66
|
-
node = RuboCop::AST::StrNode.new(:str, [string])
|
86
|
+
node = ::RuboCop::AST::StrNode.new(:str, [string])
|
67
87
|
corrector = klass.new(node, processed_source.filename, corrector_i18n_load_path, offense.source_range)
|
68
88
|
corrector.autocorrect(tag_start: '<%= ', tag_end: ' %>')
|
69
89
|
rescue MissingCorrector, MissingI18nLoadPath
|
@@ -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
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'better_html'
|
4
|
-
require 'rubocop'
|
5
4
|
require 'tempfile'
|
6
5
|
require 'erb_lint/utils/offset_corrector'
|
7
6
|
|
@@ -26,7 +25,7 @@ module ERBLint
|
|
26
25
|
super
|
27
26
|
@only_cops = @config.only
|
28
27
|
custom_config = config_from_hash(@config.rubocop_config)
|
29
|
-
@rubocop_config = RuboCop::ConfigLoader.merge_with_default(custom_config, '')
|
28
|
+
@rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, '')
|
30
29
|
end
|
31
30
|
|
32
31
|
def run(processed_source)
|
@@ -35,17 +34,29 @@ module ERBLint
|
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
offense.context[:offset]
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
if ::RuboCop::Version::STRING.to_f >= 0.87
|
38
|
+
def autocorrect(_processed_source, offense)
|
39
|
+
return unless offense.context
|
40
|
+
rubocop_correction = offense.context[:rubocop_correction]
|
41
|
+
return unless rubocop_correction
|
42
|
+
|
43
|
+
lambda do |corrector|
|
44
|
+
corrector.import!(rubocop_correction, offset: offense.context[:offset])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
def autocorrect(processed_source, offense)
|
49
|
+
return unless offense.context
|
50
|
+
|
51
|
+
lambda do |corrector|
|
52
|
+
passthrough = Utils::OffsetCorrector.new(
|
53
|
+
processed_source,
|
54
|
+
corrector,
|
55
|
+
offense.context[:offset],
|
56
|
+
offense.context[:bound_range],
|
57
|
+
)
|
58
|
+
offense.context[:rubocop_correction].call(passthrough)
|
59
|
+
end
|
49
60
|
end
|
50
61
|
end
|
51
62
|
|
@@ -62,22 +73,23 @@ module ERBLint
|
|
62
73
|
original_source = code_node.loc.source
|
63
74
|
trimmed_source = original_source.sub(BLOCK_EXPR, '').sub(SUFFIX_EXPR, '')
|
64
75
|
alignment_column = code_node.loc.column
|
76
|
+
offset = code_node.loc.begin_pos - alignment_column
|
65
77
|
aligned_source = "#{' ' * alignment_column}#{trimmed_source}"
|
66
78
|
|
67
79
|
source = rubocop_processed_source(aligned_source, processed_source.filename)
|
68
80
|
return unless source.valid_syntax?
|
69
81
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
82
|
+
activate_team(processed_source, source, offset, code_node, build_team)
|
83
|
+
end
|
84
|
+
|
85
|
+
if ::RuboCop::Version::STRING.to_f >= 0.87
|
86
|
+
def activate_team(processed_source, source, offset, code_node, team)
|
87
|
+
report = team.investigate(source)
|
88
|
+
report.offenses.each do |rubocop_offense|
|
89
|
+
next if rubocop_offense.disabled?
|
90
|
+
|
91
|
+
correction = rubocop_offense.corrector if rubocop_offense.corrected?
|
79
92
|
|
80
|
-
offset = code_node.loc.begin_pos - alignment_column
|
81
93
|
offense_range = processed_source
|
82
94
|
.to_source_range(rubocop_offense.location)
|
83
95
|
.offset(offset)
|
@@ -85,6 +97,25 @@ module ERBLint
|
|
85
97
|
add_offense(rubocop_offense, offense_range, correction, offset, code_node.loc.range)
|
86
98
|
end
|
87
99
|
end
|
100
|
+
else
|
101
|
+
def activate_team(processed_source, source, offset, code_node, team)
|
102
|
+
team.inspect_file(source)
|
103
|
+
team.cops.each do |cop|
|
104
|
+
correction_offset = 0
|
105
|
+
cop.offenses.reject(&:disabled?).each do |rubocop_offense|
|
106
|
+
if rubocop_offense.corrected?
|
107
|
+
correction = cop.corrections[correction_offset]
|
108
|
+
correction_offset += 1
|
109
|
+
end
|
110
|
+
|
111
|
+
offense_range = processed_source
|
112
|
+
.to_source_range(rubocop_offense.location)
|
113
|
+
.offset(offset)
|
114
|
+
|
115
|
+
add_offense(rubocop_offense, offense_range, correction, offset, code_node.loc.range)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
88
119
|
end
|
89
120
|
|
90
121
|
def tempfile_from(filename, content)
|
@@ -97,7 +128,7 @@ module ERBLint
|
|
97
128
|
end
|
98
129
|
|
99
130
|
def rubocop_processed_source(content, filename)
|
100
|
-
RuboCop::ProcessedSource.new(
|
131
|
+
::RuboCop::ProcessedSource.new(
|
101
132
|
content,
|
102
133
|
@rubocop_config.target_ruby_version,
|
103
134
|
filename
|
@@ -106,15 +137,15 @@ module ERBLint
|
|
106
137
|
|
107
138
|
def cop_classes
|
108
139
|
if @only_cops.present?
|
109
|
-
selected_cops = RuboCop::Cop::Cop.all.select { |cop| cop.match?(@only_cops) }
|
110
|
-
RuboCop::Cop::Registry.new(selected_cops)
|
140
|
+
selected_cops = ::RuboCop::Cop::Cop.all.select { |cop| cop.match?(@only_cops) }
|
141
|
+
::RuboCop::Cop::Registry.new(selected_cops)
|
111
142
|
else
|
112
|
-
RuboCop::Cop::Registry.new(RuboCop::Cop::Cop.all)
|
143
|
+
::RuboCop::Cop::Registry.new(::RuboCop::Cop::Cop.all)
|
113
144
|
end
|
114
145
|
end
|
115
146
|
|
116
147
|
def build_team
|
117
|
-
RuboCop::Cop::Team.new(
|
148
|
+
::RuboCop::Cop::Team.new(
|
118
149
|
cop_classes,
|
119
150
|
@rubocop_config,
|
120
151
|
extra_details: true,
|
@@ -129,7 +160,7 @@ module ERBLint
|
|
129
160
|
resolve_inheritance(hash, inherit_from)
|
130
161
|
|
131
162
|
tempfile_from('.erblint-rubocop', hash.to_yaml) do |tempfile|
|
132
|
-
RuboCop::ConfigLoader.load_file(tempfile.path)
|
163
|
+
::RuboCop::ConfigLoader.load_file(tempfile.path)
|
133
164
|
end
|
134
165
|
end
|
135
166
|
|
@@ -137,7 +168,7 @@ module ERBLint
|
|
137
168
|
base_configs(inherit_from)
|
138
169
|
.reverse_each do |base_config|
|
139
170
|
base_config.each do |k, v|
|
140
|
-
hash[k] = hash.key?(k) ? RuboCop::ConfigLoader.merge(v, hash[k]) : v if v.is_a?(Hash)
|
171
|
+
hash[k] = hash.key?(k) ? ::RuboCop::ConfigLoader.merge(v, hash[k]) : v if v.is_a?(Hash)
|
141
172
|
end
|
142
173
|
end
|
143
174
|
end
|
@@ -146,7 +177,7 @@ module ERBLint
|
|
146
177
|
regex = URI::DEFAULT_PARSER.make_regexp(%w(http https))
|
147
178
|
configs = Array(inherit_from).compact.map do |base_name|
|
148
179
|
if base_name =~ /\A#{regex}\z/
|
149
|
-
RuboCop::ConfigLoader.load_file(RuboCop::RemoteConfig.new(base_name, Dir.pwd))
|
180
|
+
::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
|
150
181
|
else
|
151
182
|
config_from_hash(@file_loader.yaml(base_name))
|
152
183
|
end
|
@@ -160,7 +191,7 @@ module ERBLint
|
|
160
191
|
{ rubocop_correction: correction, offset: offset, bound_range: bound_range }
|
161
192
|
end
|
162
193
|
|
163
|
-
super(offense_range, rubocop_offense.message.strip, context)
|
194
|
+
super(offense_range, rubocop_offense.message.strip, context, rubocop_offense.severity.name)
|
164
195
|
end
|
165
196
|
end
|
166
197
|
end
|
@@ -28,9 +28,9 @@ module ERBLint
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def cop_classes
|
31
|
-
selected_cops = RuboCop::Cop::Cop.all.select { |cop| cop.match?(@only_cops) }
|
31
|
+
selected_cops = ::RuboCop::Cop::Cop.all.select { |cop| cop.match?(@only_cops) }
|
32
32
|
|
33
|
-
RuboCop::Cop::Registry.new(selected_cops)
|
33
|
+
::RuboCop::Cop::Registry.new(selected_cops)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
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,23 +13,34 @@ 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
|
32
35
|
Range.new(source_range.line, source_range.last_line)
|
33
36
|
end
|
37
|
+
|
38
|
+
def line_number
|
39
|
+
line_range.begin
|
40
|
+
end
|
41
|
+
|
42
|
+
def column
|
43
|
+
source_range.column
|
44
|
+
end
|
34
45
|
end
|
35
46
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_support/core_ext/class'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
class Reporter
|
7
|
+
def self.create_reporter(format, *args)
|
8
|
+
reporter_klass = "#{ERBLint::Reporters}::#{format.to_s.camelize}Reporter".constantize
|
9
|
+
reporter_klass.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.available_format?(format)
|
13
|
+
available_formats.include?(format.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.available_formats
|
17
|
+
descendants
|
18
|
+
.map(&:to_s)
|
19
|
+
.map(&:demodulize)
|
20
|
+
.map(&:underscore)
|
21
|
+
.map { |klass_name| klass_name.sub("_reporter", "") }
|
22
|
+
.sort
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(stats, autocorrect)
|
26
|
+
@stats = stats
|
27
|
+
@autocorrect = autocorrect
|
28
|
+
end
|
29
|
+
|
30
|
+
def preview; end
|
31
|
+
|
32
|
+
def show; end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :stats, :autocorrect
|
37
|
+
delegate :processed_files, to: :stats
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Reporters
|
5
|
+
class CompactReporter < Reporter
|
6
|
+
def preview
|
7
|
+
puts "Linting #{stats.files} files with "\
|
8
|
+
"#{stats.linters} #{'autocorrectable ' if autocorrect}linters..."
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
processed_files.each do |filename, offenses|
|
13
|
+
offenses.each do |offense|
|
14
|
+
puts format_offense(filename, offense)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
footer
|
19
|
+
summary
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def format_offense(filename, offense)
|
25
|
+
[
|
26
|
+
"#{filename}:",
|
27
|
+
"#{offense.line_number}:",
|
28
|
+
"#{offense.column}: ",
|
29
|
+
offense.message.to_s,
|
30
|
+
].join
|
31
|
+
end
|
32
|
+
|
33
|
+
def footer; end
|
34
|
+
|
35
|
+
def summary
|
36
|
+
if stats.corrected > 0
|
37
|
+
report_corrected_offenses
|
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
|
46
|
+
else
|
47
|
+
puts Rainbow("No errors were found in ERB files").green
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def report_corrected_offenses
|
52
|
+
corrected_found_diff = stats.found - stats.corrected
|
53
|
+
|
54
|
+
if corrected_found_diff > 0
|
55
|
+
message = Rainbow(
|
56
|
+
"#{stats.corrected} error(s) corrected and #{corrected_found_diff} error(s) remaining in ERB files"
|
57
|
+
).red
|
58
|
+
|
59
|
+
warn(message)
|
60
|
+
else
|
61
|
+
puts Rainbow("#{stats.corrected} error(s) corrected in ERB files").green
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
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
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "compact_reporter"
|
3
|
+
|
4
|
+
module ERBLint
|
5
|
+
module Reporters
|
6
|
+
class MultilineReporter < CompactReporter
|
7
|
+
private
|
8
|
+
|
9
|
+
def format_offense(filename, offense)
|
10
|
+
<<~EOF
|
11
|
+
|
12
|
+
#{offense.message}#{Rainbow(' (not autocorrected)').red if autocorrect}
|
13
|
+
In file: #{filename}:#{offense.line_number}
|
14
|
+
EOF
|
15
|
+
end
|
16
|
+
|
17
|
+
def footer
|
18
|
+
puts
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/erb_lint/runner.rb
CHANGED
@@ -10,7 +10,6 @@ module ERBLint
|
|
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
|
-
LinterRegistry.load_custom_linters
|
14
13
|
linter_classes = LinterRegistry.linters.select { |klass| @config.for_linter(klass).enabled? }
|
15
14
|
@linters = linter_classes.map do |linter_class|
|
16
15
|
linter_class.new(@file_loader, @config.for_linter(linter_class))
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ERBLint
|
3
|
+
class Stats
|
4
|
+
attr_accessor :ignored,
|
5
|
+
:found,
|
6
|
+
:corrected,
|
7
|
+
:exceptions,
|
8
|
+
:linters,
|
9
|
+
:files,
|
10
|
+
:processed_files
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
ignored: 0,
|
14
|
+
found: 0,
|
15
|
+
corrected: 0,
|
16
|
+
exceptions: 0,
|
17
|
+
linters: 0,
|
18
|
+
files: 0,
|
19
|
+
processed_files: {}
|
20
|
+
)
|
21
|
+
@ignored = ignored
|
22
|
+
@found = found
|
23
|
+
@corrected = corrected
|
24
|
+
@exceptions = exceptions
|
25
|
+
@linters = linters
|
26
|
+
@files = files
|
27
|
+
@processed_files = processed_files
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -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:
|
11
|
+
date: 2021-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: better_html
|
@@ -42,16 +42,30 @@ dependencies:
|
|
42
42
|
name: rubocop
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0
|
47
|
+
version: '0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: parser
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.7.1.4
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
68
|
+
version: 2.7.1.4
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: activesupport
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +136,20 @@ dependencies:
|
|
122
136
|
- - ">="
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop-shopify
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
125
153
|
description: ERB Linter tool.
|
126
154
|
email:
|
127
155
|
- justin.the.c@gmail.com
|
@@ -132,6 +160,7 @@ extra_rdoc_files: []
|
|
132
160
|
files:
|
133
161
|
- exe/erblint
|
134
162
|
- lib/erb_lint.rb
|
163
|
+
- lib/erb_lint/all.rb
|
135
164
|
- lib/erb_lint/cli.rb
|
136
165
|
- lib/erb_lint/corrector.rb
|
137
166
|
- lib/erb_lint/file_loader.rb
|
@@ -147,6 +176,9 @@ files:
|
|
147
176
|
- lib/erb_lint/linters/hard_coded_string.rb
|
148
177
|
- lib/erb_lint/linters/no_javascript_tag_helper.rb
|
149
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
|
150
182
|
- lib/erb_lint/linters/right_trim.rb
|
151
183
|
- lib/erb_lint/linters/rubocop.rb
|
152
184
|
- lib/erb_lint/linters/rubocop_text.rb
|
@@ -157,12 +189,18 @@ files:
|
|
157
189
|
- lib/erb_lint/linters/trailing_whitespace.rb
|
158
190
|
- lib/erb_lint/offense.rb
|
159
191
|
- lib/erb_lint/processed_source.rb
|
192
|
+
- lib/erb_lint/reporter.rb
|
193
|
+
- lib/erb_lint/reporters/compact_reporter.rb
|
194
|
+
- lib/erb_lint/reporters/json_reporter.rb
|
195
|
+
- lib/erb_lint/reporters/multiline_reporter.rb
|
160
196
|
- lib/erb_lint/runner.rb
|
161
197
|
- lib/erb_lint/runner_config.rb
|
162
198
|
- lib/erb_lint/runner_config_resolver.rb
|
199
|
+
- lib/erb_lint/stats.rb
|
163
200
|
- lib/erb_lint/utils/block_map.rb
|
164
201
|
- lib/erb_lint/utils/offset_corrector.rb
|
165
202
|
- lib/erb_lint/utils/ruby_to_erb.rb
|
203
|
+
- lib/erb_lint/utils/severity_levels.rb
|
166
204
|
- lib/erb_lint/version.rb
|
167
205
|
homepage: https://github.com/Shopify/erb-lint
|
168
206
|
licenses:
|
@@ -177,14 +215,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
177
215
|
requirements:
|
178
216
|
- - ">="
|
179
217
|
- !ruby/object:Gem::Version
|
180
|
-
version: 2.
|
218
|
+
version: 2.5.0
|
181
219
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
220
|
requirements:
|
183
221
|
- - ">="
|
184
222
|
- !ruby/object:Gem::Version
|
185
223
|
version: '0'
|
186
224
|
requirements: []
|
187
|
-
rubygems_version: 3.
|
225
|
+
rubygems_version: 3.2.20
|
188
226
|
signing_key:
|
189
227
|
specification_version: 4
|
190
228
|
summary: ERB lint tool
|