rubocop-minitest 0.39.1 → 0.40.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: 105c20646b59d61d5b7782e8acd5eb2af261c82e2e570a45476c18aac021a582
4
- data.tar.gz: 899dff52feb335ffe772a5271de2d809034aaf851f26ae6da5e467e6040a4125
3
+ metadata.gz: eda17cda5caf90c2cf74b9f760a0ba38d1cf1c9e5d73c16c450dc4b905bb0105
4
+ data.tar.gz: a623715c3491d9eaa408842b98f790cc95c761d75fe5c42e0a9bb8b32c0730bf
5
5
  SHA512:
6
- metadata.gz: dd0e8fdbb2be6cc4b6987a4e4c093f68325cafcf813bdf92b4b7ab5cbbea6dce9c33549806240e7bac9c8c259767ac7024e19bc32eaa5aca1e89f99a96c56dac
7
- data.tar.gz: e0a3dd6040a6b7385d4057a62b2be39ada32de00e5e42014df2294b9a4bfb2d1633738abce251943e6948cc6391e24c3fab2fb69b9652a12c51744aa35dfa220
6
+ metadata.gz: ea451f9315708de691d4fec8a1190f7c3ad20c1f601ea784f05d20289d43cbec4f6471ef257ed0ef63341c20c8abac9f990db749a7faa1b12af6c5fe6725904d
7
+ data.tar.gz: e8404442a84c387f85c38d270a8312d2f0e0b37105cabf9bd0b5a25d3ac9bf3d48267659b25c4432f7ef335315f1fc3df585c1223ec9acc16c35e541fdc858fb
data/README.md CHANGED
@@ -78,6 +78,96 @@ Minitest/AssertNil:
78
78
  - test/my_file_to_ignore_test.rb
