erb_lint 0.0.37 → 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/all.rb +26 -0
  4. data/lib/erb_lint/cli.rb +73 -24
  5. data/lib/erb_lint/corrector.rb +1 -1
  6. data/lib/erb_lint/linter.rb +5 -5
  7. data/lib/erb_lint/linter_config.rb +3 -3
  8. data/lib/erb_lint/linter_registry.rb +2 -2
  9. data/lib/erb_lint/linters/allowed_script_type.rb +7 -7
  10. data/lib/erb_lint/linters/closing_erb_tag_indent.rb +2 -2
  11. data/lib/erb_lint/linters/deprecated_classes.rb +7 -7
  12. data/lib/erb_lint/linters/erb_safety.rb +2 -2
  13. data/lib/erb_lint/linters/extra_newline.rb +1 -1
  14. data/lib/erb_lint/linters/final_newline.rb +2 -2
  15. data/lib/erb_lint/linters/hard_coded_string.rb +36 -16
  16. data/lib/erb_lint/linters/no_javascript_tag_helper.rb +8 -8
  17. data/lib/erb_lint/linters/partial_instance_variable.rb +23 -0
  18. data/lib/erb_lint/linters/require_input_autocomplete.rb +121 -0
  19. data/lib/erb_lint/linters/require_script_nonce.rb +92 -0
  20. data/lib/erb_lint/linters/right_trim.rb +1 -1
  21. data/lib/erb_lint/linters/rubocop.rb +11 -11
  22. data/lib/erb_lint/linters/rubocop_text.rb +1 -1
  23. data/lib/erb_lint/linters/self_closing_tag.rb +5 -7
  24. data/lib/erb_lint/linters/space_around_erb_tag.rb +5 -5
  25. data/lib/erb_lint/linters/space_in_html_tag.rb +6 -6
  26. data/lib/erb_lint/linters/space_indentation.rb +1 -1
  27. data/lib/erb_lint/linters/trailing_whitespace.rb +1 -1
  28. data/lib/erb_lint/offense.rb +7 -4
  29. data/lib/erb_lint/reporter.rb +2 -2
  30. data/lib/erb_lint/reporters/compact_reporter.rb +9 -3
  31. data/lib/erb_lint/reporters/json_reporter.rb +72 -0
  32. data/lib/erb_lint/reporters/multiline_reporter.rb +1 -1
  33. data/lib/erb_lint/runner.rb +1 -1
  34. data/lib/erb_lint/runner_config.rb +8 -7
  35. data/lib/erb_lint/runner_config_resolver.rb +4 -4
  36. data/lib/erb_lint/stats.rb +9 -6
  37. data/lib/erb_lint/utils/block_map.rb +2 -2
  38. data/lib/erb_lint/utils/offset_corrector.rb +1 -1
  39. data/lib/erb_lint/utils/ruby_to_erb.rb +5 -5
  40. data/lib/erb_lint/utils/severity_levels.rb +16 -0
  41. data/lib/erb_lint/version.rb +1 -1
  42. data/lib/erb_lint.rb +1 -24
  43. metadata +9 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20ce12719e17ebfc24ca522965e85985fe665c0cbec1d9751320b1a779ec8c9c
4
- data.tar.gz: 536222d32134c71f72083c5083f575e8422024f8f5e26e0daf9b9eadd181d3e9
3
+ metadata.gz: b98507494f8af2a33e0e6fcbf28c48a2d91c78f42273a3cc6c1206578f3a55b0
4
+ data.tar.gz: c090a633a9d041a6d4d71bc292b1525b3a74dccd61311e0224bc1cbb0d8fea33
5
5
  SHA512:
6
- metadata.gz: 44f138dfdfff77b04d03f293f3b48f90744bbf3fcbcfeadc1cf3b5f0d47cae5c933b18b0e7273d64cdf549170e2a2aa33c5110cbc0aa62c84855e6e29a5f67ae
7
- data.tar.gz: 1fe70e0ddb97e0930b2645b0942d5ed08e74c5b40a41af1b6b9c7b9219b4be136495a5f3a0991363e8877aaef7bab5da2eb1f4162dc85f64b2f17d6ae2ca1184
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)
@@ -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,16 +1,19 @@
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
@@ -27,7 +30,7 @@ module ERBLint
27
30
  def run(args = ARGV)
