erb_lint 0.0.35 → 0.1.1

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/exe/erblint +1 -1
  3. data/lib/erb_lint.rb +1 -17
  4. data/lib/erb_lint/all.rb +26 -0
  5. data/lib/erb_lint/cli.rb +101 -54
  6. data/lib/erb_lint/corrector.rb +1 -1
  7. data/lib/erb_lint/linter.rb +6 -5
  8. data/lib/erb_lint/linter_config.rb +3 -3
  9. data/lib/erb_lint/linter_registry.rb +14 -5
  10. data/lib/erb_lint/linters/allowed_script_type.rb +7 -7
  11. data/lib/erb_lint/linters/closing_erb_tag_indent.rb +2 -2
  12. data/lib/erb_lint/linters/deprecated_classes.rb +7 -7
  13. data/lib/erb_lint/linters/erb_safety.rb +2 -2
  14. data/lib/erb_lint/linters/extra_newline.rb +1 -1
  15. data/lib/erb_lint/linters/final_newline.rb +2 -2
  16. data/lib/erb_lint/linters/hard_coded_string.rb +36 -16
  17. data/lib/erb_lint/linters/no_javascript_tag_helper.rb +8 -8
  18. data/lib/erb_lint/linters/partial_instance_variable.rb +23 -0
  19. data/lib/erb_lint/linters/require_input_autocomplete.rb +121 -0
  20. data/lib/erb_lint/linters/require_script_nonce.rb +92 -0
  21. data/lib/erb_lint/linters/right_trim.rb +1 -1
  22. data/lib/erb_lint/linters/rubocop.rb +11 -11
  23. data/lib/erb_lint/linters/rubocop_text.rb +1 -1
  24. data/lib/erb_lint/linters/self_closing_tag.rb +5 -7
  25. data/lib/erb_lint/linters/space_around_erb_tag.rb +5 -5
  26. data/lib/erb_lint/linters/space_in_html_tag.rb +6 -6
  27. data/lib/erb_lint/linters/space_indentation.rb +1 -1
  28. data/lib/erb_lint/linters/trailing_whitespace.rb +1 -1
  29. data/lib/erb_lint/offense.rb +15 -4
  30. data/lib/erb_lint/reporter.rb +39 -0
  31. data/lib/erb_lint/reporters/compact_reporter.rb +66 -0
  32. data/lib/erb_lint/reporters/json_reporter.rb +72 -0
  33. data/lib/erb_lint/reporters/multiline_reporter.rb +22 -0
  34. data/lib/erb_lint/runner.rb +1 -2
  35. data/lib/erb_lint/runner_config.rb +8 -7
  36. data/lib/erb_lint/runner_config_resolver.rb +4 -4
  37. data/lib/erb_lint/stats.rb +30 -0
  38. data/lib/erb_lint/utils/block_map.rb +2 -2
  39. data/lib/erb_lint/utils/offset_corrector.rb +1 -1
  40. data/lib/erb_lint/utils/ruby_to_erb.rb +5 -5
  41. data/lib/erb_lint/utils/severity_levels.rb +16 -0
  42. data/lib/erb_lint/version.rb +1 -1
  43. metadata +17 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1bec7519ea346d89eb70e485c59015ea8faae7f4c85f4e3b450a4f8853eaa9b5
4
- data.tar.gz: dba705708221bc3b5a71c57d6d9cbf1db2f7c8afbfaa7ed45fc63e71433383b5
3
+ metadata.gz: b98507494f8af2a33e0e6fcbf28c48a2d91c78f42273a3cc6c1206578f3a55b0
4
+ data.tar.gz: c090a633a9d041a6d4d71bc292b1525b3a74dccd61311e0224bc1cbb0d8fea33
5
5
  SHA512:
6
- metadata.gz: 770cd1f544b749a7210949b1221ec4401349cb30bf9a171c1b7677c3d051f9887e264d62e1c8b86c5b09c593bc96b24b867a35b2bf11f4dfe2d8a17f54332d5c
7
- data.tar.gz: ae1f2c592fba2fce5a70eb0620024c49cdca3173388791036bb9e2ed56b3b0cbdf3e6361c0b68c94fc222640aba5422c6fd5c15d8691fa019d0a9cfc038e96a3
6
+ metadata.gz: b2ac77d88b7be36010d69c072e3ee72c370edefa1fc3b68135593b5b494da8b0f9eac79e9b5408d10155a007d94948ae6a4b3b279de2541be66f0576abc964a8
7
+ data.tar.gz: f0be6a75c5060b49a8b7c5e5a5d19adb3222fb580a11c2e2e488e5b83ada00db0c4daee25e99ba5538057c749f5937b80d25a96bda1ed967e6d1baeddf787965
data/exe/erblint CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  $LOAD_PATH.unshift("#{__dir__}/../lib")
5
5
 
6
- require 'erb_lint/cli'
6
+ require "erb_lint/cli"
7
7
 
