rubocop 1.62.0 → 1.63.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +23 -3
- data/lib/rubocop/cli/command/auto_generate_config.rb +12 -3
- data/lib/rubocop/config.rb +33 -10
- data/lib/rubocop/config_obsoletion.rb +1 -1
- data/lib/rubocop/cop/base.rb +37 -0
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -1
- data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -0
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +122 -28
- data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +14 -7
- data/lib/rubocop/cop/layout/redundant_line_break.rb +8 -2
- 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/redundant_with_index.rb +4 -0
- data/lib/rubocop/cop/lint/useless_times.rb +1 -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/style/alias.rb +1 -0
- 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/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/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/multiline_method_signature.rb +10 -1
- data/lib/rubocop/cop/style/nil_comparison.rb +2 -0
- data/lib/rubocop/cop/style/raise_args.rb +1 -1
- data/lib/rubocop/cop/style/redundant_argument.rb +24 -1
- 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 +8 -14
- data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
- data/lib/rubocop/cop/team.rb +3 -0
- data/lib/rubocop/cop/utils/regexp_ranges.rb +1 -1
- data/lib/rubocop/directive_comment.rb +10 -8
- data/lib/rubocop/formatter/clang_style_formatter.rb +3 -7
- data/lib/rubocop/formatter/tap_formatter.rb +3 -7
- data/lib/rubocop/lockfile.rb +34 -4
- data/lib/rubocop/rspec/expect_offense.rb +15 -7
- data/lib/rubocop/rspec/shared_contexts.rb +13 -1
- data/lib/rubocop/runner.rb +3 -0
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +2 -2
- data/lib/rubocop.rb +1 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8eadae0a09b33bf6a4ae3621ad1b86ba4eff0e7601d3ceb89dcf115759e9c022
|
4
|
+
data.tar.gz: ae113bb4d3eab63b4a551e36541f14d4e3bf3aae3354b853fb45960e80b90bbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6343643a7515b53e3e880a6cb92e256f37a3c1ee42c757af0506bf6457d900d36ea95b48bef2c6e008b5a96ba957f28b448a8e62da10ef1c27e6fe88dfbc1935
|
7
|
+
data.tar.gz: c67f434f594eb5b3f609d1e36140c04a6c79ce784b2d614c0e57ed4d22f7bea437a4edf89de0b976c425c7a2fcaf54b46ce66391eb9e1b61f60fa03bd46fdebc
|
data/README.md
CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
|
|
53
53
|
in your `Gemfile`:
|
54
54
|
|
55
55
|
```rb
|
56
|
-
gem 'rubocop', '~> 1.
|
56
|
+
gem 'rubocop', '~> 1.63', require: false
|
57
57
|
```
|
58
58
|
|
59
59
|
See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
|
data/config/default.yml
CHANGED
@@ -156,13 +156,14 @@ AllCops:
|
|
156
156
|
# included.
|
157
157
|
SuggestExtensions:
|
158
158
|
rubocop-rails: [rails]
|
159
|
-
rubocop-rspec: [rspec
|
159
|
+
rubocop-rspec: [rspec]
|
160
160
|
rubocop-minitest: [minitest]
|
161
161
|
rubocop-sequel: [sequel]
|
162
162
|
rubocop-rake: [rake]
|
163
163
|
rubocop-graphql: [graphql]
|
164
164
|
rubocop-capybara: [capybara]
|
165
165
|
rubocop-factory_bot: [factory_bot, factory_bot_rails]
|
166
|
+
rubocop-rspec_rails: [rspec-rails]
|
166
167
|
# Enable/Disable checking the methods extended by Active Support.
|
167
168
|
ActiveSupportExtensionsEnabled: false
|
168
169
|
|
@@ -1657,7 +1658,7 @@ Lint/Debugger:
|
|
1657
1658
|
Description: 'Check for debugger calls.'
|
1658
1659
|
Enabled: true
|
1659
1660
|
VersionAdded: '0.14'
|
1660
|
-
VersionChanged: '1.
|
1661
|
+
VersionChanged: '1.63'
|
1661
1662
|
DebuggerMethods:
|
1662
1663
|
# Groups are available so that a specific group can be disabled in
|
1663
1664
|
# a user's configuration, but are otherwise not significant.
|
@@ -1670,8 +1671,14 @@ Lint/Debugger:
|
|
1670
1671
|
- Kernel.byebug
|
1671
1672
|
- Kernel.remote_byebug
|
1672
1673
|
Capybara:
|
1674
|
+
- page.save_and_open_page
|
1675
|
+
- page.save_and_open_screenshot
|
1676
|
+
- page.save_page
|
1677
|
+
- page.save_screenshot
|
1673
1678
|
- save_and_open_page
|
1674
1679
|
- save_and_open_screenshot
|
1680
|
+
- save_page
|
1681
|
+
- save_screenshot
|
1675
1682
|
debug.rb:
|
1676
1683
|
- binding.b
|
1677
1684
|
- binding.break
|
@@ -1693,6 +1700,11 @@ Lint/Debugger:
|
|
1693
1700
|
- jard
|
1694
1701
|
WebConsole:
|
1695
1702
|
- binding.console
|
1703
|
+
DebuggerRequires:
|
1704
|
+
debug.rb:
|
1705
|
+
- debug/open
|
1706
|
+
- debug/open_nonstop
|
1707
|
+
- debug/start
|
1696
1708
|
|
1697
1709
|
Lint/DeprecatedClassMethods:
|
1698
1710
|
Description: 'Check for deprecated class method calls.'
|
@@ -2762,7 +2774,8 @@ Naming/FileName:
|
|
2762
2774
|
VersionChanged: '1.23'
|
2763
2775
|
# Camel case file names listed in `AllCops:Include` and all file names listed
|
2764
2776
|
# in `AllCops:Exclude` are excluded by default. Add extra excludes here.
|
2765
|
-
Exclude:
|
2777
|
+
Exclude:
|
2778
|
+
- Rakefile.rb
|
2766
2779
|
# When `true`, requires that each source file should define a class or module
|
2767
2780
|
# with a name which matches the file name (converted to ... case).
|
2768
2781
|
# It further expects it to be nested inside modules which match the names
|
@@ -4276,6 +4289,13 @@ Style/MapCompactWithConditionalBlock:
|
|
4276
4289
|
Enabled: pending
|
4277
4290
|
VersionAdded: '1.30'
|
4278
4291
|
|
4292
|
+
Style/MapIntoArray:
|
4293
|
+
Description: 'Checks for usages of `each` with `<<`, `push`, or `append` which can be replaced by `map`.'
|
4294
|
+
StyleGuide: '#functional-code'
|
4295
|
+
Enabled: pending
|
4296
|
+
VersionAdded: '1.63'
|
4297
|
+
Safe: false
|
4298
|
+
|
4279
4299
|
Style/MapToHash:
|
4280
4300
|
Description: 'Prefer `to_h` with a block over `map.to_h`.'
|
4281
4301
|
Enabled: pending
|
@@ -17,7 +17,10 @@ module RuboCop
|
|
17
17
|
|
18
18
|
PHASE_1_OVERRIDDEN = '(skipped because the default Layout/LineLength:Max is overridden)'
|
19
19
|
PHASE_1_DISABLED = '(skipped because Layout/LineLength is disabled)'
|
20
|
-
|
20
|
+
PHASE_1_SKIPPED_ONLY_COPS =
|
21
|
+
'(skipped because a list of cops is passed to the `--only` flag)'
|
22
|
+
PHASE_1_SKIPPED_ONLY_EXCLUDE =
|
23
|
+
'(skipped because only excludes will be generated due to `--auto-gen-only-exclude` flag)'
|
21
24
|
|
22
25
|
def run
|
23
26
|
add_formatter
|
@@ -29,12 +32,14 @@ module RuboCop
|
|
29
32
|
private
|
30
33
|
|
31
34
|
def maybe_run_line_length_cop
|
32
|
-
if
|
35
|
+
if only_exclude?
|
36
|
+
skip_line_length_cop(PHASE_1_SKIPPED_ONLY_EXCLUDE)
|
37
|
+
elsif !line_length_enabled?(@config_store.for_pwd)
|
33
38
|
skip_line_length_cop(PHASE_1_DISABLED)
|
34
39
|
elsif !same_max_line_length?(@config_store.for_pwd, ConfigLoader.default_configuration)
|
35
40
|
skip_line_length_cop(PHASE_1_OVERRIDDEN)
|
36
41
|
elsif options_has_only_flag?
|
37
|
-
skip_line_length_cop(
|
42
|
+
skip_line_length_cop(PHASE_1_SKIPPED_ONLY_COPS)
|
38
43
|
else
|
39
44
|
run_line_length_cop
|
40
45
|
end
|
@@ -65,6 +70,10 @@ module RuboCop
|
|
65
70
|
@options[:only]
|
66
71
|
end
|
67
72
|
|
73
|
+
def only_exclude?
|
74
|
+
@options[:auto_gen_only_exclude]
|
75
|
+
end
|
76
|
+
|
68
77
|
# Do an initial run with only Layout/LineLength so that cops that
|
69
78
|
# depend on Layout/LineLength:Max get the correct value for that
|
70
79
|
# parameter.
|
data/lib/rubocop/config.rb
CHANGED
@@ -263,6 +263,7 @@ module RuboCop
|
|
263
263
|
PathUtil.smart_path(@loaded_path)
|
264
264
|
end
|
265
265
|
|
266
|
+
# @return [String, nil]
|
266
267
|
def bundler_lock_file_path
|
267
268
|
return nil unless loaded_path
|
268
269
|
|
@@ -286,27 +287,49 @@ module RuboCop
|
|
286
287
|
end
|
287
288
|
end
|
288
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
|
+
|
289
296
|
def inspect # :nodoc:
|
290
297
|
"#<#{self.class.name}:#{object_id} @loaded_path=#{loaded_path}>"
|
291
298
|
end
|
292
299
|
|
293
300
|
private
|
294
301
|
|
302
|
+
# @return [Float, nil] The Rails version as a `major.minor` Float.
|
295
303
|
def target_rails_version_from_bundler_lock_file
|
296
304
|
@target_rails_version_from_bundler_lock_file ||= read_rails_version_from_bundler_lock_file
|
297
305
|
end
|
298
306
|
|
307
|
+
# @return [Float, nil] The Rails version as a `major.minor` Float.
|
299
308
|
def read_rails_version_from_bundler_lock_file
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
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
|
310
333
|
end
|
311
334
|
|
312
335
|
def enable_cop?(qualified_cop_name, cop_options)
|
@@ -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)
|
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 }
|
@@ -245,6 +269,7 @@ module RuboCop
|
|
245
269
|
end
|
246
270
|
|
247
271
|
def relevant_file?(file)
|
272
|
+
return false unless target_satisfies_all_gem_version_requirements?
|
248
273
|
return true unless @config.clusivity_config_for_badge?(self.class.badge)
|
249
274
|
|
250
275
|
file == RuboCop::AST::ProcessedSource::STRING_SOURCE_NAME ||
|
@@ -496,6 +521,18 @@ module RuboCop
|
|
496
521
|
range.end_pos + @current_offset
|
497
522
|
)
|
498
523
|
end
|
524
|
+
|
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
|
535
|
+
end
|
499
536
|
end
|
500
537
|
end
|
501
538
|
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)
|
@@ -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
|
|
@@ -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
|
@@ -27,9 +27,9 @@ module RuboCop
|
|
27
27
|
MSG = 'Add an empty line after magic comments.'
|
28
28
|
|
29
29
|
def on_new_investigation
|
30
|
-
return unless processed_source
|
31
|
-
|
32
|
-
return if
|
30
|
+
return unless (last_magic_comment = last_magic_comment(processed_source))
|
31
|
+
return unless (next_line = processed_source[last_magic_comment.loc.line])
|
32
|
+
return if next_line.strip.empty?
|
33
33
|
|
34
34
|
offending_range = offending_range(last_magic_comment)
|
35
35
|
|
@@ -46,18 +46,25 @@ module RuboCop
|
|
46
46
|
|
47
47
|
# Find the last magic comment in the source file.
|
48
48
|
#
|
49
|
-
# Take all comments that precede the first line of code
|
49
|
+
# Take all comments that precede the first line of code (or just take
|
50
|
+
# them all in the case when there is no code), select the
|
50
51
|
# magic comments, and return the last magic comment in the file.
|
51
52
|
#
|
52
53
|
# @return [Parser::Source::Comment] if magic comments exist before code
|
53
54
|
# @return [nil] otherwise
|
54
55
|
def last_magic_comment(source)
|
55
|
-
source
|
56
|
-
.comments
|
57
|
-
.take_while { |comment| comment.loc.line < source.ast.loc.line }
|
56
|
+
comments_before_code(source)
|
58
57
|
.reverse
|
59
58
|
.find { |comment| MagicComment.parse(comment.text).any? }
|
60
59
|
end
|
60
|
+
|
61
|
+
def comments_before_code(source)
|
62
|
+
if source.ast
|
63
|
+
source.comments.take_while { |comment| comment.loc.line < source.ast.loc.line }
|
64
|
+
else
|
65
|
+
source.comments
|
66
|
+
end
|
67
|
+
end
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|
@@ -84,8 +84,14 @@ module RuboCop
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def offense?(node)
|
87
|
-
node.multiline?
|
88
|
-
|
87
|
+
return false if !node.multiline? || too_long?(node) || !suitable_as_single_line?(node)
|
88
|
+
return require_backslash?(node) if node.and_type? || node.or_type?
|
89
|
+
|
90
|
+
!index_access_call_chained?(node) && !configured_to_not_be_inspected?(node)
|
91
|
+
end
|
92
|
+
|
93
|
+
def require_backslash?(node)
|
94
|
+
processed_source.lines[node.loc.operator.line - 1].end_with?('\\')
|
89
95
|
end
|
90
96
|
|
91
97
|
def index_access_call_chained?(node)
|
@@ -50,7 +50,7 @@ module RuboCop
|
|
50
50
|
MSG_WITHOUT_SAFE_ASSIGNMENT_ALLOWED =
|
51
51
|
'Use `==` if you meant to do a comparison or move the assignment ' \
|
52
52
|
'up out of the condition.'
|
53
|
-
ASGN_TYPES = [:begin, *AST::Node::EQUALS_ASSIGNMENTS, :send].freeze
|
53
|
+
ASGN_TYPES = [:begin, *AST::Node::EQUALS_ASSIGNMENTS, :send, :csend].freeze
|
54
54
|
|
55
55
|
def on_if(node)
|
56
56
|
return if node.condition.block_type?
|
@@ -88,9 +88,7 @@ module RuboCop
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def skip_children?(asgn_node)
|
91
|
-
(asgn_node
|
92
|
-
empty_condition?(asgn_node) ||
|
93
|
-
(safe_assignment_allowed? && safe_assignment?(asgn_node))
|
91
|
+
empty_condition?(asgn_node) || (safe_assignment_allowed? && safe_assignment?(asgn_node))
|
94
92
|
end
|
95
93
|
|
96
94
|
def traverse_node(node, &block)
|
@@ -29,6 +29,11 @@ module RuboCop
|
|
29
29
|
# MyDebugger.debug_this
|
30
30
|
# ----
|
31
31
|
#
|
32
|
+
# Some gems also ship files that will start a debugging session when required,
|
33
|
+
# for example `require 'debug/start'` from `ruby/debug`. These requires can
|
34
|
+
# be configured through `DebuggerRequires`. It has the same structure as
|
35
|
+
# `DebuggerMethods`, which you can read about above.
|
36
|
+
#
|
32
37
|
# @example
|
33
38
|
#
|
34
39
|
# # bad (ok during development)
|
@@ -64,14 +69,20 @@ module RuboCop
|
|
64
69
|
# def some_method
|
65
70
|
# my_debugger
|
66
71
|
# end
|
72
|
+
#
|
73
|
+
# @example DebuggerRequires: [my_debugger/start]
|
74
|
+
#
|
75
|
+
# # bad (ok during development)
|
76
|
+
#
|
77
|
+
# require 'my_debugger/start'
|
67
78
|
class Debugger < Base
|
68
79
|
MSG = 'Remove debugger entry point `%<source>s`.'
|
69
80
|
BLOCK_TYPES = %i[block numblock kwbegin].freeze
|
70
81
|
|
71
82
|
def on_send(node)
|
72
|
-
return if
|
83
|
+
return if assumed_usage_context?(node)
|
73
84
|
|
74
|
-
add_offense(node)
|
85
|
+
add_offense(node) if debugger_method?(node) || debugger_require?(node)
|
75
86
|
end
|
76
87
|
|
77
88
|
private
|
@@ -87,12 +98,26 @@ module RuboCop
|
|
87
98
|
end
|
88
99
|
end
|
89
100
|
|
101
|
+
def debugger_requires
|
102
|
+
@debugger_requires ||= begin
|
103
|
+
config = cop_config.fetch('DebuggerRequires', [])
|
104
|
+
config.is_a?(Array) ? config : config.values.flatten
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
90
108
|
def debugger_method?(send_node)
|
91
109
|
return false if send_node.parent&.send_type? && send_node.parent.receiver == send_node
|
92
110
|
|
93
111
|
debugger_methods.include?(chained_method_name(send_node))
|
94
112
|
end
|
95
113
|
|
114
|
+
def debugger_require?(send_node)
|
115
|
+
return false unless send_node.method?(:require) && send_node.arguments.one?
|
116
|
+
return false unless (argument = send_node.first_argument).str_type?
|
117
|
+
|
118
|
+
debugger_requires.include?(argument.value)
|
119
|
+
end
|
120
|
+
|
96
121
|
def assumed_usage_context?(node)
|
97
122
|
# Basically, debugger methods are not used as a method argument without arguments.
|
98
123
|
return false unless node.arguments.empty? && node.each_ancestor(:send, :csend).any?
|