erb_lint 0.2.0 → 0.3.1

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
  SHA256:
3
- metadata.gz: f417b343f6b0b891bd095ba568b2d50e78bb7d619b637b3bf2d70f2607f990cc
4
- data.tar.gz: 1c080ab44d8238ad32138babe65076626e82974a96a13a1f4041bf7b9568b6ec
3
+ metadata.gz: 1bd25c794b1028b09ecec8ce3b5e77600d36364d59b85f2e4b24a18d6bb08853
4
+ data.tar.gz: e41f353e930401a024d880fc6973beb1e462f2f5372edb094523eacde0030d72
5
5
  SHA512:
6
- metadata.gz: 407d4f4813eaef3b2247357e0af4373cf7511bd8162c5706bc951af05e8577a9584bd5d3dbd00d6dba98bb62aa9d5d58a36a2e484553f7e9ade6af3f95803881
7
- data.tar.gz: 7e5fb9bdb8c0cd455279ef3a1c46df75704f6f53eea0ab2eb37b4ff2c5e8292a6ced137b0fbeb6e89c2382d5f1be3220d0e12c4889127cb6d430578f2dbe8945
6
+ metadata.gz: 94ed5b3479803ff2b51af6390c0e39c2fb37ef98e23fc2523ac204b638d8ff043665926081794ff27d77a60faae8ee7747cb4ef3d04125b7b3f7c2ae55ea268f
7
+ data.tar.gz: 9b423110f202bb9728ec57dbd60f04aa86e2901ecfa915d9ddc49fd58fac27b3daddea78780612c3f474ece4c41c01977bea72ad8353a9a113d5cee8c4a51914
data/lib/erb_lint/all.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require "rubocop"
4
4
 
5
5
  require "erb_lint"
6
+ require "erb_lint/cache"
7
+ require "erb_lint/cached_offense"
6
8
  require "erb_lint/corrector"
7
9
  require "erb_lint/file_loader"
8
10
  require "erb_lint/linter_config"
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ class Cache
5
+ CACHE_DIRECTORY = ".erb-lint-cache"
6
+
7
+ def initialize(config, cache_dir = nil)
8
+ @config = config
9
+ @cache_dir = cache_dir || CACHE_DIRECTORY
10
+ @hits = []
11
+ @new_results = []
12
+ puts "Cache mode is on"
13
+ end
14
+
15
+ def get(filename, file_content)
16
+ file_checksum = checksum(filename, file_content)
17
+ begin
18
+ cache_file_contents_as_offenses = JSON.parse(
19
+ File.read(File.join(@cache_dir, file_checksum))
20
+ ).map do |offense_hash|
21
+ ERBLint::CachedOffense.new(offense_hash)
22
+ end
23
+ rescue Errno::ENOENT
24
+ return false
25
+ end
26
+ @hits.push(file_checksum)
27
+ cache_file_contents_as_offenses
28
+ end
29
+
30
+ def set(filename, file_content, offenses_as_json)
31
+ file_checksum = checksum(filename, file_content)
32
+ @new_results.push(file_checksum)
33
+
34
+ FileUtils.mkdir_p(@cache_dir)
35
+
36
+ File.open(File.join(@cache_dir, file_checksum), "wb") do |f|
37
+ f.write(offenses_as_json)
38
+ end
39
+ end
40
+
41
+ def close
42
+ prune_cache
43
+ end
44
+
45
+ def prune_cache
46
+ if hits.empty?
47
+ puts "Cache being created for the first time, skipping prune"
48
+ return
49
+ end
50
+
51
+ cache_files = Dir.new(@cache_dir).children
52
+ cache_files.each do |cache_file|
53
+ next if hits.include?(cache_file) || new_results.include?(cache_file)
54
+
55
+ File.delete(File.join(@cache_dir, cache_file))
56
+ end
57
+ end
58
+
59
+ def cache_dir_exists?
60
+ File.directory?(@cache_dir)
61
+ end
62
+
63
+ def clear
64
+ return unless cache_dir_exists?
65
+
66
+ puts "Clearing cache by deleting cache directory"
67
+ FileUtils.rm_r(@cache_dir)
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :config, :hits, :new_results
73
+
74
+ def checksum(filename, file_content)
75
+ digester = Digest::SHA1.new
76
+ mode = File.stat(filename).mode
77
+
78
+ digester.update(
79
+ "#{mode}#{config.to_hash}#{ERBLint::VERSION}#{file_content}"
80
+ )
81
+ digester.hexdigest
82
+ rescue Errno::ENOENT
83
+ # Spurious files that come and go should not cause a crash, at least not
84
+ # here.
85
+ "_"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ # A Cached version of an Offense with only essential information represented as strings
5
+ class CachedOffense
6
+ attr_reader(
7
+ :message,
8
+ :line_number,
9
+ :severity,
10
+ :column,
11
+ :simple_name,
12
+ :last_line,
13
+ :last_column,
14
+ :length,
15
+ )
16
+
17
+ def initialize(params)
18
+ params = params.transform_keys(&:to_sym)
19
+
20
+ @message = params[:message]
21
+ @line_number = params[:line_number]
22
+ @severity = params[:severity]&.to_sym
23
+ @column = params[:column]
24
+ @simple_name = params[:simple_name]
25
+ @last_line = params[:last_line]
26
+ @last_column = params[:last_column]
27
+ @length = params[:length]
28
+ end
29
+
30
+ def self.new_from_offense(offense)
31
+ new(
32
+ {
33
+ message: offense.message,
34
+ line_number: offense.line_number,
35
+ severity: offense.severity,
36
+ column: offense.column,
37
+ simple_name: offense.simple_name,
38
+ last_line: offense.last_line,
39
+ last_column: offense.last_column,
40
+ length: offense.length,
41
+ }
42
+ )
43
+ end
44
+
45
+ def to_h
46
+ {
47
+ message: message,
48
+ line_number: line_number,
49
+ severity: severity,
50
+ column: column,
51
+ simple_name: simple_name,
52
+ last_line: last_line,
53
+ last_column: last_column,
54
+ length: length,
55
+ }
56
+ end
57
+ end
58
+ end
data/lib/erb_lint/cli.rb CHANGED
@@ -30,10 +30,27 @@ module ERBLint
30
30
  def run(args = ARGV)
