erb_lint 0.0.37 → 0.1.2

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 +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