rubocop 1.67.0 → 1.68.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 +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)
|