28
31
  dupped_args = args.dup
29
32
  load_options(dupped_args)
30
- @files = dupped_args
33
+ @files = @options[:stdin] || dupped_args
31
34
 
32
35
  load_config
33
36
 
@@ -40,10 +43,11 @@ module ERBLint
40
43
  ensure_files_exist(lint_files)
41
44
 
42
45
  if enabled_linter_classes.empty?
43
- failure!('no linter available with current configuration')
46
+ failure!("no linter available with current configuration")
44
47
  end
45
48
 
46
49
  @options[:format] ||= :multiline
50
+ @options[:fail_level] ||= severity_level_for_name(:refactor)
47
51
  @stats.files = lint_files.size
48
52
  @stats.linters = enabled_linter_classes.size
49
53
 
@@ -51,14 +55,15 @@ module ERBLint
51
55
  reporter.preview
52
56
 
53
57
  runner = ERBLint::Runner.new(file_loader, @config)
58
+ file_content = nil
54
59
 
55
60
  lint_files.each do |filename|
56
61
  runner.clear_offenses
57
62
  begin
58
- run_with_corrections(runner, filename)
63
+ file_content = run_with_corrections(runner, filename)
59
64
  rescue => e
60
65
  @stats.exceptions += 1
61
- puts "Exception occured when processing: #{relative_filename(filename)}"
66
+ puts "Exception occurred when processing: #{relative_filename(filename)}"
62
67
  puts "If this file cannot be processed by erb-lint, "\
63
68
  "you can exclude it in your configuration file."
64
69
  puts e.message
@@ -69,6 +74,12 @@ module ERBLint
69
74
 
70
75
  reporter.show
71
76
 
77
+ if stdin? && autocorrect?
78
+ # When running from stdin, we only lint a single file
79
+ puts "================ #{lint_files.first} ==================\n"
80
+ puts file_content
81
+ end
82
+
72
83
  @stats.found == 0 && @stats.exceptions == 0
73
84
  rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, ExitWithFailure => e
74
85
  warn(Rainbow(e.message).red)
@@ -88,7 +99,7 @@ module ERBLint
88
99
  end
89
100
 
90
101
  def run_with_corrections(runner, filename)
91
- file_content = File.read(filename, encoding: Encoding::UTF_8)
102
+ file_content = read_content(filename)
92
103
 
93
104
  7.times do
94
105
  processed_source = ERBLint::ProcessedSource.new(filename, file_content)
@@ -101,8 +112,11 @@ module ERBLint
101
112
 
102
113
  @stats.corrected += corrector.corrections.size
103
114
 
104
- File.open(filename, "wb") do |file|
105
- 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
106
120
  end
107
121
 
108
122
  file_content = corrector.corrected_content
@@ -111,14 +125,27 @@ module ERBLint
111
125
  offenses_filename = relative_filename(filename)
112
126
  offenses = runner.offenses || []
113
127
 
114
- @stats.found += offenses.size
128
+ @stats.ignored, @stats.found = offenses.partition do |offense|
129
+ severity_level_for_name(offense.severity) < @options[:fail_level]
130
+ end.map(&:size)
131
+ .zip([@stats.ignored, @stats.found])
132
+ .map(&:sum)
133
+
115
134
  @stats.processed_files[offenses_filename] ||= []
116
135
  @stats.processed_files[offenses_filename] |= offenses
136
+
137
+ file_content
138
+ end
139
+
140
+ def read_content(filename)
141
+ return File.read(filename, encoding: Encoding::UTF_8) unless stdin?
142
+
143
+ $stdin.binmode.read.force_encoding(Encoding::UTF_8)
117
144
  end
118
145
 
119
146
  def correct(processed_source, offenses)
120
147
  corrector = ERBLint::Corrector.new(processed_source, offenses)
121
- failure!(corrector.diagnostics.join(', ')) if corrector.diagnostics.any?
148
+ failure!(corrector.diagnostics.join(", ")) if corrector.diagnostics.any?
122
149
  corrector
123
150
  end
124
151
 
@@ -156,7 +183,7 @@ module ERBLint
156
183
  else
157
184
  @files
