erb_lint 0.0.21 → 0.0.22

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c5c19ac1eafb41b42b89239504fccaf8f1f52a0
4
- data.tar.gz: 3afb969caac48e9c7960cddbec2f55630322580d
3
+ metadata.gz: 1ec3e8e2ac9c73109d17f77b2e93a43e12f8c89e
4
+ data.tar.gz: 116d80b17dcf0ff05f356dd21fbc34f3f3af4da6
5
5
  SHA512:
6
- metadata.gz: 3716dfb6a37ffe9f3c4b27804cccc597f3da89a46c2075c0bdbb54c9b7840c952f391ff8d8a78bcc2bd07ee56d2baa5972084455671c79cd87a7f588080f1372
7
- data.tar.gz: 7582a2c11435135fa681c27b5e7bcc904a3a1e67d01c8dad930e1d89b8d8d7e9959e4b632701971f14eac3374dd2789f1b68f8c9ebaf05bd7c988f536a57dc10
6
+ metadata.gz: 300e58e388b3d6904b750bfeb2ea2576490204191a5160cd3e8929421ca47804b114f706c8cd3be96b2e22a27d8cef0a3d1970de18c4b54ed9611b16f2052874
7
+ data.tar.gz: 9a02fefe7fecd0af7cba69ba7c3777a20c51af7c0621c6b3471538fad81a510806935e8a142b4233cafe96772add6960f8d7935668a7800c88db40da5c1b518a
data/lib/erb_lint/cli.rb CHANGED
@@ -51,10 +51,9 @@ module ERBLint
51
51
  "#{enabled_linter_classes.size} #{'autocorrectable ' if autocorrect?}linters..."
52
52
  puts
53
53
 
54
- runner = ERBLint::Runner.new(file_loader, @config)
55
54
  lint_files.each do |filename|
56
55
  begin
57
- run_with_corrections(runner, filename)
56
+ run_with_corrections(filename)
58
57
  rescue => e
59
58
  puts "Exception occured when processing: #{relative_filename(filename)}"
60
59
  puts e.message
@@ -64,8 +63,9 @@ module ERBLint
64
63
  end
65
64
 
66
65
  if @stats.corrected > 0
67
- if @stats.found > 0
68
- warn "#{@stats.corrected} error(s) corrected and #{@stats.found} error(s) remaining in ERB files".red
66
+ corrected_found_diff = @stats.found - @stats.corrected
67
+ if corrected_found_diff > 0
68
+ warn "#{@stats.corrected} error(s) corrected and #{corrected_found_diff} error(s) remaining in ERB files".red
69
69
  else
70
70
  puts "#{@stats.corrected} error(s) corrected in ERB files".green
71
71
  end
@@ -93,16 +93,17 @@ module ERBLint
93
93
  @options[:autocorrect]
94
94
  end
95
95
 
96
- def run_with_corrections(runner, filename)
96
+ def run_with_corrections(filename)
97
97
  file_content = File.read(filename)
98
- offenses = []
98
+
99
+ runner = ERBLint::Runner.new(file_loader, @config)
99
100
 
100
101
  7.times do
101
102
  processed_source = ERBLint::ProcessedSource.new(filename, file_content)
102
- offenses = runner.run(processed_source)
103
- break unless autocorrect? && offenses.any?
103
+ runner.run(processed_source)
104
+ break unless autocorrect? && runner.offenses.any?
104
105
 
105
- corrector = correct(processed_source, offenses)
106
+ corrector = correct(processed_source, runner.offenses)
106
107
  break if corrector.corrections.empty?
107
108
  break if processed_source.file_content == corrector.corrected_content
108
109
 
@@ -113,10 +114,11 @@ module ERBLint
113
114
  end
114
115
 
115
116
  file_content = corrector.corrected_content
117
+ runner.clear_offenses
116
118
  end
117
119
 
118
- @stats.found += offenses.size
119
- offenses.each do |offense|
120
+ @stats.found += runner.offenses.size
121
+ runner.offenses.each do |offense|
120
122
  puts <<~EOF
121
123
  #{offense.message}#{' (not autocorrected)'.red if autocorrect?}
122
124
  In file: #{relative_filename(filename)}:#{offense.line_range.begin}
@@ -29,12 +29,15 @@ module ERBLint
29
29
  end
30
30
  end
31
31
 
32
+ attr_reader :offenses
33
+
32
34
  # Must be implemented by the concrete inheriting class.
33
35
  def initialize(file_loader, config)
34
36
  @file_loader = file_loader
35
37
  @config = config
36
38
  raise ArgumentError, "expect `config` to be #{self.class.config_schema} instance, "\
37
39
  "not #{config.class}" unless config.is_a?(self.class.config_schema)
40
+ @offenses = []
38
41
  end
39
42
 
40
43
  def enabled?
@@ -45,8 +48,16 @@ module ERBLint
45
48
  @config.excludes_file?(filename)
46
49
  end
47
50
 
48
- def offenses(_processed_source)
51
+ def run(_processed_source)
49
52
  raise NotImplementedError, "must implement ##{__method__}"
50
53
  end
54
+
55
+ def add_offense(source_range, message, context = nil)
56
+ @offenses << Offense.new(self, source_range, message, context)
57
+ end
58
+
59
+ def clear_offenses
60
+ @offenses = []
61
+ end
51
62
  end
52
63
  end
@@ -19,58 +19,52 @@ module ERBLint
19
19
  end
20
20
  self.config_schema = ConfigSchema
21
21
 
22
- def offenses(processed_source)
22
+ def run(processed_source)
23
23
  parser = processed_source.parser
24
- [].tap do |offenses|
25
- parser.nodes_with_type(:tag).each do |tag_node|
26
- tag = BetterHtml::Tree::Tag.from_node(tag_node)
27
- next if tag.closing?
28
- next unless tag.name == 'script'
24
+ parser.nodes_with_type(:tag).each do |tag_node|
25
+ tag = BetterHtml::Tree::Tag.from_node(tag_node)
26
+ next if tag.closing?
27
+ next unless tag.name == 'script'
29
28
 