8
8
  cli = ERBLint::CLI.new
9
9
  exit(cli.run)
data/lib/erb_lint.rb CHANGED
@@ -1,19 +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
- require 'erb_lint/version'
15
-
16
- # Load linters
17
- Dir[File.expand_path('erb_lint/linters/**/*.rb', File.dirname(__FILE__))].each do |file|
18
- require file
19
- end
3
+ require "erb_lint/version"
@@ -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'
4
- require 'active_support'
5
- require 'active_support/inflector'
6
- require 'optparse'
7
- require 'psych'
8
- require 'yaml'
9
- require 'rainbow'
3
+ require "erb_lint/all"
4
+ require "active_support"
5
+ require "active_support/inflector"
6
+ require "optparse"
7
+ require "psych"
8
+ require "yaml"
9
+ require "rainbow"
10
+ require "erb_lint/utils/severity_levels"
10
11
 
11
12
  module ERBLint
12
13
  class CLI
13
- DEFAULT_CONFIG_FILENAME = '.erb-lint.yml'
14
+ include Utils::SeverityLevels
15
+
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 Stats
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
 
@@ -48,22 +43,27 @@ module ERBLint
48
43
  ensure_files_exist(lint_files)
49
44
 
50
45
  if enabled_linter_classes.empty?
51
- failure!('no linter available with current configuration')
46
+ failure!("no linter available with current configuration")
52
47
  end
53
48
 
54
- puts "Linting #{lint_files.size} files with "\
55
- "#{enabled_linter_classes.size} #{'autocorrectable ' if autocorrect?}linters..."
56
- puts
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 occured when processing: #{relative_filename(filename)}"
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
- if @stats.corrected > 0
76
- corrected_found_diff = @stats.found - @stats.corrected
77
- if corrected_found_diff > 0
78
- warn(Rainbow(
79
- "#{@stats.corrected} error(s) corrected and #{corrected_found_diff} error(s) remaining in ERB files"
80
- ).red)
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 = File.read(filename, encoding: Encoding::UTF_8)
102
+ file_content = read_content(filename)
110
103
 
111
104
  7.times do
112
105
  processed_source = ERBLint::ProcessedSource.new(filename, file_content)
@@ -119,27 +112,40 @@ module ERBLint
119
112
 
120
113
  @stats.corrected += corrector.corrections.size
121
114
 
122
- File.open(filename, "wb") do |file|
123
- file.write(corrector.corrected_content)
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 += runner.offenses.size
131
- runner.offenses.each do |offense|
132
- puts <<~EOF
133
- #{offense.message}#{Rainbow(' (not autocorrected)').red if autocorrect?}
134
- In file: #{relative_filename(filename)}:#{offense.line_range.begin}
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
- EOF
137
- end
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)
141
147
  corrector = ERBLint::Corrector.new(processed_source, offenses)
142
- failure!(corrector.diagnostics.join(', ')) if corrector.diagnostics.any?
148
+ failure!(corrector.diagnostics.join(", ")) if corrector.diagnostics.any?
143
149
  corrector
144
150
  end
145
151
 
@@ -177,7 +183,7 @@ module ERBLint
177
183
  else
178
184
  @files
179
185
  .map { |f| Dir.exist?(f) ? Dir[File.join(f, glob)] : f }
180
- .map { |f| f.include?('*') ? Dir[f] : f }
186
+ .map { |f| f.include?("*") ? Dir[f] : f }
181
187
  .flatten
182
188
  .map { |f| File.expand_path(f, Dir.pwd) }
183
189
  .select { |filename| !excluded?(filename) }
@@ -190,7 +196,8 @@ module ERBLint
190
196
 
191
197
  def excluded?(filename)
192
198
  @config.global_exclude.any? do |path|
193
- File.fnmatch?(path, filename)
199
+ expanded_path = File.expand_path(path, Dir.pwd)
200
+ File.fnmatch?(expanded_path, filename)
194
201
  end
195
202
  end
196
203
 
@@ -233,14 +240,14 @@ module ERBLint
233
240
  end
234
241
 
235
242
  def relative_filename(filename)
236
- filename.sub("#{File.expand_path('.', Dir.pwd)}/", '')
243
+ filename.sub("#{File.expand_path(".", Dir.pwd)}/", "")
237
244
  end
238
245
 
239
246
  def runner_config_override
240
247
  RunnerConfig.new(
241
248
  linters: {}.tap do |linters|
242
249
  ERBLint::LinterRegistry.linters.map do |klass|
243
- linters[klass.simple_name] = { 'enabled' => enabled_linter_classes.include?(klass) }
250
+ linters[klass.simple_name] = { "enabled" => enabled_linter_classes.include?(klass) }
244
251
  end
245
252
  end
246
253
  )
@@ -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
@@ -267,19 +283,36 @@ module ERBLint
267
283
  end
268
284
 
