erb_lint 0.0.13 → 0.0.14

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: 86e60e8bfa31cd289e7ead368d8eff2832a6a8f8
4
- data.tar.gz: cae30be744c94ed73048403a4c1d911d2a5b8c15
3
+ metadata.gz: 185ec9589ef4da649b5f073871c8e1977a2820f6
4
+ data.tar.gz: 7588eff7bf3e74f3f887a5e81c6eaa72839c953a
5
5
  SHA512:
6
- metadata.gz: c5b8f374aecc1a4541bbaa363d65e8b8b38cf3d6ffb36f9f517b8057527205c48cb2225c190fcf35f2a2c0952b0aa5035439509d2c8e5b517905a18c848b2127
7
- data.tar.gz: 3661165a60a0c6f6185957a6c61948e7d66c6cdaaa27c9e205ca39e340c75aa366094f1859e3ab4a9bd4d122744f73f4c66bd2e4086e060e89ec2f310ea7a4d9
6
+ metadata.gz: 5e1d78d903307c1433b0c56a9ce63993fbda5802ba671cc4288c96096d18617dbf69c85837026b6222e4c8779c3f44fb65af44cb54d29f6fdfb6664ad2369f26
7
+ data.tar.gz: fe70e6836ed54f517b79d73261ac2c7101bfa29b335a4e09cfce39177d7b2f9d63c497e30016099793dd236266fd4e58b12a5c09a3475e555cb15f00a7dbd7b4
data/lib/erb_lint.rb CHANGED
@@ -1,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'erb_lint/version'
4
- require 'erb_lint/offense'
3
+ require 'erb_lint/corrector'
4
+ require 'erb_lint/file_loader'
5
5
  require 'erb_lint/linter_config'
6
6
  require 'erb_lint/linter_registry'
7
7
  require 'erb_lint/linter'
8
+ require 'erb_lint/offense'
9
+ require 'erb_lint/offset_corrector'
10
+ require 'erb_lint/processed_source'
8
11
  require 'erb_lint/runner_config'
9
12
  require 'erb_lint/runner'
10
- require 'erb_lint/file_loader'
13
+ require 'erb_lint/version'
11
14
 
12
15
  # Load linters
13
16
  Dir[File.expand_path('erb_lint/linters/**/*.rb', File.dirname(__FILE__))].each do |file|
data/lib/erb_lint/cli.rb CHANGED
@@ -16,10 +16,19 @@ module ERBLint
16
16
  class ExitWithFailure < RuntimeError; end
17
17
  class ExitWithSuccess < RuntimeError; end
18
18
 
19
+ class Stats
20
+ attr_accessor :found, :corrected
21
+ def initialize
22
+ @found = 0
23
+ @corrected = 0
24
+ end
25
+ end
26
+
19
27
  def initialize
20
28
  @options = {}
21
29
  @config = nil
22
30
  @files = []
31
+ @stats = Stats.new
23
32
  end
24
33
 
25
34
  def run(args = ARGV)
@@ -33,31 +42,40 @@ module ERBLint
33
42
  load_config
34
43
  ensure_files_exist(lint_files)
35
44
 
36
- puts "Linting #{lint_files.size} files with #{enabled_linter_classes.size} linters..."
45
+ if enabled_linter_classes.empty?
46
+ failure!('no linter available with current configuration')
47
+ end
48
+
49
+ puts "Linting #{lint_files.size} files with "\
50
+ "#{enabled_linter_classes.size} #{'autocorrectable ' if autocorrect?}linters..."
37
51
  puts
38
52
 
39
- errors_found = false
40
53
  runner_config = @config.merge(runner_config_override)
41
54
  runner = ERBLint::Runner.new(file_loader, runner_config)
42
55
  lint_files.each do |filename|
43
- offenses = runner.run(filename, File.read(filename))
44
- offenses.each do |offense|
45
- puts <<~EOF
46
- #{offense.message}
47
- In file: #{relative_filename(filename)}:#{offense.line_range.begin}
48
-
49
- EOF
50
- errors_found = true
56
+ begin
57
+ run_with_corrections(runner, filename)
58
+ rescue => e
59
+ puts "Exception occured when processing: #{relative_filename(filename)}"
60
+ puts e.message
61
+ puts e.backtrace.join("\n").red
62
+ puts
51
63
  end
