haml_lint 0.62.0 → 0.64.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: 3754327462146047a8677d8e91b7d47e3d538f2459f2d3ebd583ad2dafc92c3b
4
- data.tar.gz: 33ccb58e67c32798c90da242cc8708e763f93a1509c6083d54b351453f587a5f
3
+ metadata.gz: 73a515adb6ca4915d07c747133e7a657e04bd8d6a5c41cf5cd63a0adf3e03316
4
+ data.tar.gz: efb5d688d181d7906a28bef3f25b1080e1e38e2461d9343db554ed5a98037296
5
5
  SHA512:
6
- metadata.gz: 8a9ece6eda4dac35772948661486fc6c4fada9d3071b8b4ff52d37bab8930d032d0138a95f59a1a54c760951001010102317965e11aea2fd6dec4a35c7528aac
7
- data.tar.gz: 67d13e3d045dd804f7f93c662dd8dfb833ac8104a2c52bd1699ba9201861630e686228de725ede55d469a9b3c15a02fcf0b910b843c3d425314e59721ae01ba0
6
+ metadata.gz: 7a6c42a2be6709314fb8bff476e8a0ff80f0054895eeaa795b8fd741039a580bfb6ced6b0a45b293cdbe0641676a412909363b99f3638a5f8f4f5dd910de736a
7
+ data.tar.gz: 7165be1ef28719e4a08ad60a26aa6d0a8f8e1ca0507c5376cbfa776e813320fbb3fbd4357c7d74809393cd9c676ca009bab3d8d36b48770de7ffda4c680ed3a2
@@ -14,6 +14,9 @@ module HamlLint
14
14
  # @return [String] Haml template file path
15
15
  attr_reader :file
16
16
 
17
+ # @return [Boolean] true if source was read directly from `file` on-disk (rather than from stdin)
18
+ attr_reader :file_on_disk
19
+
17
20
  # @return [Boolean] true if source changes (from autocorrect) should be written to stdout instead of disk
18
21
  attr_reader :write_to_stdout
19
22
 
@@ -39,12 +42,14 @@ module HamlLint
39
42
  # @param source [String] Haml code to parse
40
43
  # @param options [Hash]
41
44
  # @option options :file [String] file name of document that was parsed
45
+ # @option options :file_on_disk [Boolean] true if source was read straight from `file` on disk
42
46
  # @option options :write_to_stdout [Boolean] true if source changes should be written to stdout
43
47
  # @raise [Haml::Parser::Error] if there was a problem parsing the document
44
48
  def initialize(source, options)
45
49
  @config = options[:config]
46
50
  @file = options.fetch(:file, STRING_SOURCE)
47
51
  @write_to_stdout = options[:write_to_stdout]
52
+ @file_on_disk = options[:file_on_disk] && @file != STRING_SOURCE
48
53
  @source_was_changed = false
49
54
  process_source(source)
50
55
  end
@@ -16,6 +16,46 @@ module HamlLint
16
16
  #
17
17
  # The work is spread across the classes in the HamlLint::RubyExtraction module.
18
18
  class Linter::RuboCop < Linter
19
+ # Processes a ruby file and reports RuboCop offenses
20
+ class Runner < ::RuboCop::Runner
21
+ attr_reader :offenses
22
+
23
+ def run(haml_path, ruby_code, config:, allow_cache: false)
24
+ @allow_cache = allow_cache
25
+ @offenses = []
26
+ @config_store.instance_variable_set(:@options_config, config)
27
+ @options[:stdin] = ruby_code
28
+ super([haml_path])
29
+ end
30
+
31
+ def corrected_code
32
+ @options[:stdin]
33
+ end
34
+
35
+ # Executed when a file has been scanned by RuboCop, adding the reported
36
+ # offenses to our collection.
37
+ #
38
+ # @param _file [String]
39
+ # @param offenses [Array<RuboCop::Cop::Offense>]
40
+ def file_finished(_file, offenses)
41
+ @offenses = offenses
42
+ end
43
+
44
+ # RuboCop caches results by taking a hash of the file contents & path, among other things.
45
+ # It disables its cache when working on file-content from stdin.
46
+ # Unfortunately we always use RuboCop's stdin, even when we're linting a file on-disk.
47
+ # So, override RuboCop::Runner#cached_run? so that it'll allow caching results, so long
48
+ # as haml-lint itself isn't being invoked with files on stdin.
49
+ def cached_run?
50
+ return false unless @allow_cache
51
+
52
+ @cached_run ||=
53
+ (@options[:cache] == 'true' ||
54
+ (@options[:cache] != 'false' && @config_store.for_pwd.for_all_cops['UseCache'])) &&
55
+ !@options[:auto_gen_config]
56
+ end
57
+ end
58
+
19
59
  include LinterRegistry