31
31
  dupped_args = args.dup
32
32
  load_options(dupped_args)
33
+
34
+ if cache? && autocorrect?
35
+ failure!("cannot run autocorrect mode with cache")
36
+ end
37
+
33
38
  @files = @options[:stdin] || dupped_args
34
39
 
35
40
  load_config
36
41
 
42
+ cache_dir = @options[:cache_dir]
43
+ @cache = Cache.new(@config, cache_dir) if cache? || clear_cache?
44
+
45
+ if clear_cache?
46
+ if cache.cache_dir_exists?
47
+ cache.clear
48
+ success!("cache directory cleared")
49
+ else
50
+ failure!("cache directory doesn't exist, skipping deletion.")
51
+ end
52
+ end
53
+
37
54
  if !@files.empty? && lint_files.empty?
38
55
  if allow_no_files?
39
56
  success!("no files found...\n")
@@ -65,7 +82,7 @@ module ERBLint
65
82
  lint_files.each do |filename|
66
83
  runner.clear_offenses
67
84
  begin
68
- file_content = run_with_corrections(runner, filename)
85
+ file_content = run_on_file(runner, filename)
69
86
  rescue => e
70
87
  @stats.exceptions += 1
71
88
  puts "Exception occurred when processing: #{relative_filename(filename)}"
@@ -77,6 +94,8 @@ module ERBLint
77
94
  end
78
95
  end
79
96
 
97
+ cache&.close
98
+
80
99
  reporter.show
81
100
 
82
101
  if stdin? && autocorrect?
@@ -99,13 +118,43 @@ module ERBLint
99
118
 
100
119
  private
101
120
 
121
+ attr_reader :cache, :config
122
+
123
+ def run_on_file(runner, filename)
124
+ file_content = read_content(filename)
125
+
126
+ if cache? && !autocorrect?
127
+ run_using_cache(runner, filename, file_content)
128
+ else
129
+ file_content = run_with_corrections(runner, filename, file_content)
130
+ end
131
+
132
+ log_offense_stats(runner, filename)
133
+ file_content
134
+ end
135
+
136
+ def run_using_cache(runner, filename, file_content)
137
+ if (cache_result_offenses = cache.get(filename, file_content))
138
+ runner.restore_offenses(cache_result_offenses)
139
+ else
140
+ run_with_corrections(runner, filename, file_content)
141
+ cache.set(filename, file_content, runner.offenses.map(&:to_cached_offense_hash).to_json)
142
+ end
143
+ end
144
+
102
145
  def autocorrect?