52
64
  end
53
65
 
54
- if errors_found
55
- warn "Errors were found in ERB files".red
66
+ 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
69
+ else
70
+ puts "#{@stats.corrected} error(s) corrected in ERB files".green
71
+ end
72
+ elsif @stats.found > 0
73
+ warn "#{@stats.found} error(s) were found in ERB files".red
56
74
  else
57
75
  puts "No errors were found in ERB files".green
58
76
  end
59
77
 
60
- !errors_found
78
+ @stats.found == 0
61
79
  rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, ExitWithFailure => e
62
80
  warn e.message.red
63
81
  false
@@ -71,6 +89,48 @@ module ERBLint
71
89
 
72
90
  private
73
91
 
92
+ def autocorrect?
93
+ @options[:autocorrect]
94
+ end
95
+
96
+ def run_with_corrections(runner, filename)
97
+ file_content = File.read(filename)
98
+ offenses = []
99
+
100
+ 7.times do
101
+ processed_source = ERBLint::ProcessedSource.new(filename, file_content)
102
+ offenses = runner.run(processed_source)
103
+ break unless autocorrect? && offenses.any?
104
+
105
+ corrector = correct(processed_source, offenses)
106
+ break if corrector.corrections.empty?
107
+ break if processed_source.file_content == corrector.corrected_content
108
+
109
+ @stats.corrected += corrector.corrections.size
110
+
111
+ File.open(filename, "wb") do |file|
112
+ file.write(corrector.corrected_content)
113
+ end
114
+
115
+ file_content = corrector.corrected_content
116
+ end
117
+
118
+ @stats.found += offenses.size
119
+ offenses.each do |offense|
120
+ puts <<~EOF
121
+ #{offense.message}#{' (not autocorrected)'.red if autocorrect?}
122
+ In file: #{relative_filename(filename)}:#{offense.line_range.begin}
123
+
124
+ EOF
125
+ end
126
+ end
127
+
128
+ def correct(processed_source, offenses)
129
+ corrector = ERBLint::Corrector.new(processed_source, offenses)
130
+ failure!(corrector.diagnostics.join(', ')) if corrector.diagnostics.any?
131
+ corrector
132
+ end
133
+
74
134
  def config_filename
75
135
  @config_filename ||= @options[:config] || DEFAULT_CONFIG_FILENAME
76
136
  end
@@ -128,12 +188,17 @@ module ERBLint
128
188
  def enabled_linter_names
129
189
  @enabled_linter_names ||=
130
190
  @options[:enabled_linters] ||
131
- known_linter_names.select { |name| @config.for_linter(name.camelize).enabled? }
191
+ known_linter_names
192
+ .select { |name| @config.for_linter(name.camelize).enabled? }
132
193
  end
133
194
 
134
195
  def enabled_linter_classes
135
196
  @enabled_linter_classes ||= ERBLint::LinterRegistry.linters
136
- .select { |klass| enabled_linter_names.include?(klass.simple_name.underscore) }
197
+ .select { |klass| linter_can_run?(klass) && enabled_linter_names.include?(klass.simple_name.underscore) }
198
+ end
199
+
200
+ def linter_can_run?(klass)
201
+ !autocorrect? || klass.support_autocorrect?
137
202
  end
138
203
 
139
204
  def relative_filename(filename)
@@ -141,17 +206,13 @@ module ERBLint
141
206
  end
142
207
 
143
208
  def runner_config_override
144
- if @options[:enabled_linters].present?
145
- RunnerConfig.new(
146
- linters: {}.tap do |linters|
147
- ERBLint::LinterRegistry.linters.map do |klass|
148
- linters[klass.simple_name] = { 'enabled' => enabled_linter_classes.include?(klass) }
149
- end
209
+ RunnerConfig.new(
210
+ linters: {}.tap do |linters|
211
+ ERBLint::LinterRegistry.linters.map do |klass|
212
+ linters[klass.simple_name] = { 'enabled' => enabled_linter_classes.include?(klass) }
150
213
  end
151
- )
152
- else
153
- RunnerConfig.new
154
- end
214
+ end
215
+ )
155
216
  end
156
217
 
157
218
  def option_parser
