erb_lint 0.2.0 → 0.3.0

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
  SHA256:
3
- metadata.gz: f417b343f6b0b891bd095ba568b2d50e78bb7d619b637b3bf2d70f2607f990cc
4
- data.tar.gz: 1c080ab44d8238ad32138babe65076626e82974a96a13a1f4041bf7b9568b6ec
3
+ metadata.gz: cbd70ba43608a99a550faaf48bff18ab2f31931810504ca9f16a45e6645530e8
4
+ data.tar.gz: c7460b7c72b7cd50b77d5b4a14de71ea0379802bc0730b7b6b52c0cc20645536
5
5
  SHA512:
6
- metadata.gz: 407d4f4813eaef3b2247357e0af4373cf7511bd8162c5706bc951af05e8577a9584bd5d3dbd00d6dba98bb62aa9d5d58a36a2e484553f7e9ade6af3f95803881
7
- data.tar.gz: 7e5fb9bdb8c0cd455279ef3a1c46df75704f6f53eea0ab2eb37b4ff2c5e8292a6ced137b0fbeb6e89c2382d5f1be3220d0e12c4889127cb6d430578f2dbe8945
6
+ metadata.gz: 2130fdbaaea0d24ae83a3cff2ac19af4b7b7720d3266190312d193f1379e51e210c432376ad5c242b064982374fd1fb8efe84baef414c3784401f018826c4569
7
+ data.tar.gz: c542ce48834276542f21d4a492fc22f012cd14451fde9f87395ad5f46060355a92467f1b4a27efd10ebd120001783af3861051e461cb1e230d52a4fa7262dbf0
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
@@ -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.0"
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.0
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-01 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