rubocop 0.54.0 → 0.55.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 +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
|