30
- if @config.disallow_inline_scripts?
31
- name_node = tag_node.to_a[1]
32
- offenses << Offense.new(
33
- self,
34
- processed_source.to_source_range(name_node.loc.start, name_node.loc.stop),
35
- "Avoid using inline `<script>` tags altogether. "\
36
- "Instead, move javascript code into a static file."
37
- )
38
- next
39
- end
29
+ if @config.disallow_inline_scripts?
30
+ name_node = tag_node.to_a[1]
31
+ add_offense(
32
+ name_node.loc,
33
+ "Avoid using inline `<script>` tags altogether. "\
34
+ "Instead, move javascript code into a static file."
35
+ )
36
+ next
37
+ end
40
38
 
41
- type_attribute = tag.attributes['type']
42
- type_present = type_attribute.present? && type_attribute.value_node.present?
39
+ type_attribute = tag.attributes['type']
40
+ type_present = type_attribute.present? && type_attribute.value_node.present?
43
41
 
44
- if !type_present && !@config.allow_blank?
45
- name_node = tag_node.to_a[1]
46
- offenses << Offense.new(
47
- self,
48
- processed_source.to_source_range(name_node.loc.start, name_node.loc.stop),
49
- "Missing a `type=\"text/javascript\"` attribute to `<script>` tag.",
50
- [type_attribute]
51
- )
52
- elsif type_present && !@config.allowed_types.include?(type_attribute.value)
53
- offenses << Offense.new(
54
- self,
55
- processed_source.to_source_range(type_attribute.loc.start, tag_node.loc.stop),
56
- "Avoid using #{type_attribute.value.inspect} as type for `<script>` tag. "\
57
- "Must be one of: #{@config.allowed_types.join(', ')}"\
58
- "#{' (or no type attribute)' if @config.allow_blank?}."
59
- )
60
- end
42
+ if !type_present && !@config.allow_blank?
43
+ name_node = tag_node.to_a[1]
44
+ add_offense(
45
+ name_node.loc,
46
+ "Missing a `type=\"text/javascript\"` attribute to `<script>` tag.",
47
+ [type_attribute]
48
+ )
49
+ elsif type_present && !@config.allowed_types.include?(type_attribute.value)
50
+ add_offense(
51
+ type_attribute.loc,
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?}."
55
+ )
61
56
  end
62
57
  end
63
58
  end
64
59
 
65
- def autocorrect(processed_source, offense)
60
+ def autocorrect(_processed_source, offense)
66
61
  return unless offense.context
67
62
  lambda do |corrector|
68
63
  type_attribute, = *offense.context
69
64
  if type_attribute.nil?
70
65
  corrector.insert_after(offense.source_range, ' type="text/javascript"')
71
66
  elsif !type_attribute.value.present?
72
- range = processed_source.to_source_range(type_attribute.node.loc.start, type_attribute.node.loc.stop)
73
- corrector.replace(range, 'type="text/javascript"')
67
+ corrector.replace(type_attribute.node.loc, 'type="text/javascript"')
74
68
  end
75
69
  end
76
70
  end
@@ -10,8 +10,8 @@ module ERBLint
10
10
  START_SPACES = /\A([[:space:]]*)/m
11
11
  END_SPACES = /([[:space:]]*)\z/m
12
12
 
13
- def offenses(processed_source)
14
- processed_source.ast.descendants(:erb).each_with_object([]) do |erb_node, offenses|
13
+ def run(processed_source)
14
+ processed_source.ast.descendants(:erb).each do |erb_node|
15
15
  _, _, code_node, = *erb_node
16
16
  code = code_node.children.first
17
17
 
@@ -22,25 +22,22 @@ module ERBLint
22
22
  end_with_newline = end_spaces.include?("\n")
23
23
 
24
24
  if !start_with_newline && end_with_newline
