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
@@ -8,7 +8,7 @@ module ERBLint
8
8
  include LinterRegistry
9
9
 
10
10
  class ConfigSchema < LinterConfig
11
- property :enforced_style, accepts: ['-', '='], default: '-'
11
+ property :enforced_style, accepts: ["-", "="], default: "-"
12
12
  end
13
13
  self.config_schema = ConfigSchema
14
14
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'tempfile'
5
- require 'erb_lint/utils/offset_corrector'
3
+ require "better_html"
4
+ require "tempfile"
5
+ require "erb_lint/utils/offset_corrector"
6
6
 
7
7
  module ERBLint
8
8
  module Linters
@@ -25,7 +25,7 @@ module ERBLint
25
25
  super
26
26
  @only_cops = @config.only
27
27
  custom_config = config_from_hash(@config.rubocop_config)
28
- @rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, '')
28
+ @rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, "")
29
29
  end
30
30
 
31
31
  def run(processed_source)
@@ -68,13 +68,13 @@ module ERBLint
68
68
 
69
69
  def inspect_content(processed_source, erb_node)
70
70
  indicator, _, code_node, = *erb_node
71
- return if indicator&.children&.first == '#'
71
+ return if indicator&.children&.first == "#"
72
72
 
73
73
  original_source = code_node.loc.source
74
- trimmed_source = original_source.sub(BLOCK_EXPR, '').sub(SUFFIX_EXPR, '')
74
+ trimmed_source = original_source.sub(BLOCK_EXPR, "").sub(SUFFIX_EXPR, "")
75
75
  alignment_column = code_node.loc.column
76
76
  offset = code_node.loc.begin_pos - alignment_column
77
- aligned_source = "#{' ' * alignment_column}#{trimmed_source}"
77
+ aligned_source = "#{" " * alignment_column}#{trimmed_source}"
78
78
 
79
79
  source = rubocop_processed_source(aligned_source, processed_source.filename)
80
80
  return unless source.valid_syntax?
@@ -156,10 +156,10 @@ module ERBLint
156
156
  end
157
157
 
158
158
  def config_from_hash(hash)
159
- inherit_from = hash&.delete('inherit_from')
159
+ inherit_from = hash&.delete("inherit_from")
160
160
  resolve_inheritance(hash, inherit_from)
161
161
 
162
- tempfile_from('.erblint-rubocop', hash.to_yaml) do |tempfile|
162
+ tempfile_from(".erblint-rubocop", hash.to_yaml) do |tempfile|
163
163
  ::RuboCop::ConfigLoader.load_file(tempfile.path)
164
164
  end
165
165
  end
@@ -174,7 +174,7 @@ module ERBLint
174
174
  end
175
175
 
176
176
  def base_configs(inherit_from)
177
- regex = URI::DEFAULT_PARSER.make_regexp(%w(http https))
177
+ regex = URI::DEFAULT_PARSER.make_regexp(["http", "https"])
178
178
  configs = Array(inherit_from).compact.map do |base_name|
179
179
  if base_name =~ /\A#{regex}\z/
180
180
  ::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
@@ -191,7 +191,7 @@ module ERBLint
191
191
  { rubocop_correction: correction, offset: offset, bound_range: bound_range }
192
192
  end
193
193
 
194
- super(offense_range, rubocop_offense.message.strip, context)
194
+ super(offense_range, rubocop_offense.message.strip, context, rubocop_offense.severity.name)
195
195
  end
196
196
  end
197
197
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'rubocop'
3
+ require_relative "rubocop"
4
4
 
5
5
  module ERBLint
6
6
  module Linters
@@ -11,10 +11,8 @@ module ERBLint
11
11
  end
12
12
  self.config_schema = ConfigSchema
13
13
 
14
- SELF_CLOSING_TAGS = %w(
15
- area base br col command embed hr input keygen
16
- link menuitem meta param source track wbr img
17
- )
14
+ SELF_CLOSING_TAGS = ["area", "base", "br", "col", "command", "embed", "hr", "input", "keygen", "link",
15
+ "menuitem", "meta", "param", "source", "track", "wbr", "img",]
18
16
 
19
17
  def run(processed_source)
20
18
  processed_source.ast.descendants(:tag).each do |tag_node|
@@ -26,7 +24,7 @@ module ERBLint
26
24
  add_offense(
27
25
  start_solidus.loc,
28
26
  "Tag `#{tag.name}` is a void element, it must not start with `</`.",
29
- ''
27
+ ""
30
28
  )
31
29
  end
32
30
 
@@ -34,7 +32,7 @@ module ERBLint
34
32
  add_offense(
35
33
  tag_node.loc.end.offset(-1),
36
34
  "Tag `#{tag.name}` is self-closing, it must end with `/>`.",
37
- '/'
35
+ "/"
38
36
  )
39
37
  end
40
38
 
