erb_lint 0.1.3 → 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: 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
  - - ">="