269
285
  opts.on("--enable-linters LINTER[,LINTER,...]", Array,
270
- "Only use specified linter", "Known linters are: #{known_linter_names.join(', ')}") do |linters|
286
+ "Only use specified linter", "Known linters are: #{known_linter_names.join(", ")}") do |linters|
271
287
  linters.each do |linter|
272
288
  unless known_linter_names.include?(linter)
273
- failure!("#{linter}: not a valid linter name (#{known_linter_names.join(', ')})")
289
+ failure!("#{linter}: not a valid linter name (#{known_linter_names.join(", ")})")
274
290
  end
275
291
  end
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
@@ -21,7 +21,7 @@ module ERBLint
21
21
  end
22
22
 
23
23
  if ::RuboCop::Version::STRING.to_f >= 0.87
24
- require 'rubocop/cop/legacy/corrector'
24
+ require "rubocop/cop/legacy/corrector"
25
25
  BASE = ::RuboCop::Cop::Legacy::Corrector
26
26
 
27
27
  def diagnostics
@@ -14,9 +14,10 @@ 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
- linter.simple_name = if linter.name.start_with?('ERBLint::Linters::')
18
- name_parts = linter.name.split('::')
19
- name_parts[2..-1].join('::')
17
+ super
18
+ linter.simple_name = if linter.name.start_with?("ERBLint::Linters::")
19
+ name_parts = linter.name.split("::")
20
+ name_parts[2..-1].join("::")
20
21
  else
21
22
  linter.name
22
23
  end
@@ -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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support'
4
- require 'smart_properties'
3
+ require "active_support"
4
+ require "smart_properties"
5
5
 
6
6
  module ERBLint
7
7
  class LinterConfig
@@ -27,7 +27,7 @@ module ERBLint
27
27
  allowed_keys = self.class.properties.keys.map(&:to_s)
28
28
  given_keys = config.keys
29
29
  if (extra_keys = given_keys - allowed_keys).any?
30
- raise Error, "Given key is not allowed: #{extra_keys.join(', ')}"
30
+ raise Error, "Given key is not allowed: #{extra_keys.join(", ")}"
31
31
  end
32
32
  super(config)
33
33
  rescue SmartProperties::InitializationError => e
@@ -3,22 +3,31 @@
3
3
  module ERBLint
4
4
  # Stores all linters available to the application.
5
5
  module LinterRegistry
6
- CUSTOM_LINTERS_DIR = '.erb-linters'
7
- @linters = []
6
+ CUSTOM_LINTERS_DIR = ".erb-linters"
7
+ @loaded_linters = []
8
8
 
9
9
  class << self
10
- attr_reader :linters
10
+ def clear
11
+ @linters = nil
12
+ end
11
13
 
12
14
  def included(linter_class)
13
- @linters << linter_class
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
- ruby_files = Dir.glob(File.expand_path(File.join(directory, '**', '*.rb')))
30
+ ruby_files = Dir.glob(File.expand_path(File.join(directory, "**", "*.rb")))
22
31
  ruby_files.each { |file| require file }
23
32
  end
24
33
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/tree/tag'
3
+ require "better_html"
4
+ require "better_html/tree/tag"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -13,7 +13,7 @@ module ERBLint
13
13
 
14
14
  class ConfigSchema < LinterConfig
15
15
  property :allowed_types, accepts: array_of?(String),
16
- default: -> { ['text/javascript'] }
16
+ default: -> { ["text/javascript"] }
17
17
  property :allow_blank, accepts: [true, false], default: true, reader: :allow_blank?
18
18
  property :disallow_inline_scripts, accepts: [true, false], default: false, reader: :disallow_inline_scripts?
19
19
  end
@@ -24,7 +24,7 @@ module ERBLint
24
24
  parser.nodes_with_type(:tag).each do |tag_node|
25
25
  tag = BetterHtml::Tree::Tag.from_node(tag_node)
26
26
  next if tag.closing?
27
- next unless tag.name == 'script'
27
+ next unless tag.name == "script"
28
28
 
29
29
  if @config.disallow_inline_scripts?
30
30
  name_node = tag_node.to_a[1]
@@ -36,7 +36,7 @@ module ERBLint
36
36
  next
37
37
  end
38
38
 
39
- type_attribute = tag.attributes['type']
39
+ type_attribute = tag.attributes["type"]
40
40
  type_present = type_attribute.present? && type_attribute.value_node.present?
41
41
 
42
42
  if !type_present && !@config.allow_blank?
@@ -50,8 +50,8 @@ module ERBLint
50
50
  add_offense(
51
51
  type_attribute.loc,
52
52
  "Avoid using #{type_attribute.value.inspect} as type for `<script>` tag. "\
53
- "Must be one of: #{@config.allowed_types.join(', ')}"\
54
- "#{' (or no type attribute)' if @config.allow_blank?}."
53
+ "Must be one of: #{@config.allowed_types.join(", ")}"\
54
+ "#{" (or no type attribute)" if @config.allow_blank?}."
55
55
  )
56
56
  end
57
57
  end