Skip to content

Commit 750fc1b

Browse files
dvandersluisbbatsov
authored andcommitted
Add InternalAffairs/NodeTypeMultiplePredicates to look for and or or conditions checking a node for / against multiple node types
1 parent bb4b87d commit 750fc1b

File tree

3 files changed

+314
-0
lines changed

3 files changed

+314
-0
lines changed

lib/rubocop/cop/internal_affairs.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require_relative 'internal_affairs/node_first_or_last_argument'
1717
require_relative 'internal_affairs/node_matcher_directive'
1818
require_relative 'internal_affairs/node_pattern_groups'
19+
require_relative 'internal_affairs/node_type_multiple_predicates'
1920
require_relative 'internal_affairs/node_type_predicate'
2021
require_relative 'internal_affairs/numblock_handler'
2122
require_relative 'internal_affairs/offense_location_keyword'
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module InternalAffairs
6+
# Use `node.type?(:foo, :bar)` instead of `node.foo_type? || node.bar_type?`,
7+
# and `!node.type?(:foo, :bar)` instead of `!node.foo_type? && !node.bar_type?`.
8+
#
9+
# @example
10+
#
11+
# # bad
12+
# node.str_type? || node.sym_type?
13+
#
14+
# # good
15+
# node.type?(:str, :sym)
16+
#
17+
# # bad
18+
# node.type?(:str, :sym) || node.boolean_type?
19+
#
20+
# # good
21+
# node.type?(:str, :sym, :boolean)
22+
#
23+
# # bad
24+
# !node.str_type? && !node.sym_type?
25+
#
26+
# # good
27+
# !node.type?(:str, :sym)
28+
#
29+
# # bad
30+
# !node.type?(:str, :sym) && !node.boolean_type?
31+
#
32+
# # good
33+
# !node.type?(:str, :sym, :boolean)
34+
#
35+
class NodeTypeMultiplePredicates < Base
36+
extend AutoCorrector
37+
38+
MSG_OR = 'Use `%<replacement>s` instead of checking for multiple node types.'
39+
MSG_AND = 'Use `%<replacement>s` instead of checking against multiple node types.'
40+
41+
# @!method one_of_node_types?(node)
42+
def_node_matcher :one_of_node_types?, <<~PATTERN
43+
(or $(call _receiver #type_predicate?) (call _receiver #type_predicate?))
44+
PATTERN
45+
46+
# @!method or_another_type?(node)
47+
def_node_matcher :or_another_type?, <<~PATTERN
48+
(or {
49+
$(call _receiver :type? sym+) (call _receiver #type_predicate?) |
50+
(call _receiver #type_predicate?) $(call _receiver :type? sym+)
51+
})
52+
PATTERN
53+
54+
# @!method none_of_node_types?(node)
55+
def_node_matcher :none_of_node_types?, <<~PATTERN
56+
(and
57+
(send $(call _receiver #type_predicate?) :!)
58+
(send (call _receiver #type_predicate?) :!)
59+
)
60+
PATTERN
61+
62+
# @!method and_not_another_type?(node)
63+
def_node_matcher :and_not_another_type?, <<~PATTERN
64+
(and {
65+
(send $(call _receiver :type? sym+) :!) (send (call _receiver #type_predicate?) :!) |
66+
(send (call _receiver #type_predicate?) :!) (send $(call _receiver :type? sym+) :!)
67+
})
68+
PATTERN
69+
70+
def on_or(node)
71+
return unless (send_node = one_of_node_types?(node) || or_another_type?(node))
72+
return unless send_node.receiver
73+
74+
replacement = replacement(node, send_node)
75+
add_offense(node, message: format(MSG_OR, replacement: replacement)) do |corrector|
76+
corrector.replace(node, replacement)
77+
end
78+
end
79+
80+
def on_and(node)
81+
return unless (send_node = none_of_node_types?(node) || and_not_another_type?(node))
82+
return unless send_node.receiver
83+
84+
replacement = "!#{replacement(node, send_node)}"
85+
86+
add_offense(node, message: format(MSG_AND, replacement: replacement)) do |corrector|
87+
corrector.replace(node, replacement)
88+
end
89+
end
90+
91+
private
92+
93+
def type_predicate?(method_name)
94+
method_name.end_with?('_type?')
95+
end
96+
97+
def replacement(node, send_node)
98+
send_node = send_node.children.first if send_node.method?(:!)
99+
100+
types = types(node)
101+
receiver = send_node.receiver.source
102+
dot = send_node.loc.dot.source
103+
104+
"#{receiver}#{dot}type?(:#{types.join(', :')})"
105+
end
106+
107+
def types(node)
108+
[types_in_branch(node.lhs), types_in_branch(node.rhs)]
109+
end
110+
111+
def types_in_branch(branch)
112+
branch = branch.children.first if branch.method?(:!)
113+
114+
if branch.method?(:type?)
115+
branch.arguments.map(&:value)
116+
elsif branch.method?(:defined_type?)
117+
# `node.defined_type?` relates to `node.type == :defined?`
118+
'defined?'
119+
else
120+
branch.method_name.to_s.delete_suffix('_type?')
121+
end
122+
end
123+
end
124+
end
125+
end
126+
end
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::InternalAffairs::NodeTypeMultiplePredicates, :config do
4+
context 'in an `or` node with multiple node type predicate branches' do
5+
it 'does not register an offense for type predicates called without a receiver' do
6+
expect_no_offenses(<<~RUBY)
7+
str_type? || sym_type?
8+
RUBY
9+
end
10+
11+
it 'does not register an offense for type predicates called with different receivers' do
12+
expect_no_offenses(<<~RUBY)
13+
foo.str_type? || bar.sym_type?
14+
RUBY
15+
end
16+
17+
it 'does not register an offense when all method calls are not type predicates' do
18+
expect_no_offenses(<<~RUBY)
19+
foo.bar? || foo.sym_type?
20+
RUBY
21+
end
22+
23+
it 'does not register an offense for negated predicates' do
24+
expect_no_offenses(<<~RUBY)
25+
!node.str_type? || !node.sym_type?
26+
RUBY
27+
end
28+
29+
it 'registers an offense and corrects' do
30+
expect_offense(<<~RUBY)
31+
node.str_type? || node.sym_type?
32+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `node.type?(:str, :sym)` instead of checking for multiple node types.
33+
RUBY
34+
35+
expect_correction(<<~RUBY)
36+
node.type?(:str, :sym)
37+
RUBY
38+
end
39+
40+
it 'registers an offense and corrects with `defined_type?`' do
41+
expect_offense(<<~RUBY)
42+
node.call_type? || node.defined_type?
43+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `node.type?(:call, :defined?)` instead of checking for multiple node types.
44+
RUBY
45+
46+
expect_correction(<<~RUBY)
47+
node.type?(:call, :defined?)
48+
RUBY
49+
end
50+
51+
it 'registers an offense and corrects for nested `or` nodes' do
52+
expect_offense(<<~RUBY)
53+
node.str_type? || node.sym_type? || node.boolean_type?
54+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `node.type?(:str, :sym)` instead of checking for multiple node types.
55+
RUBY
56+
57+
expect_correction(<<~RUBY)
58+
node.type?(:str, :sym, :boolean)
59+
RUBY
60+
end
61+
62+
it 'registers an offense and corrects when the LHS is a `type?` call' do
63+
expect_offense(<<~RUBY)
64+
node.type?(:str, :sym) || node.boolean_type?
65+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `node.type?(:str, :sym, :boolean)` instead of checking for multiple node types.
66+
RUBY
67+
68+
expect_correction(<<~RUBY)
69+
node.type?(:str, :sym, :boolean)
70+
RUBY
71+
end
72+
73+
it 'registers an offense and corrects when the RHS is a `type?` call' do
74+
expect_offense(<<~RUBY)
75+
node.boolean_type? || node.type?(:str, :sym)
76+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `node.type?(:boolean, :str, :sym)` instead of checking for multiple node types.
77+
RUBY
78+
79+
expect_correction(<<~RUBY)
80+
node.type?(:boolean, :str, :sym)
81+
RUBY
82+
end
83+
84+
it 'registers an offense and corrects with safe navigation' do
85+
expect_offense(<<~RUBY)
86+
node&.str_type? || node&.sym_type?
87+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `node&.type?(:str, :sym)` instead of checking for multiple node types.
88+
RUBY
89+
90+
expect_correction(<<~RUBY)
91+
node&.type?(:str, :sym)
92+
RUBY
93+
end
94+
end
95+
96+
context 'in an `and` node with multiple negated type predicate branches' do
97+
it 'does not register an offense for type predicates called without a receiver' do
98+
expect_no_offenses(<<~RUBY)
99+
!str_type? && !sym_type?
100+
RUBY
101+
end
102+
103+
it 'does not register an offense for type predicates called with different receivers' do
104+
expect_no_offenses(<<~RUBY)
105+
!foo.str_type? && !bar.sym_type?
106+
RUBY
107+
end
108+
109+
it 'does not register an offense when all method calls are not type predicates' do
110+
expect_no_offenses(<<~RUBY)
111+
!foo.bar? && !foo.sym_type?
112+
RUBY
113+
end
114+
115+
it 'does not register an offense for type predicates called without negation' do
116+
expect_no_offenses(<<~RUBY)
117+
node.str_type? && node.sym_type?
118+
RUBY
119+
end
120+
121+
it 'registers an offense and corrects' do
122+
expect_offense(<<~RUBY)
123+
!node.str_type? && !node.sym_type?
124+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `!node.type?(:str, :sym)` instead of checking against multiple node types.
125+
RUBY
126+
127+
expect_correction(<<~RUBY)
128+
!node.type?(:str, :sym)
129+
RUBY
130+
end
131+
132+
it 'registers an offense and corrects with `defined_type?`' do
133+
expect_offense(<<~RUBY)
134+
!node.call_type? && !node.defined_type?
135+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `!node.type?(:call, :defined?)` instead of checking against multiple node types.
136+
RUBY
137+
138+
expect_correction(<<~RUBY)
139+
!node.type?(:call, :defined?)
140+
RUBY
141+
end
142+
143+
it 'registers an offense and corrects for nested `and` nodes' do
144+
expect_offense(<<~RUBY)
145+
!node.str_type? && !node.sym_type? && !node.boolean_type?
146+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `!node.type?(:str, :sym)` instead of checking against multiple node types.
147+
RUBY
148+
149+
expect_correction(<<~RUBY)
150+
!node.type?(:str, :sym, :boolean)
151+
RUBY
152+
end
153+
154+
it 'registers an offense and corrects when the LHS is a `type?` call' do
155+
expect_offense(<<~RUBY)
156+
!node.type?(:str, :sym) && !node.boolean_type?
157+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `!node.type?(:str, :sym, :boolean)` instead of checking against multiple node types.
158+
RUBY
159+
160+
expect_correction(<<~RUBY)
161+
!node.type?(:str, :sym, :boolean)
162+
RUBY
163+
end
164+
165+
it 'registers an offense and corrects when the RHS is a `type?` call' do
166+
expect_offense(<<~RUBY)
167+
!node.boolean_type? && !node.type?(:str, :sym)
168+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `!node.type?(:boolean, :str, :sym)` instead of checking against multiple node types.
169+
RUBY
170+
171+
expect_correction(<<~RUBY)
172+
!node.type?(:boolean, :str, :sym)
173+
RUBY
174+
end
175+
176+
it 'registers an offense and corrects with safe navigation' do
177+
expect_offense(<<~RUBY)
178+
!node&.str_type? && !node&.sym_type?
179+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `!node&.type?(:str, :sym)` instead of checking against multiple node types.
180+
RUBY
181+
182+
expect_correction(<<~RUBY)
183+
!node&.type?(:str, :sym)
184+
RUBY
185+
end
186+
end
187+
end

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy