erb_lint 0.0.13 → 0.0.14

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