@@ -184,6 +245,10 @@ module ERBLint
184
245
  @options[:enabled_linters] = linters
185
246
  end
186
247
 
248
+ opts.on("--autocorrect", "Correct offenses that can be corrected automatically (default: false)") do |config|
249
+ @options[:autocorrect] = config
250
+ end
251
+
187
252
  opts.on_tail("-h", "--help", "Show this message") do
188
253
  success!(opts)
189
254
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ class Corrector
5
+ attr_reader :processed_source, :offenses, :corrected_content
6
+
7
+ def initialize(processed_source, offenses)
8
+ @processed_source = processed_source
9
+ @offenses = offenses
10
+ @corrected_content = corrector.rewrite
11
+ end
12
+
13
+ def corrections
14
+ @corrections ||= @offenses.map do |offense|
15
+ offense.linter.autocorrect(@processed_source, offense)
16
+ end.compact
17
+ end
18
+
19
+ def corrector
20
+ RuboCop::Cop::Corrector.new(@processed_source.source_buffer, corrections)
21
+ end
22
+
23
+ def diagnostics
24
+ corrector.diagnostics
25
+ end
26
+ end
27
+ end
@@ -23,6 +23,10 @@ module ERBLint
23
23
 
24
24
  linter.config_schema = LinterConfig
25
25
  end
26
+
27
+ def support_autocorrect?
28
+ method_defined?(:autocorrect)
29
+ end
26
30
  end
27
31
 
28
32
  # Must be implemented by the concrete inheriting class.
@@ -41,16 +45,7 @@ module ERBLint
41
45
  @config.excludes_file?(filename)
42
46
  end
43
47
 
44
- def lint_file(file_content)
45
- lines = file_content.scan(/[^\n]*\n|[^\n]+/)
46
- lint_lines(lines)
47
- end
48
-
49
- protected
50
-
51
- # The lint_lines method that contains the logic for the linter and returns a list of errors.
52
- # Must be implemented by the concrete inheriting class.
53
- def lint_lines(_lines)
48
+ def offenses(_processed_source)
54
49
  raise NotImplementedError, "must implement ##{__method__}"
55
50
  end
56
51
  end
@@ -28,27 +28,39 @@ module ERBLint
28
28
  @addendum = @config.addendum
29
29
  end
30
30
 
31
- def lint_file(file_content)
32
- errors = []
33
- parser = build_parser(file_content)
34
- class_name_with_loc(parser).each do |class_name, loc|
35
- errors.push(*generate_errors(class_name, loc.line_range))
36
- end
37
- text_tags_content(parser).each do |content|
38
- errors.push(*lint_file(content))
39
- end
40
- errors
31
+ def offenses(processed_source)
32
+ process_nested_offenses(
33
+ source: processed_source,
34
+ offset: 0,
35
+ parent_source: processed_source,
36
+ )
41
37
  end
42
38
 
43
39
  private
44
40
 
45
- def build_parser(file_content)
46
- BetterHtml::Parser.new(file_content, template_language: :html)
41
+ def process_nested_offenses(source:, offset:, parent_source:)
42
+ offenses = []
43
+ class_name_with_loc(source).each do |class_name, loc|
44
+ range = parent_source.to_source_range(
45
+ offset + loc.start,
46
+ offset + loc.stop
47
+ )
48
+ offenses += generate_errors(class_name, range)
49
+ end
50
+ text_tags_content(source).each do |content_node|
51
+ sub_source = ProcessedSource.new(source.filename, content_node.loc.source)
52
+ offenses += process_nested_offenses(
53
+ source: sub_source,
54
+ offset: offset + content_node.loc.start,
55
+ parent_source: parent_source
56
+ )
57
+ end
58
+ offenses
47
59
  end
48
60
 
49
- def class_name_with_loc(parser)
61
+ def class_name_with_loc(processed_source)
50
62
  Enumerator.new do |yielder|
51
- tags(parser).each do |tag|
63
+ tags(processed_source).each do |tag|
52
64
  class_value = tag.attributes['class']&.value
53
65
  next unless class_value
54
66
  class_value.split(' ').each do |class_name|
@@ -58,39 +70,39 @@ module ERBLint
58
70
  end
59
71
  end
60
72
 
61
- def text_tags_content(parser)
73
+ def text_tags_content(processed_source)
62
74
  Enumerator.new do |yielder|
