erb_lint 0.0.37 → 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/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)