erb_lint 0.0.35 → 0.1.1

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