63
- script_tags(parser)
75
+ script_tags(processed_source)
64
76
  .select { |tag| tag.attributes['type']&.value == 'text/html' }
65
77
  .each do |tag|
66
- index = parser.ast.to_a.find_index(tag.node)
67
- next_node = parser.ast.to_a[index + 1]
78
+ index = processed_source.ast.to_a.find_index(tag.node)
79
+ next_node = processed_source.ast.to_a[index + 1]
68
80
 
69
- yielder.yield(next_node.loc.source) if next_node.type == :text
81
+ yielder.yield(next_node) if next_node.type == :text
70
82
  end
71
83
  end
72
84
  end
73
85
 
74
- def script_tags(parser)
75
- tags(parser).select { |tag| tag.name == 'script' }
86
+ def script_tags(processed_source)
87
+ tags(processed_source).select { |tag| tag.name == 'script' }
76
88
  end
77
89
 
78
- def tags(parser)
79
- tag_nodes(parser).map { |tag_node| BetterHtml::Tree::Tag.from_node(tag_node) }
90
+ def tags(processed_source)
91
+ tag_nodes(processed_source).map { |tag_node| BetterHtml::Tree::Tag.from_node(tag_node) }
80
92
  end
81
93
 
82
- def tag_nodes(parser)
83
- parser.nodes_with_type(:tag)
94
+ def tag_nodes(processed_source)
95
+ processed_source.parser.nodes_with_type(:tag)
84
96
  end
85
97
 
86
- def generate_errors(class_name, line_range)
98
+ def generate_errors(class_name, range)
87
99
  violated_rules(class_name).map do |violated_rule|
88
100
  suggestion = " #{violated_rule[:suggestion]}".rstrip
89
101
  message = "Deprecated class `%s` detected matching the pattern `%s`.%s #{@addendum}".strip
90
102
 
91
103
  Offense.new(
92
104
  self,
93
- line_range,
105
+ range,
94
106
  format(message, class_name, violated_rule[:class_expr], suggestion)
95
107
  )
96
108
  end
@@ -21,13 +21,17 @@ module ERBLint
21
21
  @config_filename = @config.better_html_config
22
22
  end
23
23
 
24
- def lint_file(file_content)
25
- errors = []
26
- tester = Tester.new(file_content, config: better_html_config)
24
+ def offenses(processed_source)
25
+ offenses = []
26
+ tester = Tester.new(processed_source.file_content, config: better_html_config)
27
27
  tester.errors.each do |error|
28
- errors << format_offense(error)
28
+ offenses << Offense.new(
29
+ self,
30
+ processed_source.to_source_range(error.location.start, error.location.stop),
31
+ error.message
32
+ )
29
33
  end
30
- errors
34
+ offenses
31
35
  end
32
36
 
33
37
  private
@@ -43,14 +47,6 @@ module ERBLint
43
47
  BetterHtml::Config.new(**config_hash)
44
48
  end
45
49
  end
46
-
47
- def format_offense(error)
48
- Offense.new(
49
- self,
50
- error.location.line_range,
51
- error.message
52
- )
53
- end
54
50
  end
55
51
  end
56
52
  end
@@ -16,28 +16,39 @@ module ERBLint
16
16
  @new_lines_should_be_present = @config.present?
17
17
  end
18
18
 
19
- protected
19
+ def offenses(processed_source)
20
+ file_content = processed_source.file_content
20
21
 
21
- def lint_lines(lines)
22
- errors = []
23
- return errors if lines.empty?
22
+ offenses = []
23
+ return offenses if file_content.empty?
24
24
 
25
- ends_with_newline = lines.last.chars[-1] == "\n"
25
+ match = file_content.match(/(\n+)\z/)
26
+ ends_with_newline = match.present?
26
27
 
27
28
  if @new_lines_should_be_present && !ends_with_newline