20
60
 
21
61
  supports_autocorrect(true)
@@ -98,9 +138,9 @@ module HamlLint
98
138
 
99
139
  def rubocop_config_for(path)
100
140
  user_config_path = ENV['HAML_LINT_RUBOCOP_CONF'] || config['config_file']
101
- user_config_path ||= self.class.rubocop_config_store.user_rubocop_config_path_for(path)
141
+ user_config_path ||= rubocop_config_store.user_rubocop_config_path_for(path)
102
142
  user_config_path = File.absolute_path(user_config_path)
103
- self.class.rubocop_config_store.config_object_pointing_to(user_config_path)
143
+ rubocop_config_store.config_object_pointing_to(user_config_path)
104
144
  end
105
145
 
106
146
  # Extracted here so that tests can stub this to always return true
@@ -168,15 +208,13 @@ module HamlLint
168
208
  false
169
209
  end
170
210
 
171
- # A single CLI instance is shared between files to avoid RuboCop
211
+ # A single RuboCop runner is shared between files to avoid RuboCop
172
212
  # having to repeatedly reload .rubocop.yml.
173
- def self.rubocop_cli # rubocop:disable Lint/IneffectiveAccessModifier
174
- # The ivar is stored on the class singleton rather than the Linter instance
175
- # because it can't be Marshal.dump'd (as used by Parallel.map)
176
- @rubocop_cli ||= ::RuboCop::CLI.new
213
+ def rubocop_runner
214
+ @rubocop_runner ||= Runner.new(rubocop_options, ::RuboCop::ConfigStore.new)
177
215
  end
178
216
 
179
- def self.rubocop_config_store # rubocop:disable Lint/IneffectiveAccessModifier
217
+ def rubocop_config_store
180
218
  @rubocop_config_store ||= RubocopConfigStore.new
181
219
  end
182
220
 
@@ -190,7 +228,7 @@ module HamlLint
190
228
  def process_ruby_source(ruby_code, source_map)
191
229
  filename = document.file || 'ruby_script.rb'
192
230
 
193
- offenses, corrected_ruby = run_rubocop(self.class.rubocop_cli, ruby_code, filename)
231
+ offenses, corrected_ruby = run_rubocop(rubocop_runner, ruby_code, filename)
194
232
 
195
233
  extract_lints_from_offenses(offenses, source_map)
196
234
  corrected_ruby
@@ -199,55 +237,35 @@ module HamlLint
199
237
  # Runs RuboCop, returning the offenses and corrected code. Raises when RuboCop
200
238
  # fails to run correctly.
201
239
  #
202
- # @param rubocop_cli [RuboCop::CLI] There to simplify tests by using a stub
240
+ # @param rubocop_runner [HamlLint::Linter::RuboCop::Runner] There to simplify tests by using a stub
203
241
  # @param ruby_code [String] The ruby code to run through RuboCop
204
242
  # @param path [String] the path to tell RuboCop we are running
205
243
  # @return [Array<RuboCop::Cop::Offense>, String]
206
- def run_rubocop(rubocop_cli, ruby_code, path) # rubocop:disable Metrics
207
- rubocop_status = nil
208
- stdout_str, stderr_str = HamlLint::Utils.with_captured_streams(ruby_code) do
209
- rubocop_cli.config_store.instance_variable_set(:@options_config, rubocop_config_for(path))
210
- rubocop_status = rubocop_cli.run(rubocop_flags + ['--stdin', path])
211
- end
244
+ def run_rubocop(rubocop_runner, ruby_code, path) # rubocop:disable Metrics
245
+ rubocop_runner.run(path, ruby_code, config: rubocop_config_for(path), allow_cache: @document&.file_on_disk)
212
246
 
