erb_lint 0.0.37 → 0.1.2

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 +75 -29
  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 +5 -3
  8. data/lib/erb_lint/linter_registry.rb +2 -2
  9. data/lib/erb_lint/linters/allowed_script_type.rb +9 -8
  10. data/lib/erb_lint/linters/closing_erb_tag_indent.rb +2 -2
  11. data/lib/erb_lint/linters/deprecated_classes.rb +8 -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 +39 -16
  16. data/lib/erb_lint/linters/no_javascript_tag_helper.rb +11 -9
  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 +13 -11
  22. data/lib/erb_lint/linters/rubocop_text.rb +1 -1
  23. data/lib/erb_lint/linters/self_closing_tag.rb +6 -7
  24. data/lib/erb_lint/linters/space_around_erb_tag.rb +8 -7
  25. data/lib/erb_lint/linters/space_in_html_tag.rb +7 -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 +8 -4
  29. data/lib/erb_lint/reporter.rb +4 -2
  30. data/lib/erb_lint/reporters/compact_reporter.rb +17 -4
  31. data/lib/erb_lint/reporters/json_reporter.rb +72 -0
  32. data/lib/erb_lint/reporters/multiline_reporter.rb +2 -1
  33. data/lib/erb_lint/runner.rb +3 -2
  34. data/lib/erb_lint/runner_config.rb +9 -7
  35. data/lib/erb_lint/runner_config_resolver.rb +5 -4
  36. data/lib/erb_lint/stats.rb +13 -6
  37. data/lib/erb_lint/utils/block_map.rb +3 -2
  38. data/lib/erb_lint/utils/offset_corrector.rb +2 -2
  39. data/lib/erb_lint/utils/ruby_to_erb.rb +6 -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 +23 -17
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'better_html'
4
- require 'better_html/ast/node'
5
- require 'better_html/test_helper/ruby_node'
6
- require 'erb_lint/utils/block_map'
7
- require 'erb_lint/utils/ruby_to_erb'
3
+ require "better_html"
4
+ require "better_html/ast/node"
5
+ require "better_html/test_helper/ruby_node"
6
+ require "erb_lint/utils/block_map"
7
+ require "erb_lint/utils/ruby_to_erb"
8
8
 
9
9
  module ERBLint
10
10
  module Linters
@@ -21,7 +21,8 @@ module ERBLint
21
21
  parser.ast.descendants(:erb).each do |erb_node|
22
22
  indicator_node, _, code_node, _ = *erb_node
23
23
  indicator = indicator_node&.loc&.source
24
- next if indicator == '#'
24
+ next if indicator == "#"
25
+
25
26
  source = code_node.loc.source
26
27
 
27
28
  ruby_node =
@@ -31,13 +32,14 @@ module ERBLint
31
32
  nil
32
33
  end
33
34
  next unless ruby_node
35
+
34
36
  send_node = ruby_node.descendants(:send).first
35
37
  next unless send_node&.method_name?(:javascript_tag)
36
38
 
37
39
  add_offense(
38
40
  erb_node.loc,
39
41
  "Avoid using 'javascript_tag do' as it confuses tests "\
40
- "that validate html, use inline <script> instead",
42
+ "that validate html, use inline <script> instead",
41
43
  [erb_node, send_node]
42
44
  )
43
45
  end
@@ -63,10 +65,10 @@ module ERBLint
63
65
  return unless (0..2).cover?(argument_nodes.size)
64
66
 
65
67
  script_content = unless argument_nodes.first&.type?(:hash)
66
- Utils::RubyToERB.ruby_to_erb(argument_nodes.first, '==')
68
+ Utils::RubyToERB.ruby_to_erb(argument_nodes.first, "==")
67
69
  end
68
70
  arguments = if argument_nodes.last&.type?(:hash)
69
- ' ' + Utils::RubyToERB.html_options_to_tag_attributes(argument_nodes.last)
71
+ " " + Utils::RubyToERB.html_options_to_tag_attributes(argument_nodes.last)
70
72
  end
71
73
 
72
74
  return if end_node && script_content
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ module Linters
5
+ # Checks for instance variables in partials.
6
+ class PartialInstanceVariable < Linter
7
+ include LinterRegistry
8
+
9
+ def run(processed_source)
10
+ instance_variable_regex = /\s@\w+/
11
+ return unless processed_source.filename.match?(%r{(\A|.*/)_[^/\s]*\.html\.erb\z}) &&
12
+ processed_source.file_content.match?(instance_variable_regex)
13
+
14
+ add_offense(
15
+ processed_source.to_source_range(
16
+ processed_source.file_content =~ instance_variable_regex..processed_source.file_content.size
17
+ ),
18
+ "Instance variable detected in partial."
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "better_html"
4
+ require "better_html/tree/tag"
5
+
6
+ module ERBLint
7
+ module Linters
8
+ class RequireInputAutocomplete < Linter
9
+ include LinterRegistry
10
+
11
+ HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE = [
12
+ "color",
13
+ "date",
14
+ "datetime-local",
15
+ "email",
16
+ "month",
17
+ "number",
18
+ "password",
19
+ "range",
20
+ "search",
21
+ "tel",
22
+ "text",
23
+ "time",
24
+ "url",
25
+ "week",
26
+ ].freeze
27
+
28
+ FORM_HELPERS_REQUIRING_AUTOCOMPLETE = [
29
+ :date_field_tag,
30
+ :color_field_tag,
31
+ :email_field_tag,
32
+ :text_field_tag,
33
+ :utf8_enforcer_tag,
34
+ :month_field_tag,
35
+ :number_field_tag,
36
+ :password_field_tag,
37
+ :search_field_tag,
38
+ :telephone_field_tag,
39
+ :time_field_tag,
40
+ :url_field_tag,
41
+ :week_field_tag,
42
+ ].freeze
43
+
44
+ def run(processed_source)
45
+ parser = processed_source.parser
46
+
47
+ find_html_input_tags(parser)
48
+ find_rails_helper_input_tags(parser)
49
+ end
50
+
51
+ private
52
+
53
+ def find_html_input_tags(parser)
54
+ parser.nodes_with_type(:tag).each do |tag_node|
55
+ tag = BetterHtml::Tree::Tag.from_node(tag_node)
56
+
57
+ autocomplete_attribute = tag.attributes["autocomplete"]
58
+ type_attribute = tag.attributes["type"]
59
+
60
+ next if !html_input_tag?(tag) || autocomplete_present?(autocomplete_attribute)
61
+ next unless html_type_requires_autocomplete_attribute?(type_attribute)
62
+
63
+ add_offense(
64
+ tag_node.to_a[1].loc,
65
+ "Input tag is missing an autocomplete attribute. If no "\
66
+ "autocomplete behaviour is desired, use the value `off` or `nope`.",
67
+ [autocomplete_attribute]
68
+ )
69
+ end
70
+ end
71
+
72
+ def autocomplete_present?(autocomplete_attribute)
73
+ autocomplete_attribute.present? && autocomplete_attribute.value_node.present?
74
+ end
75
+
76
+ def html_input_tag?(tag)
77
+ !tag.closing? && tag.name == "input"
78
+ end
79
+
80
+ def html_type_requires_autocomplete_attribute?(type_attribute)
81
+ type_present = type_attribute.present? && type_attribute.value_node.present?
82
+ type_present && HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE.include?(type_attribute.value)
83
+ end
84
+
85
+ def find_rails_helper_input_tags(parser)
86
+ parser.ast.descendants(:erb).each do |erb_node|
87
+ indicator_node, _, code_node, _ = *erb_node
88
+ source = code_node.loc.source
89
+ ruby_node = extract_ruby_node(source)
90
+ send_node = ruby_node&.descendants(:send)&.first
91
+
92
+ next if code_comment?(indicator_node) ||
93
+ !ruby_node ||
94
+ !input_helper?(send_node) ||
95
+ source.include?("autocomplete")
96
+
97
+ add_offense(
98
+ erb_node.loc,
99
+ "Input field helper is missing an autocomplete attribute. If no "\
100
+ "autocomplete behaviour is desired, use the value `off` or `nope`.",
101
+ [erb_node, send_node]
102
+ )
103
+ end
104
+ end
105
+
106
+ def input_helper?(send_node)
107
+ FORM_HELPERS_REQUIRING_AUTOCOMPLETE.include?(send_node&.method_name)
108
+ end
109
+
110
+ def code_comment?(indicator_node)
111
+ indicator_node&.loc&.source == "#"
112
+ end
113
+
114
+ def extract_ruby_node(source)
115
+ BetterHtml::TestHelper::RubyNode.parse(source)
116
+ rescue ::Parser::SyntaxError
117
+ nil
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "better_html"
4
+ require "better_html/tree/tag"
5
+
6
+ module ERBLint
7
+ module Linters
8
+ # Allow inline script tags in ERB that have a nonce attribute.
9
+ # This only validates inline <script> tags, as well as rails helpers like javascript_tag.
10
+ class RequireScriptNonce < Linter
11
+ include LinterRegistry
12
+
13
+ def run(processed_source)
14
+ parser = processed_source.parser
15
+
16
+ find_html_script_tags(parser)
17
+ find_rails_helper_script_tags(parser)
18
+ end
19
+
20
+ private
21
+
22
+ def find_html_script_tags(parser)
23
+ parser.nodes_with_type(:tag).each do |tag_node|
24
+ tag = BetterHtml::Tree::Tag.from_node(tag_node)
25
+ nonce_attribute = tag.attributes["nonce"]
26
+
27
+ next if !html_javascript_tag?(tag) || nonce_present?(nonce_attribute)
28
+
29
+ add_offense(
30
+ tag_node.to_a[1].loc,
31
+ "Missing a nonce attribute. Use request.content_security_policy_nonce",
32
+ [nonce_attribute]
33
+ )
34
+ end
35
+ end
36
+
37
+ def nonce_present?(nonce_attribute)
38
+ nonce_attribute.present? && nonce_attribute.value_node.present?
39
+ end
40
+
41
+ def html_javascript_tag?(tag)
42
+ !tag.closing? &&
43
+ (tag.name == "script" && !html_javascript_type_attribute?(tag))
44
+ end
45
+
46
+ def html_javascript_type_attribute?(tag)
47
+ type_attribute = tag.attributes["type"]
48
+
49
+ type_attribute &&
50
+ type_attribute.value_node.present? &&
51
+ type_attribute.value_node.to_a[1] != "text/javascript" &&
52
+ type_attribute.value_node.to_a[1] != "application/javascript"
53
+ end
54
+
55
+ def find_rails_helper_script_tags(parser)
56
+ parser.ast.descendants(:erb).each do |erb_node|
57
+ indicator_node, _, code_node, _ = *erb_node
58
+ source = code_node.loc.source
59
+ ruby_node = extract_ruby_node(source)
60
+ send_node = ruby_node&.descendants(:send)&.first
61
+
62
+ next if code_comment?(indicator_node) ||
63
+ !ruby_node ||
64
+ !tag_helper?(send_node) ||
65
+ source.include?("nonce")
66
+
67
+ add_offense(
68
+ erb_node.loc,
69
+ "Missing a nonce attribute. Use nonce: true",
70
+ [erb_node, send_node]
71
+ )
72
+ end
73
+ end
74
+
75
+ def tag_helper?(send_node)
76
+ send_node&.method_name?(:javascript_tag) ||
77
+ send_node&.method_name?(:javascript_include_tag) ||
78
+ send_node&.method_name?(:javascript_pack_tag)
79
+ end
80
+
81
+ def code_comment?(indicator_node)
82
+ indicator_node&.loc&.source == "#"
83
+ end
84
+
85
+ def extract_ruby_node(source)
86
+ BetterHtml::TestHelper::RubyNode.parse(source)
87
+ rescue ::Parser::SyntaxError
88
+ nil
89
+ end
90
+ end
91
+ end
92
+ end
@@ -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)
@@ -37,6 +37,7 @@ module ERBLint
37
37
  if ::RuboCop::Version::STRING.to_f >= 0.87