158
185
  .map { |f| Dir.exist?(f) ? Dir[File.join(f, glob)] : f }
159
- .map { |f| f.include?('*') ? Dir[f] : f }
186
+ .map { |f| f.include?("*") ? Dir[f] : f }
160
187
  .flatten
161
188
  .map { |f| File.expand_path(f, Dir.pwd) }
162
189
  .select { |filename| !excluded?(filename) }
@@ -169,7 +196,8 @@ module ERBLint
169
196
 
170
197
  def excluded?(filename)
171
198
  @config.global_exclude.any? do |path|
172
- File.fnmatch?(path, filename)
199
+ expanded_path = File.expand_path(path, Dir.pwd)
200
+ File.fnmatch?(expanded_path, filename)
173
201
  end
174
202
  end
175
203
 
@@ -212,14 +240,14 @@ module ERBLint
212
240
  end
213
241
 
214
242
  def relative_filename(filename)
215
- filename.sub("#{File.expand_path('.', Dir.pwd)}/", '')
243
+ filename.sub("#{File.expand_path(".", Dir.pwd)}/", "")
216
244
  end
217
245
 
218
246
  def runner_config_override
219
247
  RunnerConfig.new(
220
248
  linters: {}.tap do |linters|
221
249
  ERBLint::LinterRegistry.linters.map do |klass|
222
- linters[klass.simple_name] = { 'enabled' => enabled_linter_classes.include?(klass) }
250
+ linters[klass.simple_name] = { "enabled" => enabled_linter_classes.include?(klass) }
223
251
  end
224
252
  end
225
253
  )
@@ -255,19 +283,36 @@ module ERBLint
255
283
  end
256
284
 