@@ -43,7 +41,7 @@ module ERBLint
43
41
  add_offense(
44
42
  end_solidus.loc,
45
43
  "Tag `#{tag.name}` is a void element, it must end with `>` and not `/>`.",
46
- ''
44
+ ""
47
45
  )
48
46
  end
49
47
  end
@@ -15,7 +15,7 @@ module ERBLint
15
15
  processed_source.ast.descendants(:erb).each do |erb_node|
16
16
  indicator_node, ltrim, code_node, rtrim = *erb_node
17
17
  indicator = indicator_node&.loc&.source
18
- next if indicator == '#' || indicator == '%'
18
+ next if indicator == "#" || indicator == "%"
19
19
  code = code_node.children.first
20
20
 
21
21
  start_spaces = code.match(START_SPACES)&.captures&.first || ""
@@ -23,8 +23,8 @@ module ERBLint
23
23
  add_offense(
24
24
  code_node.loc.resize(start_spaces.size),
25
25
  "Use 1 space after `<%#{indicator}#{ltrim&.loc&.source}` "\
26
- "instead of #{start_spaces.size} space#{'s' if start_spaces.size > 1}.",
27
- ' '
26
+ "instead of #{start_spaces.size} space#{"s" if start_spaces.size > 1}.",
27
+ " "
28
28
  )
29
29
  elsif start_spaces.count("\n") > 1
30
30
  lines = start_spaces.split("\n", -1)
@@ -41,8 +41,8 @@ module ERBLint
41
41
  add_offense(
42
42
  code_node.loc.end.adjust(begin_pos: -end_spaces.size),
43
43
  "Use 1 space before `#{rtrim&.loc&.source}%>` "\
44
- "instead of #{end_spaces.size} space#{'s' if start_spaces.size > 1}.",
45
- ' '
44
+ "instead of #{end_spaces.size} space#{"s" if start_spaces.size > 1}.",
45
+ " "
46
46
  )
47
47
  elsif end_spaces.count("\n") > 1
48
48
  lines = end_spaces.split("\n", -1)
@@ -50,7 +50,7 @@ module ERBLint
50
50
  add_offense(
51
51
  processed_source.to_source_range(range),
52
52
  "Extra space detected where there should be no space.",
53
- ''
53
+ ""
54
54
  )
55
55
  end
56
56
 
@@ -60,24 +60,24 @@ module ERBLint
60
60
 
61
61
  def single_space(processed_source, range, accept_newline: false)
62
62
  chars = processed_source.file_content[range]
63
- return if chars == ' '
63
+ return if chars == " "
64
64
 
65
65
  newlines = chars.include?("\n")
66
- expected = newlines && accept_newline ? "\n#{chars.split("\n", -1).last}" : ' '
66
+ expected = newlines && accept_newline ? "\n#{chars.split("\n", -1).last}" : " "
67
67
  non_space = chars.match(/([^[[:space:]]])/m)
68
68
 
69
69
  if non_space && !non_space.captures.empty?
70
70
  add_offense(
71
71
  processed_source.to_source_range(range),
72
72
  "Non-whitespace character(s) detected: "\
73
- "#{non_space.captures.map(&:inspect).join(', ')}.",
73
+ "#{non_space.captures.map(&:inspect).join(", ")}.",
74
74
  expected
75
75
  )
76
76
  elsif newlines && accept_newline
77
77
  if expected != chars
78
78
  add_offense(
79
79
  processed_source.to_source_range(range),
80
- "#{chars.empty? ? 'No' : 'Extra'} space detected where there should be "\
80
+ "#{chars.empty? ? "No" : "Extra"} space detected where there should be "\
81
81
  "a single space or a single line break.",
82
82
  expected
83
83
  )
@@ -85,7 +85,7 @@ module ERBLint
85
85
  else
86
86
  add_offense(
87
87
  processed_source.to_source_range(range),
88
- "#{chars.empty? ? 'No' : 'Extra'} space detected where there should be a single space.",
88
+ "#{chars.empty? ? "No" : "Extra"} space detected where there should be a single space.",
89
89
  expected
90
90
  )
91
91
  end
@@ -23,7 +23,7 @@ module ERBLint
23
23
  add_offense(
24
24
  processed_source.to_source_range(document_pos...(document_pos + spaces.length)),
25
25
  "Indent with spaces instead of tabs.",
26
- spaces.gsub("\t", ' ' * @config.tab_width)
26
+ spaces.gsub("\t", " " * @config.tab_width)
27
27
  )
28
28
  end
29
29
 
@@ -25,7 +25,7 @@ module ERBLint
25
25
 
26
26
  def autocorrect(_processed_source, offense)
27
27
  lambda do |corrector|
28
- corrector.replace(offense.source_range, '')
28
+ corrector.replace(offense.source_range, "")
29
29
  end
30
30
  end
31
31
  end
@@ -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