erb_lint 0.1.3 → 0.3.0

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: 84ee5b7d05405468648ea7ea174c2f8f57ec1e56490873e1e89e1e9f618df585
4
- data.tar.gz: 3f7db38955673a6218517c8a031c97a9aed5a03fe8a1a9ed11c631cc8aa920f7
3
+ metadata.gz: cbd70ba43608a99a550faaf48bff18ab2f31931810504ca9f16a45e6645530e8
4
+ data.tar.gz: c7460b7c72b7cd50b77d5b4a14de71ea0379802bc0730b7b6b52c0cc20645536
5
5
  SHA512:
6
- metadata.gz: e466a9178358d9400fa7a38cda19d1263efecaa1729391b1eea5c9dae2cf928686f97c5d84a0df85ba6774465f415ef94ec628cda8cc38834f2983fe525f41e7
7
- data.tar.gz: 1f7cc9b5a8994d9378f70085d69e0012baa78c0da7896b366dfd32f894f05b4c3978c831eab31f6b209c9c4a42bfe36dcf0bb413a872a91a2bb22bfc79effc16
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,12 +30,33 @@ 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
- failure!("no files found...\n")
55
+ if allow_no_files?
56
+ success!("no files found...\n")
57
+ else
58
+ failure!("no files found...\n")
59
+ end
39
60
  elsif lint_files.empty?
40
61
  failure!("no files found or given, specify files or config...\n#{option_parser}")
41
62
  end
@@ -61,7 +82,7 @@ module ERBLint
61
82
  lint_files.each do |filename|
62
83
  runner.clear_offenses
63
84
  begin
64
- file_content = run_with_corrections(runner, filename)
85
+ file_content = run_on_file(runner, filename)
65
86
  rescue => e
66
87
  @stats.exceptions += 1
67
88
  puts "Exception occurred when processing: #{relative_filename(filename)}"
@@ -73,6 +94,8 @@ module ERBLint
73
94
  end
74
95
  end
75
96
 
97
+ cache&.close
98
+
76
99
  reporter.show
77
100
 
78
101
  if stdin? && autocorrect?
@@ -95,13 +118,43 @@ module ERBLint
95
118
 
96
119
  private
97
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
+
98
145
  def autocorrect?
99
146
  @options[:autocorrect]
100
147
  end
101
148
 
102
- def run_with_corrections(runner, filename)
103
- file_content = read_content(filename)
149
+ def cache?
150
+ @options[:cache]
151
+ end
152
+
153
+ def clear_cache?
154
+ @options[:clear_cache]
155
+ end
104
156
 
157
+ def run_with_corrections(runner, filename, file_content)
105
158
  7.times do
106
159
  processed_source = ERBLint::ProcessedSource.new(filename, file_content)
107
160
  runner.run(processed_source)
@@ -123,6 +176,11 @@ module ERBLint
123
176
  file_content = corrector.corrected_content
124
177
  runner.clear_offenses
125
178
  end
179
+
180
+ file_content
181
+ end
182
+
183
+ def log_offense_stats(runner, filename)
126
184
  offenses_filename = relative_filename(filename)
127
185
  offenses = runner.offenses || []
128
186
 
@@ -134,8 +192,6 @@ module ERBLint
134
192
 
135
193
  @stats.processed_files[offenses_filename] ||= []
136
194
  @stats.processed_files[offenses_filename] |= offenses
137
-
138
- file_content
139
195
  end
140
196
 
141
197
  def read_content(filename)
@@ -262,7 +318,7 @@ module ERBLint
262
318
  end
263
319
  end
264
320
 
265
- opts.on("--format FORMAT", format_options_help) do |format|
321
+ opts.on("-f", "--format FORMAT", format_options_help) do |format|
266
322
  unless Reporter.available_format?(format)
267
323
  error_message = invalid_format_error_message(format)
268
324
  failure!(error_message)
@@ -279,6 +335,18 @@ module ERBLint
279
335
  @options[:enabled_linters] = known_linter_names
280
336
  end
281
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
+
282
350
  opts.on("--enable-linters LINTER[,LINTER,...]", Array,
283
351
  "Only use specified linter", "Known linters are: #{known_linter_names.join(", ")}") do |linters|
284
352
  linters.each do |linter|
@@ -302,6 +370,10 @@ module ERBLint
302
370
  @options[:autocorrect] = config
303
371
  end
304
372
 