28
- errors << Offense.new(
29
+ offenses << Offense.new(
29
30
  self,
30
- Range.new(lines.length, lines.length),
31
+ processed_source.to_source_range(file_content.size, file_content.size - 1),
31
32
  'Missing a trailing newline at the end of the file.'
32
33
  )
33
34
  elsif !@new_lines_should_be_present && ends_with_newline
34
- errors << Offense.new(
35
+ offenses << Offense.new(
35
36
  self,
36
- Range.new(lines.length, lines.length),
37
- 'Remove the trailing newline at the end of the file.'
37
+ processed_source.to_source_range(match.begin(0), match.end(0) - 1),
38
+ "Remove #{match[0].size} trailing newline at the end of the file."
38
39
  )
39
40
  end
40
- errors
41
+ offenses
42
+ end
43
+
44
+ def autocorrect(_processed_source, offense)
45
+ lambda do |corrector|
46
+ if @new_lines_should_be_present
47
+ corrector.insert_after(offense.source_range, "\n")
48
+ else
49
+ corrector.remove_trailing(offense.source_range, offense.source_range.size)
50
+ end
51
+ end
41
52
  end
42
53
  end
43
54
  end
@@ -17,6 +17,7 @@ module ERBLint
17
17
 
18
18
  self.config_schema = ConfigSchema
19
19
 
20
+ SUFFIX_EXPR = /[[:blank:]]*\Z/
20
21
  # copied from Rails: action_view/template/handlers/erb/erubi.rb
21
22
  BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
22
23
 
@@ -27,24 +28,83 @@ module ERBLint
27
28
  @rubocop_config = RuboCop::ConfigLoader.merge_with_default(custom_config, '')
28
29
  end
29
30
 
30
- def lint_file(file_content)
31
- errors = []
32
- parser = BetterHtml::Parser.new(file_content, template_language: :html)
33
- parser.ast.descendants(:erb).each do |erb_node|
34
- _, _, code_node, = *erb_node
35
- code = code_node.loc.source.sub(/\A[[:blank:]]*/, '')
36
- code = "#{' ' * erb_node.loc.column}#{code}"
37
- code = code.sub(BLOCK_EXPR, '')
38
- offenses = inspect_content(code)
39
- offenses&.each do |offense|
40
- errors << format_offense(file_content, code_node, offense)
41
- end
31
+ def offenses(processed_source)
32
+ offenses = []
33
+ processed_source.ast.descendants(:erb).each do |erb_node|
34
+ offenses.push(*inspect_content(processed_source, erb_node))
35
+ end
36
+ offenses
37
+ end
38
+
39
+ def autocorrect(processed_source, offense)
40
+ return unless offense.correction
41
+ lambda do |corrector|
42
+ passthrough = OffsetCorrector.new(
43
+ processed_source,
44
+ corrector,
45
+ offense.offset,
46
+ offense.bound_range,
47
+ )
48
+ offense.correction.call(passthrough)
42
49
  end
43
- errors
44
50
  end
45
51
 
46
52
  private
47
53
 
54
+ class OffenseWithCorrection < Offense
55
+ attr_reader :correction, :offset, :bound_range
56
+ def initialize(linter, source_range, message, correction:, offset:, bound_range:)
57
+ super(linter, source_range, message)
58
+ @correction = correction
59
+ @offset = offset
60
+ @bound_range = bound_range
61
+ end
62
+ end
63
+
64
+ def inspect_content(processed_source, erb_node)
65
+ indicator, _, code_node, = *erb_node
66
+ return if indicator == '#'
67
+
68
+ original_source = code_node.loc.source
69
+ trimmed_source = original_source.sub(BLOCK_EXPR, '').sub(SUFFIX_EXPR, '')
70
+ alignment_column = code_node.loc.column
71
+ aligned_source = "#{' ' * alignment_column}#{trimmed_source}"
72
+
73
+ source = rubocop_processed_source(aligned_source)
74
+ return unless source.valid_syntax?
75
+
76
+ offenses = []
77
+ team = build_team
78
+ team.inspect_file(source)
79
+ team.cops.each do |cop|
80
+ cop.offenses.reject(&:disabled?).each_with_index do |rubocop_offense, index|
81
+ correction = cop.corrections[index] if rubocop_offense.corrected?
82
+
83
+ offset = code_node.loc.start - alignment_column
84
+ offense_range = processed_source.to_source_range(
85
+ offset + rubocop_offense.location.begin_pos,
86
+ offset + rubocop_offense.location.end_pos - 1,
87
+ )
88
+
89
+ bound_range = processed_source.to_source_range(
90
+ code_node.loc.start,
91
+ code_node.loc.stop
92
+ )
93
+
94
+ offenses <<
95
+ OffenseWithCorrection.new(
96
+ self,
97
+ offense_range,
98
+ rubocop_offense.message.strip,
99
+ correction: correction,
100
+ offset: offset,
101
+ bound_range: bound_range,
102
+ )
103
+ end
104
+ end
105
+ offenses
106
+ end
107
+
48
108
  def tempfile_from(filename, content)
49
109
  Tempfile.create(File.basename(filename), Dir.pwd) do |tempfile|
50
110
  tempfile.write(content)
@@ -54,14 +114,7 @@ module ERBLint
54
114
  end
55
115
  end
56
116
 
57
- def inspect_content(content)
58
- source = processed_source(content)
59
- return unless source.valid_syntax?
60
- offenses = team.inspect_file(source)
61
- offenses.reject(&:disabled?)
62
- end
63
-
64
- def processed_source(content)
117
+ def rubocop_processed_source(content)
65
118
  RuboCop::ProcessedSource.new(
66
119
  content,
67
120
  @rubocop_config.target_ruby_version,
@@ -69,29 +122,25 @@ module ERBLint
69
122
  )
70
123
  end
71
124
 
72
- def team
73
- cop_classes =
74
- if @only_cops.present?
75
- selected_cops = RuboCop::Cop::Cop.all.select { |cop| cop.match?(@only_cops) }
76
- RuboCop::Cop::Registry.new(selected_cops)
77
- elsif @rubocop_config['Rails']['Enabled']
78
- RuboCop::Cop::Registry.new(RuboCop::Cop::Cop.all)
79
- else
80
- RuboCop::Cop::Cop.non_rails
81
- end
82
- RuboCop::Cop::Team.new(cop_classes, @rubocop_config, extra_details: true, display_cop_names: true)
125
+ def cop_classes
126
+ if @only_cops.present?
127
+ selected_cops = RuboCop::Cop::Cop.all.select { |cop| cop.match?(@only_cops) }
128
+ RuboCop::Cop::Registry.new(selected_cops)
129
+ elsif @rubocop_config['Rails']['Enabled']
130
+ RuboCop::Cop::Registry.new(RuboCop::Cop::Cop.all)
131
+ else
132
+ RuboCop::Cop::Cop.non_rails
133
+ end
83
134
  end
84
135
 
85
- def format_offense(file_content, code_node, offense)
86
- loc = BetterHtml::Tokenizer::Location.new(
87
- file_content,
88
- code_node.loc.start + offense.location.begin_pos,
89
- code_node.loc.start + offense.location.end_pos - 1,
90
- )
91
- Offense.new(
92
- self,
93
- loc.line_range,
94
- offense.message.strip
136
+ def build_team
137
+ RuboCop::Cop::Team.new(
138
+ cop_classes,
139
+ @rubocop_config,
140
+ extra_details: true,
141
+ display_cop_names: true,
142
+ auto_correct: true,
143
+ stdin: "",
95
144
  )
96
145
  end
97
146
 
@@ -3,25 +3,32 @@
3
3
  module ERBLint
4
4
  # Defines common functionality available to all linters.
5
5
  class Offense
6
- attr_reader :linter, :line_range, :message
6
+ attr_reader :linter, :source_range, :message
7
7
 
8
- def initialize(linter, line_range, message)
8
+ def initialize(linter, source_range, message)
9
+ unless source_range.is_a?(Parser::Source::Range)
10
+ raise ArgumentError, "expected Parser::Source::Range for arg 2"
11
+ end
9
12
  @linter = linter
10
- @line_range = line_range
13
+ @source_range = source_range
11
14
  @message = message
12
15
  end
13
16
 
14
17
  def inspect
15
18
  "#<#{self.class.name} linter=#{linter.class.name} "\
16
- "line_range=#{line_range} "\
19
+ "source_range=#{source_range.begin_pos}..#{source_range.end_pos - 1} "\
17
20
  "message=#{message}>"
18
21
  end
19
22
 
20
23
  def ==(other)
21
- other.class == self.class &&
24
+ other.class <= ERBLint::Offense &&
22
25
  other.linter == linter &&
23
- other.line_range == line_range &&
26
+ other.source_range == source_range &&
24
27
  other.message == message
25
28
  end
29
+
30
+ def line_range
31
+ Range.new(source_range.line, source_range.last_line)
32
+ end
26
33
  end
27
34
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ class OffsetCorrector
5
+ def initialize(processed_source, corrector, offset, bound_range)
6
+ @processed_source = processed_source
7
+ @corrector = corrector
8
+ @offset = offset
9
+ @bound_range = bound_range
10
+ end
11
+
12
+ def remove(range)
13
+ @corrector.remove(range_with_offset(range))
14
+ end
15
+
16
+ def insert_before(range, content)
17
+ @corrector.insert_before(range_with_offset(range), content)
18
+ end
19
+
20
+ def insert_after(range, content)
21
+ @corrector.insert_after(range_with_offset(range), content)
22
+ end
23
+
24
+ def replace(range, content)
25
+ @corrector.replace(range_with_offset(range), content)
26
+ end
27
+
28
+ def remove_preceding(range, size)
29
+ @corrector.remove_preceding(range_with_offset(range), size)
30
+ end
31
+
32
+ def remove_leading(range, size)
33
+ @corrector.remove_leading(range_with_offset(range), size)
34
+ end
35
+
36
+ def remove_trailing(range, size)
37
+ @corrector.remove_trailing(range_with_offset(range), size)
38
+ end
39
+
40
+ def range_with_offset(range)
41
+ @processed_source.to_source_range(
42
+ bound(@offset + range.begin_pos),
43
+ bound(@offset + range.end_pos - 1),
44
+ )
45
+ end
46
+
47
+ def bound(pos)
48
+ [
49
+ [pos, @bound_range.begin_pos].max,
50
+ @bound_range.end_pos - 1
51
+ ].min
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ class ProcessedSource
5
+ attr_reader :filename, :file_content, :parser
6
+
7
+ def initialize(filename, file_content)
8
+ @filename = filename
9
+ @file_content = file_content
10
+ @parser = BetterHtml::Parser.new(file_content, template_language: :html)
11
+ end
12
+
13
+ def ast
14
+ @parser.ast
15
+ end
16
+
17
+ def source_buffer
18
+ @source_buffer ||= begin
19
+ buffer = Parser::Source::Buffer.new(filename)
20
+ buffer.source = file_content
21
+ buffer
22
+ end
23
+ end
24
+
25
+ def to_source_range(begin_pos, end_pos)
26
+ Parser::Source::Range.new(source_buffer, begin_pos, end_pos + 1)
27
+ end
28
+ end
29
+ end
@@ -15,12 +15,12 @@ module ERBLint
15
15
  end
16
16
  end
17
17
 
18
- def run(filename, file_content)
18
+ def run(processed_source)
19
19
  offenses = []
20
20
  @linters
21
- .reject { |linter| linter.excludes_file?(filename) }
21
+ .reject { |linter| linter.excludes_file?(processed_source.filename) }
22
22
  .each do |linter|
23
- offenses += linter.lint_file(file_content)
23
+ offenses += linter.offenses(processed_source)
24
24
  end
25
25
  offenses
26
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ERBLint
4
- VERSION = '0.0.13'
4
+ VERSION = '0.0.14'
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.13
4
+ version: 0.0.14
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-01-10 00:00:00.000000000 Z
11
+ date: 2018-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: better_html
@@ -133,6 +133,7 @@ files:
133
133
  - exe/erblint
134
134
  - lib/erb_lint.rb
135
135
  - lib/erb_lint/cli.rb
136
+ - lib/erb_lint/corrector.rb
136
137
  - lib/erb_lint/file_loader.rb
137
138
  - lib/erb_lint/linter.rb
138
139
  - lib/erb_lint/linter_config.rb
@@ -142,6 +143,8 @@ files:
142
143
  - lib/erb_lint/linters/final_newline.rb
143
144
  - lib/erb_lint/linters/rubocop.rb
144
145
  - lib/erb_lint/offense.rb
146
+ - lib/erb_lint/offset_corrector.rb
147
+ - lib/erb_lint/processed_source.rb
145
148
  - lib/erb_lint/runner.rb
146
149
  - lib/erb_lint/runner_config.rb
147
150
  - lib/erb_lint/version.rb