38
38
  def autocorrect(_processed_source, offense)
39
39
  return unless offense.context
40
+
40
41
  rubocop_correction = offense.context[:rubocop_correction]
41
42
  return unless rubocop_correction
42
43
 
@@ -68,13 +69,13 @@ module ERBLint
68
69
 
69
70
  def inspect_content(processed_source, erb_node)
70
71
  indicator, _, code_node, = *erb_node
71
- return if indicator&.children&.first == '#'
72
+ return if indicator&.children&.first == "#"
72
73
 
73
74
  original_source = code_node.loc.source
74
- trimmed_source = original_source.sub(BLOCK_EXPR, '').sub(SUFFIX_EXPR, '')
75
+ trimmed_source = original_source.sub(BLOCK_EXPR, "").sub(SUFFIX_EXPR, "")
75
76
  alignment_column = code_node.loc.column
76
77
  offset = code_node.loc.begin_pos - alignment_column
77
- aligned_source = "#{' ' * alignment_column}#{trimmed_source}"
78
+ aligned_source = "#{" " * alignment_column}#{trimmed_source}"
78
79
 
79
80
  source = rubocop_processed_source(aligned_source, processed_source.filename)
80
81
  return unless source.valid_syntax?
@@ -150,16 +151,17 @@ module ERBLint
150
151
  @rubocop_config,