103
146
  @options[:autocorrect]
104
147
  end
105
148
 
106
- def run_with_corrections(runner, filename)
107
- file_content = read_content(filename)
149
+ def cache?
150
+ @options[:cache]
151
+ end
108
152
 
153
+ def clear_cache?
154
+ @options[:clear_cache]
155
+ end
156
+
157
+ def run_with_corrections(runner, filename, file_content)
109
158
  7.times do
110
159
  processed_source = ERBLint::ProcessedSource.new(filename, file_content)
111
160
  runner.run(processed_source)
@@ -127,6 +176,11 @@ module ERBLint
127
176
  file_content = corrector.corrected_content
128
177
  runner.clear_offenses
129
178
  end
179
+
180
+ file_content
181
+ end
182
+
183
+ def log_offense_stats(runner, filename)
130
184
  offenses_filename = relative_filename(filename)
131
185
  offenses = runner.offenses || []
132
186
 
@@ -138,8 +192,6 @@ module ERBLint
138
192
 
139
193
  @stats.processed_files[offenses_filename] ||= []
140
194
  @stats.processed_files[offenses_filename] |= offenses
141
-
142
- file_content
143
195
  end
144
196
 
145
197
  def read_content(filename)
@@ -266,7 +318,7 @@ module ERBLint
266
318
  end
267
319
  end
268
320
 
269
- opts.on("--format FORMAT", format_options_help) do |format|
321
+ opts.on("-f", "--format FORMAT", format_options_help) do |format|
270
322
  unless Reporter.available_format?(format)
271
323
  error_message = invalid_format_error_message(format)
272
324
  failure!(error_message)
@@ -283,6 +335,18 @@ module ERBLint
283
335
  @options[:enabled_linters] = known_linter_names
284
336
  end
285
337
 
338
+ opts.on("--cache", "Enable caching") do |config|
339
+ @options[:cache] = config
340
+ end
341
+
342
+ opts.on("--cache-dir DIR", "Set the cache directory") do |dir|
343
+ @options[:cache_dir] = dir
344
+ end
345
+
346
+ opts.on("--clear-cache", "Clear cache") do |config|
347
+ @options[:clear_cache] = config
348
+ end
349
+
286
350
  opts.on("--enable-linters LINTER[,LINTER,...]", Array,
287
351
  "Only use specified linter", "Known linters are: #{known_linter_names.join(", ")}") do |linters|
288
352
  linters.each do |linter|
@@ -30,7 +30,7 @@ module ERBLint
30
30
  end
31
31
  end
32
32
 
33
- attr_reader :offenses
33
+ attr_reader :offenses, :config
34
34
 
35
35
  # Must be implemented by the concrete inheriting class.
36
36
  def initialize(file_loader, config)
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ module Linters
5
+ # Detects comment syntax that isn't valid ERB.
6
+ class CommentSyntax < Linter
7
+ include LinterRegistry
8
+
9
+ def initialize(file_loader, config)
10
+ super
11
+ end
12
+
13
+ def run(processed_source)
14
+ file_content = processed_source.file_content
15
+ return if file_content.empty?
16
+
17
+ processed_source.ast.descendants(:erb).each do |erb_node|
18
+ indicator_node, _, code_node, _ = *erb_node
19
+ next if code_node.nil?
20
+
21
+ indicator_node_str = indicator_node&.deconstruct&.last
22
+ next if indicator_node_str == "#"
23
+
24
+ code_node_str = code_node.deconstruct.last
25
+ next unless code_node_str.start_with?(" #")
26
+
27
+ range = find_range(erb_node, code_node_str)
28
+ source_range = processed_source.to_source_range(range)
29
+
30
+ correct_erb_tag = indicator_node_str == "=" ? "<%#=" : "<%#"
31
+
32
+ add_offense(
33
+ source_range,
34
+ <<~EOF.chomp
35
+ Bad ERB comment syntax. Should be #{correct_erb_tag} without a space between.
36
+ Leaving a space between ERB tags and the Ruby comment character can cause parser errors.
37
+ EOF
38
+ )
39
+ end
40
+ end
41
+
42
+ def find_range(node, str)
43
+ match = node.loc.source.match(Regexp.new(Regexp.quote(str.strip)))
44
+ return unless match
45
+
46
+ range_begin = match.begin(0) + node.loc.begin_pos
47
+ range_end = match.end(0) + node.loc.begin_pos
48
+ (range_begin...range_end)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -131,11 +131,17 @@ module ERBLint
131
131
  end
