erb_lint 0.0.34 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|