213
247
  if ENV['HAML_LINT_INTERNAL_DEBUG'] == 'true'
214
- if OffenseCollector.offenses.empty?
248
+ if rubocop_runner.offenses.empty?
215
249
  puts "------ No lints found by RuboCop in #{@document.file}"
216
250
  else
217
251
  puts "------ Raw lints found by RuboCop in #{@document.file}"
218
- OffenseCollector.offenses.each do |offense|
252
+ rubocop_runner.offenses.each do |offense|
219
253
  puts offense
220
254
  end
221
255
  puts '------'
222
256
  end
223
257
  end
224
258
 
225
- unless [::RuboCop::CLI::STATUS_SUCCESS, ::RuboCop::CLI::STATUS_OFFENSES].include?(rubocop_status)
226
- if stderr_str.start_with?('Infinite loop')
227
- msg = "RuboCop exited unsuccessfully with status #{rubocop_status}." \
228
- ' This appears to be due to an autocorrection infinite loop.'
229
- if ENV['HAML_LINT_DEBUG'] == 'true'
230
- msg += " DEBUG: RuboCop's output:\n"
231
- msg += stderr_str.strip
232
- else
233
- msg += " First line of RuboCop's output (Use --debug mode to see more):\n"
234
- msg += stderr_str.each_line.first.strip
235
- end
236
-
237
- raise HamlLint::Exceptions::InfiniteLoopError, msg
238
- end
239
-
240
- raise HamlLint::Exceptions::ConfigurationError,
241
- "RuboCop exited unsuccessfully with status #{rubocop_status}." \
242
- ' Here is its output to check the stack trace or see if there was' \
243
- " a misconfiguration:\n#{stderr_str}"
244
- end
245
-
246
259
  if @autocorrect
247
- corrected_ruby = stdout_str.partition("#{'=' * 20}\n").last
260
+ corrected_ruby = rubocop_runner.corrected_code
248
261
  end
249
262
 
250
- [OffenseCollector.offenses, corrected_ruby]
263
+ [rubocop_runner.offenses, corrected_ruby]
264
+ rescue ::RuboCop::Error => e
265
+ raise HamlLint::Exceptions::ConfigurationError,
266
+ "RuboCop raised #{e}." \
267
+ ' Here is its output to check the stack trace or see if there was' \
268
+ " a misconfiguration:\n#{e.message}\n#{e.backtrace}"
251
269
  end
252
270
 
253
271
  # Aggregates RuboCop offenses and converts them to {HamlLint::Lint}s
@@ -294,14 +312,27 @@ module HamlLint
294
312
  corrected: corrected)
295
313
  end
296
314
 
297
- # Returns flags that will be passed to RuboCop CLI.
315
+ # rubocop:disable Style/MutableConstant
316
+ # Using BaseFormatter suppresses any default output
317
+ DEFAULT_FLAGS = %w[--format RuboCop::Formatter::BaseFormatter]
318
+ begin
319
+ ::RuboCop::Options.new.parse(['--raise-cop-error'])
320
+ DEFAULT_FLAGS << '--raise-cop-error'
321
+ rescue OptionParser::InvalidOption
322
+ # older versions of RuboCop don't support this flag
323
+ end
324
+ DEFAULT_FLAGS.freeze
325
+ # rubocop:enable Style/MutableConstant
326
+
327
+ # Returns options that will be passed to the RuboCop runner.
298
328
  #
299
- # @return [Array<String>]
300
- def rubocop_flags
301
- flags = %w[--format HamlLint::OffenseCollector]
329
+ # @return [Hash]
330
+ def rubocop_options
331
+ flags = DEFAULT_FLAGS
302
332
  flags += ignored_cops_flags
303
333
  flags += rubocop_autocorrect_flags
304
- flags
334
+ options, _args = ::RuboCop::Options.new.parse(flags)
335
+ options
305
336
  end
306
337
 
307
338
  def rubocop_autocorrect_flags
@@ -342,29 +373,19 @@ module HamlLint
342
373
  return [] if ignored_cops.empty?
343
374
  ['--except', ignored_cops.uniq.join(',')]
344
375
  end
345
- end
346
376
 
347
- # Collects offenses detected by RuboCop.
348
- class OffenseCollector < ::RuboCop::Formatter::BaseFormatter
349
- class << self
350
- # List of offenses reported by RuboCop.
351
- attr_accessor :offenses
352
- end
353
-
354
- # Executed when RuboCop begins linting.
355
- #
356
- # @param _target_files [Array<String>]
357
- def started(_target_files)
358
- self.class.offenses = []
377
+ # Exclude ivars that don't marshal properly
378
+ def marshal_dump
379
+ excluded_ivars = %i[@rubocop_runner @rubocop_config_store @user_config_path_to_config_object]
380
+ (instance_variables - excluded_ivars).to_h do |ivar|
381
+ [ivar, instance_variable_get(ivar)]
382
+ end
359
383
  end
360
384
 
361
- # Executed when a file has been scanned by RuboCop, adding the reported
362
- # offenses to our collection.
363
- #
364
- # @param _file [String]
365
- # @param offenses [Array<RuboCop::Cop::Offense>]
366
- def file_finished(_file, offenses)
367
- self.class.offenses += offenses
385
+ def marshal_load(ivars)
386
+ ivars.each do |k, v|
387
+ instance_variable_set(k, v)
388
+ end
368
389
  end
369
390
  end
370
391
 
@@ -89,6 +89,7 @@ module HamlLint
89
89
  begin
90
90
  document = HamlLint::Document.new source.contents, file: source.path,
91
91
  config: config,
92
+ file_on_disk: !source.stdin?,
92
93
  write_to_stdout: @autocorrect_stdout
93
94
  rescue HamlLint::Exceptions::ParseError => e
94
95
  return [HamlLint::Lint.new(HamlLint::Linter::Syntax.new(config), source.path,
@@ -20,5 +20,10 @@ module HamlLint
20
20
  def contents
21
21
  @contents ||= @io&.read || File.read(path)
22
22
  end
23
+
24
+ # @return [boolean] true if we're reading from stdin rather than a file path
25
+ def stdin?
26
+ !@io.nil?
27
+ end
23
28
  end
24
29
  end
@@ -249,34 +249,6 @@ module HamlLint
249
249
  $!
250
250
  end
251
251
 
252
- # Overrides the global stdin, stdout and stderr while within the block, to
253
- # push a string in stdin, and capture both stdout and stderr which are returned.
254
- #
255
- # @param stdin_str [String] the string to push in as stdin
256
- # @param _block [Block] the block to perform with the overridden std streams
257
- # @return [String, String]
258
- def with_captured_streams(stdin_str, &_block)
259
- original_stdin = $stdin
260
- # The dup is needed so that stdin_data isn't altered (encoding-wise at least)
261
- $stdin = StringIO.new(stdin_str.dup)
262
- begin
263
- original_stdout = $stdout
264
- $stdout = StringIO.new
265
- begin
266
- original_stderr = $stderr
267
- $stderr = StringIO.new
268
- yield
269
- [$stdout.string, $stderr.string]
270
- ensure
271
- $stderr = original_stderr
272
- end
273
- ensure
274
- $stdout = original_stdout
275
- end
276
- ensure
277
- $stdin = original_stdin
278
- end
279
-
280
252
  def regexp_for_parts(parts, join_regexp, prefix: nil, suffix: nil)
281
253
  regexp_code = parts.map { |c| Regexp.quote(c) }.join(join_regexp)
282
254
  regexp_code = "#{prefix}#{regexp_code}#{suffix}"
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the gem version.
4
4
  module HamlLint
5
- VERSION = '0.62.0'
5
+ VERSION = '0.64.0'
6
6
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haml_lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.62.0
4
+ version: 0.64.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane da Silva
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-08 00:00:00.000000000 Z
10
+ date: 2025-06-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: haml