151
152
  extra_details: true,
152
153
  display_cop_names: true,
154
+ autocorrect: true,
153
155
  auto_correct: true,
154
156
  stdin: "",
155
157
  )
156
158
  end
157
159
 
158
160
  def config_from_hash(hash)
159
- inherit_from = hash&.delete('inherit_from')
161
+ inherit_from = hash&.delete("inherit_from")
160
162
  resolve_inheritance(hash, inherit_from)
161
163
 
162
- tempfile_from('.erblint-rubocop', hash.to_yaml) do |tempfile|
164
+ tempfile_from(".erblint-rubocop", hash.to_yaml) do |tempfile|
163
165
  ::RuboCop::ConfigLoader.load_file(tempfile.path)
164
166
  end
165
167
  end
@@ -174,7 +176,7 @@ module ERBLint
174
176
  end
175
177
 
176
178
  def base_configs(inherit_from)
177
- regex = URI::DEFAULT_PARSER.make_regexp(%w(http https))
179
+ regex = URI::DEFAULT_PARSER.make_regexp(["http", "https"])
178
180
  configs = Array(inherit_from).compact.map do |base_name|
179
181
  if base_name =~ /\A#{regex}\z/
180
182
  ::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
@@ -191,7 +193,7 @@ module ERBLint
191
193
  { rubocop_correction: correction, offset: offset, bound_range: bound_range }
192
194
  end
193
195
 
194
- super(offense_range, rubocop_offense.message.strip, context)
196
+ super(offense_range, rubocop_offense.message.strip, context, rubocop_offense.severity.name)
195
197
  end
196
198
  end
197
199
  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,16 +32,17 @@ 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
 
41
39
  next unless @config.enforced_style == :never && tag.self_closing?
40
+
42
41
  end_solidus = tag_node.children.last
43
42
  add_offense(
44
43
  end_solidus.loc,
45
44
  "Tag `#{tag.name}` is a void element, it must end with `>` and not `/>`.",
46
- ''
45
+ ""
47
46
  )
48
47
  end
49
48
  end
@@ -15,7 +15,8 @@ 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
20
  code = code_node.children.first
20
21
 
21
22
  start_spaces = code.match(START_SPACES)&.captures&.first || ""
@@ -23,15 +24,15 @@ module ERBLint
23
24
  add_offense(
24
25
  code_node.loc.resize(start_spaces.size),
25
26
  "Use 1 space after `<%#{indicator}#{ltrim&.loc&.source}` "\
26
- "instead of #{start_spaces.size} space#{'s' if start_spaces.size > 1}.",
27
- ' '
27
+ "instead of #{start_spaces.size} space#{"s" if start_spaces.size > 1}.",
28
+ " "
28
29
  )
29
30
  elsif start_spaces.count("\n") > 1
30
31
  lines = start_spaces.split("\n", -1)
31
32
  add_offense(
32
33
  code_node.loc.resize(start_spaces.size),
33
34
  "Use 1 newline after `<%#{indicator&.loc&.source}#{ltrim&.loc&.source}` "\
34
- "instead of #{start_spaces.count("\n")}.",
35
+ "instead of #{start_spaces.count("\n")}.",
35
36
  "#{lines.first}\n#{lines.last}"
36
37
  )
37
38
  end