79
79
  ```
80
80
 
81
+ ## Testing your own cops
82
+
83
+ This gem provides `RuboCop::TestCase`, a base test case class for testing custom cops with Minitest.
84
+ Require `rubocop/minitest/support` in your test helper, then inherit from `RuboCop::TestCase` to get
85
+ the `assert_offense`, `assert_correction`, `assert_no_corrections`, and `assert_no_offenses` assertions:
86
+
87
+ ```ruby
88
+ require 'rubocop/minitest/support'
89
+
90
+ module CustomCops
91
+ class MyCopTest < RuboCop::TestCase
92
+ def test_registers_offense
93
+ assert_offense(<<~RUBY)
94
+ bad_method
95
+ ^^^^^^^^^^ Use `good_method` instead of `bad_method`.
96
+ RUBY
97
+
98
+ assert_correction(<<~RUBY)
99
+ good_method
100
+ RUBY
101
+ end
102
+
103
+ def test_does_not_register_offense
104
+ assert_no_offenses(<<~RUBY)
105
+ good_method
106
+ RUBY
107
+ end
108
+ end
109
+ end
110
+ ```
111
+
112
+ The cop under test is derived from the test class name: `CustomCops::MyCopTest` resolves
113
+ `CustomCops::MyCop`, then `RuboCop::Cop::Minitest::MyCop`. Define a `cop_class` method in
114
+ the test class to specify the cop explicitly:
115
+
116
+ ```ruby
117
+ class MyCopTest < RuboCop::TestCase
118
+ private
119
+
120
+ def cop_class
121
+ CustomCops::MyCop
122
+ end
123
+ end
124
+ ```
125
+
126
+ The configuration for the cop under test and the target Ruby version can be specified
127
+ by overriding `cop_config`, `other_cops`, or `target_ruby_version` in the test class:
128
+
129
+ ```ruby
130
+ class MyCopTest < RuboCop::TestCase
131
+ private
132
+
133
+ def cop_config
134
+ { 'Max' => 1 }
135
+ end
136
+
137
+ def target_ruby_version
138
+ 3.0
139
+ end
140
+ end
141
+ ```
142
+
143
+ To change them for a single test, assign them in the test method. This makes it
144
+ possible for tests targeting different Ruby versions to live in the same test class:
145
+
146
+ ```ruby
147
+ class MyCopTest < RuboCop::TestCase
148
+ def test_registers_offense_on_ruby31
149
+ self.target_ruby_version = 3.1
150
+
151
+ assert_offense(<<~RUBY)
152
+ bad_method
153
+ ^^^^^^^^^^ Use `good_method` instead of `bad_method`.
154
+ RUBY
155
+ end
156
+
157
+ def test_does_not_register_offense_on_ruby30
158
+ self.target_ruby_version = 3.0
159
+
160
+ assert_no_offenses(<<~RUBY)
161
+ bad_method
162
+ RUBY
163
+ end
164
+ end
165
+ ```
166
+
167
+ The default configuration of extensions registered as [lint_roller](https://github.com/standardrb/lint_roller) plugins
168
+ is applied automatically. For other extensions, call `RuboCop::ConfigLoader.inject_defaults!('path/to/default.yml')`
169
+ in your test helper.
170
+
81
171
  ## Documentation
82
172
 
83
173
  You can read a lot more about RuboCop Minitest in its [official docs](https://docs.rubocop.org/rubocop-minitest/).
@@ -31,7 +31,8 @@ module RuboCop
31
31
 
32
32
  def on_def(node)
33
33
  return if !test_method?(node) || !use_test_class?
34
- return if node.left_siblings.none? { |sibling| possible_test_class?(sibling) }
34
+ return if inside_test_class?(node)
35
+ return if (node.left_siblings + node.right_siblings).none? { |sibling| possible_test_class?(sibling) }
35
36
 
36
37
  add_offense(node)
37
38
  end
@@ -44,6 +45,10 @@ module RuboCop
44
45
  root_node.each_descendant(:class).any? { |class_node| test_class?(class_node) }
45
46
  end
46
47
 
48
+ def inside_test_class?(node)
49
+ node.each_ancestor(:class).any? { |class_node| test_class?(class_node) }
50
+ end
51
+
47
52
  def possible_test_class?(node)
48
53
  node.is_a?(AST::ClassNode) && test_class?(node) && node.parent_class.source.include?('Test')
49
54
  end
@@ -71,17 +71,138 @@ module RuboCop
71
71
  #
72
72
  # assert_no_corrections
73
73
  #
74
+ # The cop under test is derived from the test class name: `FooTest` resolves
75
+ # the `Foo` constant in the test class's namespace, then `RuboCop::Cop::Minitest::Foo`.
76
+ # Define a `cop_class` method in the test class to specify the cop explicitly:
77
+ #
78
+ # class AssertNilTest < RuboCop::TestCase
79
+ # private
80
+ #
81
+ # def cop_class
82
+ # RuboCop::Cop::Minitest::AssertNil
83
+ # end
84
+ # end
85
+ #
86
+ # The configuration for the cop under test and the target Ruby version can be specified
87
+ # by overriding `cop_config`, `other_cops`, or `target_ruby_version` in the test class:
88
+ #
89
+ # @example `cop_config` and `target_ruby_version`
90
+ #
91
+ # class MultipleAssertionsTest < RuboCop::TestCase
92
+ # private
93
+ #
94
+ # def cop_config
95
+ # { 'Max' => 1 }
96
+ # end
97
+ #
98
+ # def target_ruby_version
99
+ # 3.0
100
+ # end
101
+ # end
102
+ #
103
+ # To change them for a single test, assign them in the test method.
104
+ # This makes it possible for tests targeting different Ruby versions to live in the same test class:
105
+ #
106
+ # @example assigning `target_ruby_version` per test
107
+ #
108
+ # class MyCopTest < RuboCop::TestCase
109
+ # def test_registers_offense_on_ruby31
110
+ # self.target_ruby_version = 3.1
111
+ #
112
+ # assert_offense(<<~RUBY)
113
+ # ...
114
+ # RUBY
115
+ # end
116
+ #
117
+ # def test_does_not_register_offense_on_ruby30
118
+ # self.target_ruby_version = 3.0
119
+ #
120
+ # assert_no_offenses(<<~RUBY)
121
+ # ...
122
+ # RUBY
123
+ # end
124
+ # end
125
+ #
74
126
  # rubocop:disable Metrics/ModuleLength
75
127
  module AssertOffense
128
+ PLUGIN_INTEGRATION_MUTEX = Mutex.new
129
+ private_constant :PLUGIN_INTEGRATION_MUTEX
130
+
131
+ # Initialized here so that reading it under `ruby -w` does not warn before the first assignment.
132
+ @integrated_plugins = nil
133
+
134
+ class << self
135
+ # Makes the default configuration of extensions registered as lint_roller plugins visible
136
+ # through `RuboCop::ConfigLoader.default_configuration`.
137
+ # Guarded by a mutex so that parallel test threads reaching their first assertion at the same time
138
+ # do not integrate plugins twice or race on the lazy initialization of the default configuration.
139
+ def integrate_plugins!
140
+ PLUGIN_INTEGRATION_MUTEX.synchronize do
141
+ next if @integrated_plugins
142
+
143
+ plugins = Gem.loaded_specs.filter_map do |feature_name, feature_specification|
144
+ feature_name if feature_specification.metadata['default_lint_roller_plugin']
145
+ end
146
+ RuboCop::Plugin.integrate_plugins(RuboCop::Config.new, plugins)
147
+
148
+ @integrated_plugins = true
149
+ end
150
+ end
151
+ end
152
+
76
153
  private
77
154
 
78
155
  def cop
79
156
  @cop ||= begin
80
- cop_name = self.class.to_s.delete_suffix('Test')
81
- raise "Cop not defined: #{cop_name}" unless RuboCop::Cop::Minitest.const_defined?(cop_name)
157
+ unless cop_class
158
+ raise "Could not determine the cop class under test from `#{self.class}`. " \
159
+ 'The cop class is derived from the test class name (e.g. `FooTest` resolves `Foo` ' \
160
+ "in the test class's namespace, then `RuboCop::Cop::Minitest::Foo`). " \
161
+ 'Define a `cop_class` method in your test class to specify it explicitly.'
162
+ end
163
+
164
+ cop_class.new(configuration)
165
+ end
166
+ end
167
+
168
+ def cop_class
169
+ @cop_class ||= derive_cop_class
170
+ end
171
+
172
+ def cop_class=(cop_class)
173
+ @cop_class = cop_class
174
+
175
+ reset_memoization
176
+ end
177
+
178
+ def derive_cop_class
179
+ klass = self.class
180
+
181
+ while klass && klass != ::Minitest::Test
182
+ candidate = derive_cop_class_from_name(klass.name)
183
+ return candidate if candidate
82
184
 