132
132
 
133
133
  def rubocop_processed_source(content, filename)
134
- ::RuboCop::ProcessedSource.new(
134
+ source = ::RuboCop::ProcessedSource.new(
135
135
  content,
136
136
  @rubocop_config.target_ruby_version,
137
137
  filename
138
138
  )
139
+ if ::RuboCop::Version::STRING.to_f >= 1.38
140
+ registry = RuboCop::Cop::Registry.global
141
+ source.registry = registry
142
+ source.config = @rubocop_config
143
+ end
144
+ source
139
145
  end
140
146
 
141
147
  def cop_classes
@@ -17,6 +17,10 @@ module ERBLint
17
17
  @severity = severity
18
18
  end
19
19
 
20
+ def to_cached_offense_hash
21
+ ERBLint::CachedOffense.new_from_offense(self).to_h
22
+ end
23
+
20
24
  def inspect
21
25
  "#<#{self.class.name} linter=#{linter.class.name} "\
22
26
  "source_range=#{source_range.begin_pos}...#{source_range.end_pos} "\
@@ -43,5 +47,21 @@ module ERBLint
43
47
  def column
44
48
  source_range.column
45
49
  end
50
+
51
+ def simple_name
52
+ linter.class.simple_name
53
+ end
54
+
55
+ def last_line
56
+ source_range.last_line
57
+ end
58
+
59
+ def last_column
60
+ source_range.last_column
61
+ end
62
+
63
+ def length
64
+ source_range.length
65
+ end
46
66
  end
47
67
  end
@@ -56,14 +56,14 @@ module ERBLint
56
56
 
57
57
  def format_offense(offense)
58
58
  {
59
- linter: offense.linter.class.simple_name,
59
+ linter: offense.simple_name,
60
60
  message: offense.message.to_s,
61
61
  location: {
62
62
  start_line: offense.line_number,
63
63
  start_column: offense.column,
64
- last_line: offense.source_range.last_line,
65
- last_column: offense.source_range.last_column,
66
- length: offense.source_range.length,
64
+ last_line: offense.last_line,
65
+ last_column: offense.last_column,
66
+ length: offense.length,
67
67
  },
68
68
  }
69
69
  end
@@ -30,5 +30,9 @@ module ERBLint
30
30
  @offenses = []
31
31
  @linters.each(&:clear_offenses)
32
32
  end
33
+
34
+ def restore_offenses(offenses)
35
+ @offenses.concat(offenses)
36
+ end
33
37
  end
34
38
  end
@@ -63,6 +63,7 @@ module ERBLint
63
63
  SpaceInHtmlTag: { enabled: default_enabled },
64
64
  TrailingWhitespace: { enabled: default_enabled },
65
65
  RequireInputAutocomplete: { enabled: default_enabled },
66
+ CommentSyntax: { enabled: default_enabled },
66
67
  },
67
68
  )
68
69
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ERBLint
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
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.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Chan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-18 00:00:00.000000000 Z
11
+ date: 2022-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -147,6 +147,8 @@ files:
147
147
  - exe/erblint
148
148
  - lib/erb_lint.rb
149
149
  - lib/erb_lint/all.rb
150
+ - lib/erb_lint/cache.rb
151
+ - lib/erb_lint/cached_offense.rb
150
152
  - lib/erb_lint/cli.rb
151
153
  - lib/erb_lint/corrector.rb
152
154
  - lib/erb_lint/file_loader.rb
@@ -155,6 +157,7 @@ files:
155
157
  - lib/erb_lint/linter_registry.rb
156
158
  - lib/erb_lint/linters/allowed_script_type.rb
157
159
  - lib/erb_lint/linters/closing_erb_tag_indent.rb
160
+ - lib/erb_lint/linters/comment_syntax.rb
158
161
  - lib/erb_lint/linters/deprecated_classes.rb
159
162
  - lib/erb_lint/linters/erb_safety.rb
160
163
  - lib/erb_lint/linters/extra_newline.rb