25
- offenses << Offense.new(
26
- self,
27
- processed_source.to_source_range(code_node.loc.stop - end_spaces.size + 1, code_node.loc.stop),
25
+ add_offense(
26
+ code_node.loc.end.adjust(begin_pos: -end_spaces.size),
28
27
  "Remove newline before `%>` to match start of tag.",
29
28
  ' '
30
29
  )
31
30
  elsif start_with_newline && !end_with_newline
32
- offenses << Offense.new(
33
- self,
34
- processed_source.to_source_range(code_node.loc.stop, code_node.loc.stop),
31
+ add_offense(
32
+ code_node.loc.end.adjust(begin_pos: -end_spaces.size),
35
33
  "Insert newline before `%>` to match start of tag.",
36
34
  "\n"
37
35
  )
38
36
  elsif start_with_newline && end_with_newline
39
37
  current_indent = end_spaces.split("\n", -1).last
40
38
  if erb_node.loc.column != current_indent.size
41
- offenses << Offense.new(
42
- self,
43
- processed_source.to_source_range(code_node.loc.stop - current_indent.size + 1, code_node.loc.stop),
39
+ add_offense(
40
+ code_node.loc.end.adjust(begin_pos: -current_indent.size),
44
41
  "Indent `%>` on column #{erb_node.loc.column} to match start of tag.",
45
42
  ' ' * erb_node.loc.column
46
43
  )
@@ -29,7 +29,7 @@ module ERBLint
29
29
  @addendum = @config.addendum
30
30
  end
31
31
 
32
- def offenses(processed_source)
32
+ def run(processed_source)
33
33
  process_nested_offenses(
34
34
  source: processed_source,
35
35
  offset: 0,
@@ -40,23 +40,18 @@ module ERBLint
40
40
  private
41
41
 
42
42
  def process_nested_offenses(source:, offset:, parent_source:)
43
- offenses = []
44
43
  class_name_with_loc(source).each do |class_name, loc|
45
- range = parent_source.to_source_range(
46
- offset + loc.start,
47
- offset + loc.stop
48
- )
49
- offenses += generate_errors(class_name, range)
44
+ range = parent_source.to_source_range(loc).offset(offset)
45
+ generate_offenses(class_name, range)
50
46
  end
51
47
  text_tags_content(source).each do |content_node|
52
48
  sub_source = ProcessedSource.new(source.filename, content_node.loc.source)
53
- offenses += process_nested_offenses(
49
+ process_nested_offenses(
54
50
  source: sub_source,
55
- offset: offset + content_node.loc.start,
51
+ offset: offset + content_node.loc.begin_pos,
56
52
  parent_source: parent_source
57
53
  )
58
54
  end
59
- offenses
60
55
  end
61
56
 
62
57
  def class_name_with_loc(processed_source)
@@ -96,13 +91,12 @@ module ERBLint
96
91
  processed_source.parser.nodes_with_type(:tag)
97
92
  end
98
93
 
99
- def generate_errors(class_name, range)
100
- violated_rules(class_name).map do |violated_rule|
94
+ def generate_offenses(class_name, range)
95
+ violated_rules(class_name).each do |violated_rule|
101
96
  suggestion = " #{violated_rule[:suggestion]}".rstrip
102
97
  message = "Deprecated class `%s` detected matching the pattern `%s`.%s #{@addendum}".strip
103
98
 
104
- Offense.new(
105
- self,
99
+ add_offense(
106
100
  range,
107
101
  format(message, class_name, violated_rule[:class_expr], suggestion)
108
102
  )
@@ -20,21 +20,16 @@ module ERBLint
20
20
  @config_filename = @config.better_html_config
21
21
  end
22
22
 
23
- def offenses(processed_source)
24
- offenses = []
25
-
26
- parser = BetterHtml::Parser.new(processed_source.file_content, template_language: :html)
27
- testers_for(parser).each do |tester|
23
+ def run(processed_source)
24
+ testers_for(processed_source.parser).each do |tester|
28
25
  tester.validate
29
26
  tester.errors.each do |error|
30
- offenses << Offense.new(
31
- self,
32
- processed_source.to_source_range(error.location.start, error.location.stop),
27
+ add_offense(
28
+ error.location,
33
29
  error.message
34
30
  )
35
31
  end
36
32
  end
37
- offenses
38
33
  end
39
34
 
40
35
  private
@@ -8,22 +8,16 @@ module ERBLint
8
8
 
9
9
  EXTRA_NEWLINES = /(\n{3,})/m
10
10
 
11
- def offenses(processed_source)
12
- matches = processed_source.file_content.match(EXTRA_NEWLINES)
13
- return [] unless matches
11
+ def run(processed_source)
12
+ return unless (matches = processed_source.file_content.match(EXTRA_NEWLINES))
14
13
 
15
- offenses = []
16
14
  matches.captures.each_index do |index|
17
- offenses << Offense.new(
18
- self,
19
- processed_source.to_source_range(
20
- matches.begin(index) + 2,
21
- matches.end(index) - 1
22
- ),
15
+ add_offense(
16
+ processed_source
17
+ .to_source_range((matches.begin(index) + 2)...matches.end(index)),
23
18
  "Extra blank line detected."
24
19
  )
25
20
  end
26
- offenses
27
21
  end
28
22
 
29
23
  def autocorrect(_processed_source, offense)
@@ -16,40 +16,37 @@ module ERBLint
16
16
  @new_lines_should_be_present = @config.present?
17
17
  end
18
18
 
19
- def offenses(processed_source)
19
+ def run(processed_source)
20
20
  file_content = processed_source.file_content
21
21
 
22
- offenses = []
23
- return offenses if file_content.empty?
22
+ return if file_content.empty?
24
23
 
25
24
  match = file_content.match(/(\n+)\z/)
26
25
  final_newline = match&.captures&.first || ""
27
26
 
28
27
  if @new_lines_should_be_present && final_newline.size != 1
29
28
  if final_newline.empty?
30
- offenses << Offense.new(
31
- self,
32
- processed_source.to_source_range(file_content.size, file_content.size - 1),
29
+ add_offense(
30
+ processed_source.to_source_range(file_content.size...file_content.size),
33
31
  'Missing a trailing newline at the end of the file.',
34
32
  :insert
35
33
  )
36
34
  else
37
- offenses << Offense.new(
38
- self,
39
- processed_source.to_source_range(file_content.size - final_newline.size + 1, file_content.size - 1),
35
+ add_offense(
36
+ processed_source.to_source_range(
37
+ (file_content.size - final_newline.size + 1)...file_content.size
38
+ ),
40
39
  'Remove multiple trailing newline at the end of the file.',
41
40
  :remove
42
41
  )
43
42
  end
44
43
  elsif !@new_lines_should_be_present && !final_newline.empty?
45
- offenses << Offense.new(
46
- self,
47
- processed_source.to_source_range(match.begin(0), match.end(0) - 1),
44
+ add_offense(
45
+ processed_source.to_source_range(match.begin(0)...match.end(0)),
48
46
  "Remove #{final_newline.size} trailing newline at the end of the file.",
49
47
  :remove
50
48
  )
51
49
  end
52
- offenses
53
50
  end
54
51
 
55
52
  def autocorrect(_processed_source, offense)
@@ -22,24 +22,23 @@ module ERBLint
22
22
  end
23
23
  self.config_schema = ConfigSchema
24
24
 
25
- def offenses(processed_source)
25
+ def run(processed_source)
26
26
  hardcoded_strings = processed_source.ast.descendants(:text).each_with_object([]) do |text_node, to_check|
27
27
  next if javascript?(processed_source, text_node)
28
28
 
29
29
  offended_strings = text_node.to_a.select { |node| relevant_node(node) }
30
30
  offended_strings.each do |offended_string|
31
31
  offended_string.split("\n").each do |str|
32
- to_check << [text_node, str] if str.length > 1
32
+ to_check << [text_node, str] if check_string?(str)
33
33
  end
34
34
  end
35
35
  end
36
36
 
37
- hardcoded_strings.compact.map do |text_node, offended_str|
38
- range_begin, range_stop = find_range(text_node, offended_str)
39
- source_range = processed_source.to_source_range(range_begin, range_stop)
37
+ hardcoded_strings.compact.each do |text_node, offended_str|
38
+ range = find_range(text_node, offended_str)
39
+ source_range = processed_source.to_source_range(range)
40
40
 
41
- Offense.new(
42
- self,
41
+ add_offense(
43
42
  source_range,
44
43
  message(source_range.source)
45
44
  )
@@ -50,9 +49,9 @@ module ERBLint
50
49
  match = node.loc.source.match(Regexp.new(Regexp.quote(str.strip)))
51
50
  return unless match
52
51
 
53
- range_begin = match.begin(0) + node.loc.start
54
- range_end = match.end(0) + node.loc.start - 1
55
- [range_begin, range_end]
52
+ range_begin = match.begin(0) + node.loc.begin_pos
53
+ range_end = match.end(0) + node.loc.begin_pos
54
+ (range_begin...range_end)
56
55
  end
57
56
 
58
57
  def autocorrect(processed_source, offense)
@@ -69,6 +68,11 @@ module ERBLint
69
68
 
70
69
  private
71
70
 
71
+ def check_string?(str)
72
+ string = str.gsub(/\s*/, '')
73
+ string.length > 1 && !%w(&nbsp;).include?(string)
74
+ end
75
+
72
76
  def load_corrector
73
77
  corrector_name = @config['corrector'].fetch('name') { raise MissingCorrector }
74
78
  raise ForbiddenCorrector unless ALLOWED_CORRECTORS.include?(corrector_name)
@@ -15,9 +15,7 @@ module ERBLint
15
15
  end
16
16
  self.config_schema = ConfigSchema
17
17
 
18
- def offenses(processed_source)
19
- offenses = []
20
-
18
+ def run(processed_source)
21
19
  parser = processed_source.parser
22
20
  parser.ast.descendants(:erb).each do |erb_node|
23
21
  indicator_node, _, code_node, _ = *erb_node
@@ -29,16 +27,13 @@ module ERBLint
29
27
  send_node = ruby_node.descendants(:send).first
30
28
  next unless send_node&.method_name?(:javascript_tag)
31
29
 
32
- offenses << Offense.new(
33
- self,
34
- processed_source.to_source_range(erb_node.loc.start, erb_node.loc.stop),
30
+ add_offense(
31
+ erb_node.loc,
35
32
  "Avoid using 'javascript_tag do' as it confuses tests "\
36
33
  "that validate html, use inline <script> instead",
37
34
  [erb_node, send_node]
38
35
  )
39
36
  end
40
-
41
- offenses
42
37
  end
43
38
 
44
39
  def autocorrect(processed_source, offense)
@@ -56,10 +51,6 @@ module ERBLint
56
51
  return unless (1..2).cover?(nodes.size)
57
52
 
58
53
  begin_node, end_node = nodes
59
- begin_range = processed_source
60
- .to_source_range(begin_node.loc.start, begin_node.loc.stop)
61
- end_range = processed_source
62
- .to_source_range(end_node.loc.start, end_node.loc.stop) if end_node
63
54
 
64
55
  argument_nodes = send_node.arguments
65
56
  return unless (0..2).cover?(argument_nodes.size)
@@ -76,13 +67,13 @@ module ERBLint
76
67
  if end_node
77
68
  begin_content = "<script#{arguments}>"
78
69
  begin_content += "\n//<![CDATA[\n" if @config.correction_style == :cdata
79
- corrector.replace(begin_range, begin_content)
70
+ corrector.replace(begin_node.loc, begin_content)
80
71
  end_content = "</script>"
81
72
  end_content = "\n//]]>\n" + end_content if @config.correction_style == :cdata
82
- corrector.replace(end_range, end_content)
73
+ corrector.replace(end_node.loc, end_content)
83
74
  elsif script_content
84
75
  script_content = "\n//<![CDATA[\n#{script_content}\n//]]>\n" if @config.correction_style == :cdata
85
- corrector.replace(begin_range,
76
+ corrector.replace(begin_node.loc,
86
77
  "<script#{arguments}>#{script_content}</script>")
87
78
  end
88
79
  rescue Utils::RubyToERB::Error, Utils::BlockMap::ParseError
@@ -5,11 +5,10 @@ module ERBLint
5
5
  class ParserErrors < Linter
6
6
  include LinterRegistry
7
7
 
8
- def offenses(processed_source)
9
- processed_source.parser.parser_errors.map do |error|
10
- Offense.new(
11
- self,
12
- processed_source.to_source_range(error.loc.start, error.loc.stop - 1),
8
+ def run(processed_source)
9
+ processed_source.parser.parser_errors.each do |error|
10
+ add_offense(
11
+ error.loc,
13
12
  "#{error.message} (at #{error.loc.source})"
14
13
  )
15
14
  end
@@ -12,18 +12,15 @@ module ERBLint
12
12
  end
13
13
  self.config_schema = ConfigSchema
14
14
 
15
- def offenses(processed_source)
16
- [].tap do |offenses|
17
- processed_source.ast.descendants(:erb).each do |erb_node|
18
- _, _, _, trim_node = *erb_node
19
- next if trim_node.nil? || trim_node.loc.source == @config.enforced_style
15
+ def run(processed_source)
16
+ processed_source.ast.descendants(:erb).each do |erb_node|
17
+ _, _, _, trim_node = *erb_node
18
+ next if trim_node.nil? || trim_node.loc.source == @config.enforced_style
20
19
 
21
- offenses << Offense.new(
22
- self,
23
- processed_source.to_source_range(trim_node.loc.start, trim_node.loc.stop),
24
- "Prefer #{@config.enforced_style}%> instead of #{trim_node.loc.source}%> for trimming on the right."
25
- )
26
- end
20
+ add_offense(
21
+ trim_node.loc,
22
+ "Prefer #{@config.enforced_style}%> instead of #{trim_node.loc.source}%> for trimming on the right."
23
+ )
27
24
  end
28
25
  end
29
26
 
@@ -29,23 +29,23 @@ module ERBLint
29
29
  @rubocop_config = RuboCop::ConfigLoader.merge_with_default(custom_config, '')
30
30
  end
31
31
 
32
- def offenses(processed_source)
33
- descendant_nodes(processed_source).each_with_object([]) do |erb_node, offenses|
34
- offenses.push(*inspect_content(processed_source, erb_node))
32
+ def run(processed_source)
33
+ descendant_nodes(processed_source).each do |erb_node|
34
+ inspect_content(processed_source, erb_node)
35
35
  end
36
36
  end
37
37
 
38
38
  def autocorrect(processed_source, offense)
39
- return unless offense.is_a?(OffenseWithCorrection)
39
+ return unless offense.context
40
40
 
41
41
  lambda do |corrector|
42
42
  passthrough = Utils::OffsetCorrector.new(
43
43
  processed_source,
44
44
  corrector,
45
- offense.offset,
46
- offense.bound_range,
45
+ offense.context[:offset],
46
+ offense.context[:bound_range],
47
47
  )
48
- offense.correction.call(passthrough)
48
+ offense.context[:rubocop_correction].call(passthrough)
49
49
  end
50
50
  end
51
51
 
@@ -55,16 +55,6 @@ module ERBLint
55
55
  processed_source.ast.descendants(:erb)
56
56
  end
57
57
 
58
- class OffenseWithCorrection < Offense
59
- attr_reader :correction, :offset, :bound_range
60
- def initialize(linter, source_range, message, correction:, offset:, bound_range:)
61
- super(linter, source_range, message)
62
- @correction = correction
63
- @offset = offset
64
- @bound_range = bound_range
65
- end
66
- end
67
-
68
58
  def inspect_content(processed_source, erb_node)
69
59
  indicator, _, code_node, = *erb_node
70
60
  return if indicator&.children&.first == '#'
@@ -79,7 +69,7 @@ module ERBLint
79
69
 
80
70
  team = build_team
81
71
  team.inspect_file(source)
82
- team.cops.each_with_object([]) do |cop, offenses|
72
+ team.cops.each do |cop|
83
73
  correction_offset = 0
84
74
  cop.offenses.reject(&:disabled?).each do |rubocop_offense|
85
75
  if rubocop_offense.corrected?
@@ -87,18 +77,12 @@ module ERBLint
87
77
  correction_offset += 1
88
78
  end
89
79
 
90
- offset = code_node.loc.start - alignment_column
91
- offense_range = processed_source.to_source_range(
92
- offset + rubocop_offense.location.begin_pos,
93
- offset + rubocop_offense.location.end_pos - 1,
94
- )
80
+ offset = code_node.loc.begin_pos - alignment_column
81
+ offense_range = processed_source
82
+ .to_source_range(rubocop_offense.location)
83
+ .offset(offset)
95
84
 
96
- bound_range = processed_source.to_source_range(
97
- code_node.loc.start,
98
- code_node.loc.stop
99
- )
100
-
101
- offenses << add_offense(rubocop_offense, offense_range, correction, offset, bound_range)
85
+ add_offense(rubocop_offense, offense_range, correction, offset, code_node.loc.range)
102
86
  end
103
87
  end
104
88
  end
@@ -173,16 +157,12 @@ module ERBLint
173
157
  configs.compact
174
158
  end
175
159
 
176
- def add_offense(offense, offense_range, correction, offset, bound_range)
177
- if offense.corrected?
178
- klass = OffenseWithCorrection
179
- options = { correction: correction, offset: offset, bound_range: bound_range }
180
- else
181
- klass = Offense
182
- options = {}
160
+ def add_offense(rubocop_offense, offense_range, correction, offset, bound_range)
161
+ context = if rubocop_offense.corrected?
162
+ { rubocop_correction: correction, offset: offset, bound_range: bound_range }
183
163
  end
184
164
 
185
- klass.new(self, offense_range, offense.message.strip, **options)
165
+ super(offense_range, rubocop_offense.message.strip, context)
186
166
  end
187
167
  end
188
168
  end
@@ -11,25 +11,23 @@ module ERBLint
11
11
  link menuitem meta param source track wbr img
12
12
  )
13
13
 
14
- def offenses(processed_source)
15
- processed_source.ast.descendants(:tag).each_with_object([]) do |tag_node, offenses|
14
+ def run(processed_source)
15
+ processed_source.ast.descendants(:tag).each do |tag_node|
16
16
  tag = BetterHtml::Tree::Tag.from_node(tag_node)
17
17
  next unless SELF_CLOSING_TAGS.include?(tag.name)
18
18
 
19
19
  if tag.closing?
20
20
  start_solidus = tag_node.children.first
21
- offenses << Offense.new(
22
- self,
23
- processed_source.to_source_range(start_solidus.loc.start, start_solidus.loc.stop),
21
+ add_offense(
22
+ start_solidus.loc,
24
23
  "Tag `#{tag.name}` is self-closing, it must not start with `</`.",
25
24
  ''
26
25
  )
27
26
  end
28
27
 
29
28
  next if tag.self_closing?
30
- offenses << Offense.new(
31
- self,
32
- processed_source.to_source_range(tag_node.loc.stop, tag_node.loc.stop - 1),
29
+ add_offense(
30
+ tag_node.loc.end.offset(-1),
33
31
  "Tag `#{tag.name}` is self-closing, it must end with `/>`.",
34
32
  '/'
35
33
  )
@@ -11,51 +11,45 @@ module ERBLint
11
11
  START_SPACES = /\A([[:space:]]*)/m
12
12
  END_SPACES = /([[:space:]]*)\z/m
13
13
 
14
- def offenses(processed_source)
15
- [].tap do |offenses|
16
- processed_source.ast.descendants(:erb).each do |erb_node|
17
- indicator, ltrim, code_node, rtrim = *erb_node
18
- code = code_node.children.first
14
+ def run(processed_source)
15
+ processed_source.ast.descendants(:erb).each do |erb_node|
16
+ indicator, ltrim, code_node, rtrim = *erb_node
17
+ code = code_node.children.first
19
18
 
20
- start_spaces = code.match(START_SPACES)&.captures&.first || ""
21
- if start_spaces.size != 1 && !start_spaces.include?("\n")
22
- offenses << Offense.new(
23
- self,
24
- processed_source.to_source_range(code_node.loc.start, code_node.loc.start + start_spaces.size - 1),
25
- "Use 1 space after `<%#{indicator&.loc&.source}#{ltrim&.loc&.source}` "\
26
- "instead of #{start_spaces.size} space#{'s' if start_spaces.size > 1}.",
27
- ' '
28
- )
29
- elsif start_spaces.count("\n") > 1
30
- lines = start_spaces.split("\n", -1)
31
- offenses << Offense.new(
32
- self,
33
- processed_source.to_source_range(code_node.loc.start, code_node.loc.start + start_spaces.size - 1),
34
- "Use 1 newline after `<%#{indicator&.loc&.source}#{ltrim&.loc&.source}` "\
35
- "instead of #{start_spaces.count("\n")}.",
36
- "#{lines.first}\n#{lines.last}"
37
- )
38
- end
19
+ start_spaces = code.match(START_SPACES)&.captures&.first || ""
20
+ if start_spaces.size != 1 && !start_spaces.include?("\n")
21
+ add_offense(
22
+ code_node.loc.resize(start_spaces.size),
23
+ "Use 1 space after `<%#{indicator&.loc&.source}#{ltrim&.loc&.source}` "\
24
+ "instead of #{start_spaces.size} space#{'s' if start_spaces.size > 1}.",
25
+ ' '
26
+ )
27
+ elsif start_spaces.count("\n") > 1
28
+ lines = start_spaces.split("\n", -1)
29
+ add_offense(
30
+ code_node.loc.resize(start_spaces.size),
31
+ "Use 1 newline after `<%#{indicator&.loc&.source}#{ltrim&.loc&.source}` "\
32
+ "instead of #{start_spaces.count("\n")}.",
33
+ "#{lines.first}\n#{lines.last}"
34
+ )
35
+ end
39
36
 
40
- end_spaces = code.match(END_SPACES)&.captures&.first || ""
41
- if end_spaces.size != 1 && !end_spaces.include?("\n")
42
- offenses << Offense.new(
43
- self,
44
- processed_source.to_source_range(code_node.loc.stop - end_spaces.size + 1, code_node.loc.stop),
45
- "Use 1 space before `#{rtrim&.loc&.source}%>` "\
46
- "instead of #{end_spaces.size} space#{'s' if start_spaces.size > 1}.",
47
- ' '
48
- )
49
- elsif end_spaces.count("\n") > 1
50
- lines = end_spaces.split("\n", -1)
51
- offenses << Offense.new(
52
- self,
53
- processed_source.to_source_range(code_node.loc.stop - end_spaces.size + 1, code_node.loc.stop),
54
- "Use 1 newline before `#{rtrim&.loc&.source}%>` "\
55
- "instead of #{end_spaces.count("\n")}.",
56
- "#{lines.first}\n#{lines.last}"
57
- )
58
- end
37
+ end_spaces = code.match(END_SPACES)&.captures&.first || ""
38
+ if end_spaces.size != 1 && !end_spaces.include?("\n")
39
+ add_offense(
40
+ code_node.loc.end.adjust(begin_pos: -end_spaces.size),
41
+ "Use 1 space before `#{rtrim&.loc&.source}%>` "\
42
+ "instead of #{end_spaces.size} space#{'s' if start_spaces.size > 1}.",
43
+ ' '
44
+ )
45
+ elsif end_spaces.count("\n") > 1
46
+ lines = end_spaces.split("\n", -1)
47
+ add_offense(
48
+ code_node.loc.end.adjust(begin_pos: -end_spaces.size),
49
+ "Use 1 newline before `#{rtrim&.loc&.source}%>` "\
50
+ "instead of #{end_spaces.count("\n")}.",
51
+ "#{lines.first}\n#{lines.last}"
52
+ )
59
53
  end
60
54
  end
61
55
  end
@@ -6,35 +6,33 @@ module ERBLint
6
6
  class SpaceInHtmlTag < Linter
7
7
  include LinterRegistry
8
8
 
9
- def offenses(processed_source)
10
- offenses = []
9
+ def run(processed_source)
11
10
  processed_source.ast.descendants(:tag).each do |tag_node|
12
11
  start_solidus, name, attributes, end_solidus = *tag_node
13
12
 
14
- next_loc = name&.loc&.start || attributes&.loc&.start ||
15
- end_solidus&.loc&.start || tag_node.loc.stop
13
+ next_loc = name&.loc&.begin_pos || attributes&.loc&.begin_pos ||
14
+ end_solidus&.loc&.begin_pos || (tag_node.loc.end_pos - 1)
16
15
  if start_solidus
17
- offenses << no_space(processed_source, tag_node.loc.start + 1, start_solidus.loc.start)
18
- offenses << no_space(processed_source, start_solidus.loc.stop + 1, next_loc)
16
+ no_space(processed_source, (tag_node.loc.begin_pos + 1)...start_solidus.loc.begin_pos)
17
+ no_space(processed_source, start_solidus.loc.end_pos...next_loc)
19
18
  else
20
- offenses << no_space(processed_source, tag_node.loc.start + 1, next_loc)
19
+ no_space(processed_source, (tag_node.loc.begin_pos + 1)...next_loc)
21
20
  end
22
21
 
23
22
  if attributes
24
- offenses << single_space_or_newline(processed_source, name.loc.stop + 1, attributes.loc.start) if name
25
- offenses.concat(process_attributes(processed_source, attributes) || [])
23
+ single_space_or_newline(processed_source, name.loc.end_pos...attributes.loc.begin_pos) if name
24
+ process_attributes(processed_source, attributes)
26
25
  end
27
26
 
28
- previous_loc = attributes&.loc&.stop || name&.loc&.stop ||
29
- start_solidus&.loc&.stop || tag_node.loc.start
27
+ previous_loc = attributes&.loc&.end_pos || name&.loc&.end_pos ||
28
+ start_solidus&.loc&.end_pos || (tag_node.loc.begin_pos + 1)
30
29
  if end_solidus
31
- offenses << single_space(processed_source, previous_loc + 1, end_solidus.loc.start)
32
- offenses << no_space(processed_source, end_solidus.loc.stop + 1, tag_node.loc.stop)
30
+ single_space(processed_source, previous_loc...end_solidus.loc.begin_pos)
31
+ no_space(processed_source, end_solidus.loc.end_pos...(tag_node.loc.end_pos - 1))
33
32
  else
34
- offenses << no_space(processed_source, previous_loc + 1, tag_node.loc.stop)
33
+ no_space(processed_source, previous_loc...(tag_node.loc.end_pos - 1))
35
34
  end
36
35
  end
37
- offenses.compact
38
36
  end
39
37
 
40
38
  def autocorrect(_processed_source, offense)
@@ -45,25 +43,22 @@ module ERBLint
45
43
 
46
44
  private
47
45
 
48
- def no_space(processed_source, begin_pos, end_pos)
49
- range = Range.new(begin_pos, end_pos - 1)
46
+ def no_space(processed_source, range)
50
47
  chars = processed_source.file_content[range]
51
48
  return if chars.empty?
52
49
 
53
- Offense.new(
54
- self,
55
- processed_source.to_source_range(begin_pos, end_pos - 1),
50
+ add_offense(
51
+ processed_source.to_source_range(range),
56
52
  "Extra space detected where there should be no space.",
57
53
  ''
58
54
  )
59
55
  end
60
56
 
61
- def single_space_or_newline(processed_source, begin_pos, end_pos)
62
- single_space(processed_source, begin_pos, end_pos, accept_newline: true)
57
+ def single_space_or_newline(processed_source, range)
58
+ single_space(processed_source, range, accept_newline: true)
63
59
  end
64
60
 
65
- def single_space(processed_source, begin_pos, end_pos, accept_newline: false)
66
- range = Range.new(begin_pos, end_pos - 1)
61
+ def single_space(processed_source, range, accept_newline: false)
67
62
  chars = processed_source.file_content[range]
68
63
  return if chars == ' '
69
64
 
@@ -72,27 +67,24 @@ module ERBLint
72
67
  non_space = chars.match(/([^[[:space:]]])/m)
73
68
 
74
69
  if non_space && !non_space.captures.empty?
75
- Offense.new(
76
- self,
77
- processed_source.to_source_range(begin_pos, end_pos - 1),
70
+ add_offense(
71
+ processed_source.to_source_range(range),
78
72
  "Non-whitespace character(s) detected: "\
79
73
  "#{non_space.captures.map(&:inspect).join(', ')}.",
80
74
  expected
81
75
  )
82
76
  elsif newlines && accept_newline
83
77
  if expected != chars
84
- Offense.new(
85
- self,
86
- processed_source.to_source_range(begin_pos, end_pos - 1),
78
+ add_offense(
79
+ processed_source.to_source_range(range),
87
80
  "#{chars.empty? ? 'No' : 'Extra'} space detected where there should be "\
88
81
  "a single space or a single line break.",
89
82
  expected
90
83
  )
91
84
  end
92
85
  else
93
- Offense.new(
94
- self,
95
- processed_source.to_source_range(begin_pos, end_pos - 1),
86
+ add_offense(
87
+ processed_source.to_source_range(range),
96
88
  "#{chars.empty? ? 'No' : 'Extra'} space detected where there should be a single space.",
97
89
  expected
98
90
  )
@@ -100,19 +92,19 @@ module ERBLint
100
92
  end
101
93
 
102
94
  def process_attributes(processed_source, attributes)
103
- offenses = []
104
95
  attributes.children.each_with_index do |attribute, index|
105
96
  name, equal, value = *attribute
106
- offenses << no_space(processed_source, name.loc.stop + 1, equal.loc.start) if equal
107
- offenses << no_space(processed_source, equal.loc.stop + 1, value.loc.start) if equal && value
97
+ no_space(processed_source, name.loc.end_pos...equal.loc.begin_pos) if equal
98
+ no_space(processed_source, equal.loc.end_pos...value.loc.begin_pos) if equal && value
108
99
 
109
100
  next if index >= attributes.children.size - 1
110
101
  next_attribute = attributes.children[index + 1]
111
102
 
112
- offenses << single_space_or_newline(processed_source,
113
- attribute.loc.stop + 1, next_attribute.loc.start)
103
+ single_space_or_newline(
104
+ processed_source,
105
+ attribute.loc.end_pos...next_attribute.loc.begin_pos
106
+ )
114
107
  end
115
- offenses
116
108
  end
117
109
  end
118
110
  end
@@ -13,16 +13,15 @@ module ERBLint
13
13
 
14
14
  START_SPACES = /\A([[:blank:]]*)/
15
15
 
16
- def offenses(processed_source)
16
+ def run(processed_source)
17
17
  lines = processed_source.file_content.split("\n", -1)
18
18
  document_pos = 0
19
- lines.each_with_object([]) do |line, offenses|
19
+ lines.each do |line|
20
20
  spaces = line.match(START_SPACES)&.captures&.first
21
21
 
22
22
  if spaces.include?("\t")
23
- offenses << Offense.new(
24
- self,
25
- processed_source.to_source_range(document_pos, document_pos + spaces.length - 1),
23
+ add_offense(
24
+ processed_source.to_source_range(document_pos...(document_pos + spaces.length)),
26
25
  "Indent with spaces instead of tabs.",
27
26
  spaces.gsub("\t", ' ' * @config.tab_width)
28
27
  )
@@ -8,17 +8,16 @@ module ERBLint
8
8
 
9
9
  TRAILING_WHITESPACE = /([[:space:]]*)\Z/
10
10
 
11
- def offenses(processed_source)
11
+ def run(processed_source)
12
12
  lines = processed_source.file_content.split("\n", -1)
13
13
  document_pos = 0
14
- lines.each_with_object([]) do |line, offenses|
14
+ lines.each do |line|
15
15
  document_pos += line.length + 1
16
16
  whitespace = line.match(TRAILING_WHITESPACE)&.captures&.first
17
17
  next unless whitespace && !whitespace.empty?
18
18
 
19
- offenses << Offense.new(
20
- self,
21
- processed_source.to_source_range(document_pos - whitespace.length - 1, document_pos - 2),
19
+ add_offense(
20
+ processed_source.to_source_range((document_pos - whitespace.length - 1)...(document_pos - 1)),
22
21
  "Extra whitespace detected at end of line."
23
22
  )
24
23
  end
@@ -17,7 +17,7 @@ module ERBLint
17
17
 
18
18
  def inspect
19
19
  "#<#{self.class.name} linter=#{linter.class.name} "\
20
- "source_range=#{source_range.begin_pos}..#{source_range.end_pos - 1} "\
20
+ "source_range=#{source_range.begin_pos}...#{source_range.end_pos} "\
21
21
  "message=#{message}>"
22
22
  end
23
23
 
@@ -7,7 +7,7 @@ module ERBLint
7
7
  def initialize(filename, file_content)
8
8
  @filename = filename
9
9
  @file_content = file_content
10
- @parser = BetterHtml::Parser.new(file_content, template_language: :html)
10
+ @parser = BetterHtml::Parser.new(source_buffer, template_language: :html)
11
11
  end
12
12
 
13
13
  def ast
@@ -22,8 +22,13 @@ module ERBLint
22
22
  end
23
23
  end
24
24
 
25
- def to_source_range(begin_pos, end_pos)
26
- Parser::Source::Range.new(source_buffer, begin_pos, end_pos + 1)
25
+ def to_source_range(range)
26
+ range = (range.begin_pos...range.end_pos) if range.is_a?(::Parser::Source::Range)
27
+ BetterHtml::Tokenizer::Location.new(
28
+ source_buffer,
29
+ range.begin,
30
+ range.exclude_end? ? range.end : range.end + 1
31
+ )
27
32
  end
28
33
  end
29
34
  end
@@ -3,6 +3,8 @@
3
3
  module ERBLint
4
4
  # Runs all enabled linters against an html.erb file.
5
5
  class Runner
6
+ attr_reader :offenses
7
+
6
8
  def initialize(file_loader, config)
7
9
  @file_loader = file_loader
8
10
  @config = config || RunnerConfig.default
@@ -13,16 +15,21 @@ module ERBLint
13
15
  @linters = linter_classes.map do |linter_class|
14
16
  linter_class.new(@file_loader, @config.for_linter(linter_class))
15
17
  end
18
+ @offenses = []
16
19
  end
17
20
 
18
21
  def run(processed_source)
19
- offenses = []
20
22
  @linters
21
23
  .reject { |linter| linter.excludes_file?(processed_source.filename) }
22
24
  .each do |linter|
23
- offenses += linter.offenses(processed_source)
25
+ linter.run(processed_source)
26
+ @offenses.concat(linter.offenses)
24
27
  end
25
- offenses
28
+ end
29
+
30
+ def clear_offenses
31
+ @offenses = []
32
+ @linters.each(&:clear_offenses)
26
33
  end
27
34
  end
28
35
  end
@@ -26,7 +26,7 @@ module ERBLint
26
26
  private
27
27
 
28
28
  def erb_nodes
29
- erb_ast.descendants(:erb).sort { |a, b| a.loc.start <=> b.loc.start }
29
+ erb_ast.descendants(:erb).sort { |a, b| a.loc.begin_pos <=> b.loc.begin_pos }
30
30
  end
31
31
 
32
32
  class Entry
@@ -72,14 +72,14 @@ module ERBLint
72
72
  def ordered(nodes)
73
73
  nodes
74
74
  .uniq(&:loc)
75
- .sort { |a, b| a.loc.start <=> b.loc.start }
75
+ .sort { |a, b| a.loc.begin_pos <=> b.loc.begin_pos }
76
76
  end
77
77
  end
78
78
 
79
79
  def build_map
80
80
  erb_nodes.each do |erb_node|
81
81
  indicator_node, _, code_node, _ = *erb_node
82
- length = code_node.loc.stop - code_node.loc.start
82
+ length = code_node.loc.size
83
83
  start = current_pos
84
84
  if indicator_node.nil?
85
85
  append("#{code_node.loc.source}\n")
@@ -40,15 +40,14 @@ module ERBLint
40
40
 
41
41
  def range_with_offset(range)
42
42
  @processed_source.to_source_range(
43
- bound(@offset + range.begin_pos),
44
- bound(@offset + range.end_pos - 1),
43
+ bound(@offset + range.begin_pos)..bound(@offset + (range.end_pos - 1))
45
44
  )
46
45
  end
47
46
 
48
47
  def bound(pos)
49
48
  [
50
- [pos, @bound_range.begin_pos].max,
51
- @bound_range.end_pos - 1
49
+ [pos, @bound_range.min].max,
50
+ @bound_range.max
52
51
  ].min
53
52
  end
54
53
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ERBLint
4
- VERSION = '0.0.21'
4
+ VERSION = '0.0.22'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erb_lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Chan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-02 00:00:00.000000000 Z
11
+ date: 2018-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: better_html
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.0.5
19
+ version: 1.0.6
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.0.5
26
+ version: 1.0.6
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: html_tokenizer
29
29
  requirement: !ruby/object:Gem::Requirement