257
285
  opts.on("--enable-linters LINTER[,LINTER,...]", Array,
258
- "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|
259
287
  linters.each do |linter|
260
288
  unless known_linter_names.include?(linter)
261
- failure!("#{linter}: not a valid linter name (#{known_linter_names.join(', ')})")
289
+ failure!("#{linter}: not a valid linter name (#{known_linter_names.join(", ")})")
262
290
  end
263
291
  end
264
292
  @options[:enabled_linters] = linters
265
293
  end
266
294
 
295
+ opts.on("--fail-level SEVERITY", "Minimum severity for exit with error code") do |level|
296
+ parsed_severity = SEVERITY_CODE_TABLE[level.upcase.to_sym] || (SEVERITY_NAMES & [level.downcase]).first
297
+
298
+ if parsed_severity.nil?
299
+ failure!("#{level}: not a valid failure level (#{SEVERITY_NAMES.join(", ")})")
300
+ end
301
+ @options[:fail_level] = severity_level_for_name(parsed_severity)
302
+ end
303
+
267
304
  opts.on("-a", "--autocorrect", "Correct offenses automatically if possible (default: false)") do |config|
268
305
  @options[:autocorrect] = config
269
306
  end
270
307
 
308
+ opts.on(
309
+ "-sFILE",
310
+ "--stdin FILE",
311
+ "Pipe source from STDIN. Takes the path to be used to check which rules to apply."
312
+ ) do |file|
313
+ @options[:stdin] = [file]
314
+ end
315
+
271
316
  opts.on_tail("-h", "--help", "Show this message") do
272
317
  success!(opts)
273
318
  end
@@ -280,12 +325,16 @@ module ERBLint
280
325
 
281
326
  def format_options_help
282
327
  "Report offenses in the given format: "\
283
- "(#{Reporter.available_formats.join(', ')}) (default: multiline)"
328
+ "(#{Reporter.available_formats.join(", ")}) (default: multiline)"
284
329
  end
285
330
 
286
331
  def invalid_format_error_message(given_format)
287
332
  formats = Reporter.available_formats.map { |format| " - #{format}\n" }
288
333
  "#{given_format}: is not a valid format. Available formats:\n#{formats.join}"
289
334
  end
335
+
336
+ def stdin?
337
+ @options[:stdin].present?
338
+ end
290
339
  end
291
340
  end
@@ -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
@@ -15,9 +15,9 @@ module ERBLint
15
15
  # `ERBLint::Linters::Compass::Bar.simple_name` #=> "Compass::Bar"
16
16
  def inherited(linter)
17
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('::')
18
+ linter.simple_name = if linter.name.start_with?("ERBLint::Linters::")
19
+ name_parts = linter.name.split("::")
20
+ name_parts[2..-1].join("::")
21
21
  else
22
22
  linter.name
23
23
  end
@@ -53,8 +53,8 @@ module ERBLint
53
53
  raise NotImplementedError, "must implement ##{__method__}"
54
54
  end
55
55
 
56
- def add_offense(source_range, message, context = nil)
57
- @offenses << Offense.new(self, source_range, message, context)
56
+ def add_offense(source_range, message, context = nil, severity = nil)
57
+ @offenses << Offense.new(self, source_range, message, context, severity)
58
58
  end
59
59
 
60
60
  def clear_offenses
@@ -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,7 +3,7 @@
3
3
  module ERBLint
4
4
  # Stores all linters available to the application.
5
5
  module LinterRegistry
6
- CUSTOM_LINTERS_DIR = '.erb-linters'
6
+ CUSTOM_LINTERS_DIR = ".erb-linters"
7
7
  @loaded_linters = []
8
8
 
9
9
  class << self
@@ -27,7 +27,7 @@ module ERBLint
27
27
  end
28
28
 
29
29
  def load_custom_linters(directory = CUSTOM_LINTERS_DIR)
30
- ruby_files = Dir.glob(File.expand_path(File.join(directory, '**', '*.rb')))
30
+ ruby_files = Dir.glob(File.expand_path(File.join(directory, "**", "*.rb")))
31
31
  ruby_files.each { |file| require file }
32
32
  end
33
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
@@ -25,7 +25,7 @@ module ERBLint
25
25
  add_offense(
26
26
  code_node.loc.end.adjust(begin_pos: -end_spaces.size),
27
27
  "Remove newline before `%>` to match start of tag.",
28
- ' '
28
+ " "
29
29
  )
30
30
  elsif start_with_newline && !end_with_newline
31
31
  add_offense(
@@ -39,7 +39,7 @@ module ERBLint
39
39
  add_offense(
40
40
  code_node.loc.end.adjust(begin_pos: -current_indent.size),
41
41
  "Indent `%>` on column #{erb_node.loc.column} to match start of tag.",
42
- ' ' * erb_node.loc.column
42
+ " " * erb_node.loc.column
43
43
  )
44
44
  end
45
45
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/parser'
3
+ require "better_html"
4
+ require "better_html/parser"
5
5
 
6
6
  module ERBLint
7
7
  module Linters
@@ -11,7 +11,7 @@ module ERBLint
11
11
 
12
12
  class RuleSet
13
13
  include SmartProperties
14
- property :suggestion, accepts: String, default: ''
14
+ property :suggestion, accepts: String, default: ""
15
15
  property :deprecated, accepts: LinterConfig.array_of?(String), default: -> { [] }
16
16
  end
17
17
 
@@ -57,9 +57,9 @@ module ERBLint
57
57
  def class_name_with_loc(processed_source)
58
58
  Enumerator.new do |yielder|
59
59
  tags(processed_source).each do |tag|
60
- class_value = tag.attributes['class']&.value
60
+ class_value = tag.attributes["class"]&.value
61
61
  next unless class_value
62
- class_value.split(' ').each do |class_name|
62
+ class_value.split(" ").each do |class_name|
63
63
  yielder.yield(class_name, tag.loc)
64
64
  end
65
65
  end
@@ -69,7 +69,7 @@ module ERBLint
69
69
  def text_tags_content(processed_source)
70
70
  Enumerator.new do |yielder|
71
71
  script_tags(processed_source)
72
- .select { |tag| tag.attributes['type']&.value == 'text/html' }
72
+ .select { |tag| tag.attributes["type"]&.value == "text/html" }
73
73
  .each do |tag|
74
74
  index = processed_source.ast.to_a.find_index(tag.node)
75
75
  next_node = processed_source.ast.to_a[index + 1]
@@ -80,7 +80,7 @@ module ERBLint
80
80
  end
81
81
 
82
82
  def script_tags(processed_source)
83
- tags(processed_source).select { |tag| tag.name == 'script' }
83
+ tags(processed_source).select { |tag| tag.name == "script" }
84
84
  end
85
85
 
86
86
  def tags(processed_source)