83
- RuboCop::Cop::Minitest.const_get(cop_name).new(configuration)
185
+ klass = klass.superclass
84
186
  end
187
+
188
+ nil
189
+ end
190
+
191
+ def derive_cop_class_from_name(test_class_name)
192
+ return unless test_class_name
193
+
194
+ cop_name = test_class_name.delete_suffix('Test')
195
+ return if cop_name.empty?
196
+
197
+ constant_from(Object, cop_name) || constant_from(RuboCop::Cop::Minitest, cop_name.split('::').last)
198
+ end
199
+
200
+ def constant_from(namespace, constant_name)
201
+ return unless namespace.const_defined?(constant_name)
202
+
203
+ candidate = namespace.const_get(constant_name)
204
+
205
+ candidate if candidate.is_a?(Class) && candidate < RuboCop::Cop::Base
85
206
  end
86
207
 
87
208
  def format_offense(source, **replacements)
@@ -97,7 +218,7 @@ module RuboCop
97
218
  def assert_no_offenses(source, file = nil)
98
219
  setup_assertion
99
220
 
100
- offenses = inspect_source(source, cop, file)
221
+ offenses = inspect_source(source, file)
101
222
 
102
223
  expected_annotations = RuboCop::RSpec::ExpectOffense::AnnotatedSource.parse(source)
103
224
  actual_annotations = expected_annotations.with_offense_annotations(offenses)
@@ -180,19 +301,15 @@ module RuboCop
180
301
  RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
181
302
  end
182
303
 
183
- def inspect_source(source, cop, file = nil)
304
+ def inspect_source(source, file = nil)
184
305
  processed_source = parse_source!(source, file)
185
306
  raise 'Error parsing example code' unless processed_source.valid_syntax?
186
307
 
187
308
  _investigate(cop, processed_source)
188
309
  end
189
310
 
190
- def investigate(cop, processed_source)
191
- needed = Hash.new { |h, k| h[k] = [] }
192
- Array(cop.class.joining_forces).each { |force| needed[force] << cop }
193
- forces = needed.map do |force_class, joining_cops|
194
- force_class.new(joining_cops)
195
- end
311
+ def investigate(processed_source)
312
+ forces = Array(cop.class.joining_forces).map { |force_class| force_class.new([cop]) }
196
313
 
197
314
  commissioner = RuboCop::Cop::Commissioner.new([cop], forces, raise_error: true)
198
315
  commissioner.investigate(processed_source)
@@ -206,7 +323,7 @@ module RuboCop
206
323
  file = file.path
207
324
  end
208
325
 
209
- processed_source = RuboCop::ProcessedSource.new(source, ruby_version, file, parser_engine: parser_engine)
326
+ processed_source = RuboCop::ProcessedSource.new(source, target_ruby_version, file, parser_engine: parser_engine)
210
327
  processed_source.config = configuration
211
328
  processed_source.registry = registry
212
329
  processed_source
@@ -216,22 +333,71 @@ module RuboCop
216
333
  @configuration ||= if defined?(config)
217
334
  config
218
335
  else
219
- RuboCop::Config.new({}, "#{Dir.pwd}/.rubocop.yml")
336
+ RuboCop::Config.new(configuration_hash, "#{Dir.pwd}/.rubocop.yml")
220
337
  end
221
338
  end
222
339
 
