erb_lint 0.0.21 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
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