373
+ opts.on("--allow-no-files", "When no matching files found, exit successfully (default: false)") do |config|
374
+ @options[:allow_no_files] = config
375
+ end
376
+
305
377
  opts.on(
306
378
  "-sFILE",
307
379
  "--stdin FILE",
@@ -333,5 +405,9 @@ module ERBLint
333
405
  def stdin?
334
406
  @options[:stdin].present?
335
407
  end
408
+
409
+ def allow_no_files?
410
+ @options[:allow_no_files]
411
+ end
336
412
  end
337
413
  end
@@ -12,7 +12,7 @@ module ERBLint
12
12
 
13
13
  def corrections
14
14
  @corrections ||= @offenses.map do |offense|
15
- offense.linter.autocorrect(@processed_source, offense)
15
+ offense.linter.autocorrect(@processed_source, offense) if offense.linter.class.support_autocorrect?
16
16
  end.compact
17
17
  end
18
18
 
@@ -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
@@ -13,6 +13,7 @@ module ERBLint
13
13
  class ConfigSchema < LinterConfig
14
14
  property :only, accepts: array_of?(String)
15
15
  property :rubocop_config, accepts: Hash, default: -> { {} }
16
+ property :config_file_path, accepts: String
16
17
  end
17
18
 
18
19
  self.config_schema = ConfigSchema
@@ -24,7 +25,8 @@ module ERBLint
24
25
  def initialize(file_loader, config)
25
26
  super
26
27
  @only_cops = @config.only
27
- custom_config = config_from_hash(@config.rubocop_config)
28
+ custom_config = config_from_path(@config.config_file_path) if @config.config_file_path
29
+ custom_config ||= config_from_hash(@config.rubocop_config)
28
30
  @rubocop_config = ::RuboCop::ConfigLoader.merge_with_default(custom_config, "")
29
31
  end
30
32
 
@@ -158,34 +160,13 @@ module ERBLint
158
160
  end
159
161
 
160
162
  def config_from_hash(hash)
161
- inherit_from = hash&.delete("inherit_from")
162
- resolve_inheritance(hash, inherit_from)
163
-
164
163
  tempfile_from(".erblint-rubocop", hash.to_yaml) do |tempfile|
165
- ::RuboCop::ConfigLoader.load_file(tempfile.path)
166
- end
167
- end
168
-
169
- def resolve_inheritance(hash, inherit_from)
170
- base_configs(inherit_from)
171
- .reverse_each do |base_config|
172
- base_config.each do |k, v|
173
- hash[k] = hash.key?(k) ? ::RuboCop::ConfigLoader.merge(v, hash[k]) : v if v.is_a?(Hash)
174
- end
164
+ config_from_path(tempfile.path)
175
165
  end
176
166
  end
177
167
 
178
- def base_configs(inherit_from)
179
- regex = URI::DEFAULT_PARSER.make_regexp(["http", "https"])
180
- configs = Array(inherit_from).compact.map do |base_name|
181
- if base_name =~ /\A#{regex}\z/
182
- ::RuboCop::ConfigLoader.load_file(::RuboCop::RemoteConfig.new(base_name, Dir.pwd))
183
- else
184
- config_from_hash(@file_loader.yaml(base_name))
185
- end
186
- end
187
-
188
- configs.compact
168
+ def config_from_path(path)
169
+ ::RuboCop::ConfigLoader.load_file(path)
189
170
  end
190
171
 
191
172
  def add_offense(rubocop_offense, offense_range, correction, offset, bound_range)
@@ -10,6 +10,7 @@ module ERBLint
10
10
  class ConfigSchema < LinterConfig
11
11
  property :only, accepts: array_of?(String)
12
12
  property :rubocop_config, accepts: Hash
13
+ property :config_file_path, accepts: String
13
14
  end
14
15
 
15
16
  self.config_schema = ConfigSchema
@@ -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.1.3"
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.1.3
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-06-16 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
@@ -26,32 +26,18 @@ dependencies:
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: better_html
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 1.0.7
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 1.0.7
41
- - !ruby/object:Gem::Dependency
42
- name: html_tokenizer
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - ">="
46
32
  - !ruby/object:Gem::Version
47
- version: '0'
33
+ version: 2.0.1
48
34
  type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - ">="
53
39
  - !ruby/object:Gem::Version
54
- version: '0'
40
+ version: 2.0.1
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: parser
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -161,6 +147,8 @@ files:
161
147
  - exe/erblint
162
148
  - lib/erb_lint.rb
163
149
  - lib/erb_lint/all.rb
150
+ - lib/erb_lint/cache.rb
151
+ - lib/erb_lint/cached_offense.rb
164
152
  - lib/erb_lint/cli.rb
165
153
  - lib/erb_lint/corrector.rb
166
154
  - lib/erb_lint/file_loader.rb
@@ -169,6 +157,7 @@ files:
169
157
  - lib/erb_lint/linter_registry.rb
170
158
  - lib/erb_lint/linters/allowed_script_type.rb
171
159
  - lib/erb_lint/linters/closing_erb_tag_indent.rb
160
+ - lib/erb_lint/linters/comment_syntax.rb
172
161
  - lib/erb_lint/linters/deprecated_classes.rb
173
162
  - lib/erb_lint/linters/erb_safety.rb
174
163
  - lib/erb_lint/linters/extra_newline.rb
@@ -215,7 +204,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
215
204
  requirements:
216
205
  - - ">="
217
206
  - !ruby/object:Gem::Version
218
- version: 2.5.0
207
+ version: 2.7.0
219
208
  required_rubygems_version: !ruby/object:Gem::Requirement
220
209
  requirements:
221
210
  - - ">="