@@ -41,15 +42,15 @@ module ERBLint
41
42
  add_offense(
42
43
  code_node.loc.end.adjust(begin_pos: -end_spaces.size),
43
44
  "Use 1 space before `#{rtrim&.loc&.source}%>` "\
44
- "instead of #{end_spaces.size} space#{'s' if start_spaces.size > 1}.",
45
- ' '
45
+ "instead of #{end_spaces.size} space#{"s" if start_spaces.size > 1}.",
46
+ " "
46
47
  )
47
48
  elsif end_spaces.count("\n") > 1
48
49
  lines = end_spaces.split("\n", -1)
49
50
  add_offense(
50
51
  code_node.loc.end.adjust(begin_pos: -end_spaces.size),
51
52
  "Use 1 newline before `#{rtrim&.loc&.source}%>` "\
52
- "instead of #{end_spaces.count("\n")}.",
53
+ "instead of #{end_spaces.count("\n")}.",
53
54
  "#{lines.first}\n#{lines.last}"
54
55
  )
55
56
  end
@@ -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
@@ -98,6 +98,7 @@ module ERBLint
98
98
  no_space(processed_source, equal.loc.end_pos...value.loc.begin_pos) if equal && value
99
99
 
100
100
  next if index >= attributes.children.size - 1
101
+
101
102
  next_attribute = attributes.children[index + 1]
102
103
 
103
104
  single_space_or_newline(
@@ -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,29 +3,33 @@
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
12
+
12
13
  @linter = linter
13
14
  @source_range = source_range
14
15
  @message = message
15
16
  @context = context
17
+ @severity = severity
16
18
  end
17
19
 
18
20
  def inspect
19
21
  "#<#{self.class.name} linter=#{linter.class.name} "\
20
22
  "source_range=#{source_range.begin_pos}...#{source_range.end_pos} "\
21
- "message=#{message}>"
23
+ "message=#{message}> "\
24
+ "severity=#{severity}"
22
25
  end
23
26
 
24
27
  def ==(other)
25
28
  other.class <= ERBLint::Offense &&
26
29
  other.linter == linter &&
27
30
  other.source_range == source_range &&
28
- other.message == message
31
+ other.message == message &&
32
+ other.severity == severity
29
33
  end
30
34
 
31
35
  def line_range
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require 'active_support/core_ext/class'
3
- require 'active_support/core_ext/module/delegation'
2
+
3
+ require "active_support/core_ext/class"
4
+ require "active_support/core_ext/module/delegation"
4
5
 
5
6
  module ERBLint
6
7
  class Reporter
@@ -34,6 +35,7 @@ module ERBLint
34
35
  private
35
36
 
36
37
  attr_reader :stats, :autocorrect
38
+
37
39
  delegate :processed_files, to: :stats
38
40
  end
39
41
  end
@@ -4,8 +4,7 @@ module ERBLint
4
4
  module Reporters
5
5
  class CompactReporter < Reporter
6
6
  def preview
7
- puts "Linting #{stats.files} files with "\
8
- "#{stats.linters} #{'autocorrectable ' if autocorrect}linters..."
7
+ puts "#{linting} #{stats.files} files with #{linters}..."
9
8
  end
10
9
 
11
10
  def show
@@ -21,6 +20,14 @@ module ERBLint
21
20
 
22
21
  private
23
22
 
23
+ def linting
24
+ "Linting" + (autocorrect ? " and autocorrecting" : "")
25
+ end
26
+
27
+ def linters
28
+ "#{stats.linters} linters" + (autocorrect ? " (#{stats.autocorrectable_linters} autocorrectable)" : "")
29
+ end
30
+
24
31
  def format_offense(filename, offense)
25
32
  [
26
33
  "#{filename}:",
@@ -35,8 +42,14 @@ module ERBLint
35
42
  def summary
36
43
  if stats.corrected > 0
37
44
  report_corrected_offenses
38
- elsif stats.found > 0
39
- warn(Rainbow("#{stats.found} error(s) were found in ERB files").red)
45
+ elsif stats.ignored > 0 || stats.found > 0
46
+ if stats.ignored > 0
47
+ warn(Rainbow("#{stats.ignored} error(s) were ignored in ERB files").yellow)
48
+ end
49
+
50
+ if stats.found > 0
51
+ warn(Rainbow("#{stats.found} error(s) were found in ERB files").red)
52
+ end
40
53
  else
41
54
  puts Rainbow("No errors were found in ERB files").green
42
55
  end