340
+ def configuration_hash
341
+ RuboCop::Minitest::AssertOffense.integrate_plugins!
342
+
343
+ hash = { 'AllCops' => { 'TargetRubyVersion' => target_ruby_version } }
344
+ if cop_class
345
+ hash[cop_class.cop_name] = RuboCop::ConfigLoader.default_configuration.for_cop(cop_class).merge(
346
+ 'Enabled' => true, 'AutoCorrect' => 'always'
347
+ ).merge(cop_config)
348
+ end
349
+
350
+ hash.merge(other_cops)
351
+ end
352
+
353
+ def cop_config
354
+ @cop_config ||= {}
355
+ end
356
+
357
+ def cop_config=(config)
358
+ @cop_config = config
359
+
360
+ reset_memoization
361
+ end
362
+
363
+ def other_cops
364
+ @other_cops ||= {}
365
+ end
366
+
367
+ def other_cops=(other_cops)
368
+ @other_cops = other_cops
369
+
370
+ reset_memoization
371
+ end
372
+
223
373
  def registry
224
374
  @registry ||= begin
225
375
  cops = configuration.keys.map { |cop| RuboCop::Cop::Registry.global.find_by_cop_name(cop) }
226
- cops << cop_class if defined?(cop_class) && !cops.include?(cop_class)
376
+ cops << cop_class if cop_class && !cops.include?(cop_class)
227
377
  cops.compact!
228
378
  RuboCop::Cop::Registry.new(cops)
229
379
  end
230
380
  end
231
381
 
232
- def ruby_version
382
+ def target_ruby_version
233
383
  # Prism is the default backend parser for Ruby 3.4+.
234
- ENV['PARSER_ENGINE'] == 'parser_prism' ? 3.4 : RuboCop::TargetRuby::DEFAULT_VERSION
384
+ @target_ruby_version ||= (ENV['PARSER_ENGINE'] == 'parser_prism' ? 3.4 : RuboCop::TargetRuby::DEFAULT_VERSION)
385
+ end
386
+
387
+ def target_ruby_version=(version)
388
+ @target_ruby_version = version
389
+
390
+ reset_memoization
391
+ end
392
+
393
+ def reset_memoization
394
+ @cop = nil
395
+ @configuration = nil
396
+ @registry = nil
397
+ end
398
+
399
+ def ruby_version
400
+ target_ruby_version
235
401
  end
236
402
 
237
403
  def parser_engine
@@ -5,5 +5,6 @@
5
5
  require 'rubocop'
6
6
  require 'minitest'
7
7
  require_relative 'assert_offense'
8
+ require_relative '../test_case'
8
9
 
9
10
  Minitest::Test.include RuboCop::Minitest::AssertOffense
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Minitest
5
5
  # This module holds the RuboCop Minitest version information.
6
6
  module Version
7
- STRING = '0.39.1'
7
+ STRING = '0.40.0'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+ require 'minitest'
5
+ require_relative 'minitest/assert_offense'
6
+
7
+ module RuboCop
8
+ # Base test case class for testing custom cops with Minitest.
9
+ #
10
+ # @example Usage
11
+ #
12
+ # class MyCopTest < RuboCop::TestCase
13
+ # def test_registers_offense
14
+ # assert_offense(<<~RUBY)
15
+ # bad_method
16
+ # ^^^^^^^^^^ Use `good_method` instead of `bad_method`.
17
+ # RUBY
18
+ #
19
+ # assert_correction(<<~RUBY)
20
+ # good_method
21
+ # RUBY
22
+ # end
23
+ # end
24
+ #
25
+ # See the documentation of `RuboCop::Minitest::AssertOffense` for details.
26
+ class TestCase < ::Minitest::Test
27
+ include RuboCop::Minitest::AssertOffense
28
+ end
29
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-minitest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.39.1
4
+ version: 0.40.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -145,13 +145,14 @@ files:
145
145
  - lib/rubocop/minitest/plugin.rb
146
146
  - lib/rubocop/minitest/support.rb
147
147
  - lib/rubocop/minitest/version.rb
148
+ - lib/rubocop/test_case.rb
148
149
  licenses:
149
150
  - MIT
150
151
  metadata:
151
152
  homepage_uri: https://docs.rubocop.org/rubocop-minitest/
152
153
  changelog_uri: https://github.com/rubocop/rubocop-minitest/blob/master/CHANGELOG.md
153
154
  source_code_uri: https://github.com/rubocop/rubocop-minitest
154
- documentation_uri: https://docs.rubocop.org/rubocop-minitest/0.39
155
+ documentation_uri: https://docs.rubocop.org/rubocop-minitest/0.40
155
156
  bug_tracker_uri: https://github.com/rubocop/rubocop-minitest/issues
156
157
  rubygems_mfa_required: 'true'
157
158
  default_lint_roller_plugin: RuboCop::Minitest::Plugin
@@ -169,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
170
  - !ruby/object:Gem::Version
170
171
  version: '0'
171
172
  requirements: []
172
- rubygems_version: 4.0.0.dev
173
+ rubygems_version: 4.0.16
173
174
  specification_version: 4
174
175
  summary: Automatic Minitest code style checking tool.
175
176
  test_files: []