rubocop 1.60.2 → 1.63.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 +4 -4
- data/README.md +1 -1
- data/assets/output.css.erb +159 -0
- data/assets/output.html.erb +1 -160
- data/config/default.yml +64 -15
- data/lib/rubocop/cli/command/auto_generate_config.rb +12 -3
- data/lib/rubocop/cli/command/lsp.rb +2 -2
- data/lib/rubocop/cli.rb +6 -1
- data/lib/rubocop/config.rb +37 -10
- data/lib/rubocop/config_finder.rb +12 -2
- data/lib/rubocop/config_obsoletion.rb +1 -1
- data/lib/rubocop/config_validator.rb +14 -5
- data/lib/rubocop/cop/autocorrect_logic.rb +6 -1
- data/lib/rubocop/cop/base.rb +52 -6
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +4 -8
- data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +5 -13
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +5 -1
- data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -0
- data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +8 -6
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +122 -28
- data/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb +34 -0
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +14 -7
- data/lib/rubocop/cop/layout/end_alignment.rb +3 -1
- data/lib/rubocop/cop/layout/redundant_line_break.rb +11 -3
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +19 -10
- data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +1 -1
- data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -4
- data/lib/rubocop/cop/lint/debugger.rb +27 -2
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -9
- data/lib/rubocop/cop/lint/redundant_with_index.rb +4 -0
- data/lib/rubocop/cop/lint/rescue_type.rb +1 -3
- data/lib/rubocop/cop/lint/script_permission.rb +3 -3
- data/lib/rubocop/cop/lint/syntax.rb +1 -1
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -2
- data/lib/rubocop/cop/lint/useless_times.rb +1 -1
- data/lib/rubocop/cop/lint/void.rb +6 -1
- data/lib/rubocop/cop/mixin/code_length.rb +12 -1
- data/lib/rubocop/cop/mixin/method_complexity.rb +15 -6
- data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
- data/lib/rubocop/cop/mixin/safe_assignment.rb +1 -1
- data/lib/rubocop/cop/naming/block_forwarding.rb +31 -12
- data/lib/rubocop/cop/naming/file_name.rb +2 -2
- data/lib/rubocop/cop/naming/inclusive_language.rb +1 -2
- data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
- data/lib/rubocop/cop/registry.rb +1 -1
- data/lib/rubocop/cop/style/alias.rb +1 -0
- data/lib/rubocop/cop/style/arguments_forwarding.rb +29 -8
- data/lib/rubocop/cop/style/case_like_if.rb +1 -1
- data/lib/rubocop/cop/style/class_vars.rb +3 -3
- data/lib/rubocop/cop/style/collection_compact.rb +3 -3
- data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
- data/lib/rubocop/cop/style/conditional_assignment.rb +4 -5
- data/lib/rubocop/cop/style/copyright.rb +16 -11
- data/lib/rubocop/cop/style/eval_with_location.rb +2 -0
- data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -1
- data/lib/rubocop/cop/style/for.rb +2 -0
- data/lib/rubocop/cop/style/format_string.rb +9 -9
- data/lib/rubocop/cop/style/hash_each_methods.rb +1 -1
- data/lib/rubocop/cop/style/hash_syntax.rb +6 -2
- data/lib/rubocop/cop/style/inverse_methods.rb +8 -8
- data/lib/rubocop/cop/style/invertible_unless_condition.rb +10 -5
- data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +5 -8
- data/lib/rubocop/cop/style/map_into_array.rb +175 -0
- data/lib/rubocop/cop/style/map_to_hash.rb +1 -1
- data/lib/rubocop/cop/style/map_to_set.rb +1 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/multiline_method_signature.rb +10 -1
- data/lib/rubocop/cop/style/multiline_ternary_operator.rb +4 -0
- data/lib/rubocop/cop/style/nil_comparison.rb +2 -0
- data/lib/rubocop/cop/style/object_then.rb +5 -3
- data/lib/rubocop/cop/style/parallel_assignment.rb +1 -3
- data/lib/rubocop/cop/style/raise_args.rb +4 -1
- data/lib/rubocop/cop/style/redundant_argument.rb +25 -2
- data/lib/rubocop/cop/style/redundant_assignment.rb +10 -2
- data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +5 -4
- data/lib/rubocop/cop/style/redundant_each.rb +1 -1
- data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +5 -0
- data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
- data/lib/rubocop/cop/style/redundant_return.rb +6 -0
- data/lib/rubocop/cop/style/sample.rb +1 -3
- data/lib/rubocop/cop/team.rb +3 -0
- data/lib/rubocop/cop/utils/regexp_ranges.rb +1 -1
- data/lib/rubocop/cops_documentation_generator.rb +4 -2
- data/lib/rubocop/directive_comment.rb +10 -8
- data/lib/rubocop/formatter/clang_style_formatter.rb +3 -7
- data/lib/rubocop/formatter/html_formatter.rb +30 -10
- data/lib/rubocop/formatter/offense_count_formatter.rb +12 -2
- data/lib/rubocop/formatter/tap_formatter.rb +3 -7
- data/lib/rubocop/lockfile.rb +34 -4
- data/lib/rubocop/lsp/logger.rb +1 -1
- data/lib/rubocop/lsp/routes.rb +1 -1
- data/lib/rubocop/lsp/runtime.rb +1 -1
- data/lib/rubocop/lsp/server.rb +5 -2
- data/lib/rubocop/lsp/severity.rb +1 -1
- data/lib/rubocop/lsp.rb +29 -0
- data/lib/rubocop/magic_comment.rb +1 -1
- data/lib/rubocop/options.rb +11 -0
- data/lib/rubocop/path_util.rb +6 -2
- data/lib/rubocop/rspec/cop_helper.rb +8 -2
- data/lib/rubocop/rspec/expect_offense.rb +16 -8
- data/lib/rubocop/rspec/shared_contexts.rb +49 -18
- data/lib/rubocop/rspec/support.rb +2 -1
- data/lib/rubocop/runner.rb +12 -2
- data/lib/rubocop/target_finder.rb +84 -78
- data/lib/rubocop/target_ruby.rb +82 -80
- data/lib/rubocop/version.rb +19 -4
- data/lib/rubocop.rb +1 -0
- metadata +10 -6
data/lib/rubocop/config.rb
CHANGED
@@ -244,6 +244,10 @@ module RuboCop
|
|
244
244
|
end
|
245
245
|
end
|
246
246
|
|
247
|
+
def parser_engine
|
248
|
+
@parser_engine ||= for_all_cops.fetch('ParserEngine', :parser_whitequark).to_sym
|
249
|
+
end
|
250
|
+
|
247
251
|
def target_rails_version
|
248
252
|
@target_rails_version ||=
|
249
253
|
if for_all_cops['TargetRailsVersion']
|
@@ -259,6 +263,7 @@ module RuboCop
|
|
259
263
|
PathUtil.smart_path(@loaded_path)
|
260
264
|
end
|
261
265
|
|
266
|
+
# @return [String, nil]
|
262
267
|
def bundler_lock_file_path
|
263
268
|
return nil unless loaded_path
|
264
269
|
|
@@ -282,27 +287,49 @@ module RuboCop
|
|
282
287
|
end
|
283
288
|
end
|
284
289
|
|
290
|
+
# Returns target's locked gem versions (i.e. from Gemfile.lock or gems.locked)
|
291
|
+
# @returns [Hash{String => Gem::Version}] The locked gem versions, keyed by the gems' names.
|
292
|
+
def gem_versions_in_target
|
293
|
+
@gem_versions_in_target ||= read_gem_versions_from_target_lockfile
|
294
|
+
end
|
295
|
+
|
285
296
|
def inspect # :nodoc:
|
286
297
|
"#<#{self.class.name}:#{object_id} @loaded_path=#{loaded_path}>"
|
287
298
|
end
|
288
299
|
|
289
300
|
private
|
290
301
|
|
302
|
+
# @return [Float, nil] The Rails version as a `major.minor` Float.
|
291
303
|
def target_rails_version_from_bundler_lock_file
|
292
304
|
@target_rails_version_from_bundler_lock_file ||= read_rails_version_from_bundler_lock_file
|
293
305
|
end
|
294
306
|
|
307
|
+
# @return [Float, nil] The Rails version as a `major.minor` Float.
|
295
308
|
def read_rails_version_from_bundler_lock_file
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
309
|
+
return nil unless gem_versions_in_target
|
310
|
+
|
311
|
+
# Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
|
312
|
+
# See https://github.com/rubocop/rubocop/pull/11289
|
313
|
+
rails_version_in_target = gem_versions_in_target['railties']
|
314
|
+
return nil unless rails_version_in_target
|
315
|
+
|
316
|
+
gem_version_to_major_minor_float(rails_version_in_target)
|
317
|
+
end
|
318
|
+
|
319
|
+
# @param [Gem::Version] gem_version an object like `Gem::Version.new("7.1.2.3")`
|
320
|
+
# @return [Float] The major and minor version, like `7.1`
|
321
|
+
def gem_version_to_major_minor_float(gem_version)
|
322
|
+
segments = gem_version.canonical_segments
|
323
|
+
# segments.fetch(0).to_f + (segments.fetch(1, 0.0).to_f / 10)
|
324
|
+
Float("#{segments.fetch(0)}.#{segments.fetch(1, 0)}")
|
325
|
+
end
|
326
|
+
|
327
|
+
# @returns [Hash{String => Gem::Version}] The locked gem versions, keyed by the gems' names.
|
328
|
+
def read_gem_versions_from_target_lockfile
|
329
|
+
lockfile_path = bundler_lock_file_path
|
330
|
+
return nil unless lockfile_path
|
331
|
+
|
332
|
+
Lockfile.new(lockfile_path).gem_versions
|
306
333
|
end
|
307
334
|
|
308
335
|
def enable_cop?(qualified_cop_name, cop_options)
|
@@ -17,8 +17,8 @@ module RuboCop
|
|
17
17
|
attr_writer :project_root
|
18
18
|
|
19
19
|
def find_config_path(target_dir)
|
20
|
-
find_project_dotfile(target_dir) ||
|
21
|
-
DEFAULT_FILE
|
20
|
+
find_project_dotfile(target_dir) || find_project_root_dot_config ||
|
21
|
+
find_user_dotfile || find_user_xdg_config || DEFAULT_FILE
|
22
22
|
end
|
23
23
|
|
24
24
|
# Returns the path RuboCop inferred as the root of the project. No file
|
@@ -41,6 +41,16 @@ module RuboCop
|
|
41
41
|
find_file_upwards(DOTFILE, target_dir, project_root)
|
42
42
|
end
|
43
43
|
|
44
|
+
def find_project_root_dot_config
|
45
|
+
return unless project_root
|
46
|
+
|
47
|
+
dotfile = File.join(project_root, '.config', DOTFILE)
|
48
|
+
return dotfile if File.exist?(dotfile)
|
49
|
+
|
50
|
+
xdg_config = File.join(project_root, '.config', 'rubocop', XDG_CONFIG)
|
51
|
+
xdg_config if File.exist?(xdg_config)
|
52
|
+
end
|
53
|
+
|
44
54
|
def find_user_dotfile
|
45
55
|
return unless ENV.key?('HOME')
|
46
56
|
|
@@ -52,7 +52,7 @@ module RuboCop
|
|
52
52
|
def load_rules # rubocop:disable Metrics/AbcSize
|
53
53
|
rules = LOAD_RULES_CACHE[self.class.files] ||=
|
54
54
|
self.class.files.each_with_object({}) do |filename, hash|
|
55
|
-
hash.merge!(YAML.safe_load(File.read(filename))) do |_key, first, second|
|
55
|
+
hash.merge!(YAML.safe_load(File.read(filename)) || {}) do |_key, first, second|
|
56
56
|
case first
|
57
57
|
when Hash
|
58
58
|
first.merge(second)
|
@@ -18,6 +18,7 @@ module RuboCop
|
|
18
18
|
# @api private
|
19
19
|
CONFIG_CHECK_KEYS = %w[Enabled Safe SafeAutoCorrect AutoCorrect].to_set.freeze
|
20
20
|
CONFIG_CHECK_DEPARTMENTS = %w[pending override_department].freeze
|
21
|
+
CONFIG_CHECK_AUTOCORRECTS = %w[always contextual disabled].freeze
|
21
22
|
private_constant :CONFIG_CHECK_KEYS, :CONFIG_CHECK_DEPARTMENTS
|
22
23
|
|
23
24
|
def_delegators :@config, :smart_loaded_path, :for_all_cops
|
@@ -248,23 +249,31 @@ module RuboCop
|
|
248
249
|
end
|
249
250
|
end
|
250
251
|
|
252
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
251
253
|
def check_cop_config_value(hash, parent = nil)
|
252
254
|
hash.each do |key, value|
|
253
255
|
check_cop_config_value(value, key) if value.is_a?(Hash)
|
254
256
|
|
255
257
|
next unless CONFIG_CHECK_KEYS.include?(key) && value.is_a?(String)
|
256
258
|
|
257
|
-
|
259
|
+
if key == 'Enabled' && !CONFIG_CHECK_DEPARTMENTS.include?(value)
|
260
|
+
supposed_values = 'a boolean'
|
261
|
+
elsif key == 'AutoCorrect' && !CONFIG_CHECK_AUTOCORRECTS.include?(value)
|
262
|
+
supposed_values = '`always`, `contextual`, `disabled`, or a boolean'
|
263
|
+
else
|
264
|
+
next
|
265
|
+
end
|
258
266
|
|
259
|
-
raise ValidationError,
|
267
|
+
raise ValidationError, param_error_message(parent, key, value, supposed_values)
|
260
268
|
end
|
261
269
|
end
|
270
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
262
271
|
|
263
272
|
# FIXME: Handling colors in exception messages like this is ugly.
|
264
|
-
def
|
273
|
+
def param_error_message(parent, key, value, supposed_values)
|
265
274
|
"#{Rainbow('').reset}" \
|
266
|
-
"Property #{Rainbow(key).yellow} of
|
267
|
-
"is supposed to be
|
275
|
+
"Property #{Rainbow(key).yellow} of #{Rainbow(parent).yellow} cop " \
|
276
|
+
"is supposed to be #{supposed_values} and #{Rainbow(value).yellow} is not."
|
268
277
|
end
|
269
278
|
end
|
270
279
|
end
|
@@ -32,7 +32,12 @@ module RuboCop
|
|
32
32
|
# allow turning off autocorrect on a cop by cop basis
|
33
33
|
return true unless cop_config
|
34
34
|
|
35
|
-
|
35
|
+
# `false` is the same as `disabled` for backward compatibility.
|
36
|
+
return false if ['disabled', false].include?(cop_config['AutoCorrect'])
|
37
|
+
|
38
|
+
# When LSP is enabled, it is considered as editing source code,
|
39
|
+
# and autocorrection with `AutoCorrect: contextual` will not be performed.
|
40
|
+
return false if contextual_autocorrect? && LSP.enabled?
|
36
41
|
|
37
42
|
# :safe_autocorrect is a derived option based on several command-line
|
38
43
|
# arguments - see RuboCop::Options#add_autocorrection_options
|
data/lib/rubocop/cop/base.rb
CHANGED
@@ -70,6 +70,7 @@ module RuboCop
|
|
70
70
|
|
71
71
|
def self.inherited(subclass)
|
72
72
|
super
|
73
|
+
subclass.instance_variable_set(:@gem_requirements, gem_requirements.dup)
|
73
74
|
Registry.global.enlist(subclass)
|
74
75
|
end
|
75
76
|
|
@@ -126,6 +127,29 @@ module RuboCop
|
|
126
127
|
false
|
127
128
|
end
|
128
129
|
|
130
|
+
## Gem requirements
|
131
|
+
|
132
|
+
@gem_requirements = {}
|
133
|
+
|
134
|
+
class << self
|
135
|
+
attr_reader :gem_requirements
|
136
|
+
|
137
|
+
# Register a version requirement for the given gem name.
|
138
|
+
# This cop will be skipped unless the target satisfies *all* requirements.
|
139
|
+
# @param [String] gem_name
|
140
|
+
# @param [Array<String>] version_requirements The version requirements,
|
141
|
+
# using the same syntax as a Gemfile, e.g. ">= 1.2.3"
|
142
|
+
#
|
143
|
+
# If omitted, any version of the gem will be accepted.
|
144
|
+
#
|
145
|
+
# https://guides.rubygems.org/patterns/#declaring-dependencies
|
146
|
+
#
|
147
|
+
# @api public
|
148
|
+
def requires_gem(gem_name, *version_requirements)
|
149
|
+
@gem_requirements[gem_name] = Gem::Requirement.new(version_requirements)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
129
153
|
def initialize(config = nil, options = nil)
|
130
154
|
@config = config || Config.new
|
131
155
|
@options = options || { debug: false }
|
@@ -232,6 +256,10 @@ module RuboCop
|
|
232
256
|
@config.target_ruby_version
|
233
257
|
end
|
234
258
|
|
259
|
+
def parser_engine
|
260
|
+
@config.parser_engine
|
261
|
+
end
|
262
|
+
|
235
263
|
def target_rails_version
|
236
264
|
@config.target_rails_version
|
237
265
|
end
|
@@ -241,6 +269,7 @@ module RuboCop
|
|
241
269
|
end
|
242
270
|
|
243
271
|
def relevant_file?(file)
|
272
|
+
return false unless target_satisfies_all_gem_version_requirements?
|
244
273
|
return true unless @config.clusivity_config_for_badge?(self.class.badge)
|
245
274
|
|
246
275
|
file == RuboCop::AST::ProcessedSource::STRING_SOURCE_NAME ||
|
@@ -254,7 +283,7 @@ module RuboCop
|
|
254
283
|
|
255
284
|
# There should be very limited reasons for a Cop to do it's own parsing
|
256
285
|
def parse(source, path = nil)
|
257
|
-
ProcessedSource.new(source, target_ruby_version, path)
|
286
|
+
ProcessedSource.new(source, target_ruby_version, path, parser_engine: parser_engine)
|
258
287
|
end
|
259
288
|
|
260
289
|
# @api private
|
@@ -305,6 +334,17 @@ module RuboCop
|
|
305
334
|
@current_original = original
|
306
335
|
end
|
307
336
|
|
337
|
+
# @api private
|
338
|
+
def always_autocorrect?
|
339
|
+
# `true` is the same as `'always'` for backward compatibility.
|
340
|
+
['always', true].include?(cop_config.fetch('AutoCorrect', 'always'))
|
341
|
+
end
|
342
|
+
|
343
|
+
# @api private
|
344
|
+
def contextual_autocorrect?
|
345
|
+
cop_config.fetch('AutoCorrect', 'always') == 'contextual'
|
346
|
+
end
|
347
|
+
|
308
348
|
def inspect # :nodoc:
|
309
349
|
"#<#{self.class.name}:#{object_id} @config=#{@config} @options=#{@options}>"
|
310
350
|
end
|
@@ -389,7 +429,7 @@ module RuboCop
|
|
389
429
|
def use_corrector(range, corrector)
|
390
430
|
if autocorrect?
|
391
431
|
attempt_correction(range, corrector)
|
392
|
-
elsif corrector &&
|
432
|
+
elsif corrector && (always_autocorrect? || (contextual_autocorrect? && !LSP.enabled?))
|
393
433
|
:uncorrected
|
394
434
|
else
|
395
435
|
:unsupported
|
@@ -482,10 +522,16 @@ module RuboCop
|
|
482
522
|
)
|
483
523
|
end
|
484
524
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
525
|
+
def target_satisfies_all_gem_version_requirements?
|
526
|
+
self.class.gem_requirements.all? do |gem_name, version_req|
|
527
|
+
all_gem_versions_in_target = @config.gem_versions_in_target
|
528
|
+
next false unless all_gem_versions_in_target
|
529
|
+
|
530
|
+
gem_version_in_target = all_gem_versions_in_target[gem_name]
|
531
|
+
next false unless gem_version_in_target
|
532
|
+
|
533
|
+
version_req.satisfied_by?(gem_version_in_target)
|
534
|
+
end
|
489
535
|
end
|
490
536
|
end
|
491
537
|
end
|
@@ -34,18 +34,14 @@ module RuboCop
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def offending_range
|
37
|
+
begin_range = block_node.source_range.begin
|
38
|
+
|
37
39
|
if block_node.arguments?
|
38
|
-
|
40
|
+
begin_range.join(argument_node.source_range.end)
|
39
41
|
else
|
40
|
-
|
42
|
+
begin_range.join(block_node.loc.begin.end)
|
41
43
|
end
|
42
44
|
end
|
43
|
-
|
44
|
-
def replacement_range(end_pos)
|
45
|
-
Parser::Source::Range.new(block_node.source_range.source_buffer,
|
46
|
-
block_node.source_range.begin_pos,
|
47
|
-
end_pos)
|
48
|
-
end
|
49
45
|
end
|
50
46
|
end
|
51
47
|
end
|
@@ -15,6 +15,8 @@ module RuboCop
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def call(corrector)
|
18
|
+
offending_range = for_node.source_range.begin.join(end_range)
|
19
|
+
|
18
20
|
corrector.replace(offending_range, correction)
|
19
21
|
end
|
20
22
|
|
@@ -40,11 +42,11 @@ module RuboCop
|
|
40
42
|
collection_node.range_type? || collection_node.or_type? || collection_node.and_type?
|
41
43
|
end
|
42
44
|
|
43
|
-
def
|
45
|
+
def end_range
|
44
46
|
if for_node.do?
|
45
|
-
keyword_begin.
|
47
|
+
keyword_begin.end
|
46
48
|
else
|
47
|
-
collection_end.
|
49
|
+
collection_end.end
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
@@ -59,16 +61,6 @@ module RuboCop
|
|
59
61
|
collection_node.source_range
|
60
62
|
end
|
61
63
|
end
|
62
|
-
|
63
|
-
def offending_range
|
64
|
-
replacement_range(end_position)
|
65
|
-
end
|
66
|
-
|
67
|
-
def replacement_range(end_pos)
|
68
|
-
Parser::Source::Range.new(for_node.source_range.source_buffer,
|
69
|
-
for_node.source_range.begin_pos,
|
70
|
-
end_pos)
|
71
|
-
end
|
72
64
|
end
|
73
65
|
end
|
74
66
|
end
|
@@ -76,7 +76,9 @@ module RuboCop
|
|
76
76
|
PATTERN
|
77
77
|
|
78
78
|
def on_new_investigation
|
79
|
-
|
79
|
+
return if processed_source.ast && required_ruby_version?(processed_source.ast)
|
80
|
+
|
81
|
+
add_global_offense(MISSING_MSG)
|
80
82
|
end
|
81
83
|
|
82
84
|
def on_send(node)
|
@@ -110,6 +112,8 @@ module RuboCop
|
|
110
112
|
end
|
111
113
|
end
|
112
114
|
|
115
|
+
return unless required_ruby_version
|
116
|
+
|
113
117
|
required_ruby_version.str_content.scan(/\d/).first(2).join('.')
|
114
118
|
end
|
115
119
|
|
@@ -45,6 +45,7 @@ module RuboCop
|
|
45
45
|
EXPECT_OFFENSE_DESCRIPTION_MAPPING = {
|
46
46
|
/\A(does not|doesn't) (register|find|flag|report)/ => 'registers',
|
47
47
|
/\A(does not|doesn't) add (a|an|any )?offense/ => 'registers an offense',
|
48
|
+
/\Aregisters no offense/ => 'registers an offense',
|
48
49
|
/\Aaccepts\b/ => 'registers'
|
49
50
|
}.freeze
|
50
51
|
|
@@ -30,6 +30,7 @@ module RuboCop
|
|
30
30
|
extend AutoCorrector
|
31
31
|
|
32
32
|
MSG = 'Use `%<method_name>s` instead of `%<method_suffix>s`.'
|
33
|
+
RESTRICT_ON_SEND = %i[end_with?].freeze
|
33
34
|
SUGGEST_METHOD_FOR_SUFFIX = {
|
34
35
|
'=' => 'assignment_method?',
|
35
36
|
'!' => 'bang_method?',
|
@@ -51,14 +52,15 @@ module RuboCop
|
|
51
52
|
|
52
53
|
def on_send(node)
|
53
54
|
method_name_end_with?(node) do |method_name_node, end_with_arg|
|
55
|
+
next unless method_name_node.receiver
|
56
|
+
|
57
|
+
preferred_method = SUGGEST_METHOD_FOR_SUFFIX[end_with_arg.value]
|
54
58
|
range = range(method_name_node, node)
|
55
|
-
message = format(
|
56
|
-
MSG,
|
57
|
-
method_name: SUGGEST_METHOD_FOR_SUFFIX[end_with_arg.value],
|
58
|
-
method_suffix: range.source
|
59
|
-
)
|
59
|
+
message = format(MSG, method_name: preferred_method, method_suffix: range.source)
|
60
60
|
|
61
|
-
add_offense(range, message: message)
|
61
|
+
add_offense(range, message: message) do |corrector|
|
62
|
+
corrector.replace(range, preferred_method)
|
63
|
+
end
|
62
64
|
end
|
63
65
|
end
|
64
66
|
alias on_csend on_send
|
@@ -26,10 +26,18 @@ module RuboCop
|
|
26
26
|
MSG = 'Precede `%<method>s` with a `@!method` YARD directive.'
|
27
27
|
MSG_WRONG_NAME = '`@!method` YARD directive has invalid method name, ' \
|
28
28
|
'use `%<expected>s` instead of `%<actual>s`.'
|
29
|
+
MSG_MISSING_SCOPE_SELF = 'Follow the `@!method` YARD directive with ' \
|
30
|
+
'`@!scope class` if it is a class method.'
|
31
|
+
MSG_WRONG_SCOPE_SELF = 'Do not use the `@!scope class` YARD directive if it ' \
|
32
|
+
'is not a class method.'
|
29
33
|
MSG_TOO_MANY = 'Multiple `@!method` YARD directives found for this matcher.'
|
30
34
|
|
31
35
|
RESTRICT_ON_SEND = %i[def_node_matcher def_node_search].to_set.freeze
|
32
|
-
|
36
|
+
REGEXP_METHOD = /
|
37
|
+
^\s*\#\s*
|
38
|
+
@!method\s+(?<receiver>self\.)?(?<method_name>[a-z0-9_]+[?!]?)(?:\((?<args>.*)\))?
|
39
|
+
/x.freeze
|
40
|
+
REGEXP_SCOPE = /^\s*\#\s*@!scope class/.freeze
|
33
41
|
|
34
42
|
# @!method pattern_matcher?(node)
|
35
43
|
def_node_matcher :pattern_matcher?, <<~PATTERN
|
@@ -40,14 +48,15 @@ module RuboCop
|
|
40
48
|
return if node.arguments.none?
|
41
49
|
return unless valid_method_name?(node)
|
42
50
|
|
43
|
-
actual_name = node.first_argument.value
|
51
|
+
actual_name = node.first_argument.value.to_s
|
52
|
+
|
53
|
+
# Ignore cases where the method has a receiver that isn't self
|
54
|
+
return if actual_name.include?('.') && !actual_name.start_with?('self.')
|
55
|
+
|
44
56
|
directives = method_directives(node)
|
45
57
|
return too_many_directives(node) if directives.size > 1
|
46
58
|
|
47
|
-
|
48
|
-
return if directive_correct?(directive, actual_name)
|
49
|
-
|
50
|
-
register_offense(node, directive, actual_name)
|
59
|
+
process_directive(node, actual_name, directives.first)
|
51
60
|
end
|
52
61
|
|
53
62
|
private
|
@@ -58,44 +67,112 @@ module RuboCop
|
|
58
67
|
|
59
68
|
def method_directives(node)
|
60
69
|
comments = processed_source.ast_with_comments[node]
|
61
|
-
|
62
|
-
|
63
|
-
match = comment.text.match(REGEXP)
|
70
|
+
group_comments(comments).filter_map do |comment_method, comment_scope|
|
71
|
+
match = comment_method.text.match(REGEXP_METHOD)
|
64
72
|
next unless match
|
65
73
|
|
66
|
-
{
|
74
|
+
{
|
75
|
+
node_method: comment_method,
|
76
|
+
node_scope: comment_scope,
|
77
|
+
method_name: match[:method_name],
|
78
|
+
args: match[:args],
|
79
|
+
receiver: match[:receiver],
|
80
|
+
has_scope_directive: comment_scope&.text&.match?(REGEXP_SCOPE)
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def group_comments(comments)
|
86
|
+
result = []
|
87
|
+
comments.each.with_index do |comment, index|
|
88
|
+
# Grab the scope directive if it is preceded by a method directive
|
89
|
+
if comment.text.include?('@!method')
|
90
|
+
result << if (next_comment = comments[index + 1])&.text&.include?('@!scope')
|
91
|
+
[comment, next_comment]
|
92
|
+
else
|
93
|
+
[comment, nil]
|
94
|
+
end
|
95
|
+
end
|
67
96
|
end
|
97
|
+
result
|
68
98
|
end
|
69
99
|
|
70
100
|
def too_many_directives(node)
|
71
101
|
add_offense(node, message: MSG_TOO_MANY)
|
72
102
|
end
|
73
103
|
|
74
|
-
def
|
75
|
-
|
104
|
+
def process_directive(node, actual_name, directive)
|
105
|
+
return unless (offense_type = directive_offense_type(directive, actual_name))
|
106
|
+
|
107
|
+
register_offense(offense_type, node, directive, actual_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
def directive_offense_type(directive, actual_name)
|
111
|
+
return :missing_directive unless directive
|
112
|
+
|
113
|
+
return :wrong_scope if wrong_scope(directive, actual_name)
|
114
|
+
return :no_scope if no_scope(directive, actual_name)
|
115
|
+
|
116
|
+
# The method directive being prefixed by 'self.' is always an offense.
|
117
|
+
# The matched method_name does not contain the receiver but the
|
118
|
+
# def_node_match method name may so it must be removed.
|
119
|
+
if directive[:method_name] != remove_receiver(actual_name) || directive[:receiver]
|
120
|
+
:wrong_name
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def wrong_scope(directive, actual_name)
|
125
|
+
!actual_name.start_with?('self.') && directive[:has_scope_directive]
|
126
|
+
end
|
127
|
+
|
128
|
+
def no_scope(directive, actual_name)
|
129
|
+
actual_name.start_with?('self.') && !directive[:has_scope_directive]
|
76
130
|
end
|
77
131
|
|
78
|
-
def register_offense(node, directive, actual_name)
|
79
|
-
message = formatted_message(directive, actual_name, node.method_name)
|
132
|
+
def register_offense(offense_type, node, directive, actual_name)
|
133
|
+
message = formatted_message(offense_type, directive, actual_name, node.method_name)
|
80
134
|
|
81
135
|
add_offense(node, message: message) do |corrector|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
136
|
+
case offense_type
|
137
|
+
when :wrong_name
|
138
|
+
correct_method_directive(corrector, directive, actual_name)
|
139
|
+
when :wrong_scope
|
140
|
+
remove_scope_directive(corrector, directive)
|
141
|
+
when :no_scope
|
142
|
+
insert_scope_directive(corrector, directive[:node_method])
|
143
|
+
when :missing_directive
|
144
|
+
insert_method_directive(corrector, node, actual_name)
|
86
145
|
end
|
87
146
|
end
|
88
147
|
end
|
89
148
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
149
|
+
# rubocop:disable Metrics/MethodLength
|
150
|
+
def formatted_message(offense_type, directive, actual_name, method_name)
|
151
|
+
case offense_type
|
152
|
+
when :wrong_name
|
153
|
+
# Add the receiver to the name when showing an offense
|
154
|
+
current_name = if directive[:receiver]
|
155
|
+
directive[:receiver] + directive[:method_name]
|
156
|
+
else
|
157
|
+
directive[:method_name]
|
158
|
+
end
|
159
|
+
# The correct name will never include a receiver, remove it
|
160
|
+
format(MSG_WRONG_NAME, expected: remove_receiver(actual_name), actual: current_name)
|
161
|
+
when :wrong_scope
|
162
|
+
MSG_WRONG_SCOPE_SELF
|
163
|
+
when :no_scope
|
164
|
+
MSG_MISSING_SCOPE_SELF
|
165
|
+
when :missing_directive
|
94
166
|
format(MSG, method: method_name)
|
95
167
|
end
|
96
168
|
end
|
169
|
+
# rubocop:enable Metrics/MethodLength
|
170
|
+
|
171
|
+
def remove_receiver(current)
|
172
|
+
current.delete_prefix('self.')
|
173
|
+
end
|
97
174
|
|
98
|
-
def
|
175
|
+
def insert_method_directive(corrector, node, actual_name)
|
99
176
|
# If the pattern matcher uses arguments (`%1`, `%2`, etc.), include them in the directive
|
100
177
|
arguments = pattern_arguments(node.arguments[1].source)
|
101
178
|
|
@@ -107,6 +184,14 @@ module RuboCop
|
|
107
184
|
corrector.insert_before(range, directive)
|
108
185
|
end
|
109
186
|
|
187
|
+
def insert_scope_directive(corrector, node)
|
188
|
+
range = range_with_surrounding_space(node.source_range, side: :left, newlines: false)
|
189
|
+
indentation = range.source.match(/^\s*/)[0]
|
190
|
+
directive = "\n#{indentation}# @!scope class"
|
191
|
+
|
192
|
+
corrector.insert_after(node, directive)
|
193
|
+
end
|
194
|
+
|
110
195
|
def pattern_arguments(pattern)
|
111
196
|
arguments = %w[node]
|
112
197
|
max_pattern_var = pattern.scan(/(?<=%)\d+/).map(&:to_i).max
|
@@ -134,12 +219,21 @@ module RuboCop
|
|
134
219
|
end
|
135
220
|
end
|
136
221
|
|
137
|
-
def
|
138
|
-
correct = "@!method #{actual_name}"
|
139
|
-
|
222
|
+
def correct_method_directive(corrector, directive, actual_name)
|
223
|
+
correct = "@!method #{remove_receiver(actual_name)}"
|
224
|
+
current_name = (directive[:receiver] || '') + directive[:method_name]
|
225
|
+
regexp = /@!method\s+#{Regexp.escape(current_name)}/
|
226
|
+
|
227
|
+
replacement = directive[:node_method].text.gsub(regexp, correct)
|
228
|
+
corrector.replace(directive[:node_method], replacement)
|
229
|
+
end
|
140
230
|
|
141
|
-
|
142
|
-
|
231
|
+
def remove_scope_directive(corrector, directive)
|
232
|
+
range = range_by_whole_lines(
|
233
|
+
directive[:node_scope].source_range,
|
234
|
+
include_final_newline: true
|
235
|
+
)
|
236
|
+
corrector.remove(range)
|
143
237
|
end
|
144
238
|
end
|
145
239
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module InternalAffairs
|
6
|
+
# Checks for redundant arguments of `RuboCop::RSpec::ExpectOffense`'s methods.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# expect_no_offenses('code', keyword: keyword)
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# expect_no_offenses('code')
|
15
|
+
#
|
16
|
+
class RedundantExpectOffenseArguments < Base
|
17
|
+
extend AutoCorrector
|
18
|
+
|
19
|
+
MSG = 'Remove the redundant arguments.'
|
20
|
+
RESTRICT_ON_SEND = %i[expect_no_offenses].freeze
|
21
|
+
|
22
|
+
def on_send(node)
|
23
|
+
return if node.arguments.one? || !node.arguments[1]&.hash_type?
|
24
|
+
|
25
|
+
range = node.first_argument.source_range.end.join(node.last_argument.source_range.end)
|
26
|
+
|
27
|
+
add_offense(range) do |corrector|
|
28
|
+
corrector.remove(range)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -20,6 +20,7 @@ require_relative 'internal_affairs/offense_location_keyword'
|
|
20
20
|
require_relative 'internal_affairs/processed_source_buffer_name'
|
21
21
|
require_relative 'internal_affairs/redundant_context_config_parameter'
|
22
22
|
require_relative 'internal_affairs/redundant_described_class_as_subject'
|
23
|
+
require_relative 'internal_affairs/redundant_expect_offense_arguments'
|
23
24
|
require_relative 'internal_affairs/redundant_let_rubocop_config_new'
|
24
25
|
require_relative 'internal_affairs/redundant_location_argument'
|
25
26
|
require_relative 'internal_affairs/redundant_message_argument'
|