rubocop 1.67.0 → 1.68.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/config/default.yml +40 -0
- data/lib/rubocop/cached_data.rb +12 -4
- data/lib/rubocop/cli/command/execute_runner.rb +1 -1
- data/lib/rubocop/cli/command/version.rb +2 -2
- data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
- data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
- data/lib/rubocop/cop/layout/leading_comment_space.rb +29 -1
- data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
- data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
- data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +7 -0
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +3 -1
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
- data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
- data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/offense.rb +2 -3
- data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
- data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
- data/lib/rubocop/cop/style/block_delimiters.rb +17 -2
- data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
- data/lib/rubocop/cop/style/endless_method.rb +1 -14
- data/lib/rubocop/cop/style/guard_clause.rb +14 -1
- data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
- data/lib/rubocop/cop/style/map_into_array.rb +6 -1
- data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +20 -2
- data/lib/rubocop/cop/style/redundant_parentheses.rb +8 -10
- data/lib/rubocop/cop/style/safe_navigation.rb +12 -0
- data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
- data/lib/rubocop/cop/style/ternary_parentheses.rb +25 -4
- data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
- data/lib/rubocop/cop/variable_force/branch.rb +1 -1
- data/lib/rubocop/cop/variable_force/variable.rb +5 -1
- data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
- data/lib/rubocop/cops_documentation_generator.rb +11 -9
- data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
- data/lib/rubocop/runner.rb +16 -8
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +27 -8
- data/lib/rubocop.rb +8 -0
- metadata +15 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f0623068e9eac24fe4b1f5c7e4987d3a8adc7a2ec7f5f65c6cfb9d0ad9d1278
|
|
4
|
+
data.tar.gz: 9abb03dde6881cb7c11a21ee947c33a7d8874b9893f81e81cd7617f52f1bb714
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3392244d7137845de4f7c9867695350666750b349879ac4796590d090b0b1a182f21fb9c96f6d284bfd9eaf21a7594f8b7de2d59da62458400a709430bb6f7a2
|
|
7
|
+
data.tar.gz: e2a5a3dd61755c091a91ea99828846fa875b3b7ca8ffaea67425245755e147159691c587a7a80089a428480b7ad07fef8da1be7dc209ac0a60bb46cff3a37d0f
|
data/README.md
CHANGED
|
@@ -52,7 +52,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
|
|
|
52
52
|
in your `Gemfile`:
|
|
53
53
|
|
|
54
54
|
```rb
|
|
55
|
-
gem 'rubocop', '~> 1.
|
|
55
|
+
gem 'rubocop', '~> 1.68', require: false
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
|
data/config/default.yml
CHANGED
|
@@ -1038,6 +1038,7 @@ Layout/LeadingCommentSpace:
|
|
|
1038
1038
|
AllowDoxygenCommentStyle: false
|
|
1039
1039
|
AllowGemfileRubyComment: false
|
|
1040
1040
|
AllowRBSInlineAnnotation: false
|
|
1041
|
+
AllowSteepAnnotation: false
|
|
1041
1042
|
|
|
1042
1043
|
Layout/LeadingEmptyLines:
|
|
1043
1044
|
Description: Check for unnecessary blank lines at the beginning of a file.
|
|
@@ -1785,6 +1786,7 @@ Lint/DuplicateBranch:
|
|
|
1785
1786
|
VersionChanged: '1.7'
|
|
1786
1787
|
IgnoreLiteralBranches: false
|
|
1787
1788
|
IgnoreConstantBranches: false
|
|
1789
|
+
IgnoreDuplicateElseBranch: false
|
|
1788
1790
|
|
|
1789
1791
|
Lint/DuplicateCaseCondition:
|
|
1790
1792
|
Description: 'Do not repeat values in case conditionals.'
|
|
@@ -2455,6 +2457,11 @@ Lint/UnderscorePrefixedVariableName:
|
|
|
2455
2457
|
VersionAdded: '0.21'
|
|
2456
2458
|
AllowKeywordBlockArguments: false
|
|
2457
2459
|
|
|
2460
|
+
Lint/UnescapedBracketInRegexp:
|
|
2461
|
+
Description: 'Checks for unescaped literal `]` in Regexp.'
|
|
2462
|
+
Enabled: pending
|
|
2463
|
+
VersionAdded: '1.68'
|
|
2464
|
+
|
|
2458
2465
|
Lint/UnexpectedBlockArity:
|
|
2459
2466
|
Description: 'Looks for blocks that have fewer arguments that the calling method expects.'
|
|
2460
2467
|
Enabled: pending
|
|
@@ -3143,6 +3150,12 @@ Style/Alias:
|
|
|
3143
3150
|
- prefer_alias
|
|
3144
3151
|
- prefer_alias_method
|
|
3145
3152
|
|
|
3153
|
+
Style/AmbiguousEndlessMethodDefinition:
|
|
3154
|
+
Description: 'Checks for endless methods inside operators of lower precedence.'
|
|
3155
|
+
StyleGuide: '#ambiguous-endless-method-defintions'
|
|
3156
|
+
Enabled: pending
|
|
3157
|
+
VersionAdded: '1.68'
|
|
3158
|
+
|
|
3146
3159
|
Style/AndOr:
|
|
3147
3160
|
Description: 'Use &&/|| instead of and/or.'
|
|
3148
3161
|
StyleGuide: '#no-and-or-or'
|
|
@@ -3250,6 +3263,13 @@ Style/BisectedAttrAccessor:
|
|
|
3250
3263
|
Enabled: true
|
|
3251
3264
|
VersionAdded: '0.87'
|
|
3252
3265
|
|
|
3266
|
+
Style/BitwisePredicate:
|
|
3267
|
+
Description: 'Prefer bitwise predicate methods over direct comparison operations.'
|
|
3268
|
+
StyleGuide: '#bitwise-predicate-methods'
|
|
3269
|
+
Enabled: pending
|
|
3270
|
+
Safe: false
|
|
3271
|
+
VersionAdded: '1.68'
|
|
3272
|
+
|
|
3253
3273
|
Style/BlockComments:
|
|
3254
3274
|
Description: 'Do not use block comments.'
|
|
3255
3275
|
StyleGuide: '#no-block-comments'
|
|
@@ -3533,6 +3553,11 @@ Style/ColonMethodDefinition:
|
|
|
3533
3553
|
Enabled: true
|
|
3534
3554
|
VersionAdded: '0.52'
|
|
3535
3555
|
|
|
3556
|
+
Style/CombinableDefined:
|
|
3557
|
+
Description: 'Checks successive `defined?` calls that can be combined into a single call.'
|
|
3558
|
+
Enabled: pending
|
|
3559
|
+
VersionAdded: '1.68'
|
|
3560
|
+
|
|
3536
3561
|
Style/CombinableLoops:
|
|
3537
3562
|
Description: >-
|
|
3538
3563
|
Checks for places where multiple consecutive loops over the same data
|
|
@@ -4256,6 +4281,14 @@ Style/IpAddresses:
|
|
|
4256
4281
|
- '**/gems.rb'
|
|
4257
4282
|
- '**/*.gemspec'
|
|
4258
4283
|
|
|
4284
|
+
Style/KeywordArgumentsMerging:
|
|
4285
|
+
Description: >-
|
|
4286
|
+
When passing an existing hash as keyword arguments, provide additional arguments
|
|
4287
|
+
directly rather than using `merge`.
|
|
4288
|
+
StyleGuide: '#merging-keyword-arguments'
|
|
4289
|
+
Enabled: pending
|
|
4290
|
+
VersionAdded: '1.68'
|
|
4291
|
+
|
|
4259
4292
|
Style/KeywordParametersOrder:
|
|
4260
4293
|
Description: 'Enforces that optional keyword parameters are placed at the end of the parameters list.'
|
|
4261
4294
|
StyleGuide: '#keyword-parameters-order'
|
|
@@ -5241,6 +5274,13 @@ Style/SafeNavigation:
|
|
|
5241
5274
|
# Maximum length of method chains for register an offense.
|
|
5242
5275
|
MaxChainLength: 2
|
|
5243
5276
|
|
|
5277
|
+
Style/SafeNavigationChainLength:
|
|
5278
|
+
Description: 'Enforces safe navigation chains length to not exceed the configured maximum.'
|
|
5279
|
+
StyleGuide: '#safe-navigation'
|
|
5280
|
+
Enabled: pending
|
|
5281
|
+
VersionAdded: '1.68'
|
|
5282
|
+
Max: 2
|
|
5283
|
+
|
|
5244
5284
|
Style/Sample:
|
|
5245
5285
|
Description: >-
|
|
5246
5286
|
Use `sample` instead of `shuffle.first`,
|
data/lib/rubocop/cached_data.rb
CHANGED
|
@@ -45,15 +45,13 @@ module RuboCop
|
|
|
45
45
|
|
|
46
46
|
# Restore an offense object loaded from a JSON file.
|
|
47
47
|
def deserialize_offenses(offenses)
|
|
48
|
-
source_buffer = Parser::Source::Buffer.new(@filename)
|
|
49
|
-
source_buffer.source = File.read(@filename, encoding: Encoding::UTF_8)
|
|
50
48
|
offenses.map! do |o|
|
|
51
|
-
location = location_from_source_buffer(o
|
|
49
|
+
location = location_from_source_buffer(o)
|
|
52
50
|
Cop::Offense.new(o['severity'], location, o['message'], o['cop_name'], o['status'].to_sym)
|
|
53
51
|
end
|
|
54
52
|
end
|
|
55
53
|
|
|
56
|
-
def location_from_source_buffer(offense
|
|
54
|
+
def location_from_source_buffer(offense)
|
|
57
55
|
begin_pos = offense['location']['begin_pos']
|
|
58
56
|
end_pos = offense['location']['end_pos']
|
|
59
57
|
if begin_pos.zero? && end_pos.zero?
|
|
@@ -62,5 +60,15 @@ module RuboCop
|
|
|
62
60
|
Parser::Source::Range.new(source_buffer, begin_pos, end_pos)
|
|
63
61
|
end
|
|
64
62
|
end
|
|
63
|
+
|
|
64
|
+
# Delay creation until needed. Some type of offenses will have no buffer associated with them
|
|
65
|
+
# and be global only. For these, trying to create the buffer will likely fail, for example
|
|
66
|
+
# because of unknown encoding comments.
|
|
67
|
+
def source_buffer
|
|
68
|
+
@source_buffer ||= begin
|
|
69
|
+
source = File.read(@filename, encoding: Encoding::UTF_8)
|
|
70
|
+
Parser::Source::Buffer.new(@filename, source: source)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
65
73
|
end
|
|
66
74
|
end
|
|
@@ -9,8 +9,8 @@ module RuboCop
|
|
|
9
9
|
self.command_name = :version
|
|
10
10
|
|
|
11
11
|
def run
|
|
12
|
-
puts RuboCop::Version
|
|
13
|
-
puts RuboCop::Version.
|
|
12
|
+
puts RuboCop::Version::STRING if @options[:version]
|
|
13
|
+
puts RuboCop::Version.verbose(env: env) if @options[:verbose_version]
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -49,7 +49,9 @@ module RuboCop
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
51
|
def disable_offense(offense_range)
|
|
52
|
-
range = surrounding_heredoc(offense_range) ||
|
|
52
|
+
range = surrounding_heredoc(offense_range) ||
|
|
53
|
+
surrounding_percent_array(offense_range) ||
|
|
54
|
+
string_continuation(offense_range)
|
|
53
55
|
|
|
54
56
|
if range
|
|
55
57
|
disable_offense_before_and_after(range_by_lines(range))
|
|
@@ -88,10 +90,28 @@ module RuboCop
|
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
percent_array.map(&:source_range).find do |range|
|
|
91
|
-
offense_range
|
|
93
|
+
range_overlaps_offense?(offense_range, range)
|
|
92
94
|
end
|
|
93
95
|
end
|
|
94
96
|
|
|
97
|
+
def string_continuation(offense_range)
|
|
98
|
+
return nil if offense_range.empty?
|
|
99
|
+
|
|
100
|
+
string_continuation_nodes = processed_source.ast.each_descendant.filter_map do |node|
|
|
101
|
+
range_by_lines(node.source_range) if string_continuation?(node)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
string_continuation_nodes.find { |range| range_overlaps_offense?(offense_range, range) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def range_overlaps_offense?(offense_range, range)
|
|
108
|
+
offense_range.begin_pos > range.begin_pos && range.overlaps?(offense_range)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def string_continuation?(node)
|
|
112
|
+
(node.str_type? || node.dstr_type? || node.xstr_type?) && node.source.match?(/\\\s*$/)
|
|
113
|
+
end
|
|
114
|
+
|
|
95
115
|
def range_of_first_line(range)
|
|
96
116
|
begin_of_first_line = range.begin_pos - range.column
|
|
97
117
|
end_of_first_line = begin_of_first_line + range.source_line.length
|
|
@@ -47,7 +47,7 @@ module RuboCop
|
|
|
47
47
|
if column_delta.positive? && range.resize(1).source != "\n"
|
|
48
48
|
corrector.insert_before(range, ' ' * column_delta)
|
|
49
49
|
elsif /\A[ \t]+\z/.match?(range.source)
|
|
50
|
-
remove(range
|
|
50
|
+
corrector.remove(range)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -96,17 +96,6 @@ module RuboCop
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
def remove(range, corrector)
|
|
100
|
-
original_stderr = $stderr
|
|
101
|
-
$stderr = StringIO.new # Avoid error messages on console
|
|
102
|
-
corrector.remove(range)
|
|
103
|
-
rescue RuntimeError
|
|
104
|
-
range = range_between(range.begin_pos + 1, range.end_pos + 1)
|
|
105
|
-
retry if /^ +$/.match?(range.source)
|
|
106
|
-
ensure
|
|
107
|
-
$stderr = original_stderr
|
|
108
|
-
end
|
|
109
|
-
|
|
110
99
|
def each_line(expr)
|
|
111
100
|
line_begin_pos = expr.begin_pos
|
|
112
101
|
expr.source.each_line do |line|
|
|
@@ -94,6 +94,16 @@ module RuboCop
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def substitute_escaped_delimiters(content, delimiters)
|
|
97
|
+
if delimiters.first != delimiters.last
|
|
98
|
+
# With different delimiters (eg. `[]`, `()`), if there are the same
|
|
99
|
+
# number of each, escaping is not necessary
|
|
100
|
+
delimiter_counts = delimiters.each_with_object({}) do |delimiter, counts|
|
|
101
|
+
counts[delimiter] = content.count(delimiter)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
return content if delimiter_counts[delimiters.first] == delimiter_counts[delimiters.last]
|
|
105
|
+
end
|
|
106
|
+
|
|
97
107
|
delimiters.each { |delim| content.gsub!(delim, "\\#{delim}") }
|
|
98
108
|
end
|
|
99
109
|
|
|
@@ -67,19 +67,39 @@ module RuboCop
|
|
|
67
67
|
# attr_reader :name #: String
|
|
68
68
|
# attr_reader :age #: Integer?
|
|
69
69
|
#
|
|
70
|
+
# @example AllowSteepAnnotation: false (default)
|
|
71
|
+
#
|
|
72
|
+
# # bad
|
|
73
|
+
# [1, 2, 3].each_with_object([]) do |n, list| #$ Array[Integer]
|
|
74
|
+
# list << n
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# name = 'John' #: String
|
|
78
|
+
#
|
|
79
|
+
# @example AllowSteepAnnotation: true
|
|
80
|
+
#
|
|
81
|
+
# # good
|
|
82
|
+
#
|
|
83
|
+
# [1, 2, 3].each_with_object([]) do |n, list| #$ Array[Integer]
|
|
84
|
+
# list << n
|
|
85
|
+
# end
|
|
86
|
+
#
|
|
87
|
+
# name = 'John' #: String
|
|
88
|
+
#
|
|
70
89
|
class LeadingCommentSpace < Base
|
|
71
90
|
include RangeHelp
|
|
72
91
|
extend AutoCorrector
|
|
73
92
|
|
|
74
93
|
MSG = 'Missing space after `#`.'
|
|
75
94
|
|
|
76
|
-
def on_new_investigation # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
95
|
+
def on_new_investigation # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
77
96
|
processed_source.comments.each do |comment|
|
|
78
97
|
next unless /\A(?!#\+\+|#--)(#+[^#\s=])/.match?(comment.text)
|
|
79
98
|
next if comment.loc.line == 1 && allowed_on_first_line?(comment)
|
|
80
99
|
next if doxygen_comment_style?(comment)
|
|
81
100
|
next if gemfile_ruby_comment?(comment)
|
|
82
101
|
next if rbs_inline_annotation?(comment)
|
|
102
|
+
next if steep_annotation?(comment)
|
|
83
103
|
|
|
84
104
|
add_offense(comment) do |corrector|
|
|
85
105
|
expr = comment.source_range
|
|
@@ -142,6 +162,14 @@ module RuboCop
|
|
|
142
162
|
def rbs_inline_annotation?(comment)
|
|
143
163
|
allow_rbs_inline_annotation? && comment.text.start_with?(/#:|#\[.+\]/)
|
|
144
164
|
end
|
|
165
|
+
|
|
166
|
+
def allow_steep_annotation?
|
|
167
|
+
cop_config['AllowSteepAnnotation']
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def steep_annotation?(comment)
|
|
171
|
+
allow_steep_annotation? && comment.text.start_with?(/#[$:]/)
|
|
172
|
+
end
|
|
145
173
|
end
|
|
146
174
|
end
|
|
147
175
|
end
|
|
@@ -33,12 +33,12 @@ module RuboCop
|
|
|
33
33
|
private
|
|
34
34
|
|
|
35
35
|
def offense_range(node, begin_pos)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return if dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
|
|
36
|
+
receiver_end_pos = node.receiver.source_range.end_pos
|
|
37
|
+
selector_begin_pos = node.loc.selector.begin_pos
|
|
38
|
+
return if receiver_end_pos >= selector_begin_pos
|
|
39
|
+
return if dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
|
|
41
40
|
|
|
41
|
+
if reference_variable_with_brackets?(node)
|
|
42
42
|
range_between(receiver_end_pos, selector_begin_pos)
|
|
43
43
|
elsif node.method?(:[]=)
|
|
44
44
|
offense_range_for_assignment(node, begin_pos)
|
|
@@ -15,6 +15,9 @@ module RuboCop
|
|
|
15
15
|
# With `IgnoreConstantBranches: true`, branches are not registered
|
|
16
16
|
# as offenses if they return a constant value.
|
|
17
17
|
#
|
|
18
|
+
# With `IgnoreDuplicateElseBranch: true`, in conditionals with multiple branches,
|
|
19
|
+
# duplicate 'else' branches are not registered as offenses.
|
|
20
|
+
#
|
|
18
21
|
# @example
|
|
19
22
|
# # bad
|
|
20
23
|
# if foo
|
|
@@ -83,21 +86,37 @@ module RuboCop
|
|
|
83
86
|
# else MEDIUM_SIZE
|
|
84
87
|
# end
|
|
85
88
|
#
|
|
89
|
+
# @example IgnoreDuplicateElseBranch: true
|
|
90
|
+
# # good
|
|
91
|
+
# if foo
|
|
92
|
+
# do_foo
|
|
93
|
+
# elsif bar
|
|
94
|
+
# do_bar
|
|
95
|
+
# else
|
|
96
|
+
# do_foo
|
|
97
|
+
# end
|
|
98
|
+
#
|
|
86
99
|
class DuplicateBranch < Base
|
|
87
100
|
MSG = 'Duplicate branch body detected.'
|
|
88
101
|
|
|
89
102
|
def on_branching_statement(node)
|
|
90
|
-
branches(node)
|
|
91
|
-
|
|
103
|
+
branches = branches(node)
|
|
104
|
+
branches.each_with_object(Set.new) do |branch, previous|
|
|
105
|
+
next unless consider_branch?(branches, branch)
|
|
92
106
|
|
|
93
107
|
add_offense(offense_range(branch)) unless previous.add?(branch)
|
|
94
108
|
end
|
|
95
109
|
end
|
|
96
|
-
alias on_if on_branching_statement
|
|
97
110
|
alias on_case on_branching_statement
|
|
98
111
|
alias on_case_match on_branching_statement
|
|
99
112
|
alias on_rescue on_branching_statement
|
|
100
113
|
|
|
114
|
+
def on_if(node)
|
|
115
|
+
# Ignore 'elsif' nodes, because we don't want to check them separately whether
|
|
116
|
+
# the 'else' branch is duplicated. We want to check only on the outermost conditional.
|
|
117
|
+
on_branching_statement(node) unless node.elsif?
|
|
118
|
+
end
|
|
119
|
+
|
|
101
120
|
private
|
|
102
121
|
|
|
103
122
|
def offense_range(duplicate_branch)
|
|
@@ -118,10 +137,14 @@ module RuboCop
|
|
|
118
137
|
node.branches.compact
|
|
119
138
|
end
|
|
120
139
|
|
|
121
|
-
def consider_branch?(branch)
|
|
140
|
+
def consider_branch?(branches, branch)
|
|
122
141
|
return false if ignore_literal_branches? && literal_branch?(branch)
|
|
123
142
|
return false if ignore_constant_branches? && const_branch?(branch)
|
|
124
143
|
|
|
144
|
+
if ignore_duplicate_else_branches? && duplicate_else_branch?(branches, branch)
|
|
145
|
+
return false
|
|
146
|
+
end
|
|
147
|
+
|
|
125
148
|
true
|
|
126
149
|
end
|
|
127
150
|
|
|
@@ -133,6 +156,10 @@ module RuboCop
|
|
|
133
156
|
cop_config.fetch('IgnoreConstantBranches', false)
|
|
134
157
|
end
|
|
135
158
|
|
|
159
|
+
def ignore_duplicate_else_branches?
|
|
160
|
+
cop_config.fetch('IgnoreDuplicateElseBranch', false)
|
|
161
|
+
end
|
|
162
|
+
|
|
136
163
|
def literal_branch?(branch) # rubocop:disable Metrics/CyclomaticComplexity
|
|
137
164
|
return false if !branch.literal? || branch.xstr_type?
|
|
138
165
|
return true if branch.basic_literal?
|
|
@@ -147,6 +174,14 @@ module RuboCop
|
|
|
147
174
|
def const_branch?(branch)
|
|
148
175
|
branch.const_type?
|
|
149
176
|
end
|
|
177
|
+
|
|
178
|
+
def duplicate_else_branch?(branches, branch)
|
|
179
|
+
return false unless (parent = branch.parent)
|
|
180
|
+
|
|
181
|
+
branches.size > 2 &&
|
|
182
|
+
branch.equal?(branches.last) &&
|
|
183
|
+
parent.respond_to?(:else?) && parent.else?
|
|
184
|
+
end
|
|
150
185
|
end
|
|
151
186
|
end
|
|
152
187
|
end
|
|
@@ -134,6 +134,7 @@ module RuboCop
|
|
|
134
134
|
|
|
135
135
|
corrector.replace(node.child_nodes.first.loc.name, 'FileUtils')
|
|
136
136
|
corrector.replace(node.loc.selector, replacement_method(node))
|
|
137
|
+
corrector.insert_before(node.last_argument, 'mode: ') if require_mode_keyword?(node)
|
|
137
138
|
end
|
|
138
139
|
|
|
139
140
|
def replacement_method(node)
|
|
@@ -152,6 +153,12 @@ module RuboCop
|
|
|
152
153
|
force_method_name?(node) || force_option?(node)
|
|
153
154
|
end
|
|
154
155
|
|
|
156
|
+
def require_mode_keyword?(node)
|
|
157
|
+
return false unless node.receiver.const_name == 'Dir'
|
|
158
|
+
|
|
159
|
+
replacement_method(node) == 'mkdir_p' && node.arguments.length == 2
|
|
160
|
+
end
|
|
161
|
+
|
|
155
162
|
def force_option?(node)
|
|
156
163
|
node.arguments.any? { |arg| force?(arg) }
|
|
157
164
|
end
|
|
@@ -38,6 +38,8 @@ module RuboCop
|
|
|
38
38
|
PATTERN
|
|
39
39
|
|
|
40
40
|
def on_send(node)
|
|
41
|
+
return unless require_safe_navigation?(node)
|
|
42
|
+
|
|
41
43
|
bad_method?(node) do |safe_nav, method|
|
|
42
44
|
return if nil_methods.include?(method) || PLUS_MINUS_METHODS.include?(node.method_name)
|
|
43
45
|
|
|
@@ -52,6 +54,13 @@ module RuboCop
|
|
|
52
54
|
|
|
53
55
|
private
|
|
54
56
|
|
|
57
|
+
def require_safe_navigation?(node)
|
|
58
|
+
parent = node.parent
|
|
59
|
+
return true unless parent&.and_type?
|
|
60
|
+
|
|
61
|
+
parent.rhs != node || parent.lhs.receiver != parent.rhs.receiver
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
# @param [Parser::Source::Range] offense_range
|
|
56
65
|
# @param [RuboCop::AST::SendNode] send_node
|
|
57
66
|
# @return [String]
|
|
@@ -94,7 +94,9 @@ module RuboCop
|
|
|
94
94
|
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
95
95
|
|
|
96
96
|
def already_appropriate_call?(operand, dot_op)
|
|
97
|
-
|
|
97
|
+
return true if operand.safe_navigation? && dot_op == '&.'
|
|
98
|
+
|
|
99
|
+
(operand.dot? || operand.operator_method?) && dot_op == '.'
|
|
98
100
|
end
|
|
99
101
|
|
|
100
102
|
def register_offense(operand, dot_operator)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Lint
|
|
6
|
+
# Checks for Regexpes (both literals and via `Regexp.new` / `Regexp.compile`)
|
|
7
|
+
# that contain unescaped `]` characters.
|
|
8
|
+
#
|
|
9
|
+
# It emulates the following Ruby warning:
|
|
10
|
+
#
|
|
11
|
+
# [source,ruby]
|
|
12
|
+
# ----
|
|
13
|
+
# $ ruby -e '/abc]123/'
|
|
14
|
+
# -e:1: warning: regular expression has ']' without escape: /abc]123/
|
|
15
|
+
# ----
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# # bad
|
|
19
|
+
# /abc]123/
|
|
20
|
+
# %r{abc]123}
|
|
21
|
+
# Regexp.new('abc]123')
|
|
22
|
+
# Regexp.compile('abc]123')
|
|
23
|
+
#
|
|
24
|
+
# # good
|
|
25
|
+
# /abc\]123/
|
|
26
|
+
# %r{abc\]123}
|
|
27
|
+
# Regexp.new('abc\]123')
|
|
28
|
+
# Regexp.compile('abc\]123')
|
|
29
|
+
#
|
|
30
|
+
class UnescapedBracketInRegexp < Base
|
|
31
|
+
extend AutoCorrector
|
|
32
|
+
|
|
33
|
+
MSG = 'Regular expression has `]` without escape.'
|
|
34
|
+
RESTRICT_ON_SEND = %i[new compile].freeze
|
|
35
|
+
|
|
36
|
+
# @!method regexp_constructor(node)
|
|
37
|
+
def_node_search :regexp_constructor, <<~PATTERN
|
|
38
|
+
(send
|
|
39
|
+
(const {nil? cbase} :Regexp) {:new :compile}
|
|
40
|
+
$str
|
|
41
|
+
...
|
|
42
|
+
)
|
|
43
|
+
PATTERN
|
|
44
|
+
|
|
45
|
+
def on_regexp(node)
|
|
46
|
+
RuboCop::Util.silence_warnings do
|
|
47
|
+
node.parsed_tree&.each_expression do |expr|
|
|
48
|
+
detect_offenses(node, expr)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def on_send(node)
|
|
54
|
+
# Ignore nodes that contain interpolation
|
|
55
|
+
return if node.each_descendant(:dstr).any?
|
|
56
|
+
|
|
57
|
+
regexp_constructor(node) do |text|
|
|
58
|
+
Regexp::Parser.parse(text.value)&.each_expression do |expr|
|
|
59
|
+
detect_offenses(text, expr)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def detect_offenses(node, expr)
|
|
67
|
+
return unless expr.type?(:literal)
|
|
68
|
+
|
|
69
|
+
expr.text.scan(/(?<!\\)\]/) do
|
|
70
|
+
pos = Regexp.last_match.begin(0)
|
|
71
|
+
next if pos.zero? # if the unescaped bracket is the first character, Ruby does not warn
|
|
72
|
+
|
|
73
|
+
location = range_at_index(node, expr.ts, pos)
|
|
74
|
+
|
|
75
|
+
add_offense(location) do |corrector|
|
|
76
|
+
corrector.replace(location, '\]')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def range_at_index(node, index, offset)
|
|
82
|
+
adjustment = index + offset
|
|
83
|
+
node.loc.begin.end.adjust(begin_pos: adjustment, end_pos: adjustment + 1)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -14,11 +14,14 @@ module RuboCop
|
|
|
14
14
|
# and ||/or is shorthand for a sequence of ifs, so they also add one.
|
|
15
15
|
# Loops can be said to have an exit condition, so they add one.
|
|
16
16
|
# Blocks that are calls to builtin iteration methods
|
|
17
|
-
# (e.g. `ary.map{...}) also add one, others are ignored.
|
|
17
|
+
# (e.g. `ary.map{...}`) also add one, others are ignored.
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
18
20
|
#
|
|
19
21
|
# def each_child_node(*types) # count begins: 1
|
|
20
22
|
# unless block_given? # unless: +1
|
|
21
23
|
# return to_enum(__method__, *types)
|
|
24
|
+
# end
|
|
22
25
|
#
|
|
23
26
|
# children.each do |child| # each{}: +1
|
|
24
27
|
# next unless child.is_a?(Node) # unless: +1
|
|
@@ -44,6 +44,8 @@ module RuboCop
|
|
|
44
44
|
module CheckLineBreakable
|
|
45
45
|
def extract_breakable_node(node, max)
|
|
46
46
|
if node.send_type?
|
|
47
|
+
return if chained_to_heredoc?(node)
|
|
48
|
+
|
|
47
49
|
args = process_args(node.arguments)
|
|
48
50
|
return extract_breakable_node_from_elements(node, args, max)
|
|
49
51
|
elsif node.def_type?
|
|
@@ -222,6 +224,14 @@ module RuboCop
|
|
|
222
224
|
|
|
223
225
|
!node.single_line?
|
|
224
226
|
end
|
|
227
|
+
|
|
228
|
+
def chained_to_heredoc?(node)
|
|
229
|
+
while (node = node.receiver)
|
|
230
|
+
return true if (node.str_type? || node.dstr_type? || node.xstr_type?) && node.heredoc?
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
false
|
|
234
|
+
end
|
|
225
235
|
end
|
|
226
236
|
end
|
|
227
237
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
# Common functionality for rewriting endless methods to normal method definitions
|
|
6
|
+
module EndlessMethodRewriter
|
|
7
|
+
def correct_to_multiline(corrector, node)
|
|
8
|
+
replacement = <<~RUBY.strip
|
|
9
|
+
def #{node.method_name}#{arguments(node)}
|
|
10
|
+
#{node.body.source}
|
|
11
|
+
end
|
|
12
|
+
RUBY
|
|
13
|
+
|
|
14
|
+
corrector.replace(node, replacement)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def arguments(node, missing = '')
|
|
20
|
+
node.arguments.any? ? node.arguments.source : missing
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -29,7 +29,9 @@ module RuboCop
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def uninterpolated_string?(node)
|
|
32
|
-
node.str_type? || (
|
|
32
|
+
node.str_type? || (
|
|
33
|
+
node.dstr_type? && node.each_descendant(:begin, :ivar, :cvar, :gvar).none?
|
|
34
|
+
)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def uninterpolated_heredoc?(node)
|
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
|
48
48
|
MSG = 'Use %<style>s block forwarding.'
|
|
49
49
|
|
|
50
50
|
def self.autocorrect_incompatible_with
|
|
51
|
-
[Lint::AmbiguousOperator, Style::ArgumentsForwarding]
|
|
51
|
+
[Lint::AmbiguousOperator, Style::ArgumentsForwarding, Style::ExplicitBlockArgument]
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def on_def(node)
|