rubocop 0.54.0 → 0.55.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 +5 -1
- data/config/default.yml +17 -2
- data/config/enabled.yml +13 -0
- data/lib/rubocop.rb +4 -0
- data/lib/rubocop/ast/node/mixin/binary_operator_node.rb +20 -0
- data/lib/rubocop/cli.rb +6 -2
- data/lib/rubocop/cop/commissioner.rb +21 -25
- data/lib/rubocop/cop/layout/end_of_line.rb +33 -0
- data/lib/rubocop/cop/layout/space_inside_parens.rb +64 -5
- data/lib/rubocop/cop/layout/trailing_whitespace.rb +20 -0
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +80 -0
- data/lib/rubocop/cop/lint/shadowed_argument.rb +3 -0
- data/lib/rubocop/cop/lint/void.rb +20 -9
- data/lib/rubocop/cop/metrics/block_length.rb +17 -1
- data/lib/rubocop/cop/metrics/line_length.rb +2 -3
- data/lib/rubocop/cop/mixin/percent_literal.rb +9 -8
- data/lib/rubocop/cop/performance/end_with.rb +2 -1
- data/lib/rubocop/cop/performance/regexp_match.rb +43 -7
- data/lib/rubocop/cop/performance/start_with.rb +2 -1
- data/lib/rubocop/cop/performance/unneeded_sort.rb +130 -0
- data/lib/rubocop/cop/rails/http_status.rb +19 -16
- data/lib/rubocop/cop/rails/inverse_of.rb +29 -22
- data/lib/rubocop/cop/rails/read_write_attribute.rb +9 -2
- data/lib/rubocop/cop/style/array_join.rb +1 -1
- data/lib/rubocop/cop/style/class_vars.rb +5 -4
- data/lib/rubocop/cop/style/commented_keyword.rb +2 -3
- data/lib/rubocop/cop/style/empty_line_after_guard_clause.rb +39 -8
- data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +22 -11
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +5 -0
- data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
- data/lib/rubocop/cop/style/negated_while.rb +18 -0
- data/lib/rubocop/cop/style/nested_ternary_operator.rb +11 -0
- data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +17 -0
- data/lib/rubocop/cop/style/option_hash.rb +6 -0
- data/lib/rubocop/cop/style/single_line_block_params.rb +20 -0
- data/lib/rubocop/cop/style/special_global_vars.rb +52 -0
- data/lib/rubocop/cop/style/string_literals.rb +1 -1
- data/lib/rubocop/cop/style/unpack_first.rb +0 -2
- data/lib/rubocop/formatter/auto_gen_config_formatter.rb +16 -0
- data/lib/rubocop/formatter/formatter_set.rb +14 -13
- data/lib/rubocop/node_pattern.rb +2 -2
- data/lib/rubocop/options.rb +1 -0
- data/lib/rubocop/version.rb +1 -1
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6193bafb35931ae47f1a4d40c8b9919f4f8df07
|
4
|
+
data.tar.gz: 8b4c066f46a71fe4d35d84d0b1063f22130e2b4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a619c39a295a5cfdc1a2d82a51391f8d995ea04e20a865eba6951056f57766be7df10718194782f715750f0d9007c6ee730688289acd277c371fc408e137c065
|
7
|
+
data.tar.gz: 3ad640cddf0bedeaa238e9e13beef2da09f156c1bc957784c2779cc65076a7f142e0b85c113a6221e7792e491db4ebabeafea4c7424a92750523ddd55ecb4128
|
data/README.md
CHANGED
@@ -52,7 +52,7 @@ haven't reached version 1.0 yet). To prevent an unwanted RuboCop update you
|
|
52
52
|
might want to use a conservative version locking in your `Gemfile`:
|
53
53
|
|
54
54
|
```rb
|
55
|
-
gem 'rubocop', '~> 0.
|
55
|
+
gem 'rubocop', '~> 0.55.0', require: false
|
56
56
|
```
|
57
57
|
|
58
58
|
## Quickstart
|
@@ -75,6 +75,10 @@ RuboCop supports the following Ruby implementations:
|
|
75
75
|
* MRI 2.1+
|
76
76
|
* JRuby 9.0+
|
77
77
|
|
78
|
+
The Rails cops support the following versions:
|
79
|
+
|
80
|
+
* Rails 4.0+
|
81
|
+
|
78
82
|
## Team
|
79
83
|
|
80
84
|
Here's a list of RuboCop's core developers:
|
data/config/default.yml
CHANGED
@@ -604,6 +604,12 @@ Layout/SpaceInsideHashLiteralBraces:
|
|
604
604
|
- space
|
605
605
|
- no_space
|
606
606
|
|
607
|
+
Layout/SpaceInsideParens:
|
608
|
+
EnforcedStyle: no_space
|
609
|
+
SupportedStyles:
|
610
|
+
- space
|
611
|
+
- no_space
|
612
|
+
|
607
613
|
Layout/SpaceInsideReferenceBrackets:
|
608
614
|
EnforcedStyle: no_space
|
609
615
|
SupportedStyles:
|
@@ -648,6 +654,9 @@ Layout/TrailingBlankLines:
|
|
648
654
|
- final_newline
|
649
655
|
- final_blank_line
|
650
656
|
|
657
|
+
Layout/TrailingWhitespace:
|
658
|
+
AllowInHeredoc: false
|
659
|
+
|
651
660
|
#################### Naming ##########################
|
652
661
|
|
653
662
|
Naming/FileName:
|
@@ -712,8 +721,7 @@ Naming/FileName:
|
|
712
721
|
|
713
722
|
Naming/HeredocDelimiterNaming:
|
714
723
|
Blacklist:
|
715
|
-
- END
|
716
|
-
- !ruby/regexp '/EO[A-Z]{1}/'
|
724
|
+
- !ruby/regexp '/(^|\s)(EO[A-Z]{1}|END)(\s|$)/'
|
717
725
|
|
718
726
|
Naming/HeredocDelimiterCase:
|
719
727
|
EnforcedStyle: uppercase
|
@@ -769,6 +777,10 @@ Naming/UncommunicativeMethodParamName:
|
|
769
777
|
- io
|
770
778
|
- id
|
771
779
|
- to
|
780
|
+
- by
|
781
|
+
- 'on'
|
782
|
+
- in
|
783
|
+
- at
|
772
784
|
# Blacklisted names that will register an offense
|
773
785
|
ForbiddenNames: []
|
774
786
|
|
@@ -1121,6 +1133,9 @@ Style/MethodCallWithArgsParentheses:
|
|
1121
1133
|
IgnoreMacros: true
|
1122
1134
|
IgnoredMethods: []
|
1123
1135
|
|
1136
|
+
Style/MethodCallWithoutArgsParentheses:
|
1137
|
+
IgnoredMethods: []
|
1138
|
+
|
1124
1139
|
Style/MethodDefParentheses:
|
1125
1140
|
EnforcedStyle: require_parentheses
|
1126
1141
|
SupportedStyles:
|
data/config/enabled.yml
CHANGED
@@ -654,6 +654,13 @@ Lint/SafeNavigationChain:
|
|
654
654
|
Description: 'Do not chain ordinary method call after safe navigation operator.'
|
655
655
|
Enabled: true
|
656
656
|
|
657
|
+
Lint/SafeNavigationConsistency:
|
658
|
+
Description: >-
|
659
|
+
Check to make sure that if safe navigation is used for a method
|
660
|
+
call in an `&&` or `||` condition that safe navigation is used
|
661
|
+
for all method calls on that same object.
|
662
|
+
Enabled: true
|
663
|
+
|
657
664
|
Lint/ScriptPermission:
|
658
665
|
Description: 'Grant script file execute permission.'
|
659
666
|
Enabled: true
|
@@ -1056,6 +1063,12 @@ Performance/UnfreezeString:
|
|
1056
1063
|
Description: 'Use unary plus to get an unfrozen string literal.'
|
1057
1064
|
Enabled: true
|
1058
1065
|
|
1066
|
+
Performance/UnneededSort:
|
1067
|
+
Description: >-
|
1068
|
+
Use `min` instead of `sort.first`,
|
1069
|
+
`max_by` instead of `sort_by...last`, etc.
|
1070
|
+
Enabled: true
|
1071
|
+
|
1059
1072
|
Performance/UriDefaultParser:
|
1060
1073
|
Description: 'Use `URI::DEFAULT_PARSER` instead of `URI::Parser.new`.'
|
1061
1074
|
Enabled: true
|
data/lib/rubocop.rb
CHANGED
@@ -293,6 +293,7 @@ require_relative 'rubocop/cop/lint/require_parentheses'
|
|
293
293
|
require_relative 'rubocop/cop/lint/rescue_exception'
|
294
294
|
require_relative 'rubocop/cop/lint/rescue_type'
|
295
295
|
require_relative 'rubocop/cop/lint/return_in_void_context'
|
296
|
+
require_relative 'rubocop/cop/lint/safe_navigation_consistency'
|
296
297
|
require_relative 'rubocop/cop/lint/safe_navigation_chain'
|
297
298
|
require_relative 'rubocop/cop/lint/script_permission'
|
298
299
|
require_relative 'rubocop/cop/lint/shadowed_argument'
|
@@ -370,6 +371,7 @@ require_relative 'rubocop/cop/performance/start_with'
|
|
370
371
|
require_relative 'rubocop/cop/performance/string_replacement'
|
371
372
|
require_relative 'rubocop/cop/performance/times_map'
|
372
373
|
require_relative 'rubocop/cop/performance/unfreeze_string'
|
374
|
+
require_relative 'rubocop/cop/performance/unneeded_sort'
|
373
375
|
require_relative 'rubocop/cop/performance/uri_default_parser'
|
374
376
|
|
375
377
|
require_relative 'rubocop/cop/style/alias'
|
@@ -598,6 +600,8 @@ require_relative 'rubocop/formatter/progress_formatter'
|
|
598
600
|
require_relative 'rubocop/formatter/quiet_formatter'
|
599
601
|
require_relative 'rubocop/formatter/tap_formatter'
|
600
602
|
require_relative 'rubocop/formatter/worst_offenders_formatter'
|
603
|
+
# relies on progress formatter
|
604
|
+
require_relative 'rubocop/formatter/auto_gen_config_formatter'
|
601
605
|
|
602
606
|
require_relative 'rubocop/formatter/formatter_set'
|
603
607
|
|
@@ -18,6 +18,26 @@ module RuboCop
|
|
18
18
|
def rhs
|
19
19
|
node_parts[1]
|
20
20
|
end
|
21
|
+
|
22
|
+
# Returns all of the conditions, including nested conditions,
|
23
|
+
# of the binary operation.
|
24
|
+
#
|
25
|
+
# @return [Array<Node>] the left and right hand side of the binary
|
26
|
+
# operation and the let and right hand side of any nested binary
|
27
|
+
# operators
|
28
|
+
def conditions
|
29
|
+
lhs, rhs = *self
|
30
|
+
lhs = lhs.children.first if lhs.begin_type?
|
31
|
+
rhs = rhs.children.first if rhs.begin_type?
|
32
|
+
|
33
|
+
[lhs, rhs].each_with_object([]) do |side, collection|
|
34
|
+
if AST::Node::OPERATOR_KEYWORDS.include?(side.type)
|
35
|
+
collection.concat(side.conditions)
|
36
|
+
else
|
37
|
+
collection << side
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
21
41
|
end
|
22
42
|
end
|
23
43
|
end
|
data/lib/rubocop/cli.rb
CHANGED
@@ -178,8 +178,12 @@ module RuboCop
|
|
178
178
|
# This must be done after the options have already been processed,
|
179
179
|
# because they can affect how ConfigStore behaves
|
180
180
|
@options[:formatters] ||= begin
|
181
|
-
|
182
|
-
|
181
|
+
if @options[:auto_gen_config]
|
182
|
+
formatter = 'autogenconf'
|
183
|
+
else
|
184
|
+
cfg = @config_store.for(Dir.pwd).for_all_cops
|
185
|
+
formatter = cfg['DefaultFormatter'] || 'progress'
|
186
|
+
end
|
183
187
|
[[formatter, @options[:output_path]]]
|
184
188
|
end
|
185
189
|
|
@@ -11,10 +11,6 @@ module RuboCop
|
|
11
11
|
|
12
12
|
attr_reader :errors
|
13
13
|
|
14
|
-
def self.callback_methods
|
15
|
-
Parser::Meta::NODE_TYPES.map { |type| :"on_#{type}" }
|
16
|
-
end
|
17
|
-
|
18
14
|
def initialize(cops, forces = [], options = {})
|
19
15
|
@cops = cops
|
20
16
|
@forces = forces
|
@@ -24,30 +20,19 @@ module RuboCop
|
|
24
20
|
reset_errors
|
25
21
|
end
|
26
22
|
|
27
|
-
#
|
23
|
+
# Create methods like :on_send, :on_super, etc. They will be called
|
24
|
+
# during AST traversal and try to call corresponding methods on cops.
|
25
|
+
# A call to `super` is used
|
28
26
|
# to continue iterating over the children of a node.
|
29
27
|
# However, if we know that a certain node type (like `int`) never has
|
30
28
|
# child nodes, there is no reason to pay the cost of calling `super`.
|
31
|
-
|
32
|
-
:"on_#{
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def #{callback}(node)
|
39
|
-
@callbacks[:"#{callback}"] ||= @cops.select do |cop|
|
40
|
-
cop.respond_to?(:"#{callback}")
|
41
|
-
end
|
42
|
-
@callbacks[:#{callback}].each do |cop|
|
43
|
-
with_cop_error_handling(cop, node) do
|
44
|
-
cop.send(:#{callback}, node)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
#{!no_child_callbacks.include?(callback) && 'super'}
|
49
|
-
end
|
50
|
-
RUBY
|
29
|
+
Parser::Meta::NODE_TYPES.each do |node_type|
|
30
|
+
method_name = :"on_#{node_type}"
|
31
|
+
next unless method_defined?(method_name)
|
32
|
+
define_method(method_name) do |node|
|
33
|
+
trigger_responding_cops(method_name, node)
|
34
|
+
super(node) unless NO_CHILD_NODES.include?(node_type)
|
35
|
+
end
|
51
36
|
end
|
52
37
|
|
53
38
|
def investigate(processed_source)
|
@@ -63,6 +48,17 @@ module RuboCop
|
|
63
48
|
|
64
49
|
private
|
65
50
|
|
51
|
+
def trigger_responding_cops(callback, node)
|
52
|
+
@callbacks[callback] ||= @cops.select do |cop|
|
53
|
+
cop.respond_to?(callback)
|
54
|
+
end
|
55
|
+
@callbacks[callback].each do |cop|
|
56
|
+
with_cop_error_handling(cop, node) do
|
57
|
+
cop.send(callback, node)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
66
62
|
def reset_errors
|
67
63
|
@errors = Hash.new { |hash, k| hash[k] = [] }
|
68
64
|
end
|
@@ -4,6 +4,39 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Layout
|
6
6
|
# This cop checks for Windows-style line endings in the source code.
|
7
|
+
#
|
8
|
+
# @example EnforcedStyle: native (default)
|
9
|
+
# # The `native` style means that CR+LF (Carriage Return + Line Feed) is
|
10
|
+
# # enforced on Windows, and LF is enforced on other platforms.
|
11
|
+
#
|
12
|
+
# # bad
|
13
|
+
# puts 'Hello' # Return character is LF on Windows.
|
14
|
+
# puts 'Hello' # Return character is CR+LF on other than Windows.
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# puts 'Hello' # Return character is CR+LF on Windows.
|
18
|
+
# puts 'Hello' # Return character is LF on other than Windows.
|
19
|
+
#
|
20
|
+
# @example EnforcedStyle: lf
|
21
|
+
# # The `lf` style means that LF (Line Feed) is enforced on
|
22
|
+
# # all platforms.
|
23
|
+
#
|
24
|
+
# # bad
|
25
|
+
# puts 'Hello' # Return character is CR+LF on all platfoms.
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# puts 'Hello' # Return character is LF on all platfoms.
|
29
|
+
#
|
30
|
+
# @example EnforcedStyle: crlf
|
31
|
+
# # The `crlf` style means that CR+LF (Carriage Return + Line Feed) is
|
32
|
+
# # enforced on all platforms.
|
33
|
+
#
|
34
|
+
# # bad
|
35
|
+
# puts 'Hello' # Return character is LF on all platfoms.
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# puts 'Hello' # Return character is CR+LF on all platfoms.
|
39
|
+
#
|
7
40
|
class EndOfLine < Cop
|
8
41
|
include ConfigurableEnforcedStyle
|
9
42
|
include RangeHelp
|
@@ -5,7 +5,9 @@ module RuboCop
|
|
5
5
|
module Layout
|
6
6
|
# Checks for spaces inside ordinary round parentheses.
|
7
7
|
#
|
8
|
-
# @example
|
8
|
+
# @example EnforcedStyle: no_space (default)
|
9
|
+
# # The `no_space` style enforces that parentheses do not have spaces.
|
10
|
+
#
|
9
11
|
# # bad
|
10
12
|
# f( 3)
|
11
13
|
# g = (a + 3 )
|
@@ -13,21 +15,52 @@ module RuboCop
|
|
13
15
|
# # good
|
14
16
|
# f(3)
|
15
17
|
# g = (a + 3)
|
18
|
+
#
|
19
|
+
# @example EnforcedStyle: space
|
20
|
+
# # The `space` style enforces that parentheses have a space at the
|
21
|
+
# # beginning and end.
|
22
|
+
# # Note: Empty parentheses should not have spaces.
|
23
|
+
#
|
24
|
+
# # bad
|
25
|
+
# f(3)
|
26
|
+
# g = (a + 3)
|
27
|
+
# y( )
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# f( 3 )
|
31
|
+
# g = ( a + 3 )
|
32
|
+
# y()
|
33
|
+
#
|
16
34
|
class SpaceInsideParens < Cop
|
17
35
|
include SurroundingSpace
|
18
36
|
include RangeHelp
|
37
|
+
include ConfigurableEnforcedStyle
|
19
38
|
|
20
|
-
MSG
|
39
|
+
MSG = 'Space inside parentheses detected.'.freeze
|
40
|
+
MSG_SPACE = 'No space inside parentheses detected.'.freeze
|
21
41
|
|
22
42
|
def investigate(processed_source)
|
23
43
|
@processed_source = processed_source
|
24
|
-
|
25
|
-
|
44
|
+
|
45
|
+
if style == :space
|
46
|
+
each_missing_space(processed_source.tokens) do |range|
|
47
|
+
add_offense(range, location: range, message: MSG_SPACE)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
each_extraneous_space(processed_source.tokens) do |range|
|
51
|
+
add_offense(range, location: range)
|
52
|
+
end
|
26
53
|
end
|
27
54
|
end
|
28
55
|
|
29
56
|
def autocorrect(range)
|
30
|
-
|
57
|
+
lambda do |corrector|
|
58
|
+
if style == :space
|
59
|
+
corrector.insert_before(range, ' ')
|
60
|
+
else
|
61
|
+
corrector.remove(range)
|
62
|
+
end
|
63
|
+
end
|
31
64
|
end
|
32
65
|
|
33
66
|
private
|
@@ -45,9 +78,35 @@ module RuboCop
|
|
45
78
|
end
|
46
79
|
end
|
47
80
|
|
81
|
+
def each_missing_space(tokens)
|
82
|
+
tokens.each_cons(2) do |token1, token2|
|
83
|
+
next if can_be_ignored?(token1, token2)
|
84
|
+
|
85
|
+
next unless token2.line == token1.line && !token1.space_after?
|
86
|
+
|
87
|
+
if token1.left_parens?
|
88
|
+
yield range_between(token2.begin_pos, token2.begin_pos + 1)
|
89
|
+
|
90
|
+
elsif token2.right_parens?
|
91
|
+
yield range_between(token2.begin_pos, token2.end_pos)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
48
96
|
def parens?(token1, token2)
|
49
97
|
token1.left_parens? || token2.right_parens?
|
50
98
|
end
|
99
|
+
|
100
|
+
def can_be_ignored?(token1, token2)
|
101
|
+
return true unless parens?(token1, token2)
|
102
|
+
|
103
|
+
# If the second token is a comment, that means that a line break
|
104
|
+
# follows, and that the rules for space inside don't apply.
|
105
|
+
return true if token2.comment?
|
106
|
+
|
107
|
+
# Ignore empty parens. # TODO: Could be configurable.
|
108
|
+
return true if token1.left_parens? && token2.right_parens?
|
109
|
+
end
|
51
110
|
end
|
52
111
|
end
|
53
112
|
end
|
@@ -20,8 +20,10 @@ module RuboCop
|
|
20
20
|
MSG = 'Trailing whitespace detected.'.freeze
|
21
21
|
|
22
22
|
def investigate(processed_source)
|
23
|
+
heredoc_ranges = extract_heredoc_ranges(processed_source.ast)
|
23
24
|
processed_source.lines.each_with_index do |line, index|
|
24
25
|
next unless line.end_with?(' ', "\t")
|
26
|
+
next if skip_heredoc? && inside_heredoc?(heredoc_ranges, index + 1)
|
25
27
|
|
26
28
|
range = source_range(processed_source.buffer,
|
27
29
|
index + 1,
|
@@ -34,6 +36,24 @@ module RuboCop
|
|
34
36
|
def autocorrect(range)
|
35
37
|
->(corrector) { corrector.remove(range) }
|
36
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def skip_heredoc?
|
43
|
+
cop_config.fetch('AllowInHeredoc', false)
|
44
|
+
end
|
45
|
+
|
46
|
+
def inside_heredoc?(heredoc_ranges, line_number)
|
47
|
+
heredoc_ranges.any? { |r| r.include?(line_number) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_heredoc_ranges(ast)
|
51
|
+
return [] unless ast
|
52
|
+
ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
|
53
|
+
body = node.location.heredoc_body
|
54
|
+
(body.first_line...body.last_line)
|
55
|
+
end
|
56
|
+
end
|
37
57
|
end
|
38
58
|
end
|
39
59
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop check to make sure that if safe navigation is used for a method
|
7
|
+
# call in an `&&` or `||` condition that safe navigation is used for all
|
8
|
+
# method calls on that same object.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# foo&.bar && foo.baz
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
# foo.bar || foo&.baz
|
16
|
+
#
|
17
|
+
# # bad
|
18
|
+
# foo&.bar && (foobar.baz || foo.baz)
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# foo.bar && foo.baz
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# foo&.bar || foo&.baz
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# foo&.bar && (foobar.baz || foo&.baz)
|
28
|
+
#
|
29
|
+
class SafeNavigationConsistency < Cop
|
30
|
+
MSG = 'Ensure that safe navigation is used consistently ' \
|
31
|
+
'inside of `&&` and `||`.'.freeze
|
32
|
+
|
33
|
+
def on_csend(node)
|
34
|
+
return unless node.parent &&
|
35
|
+
AST::Node::OPERATOR_KEYWORDS.include?(node.parent.type)
|
36
|
+
check(node)
|
37
|
+
end
|
38
|
+
|
39
|
+
def check(node)
|
40
|
+
ancestor = top_conditional_ancestor(node)
|
41
|
+
conditions = ancestor.conditions
|
42
|
+
safe_nav_receiver = node.receiver
|
43
|
+
|
44
|
+
method_calls = conditions.select(&:send_type?)
|
45
|
+
unsafe_method_calls = method_calls.select do |method_call|
|
46
|
+
safe_nav_receiver == method_call.receiver
|
47
|
+
end
|
48
|
+
|
49
|
+
unsafe_method_calls.each do |unsafe_method_call|
|
50
|
+
location =
|
51
|
+
node.loc.expression.join(unsafe_method_call.loc.expression)
|
52
|
+
add_offense(unsafe_method_call,
|
53
|
+
location: location)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def autocorrect(node)
|
58
|
+
return unless node.dot?
|
59
|
+
|
60
|
+
lambda do |corrector|
|
61
|
+
corrector.insert_before(node.loc.dot, '&')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def top_conditional_ancestor(node)
|
68
|
+
parent = node.parent
|
69
|
+
unless parent &&
|
70
|
+
(AST::Node::OPERATOR_KEYWORDS.include?(parent.type) ||
|
71
|
+
(parent.begin_type? &&
|
72
|
+
AST::Node::OPERATOR_KEYWORDS.include?(parent.parent.type)))
|
73
|
+
return node
|
74
|
+
end
|
75
|
+
top_conditional_ancestor(parent)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|