File: method_dispatch_node.rb

package info (click to toggle)
ruby-rubocop-ast 0.3.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 892 kB
  • sloc: ruby: 10,886; makefile: 4
file content (263 lines) | stat: -rw-r--r-- 8,639 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# frozen_string_literal: true

module RuboCop
  module AST
    # Common functionality for nodes that are a kind of method dispatch:
    # `send`, `csend`, `super`, `zsuper`, `yield`, `defined?`,
    # and (modern only): `index`, `indexasgn`, `lambda`
    module MethodDispatchNode
      extend NodePattern::Macros
      include MethodIdentifierPredicates

      ARITHMETIC_OPERATORS = %i[+ - * / % **].freeze
      SPECIAL_MODIFIERS = %w[private protected].freeze

      # The receiving node of the method dispatch.
      #
      # @return [Node, nil] the receiver of the dispatched method or `nil`
      def receiver
        node_parts[0]
      end

      # The name of the dispatched method as a symbol.
      #
      # @return [Symbol] the name of the dispatched method
      def method_name
        node_parts[1]
      end

      # The `block` node associated with this method dispatch, if any.
      #
      # @return [BlockNode, nil] the `block` node associated with this method
      #                          call or `nil`
      def block_node
        parent if block_literal?
      end

      # Checks whether the dispatched method is a macro method. A macro method
      # is defined as a method that sits in a class, module, or block body and
      # has an implicit receiver.
      #
      # @note This does not include DSLs that use nested blocks, like RSpec
      #
      # @return [Boolean] whether the dispatched method is a macro method
      def macro?
        !receiver && macro_scope?
      end

      # Checks whether the dispatched method is an access modifier.
      #
      # @return [Boolean] whether the dispatched method is an access modifier
      def access_modifier?
        bare_access_modifier? || non_bare_access_modifier?
      end

      # Checks whether the dispatched method is a bare access modifier that
      # affects all methods defined after the macro.
      #
      # @return [Boolean] whether the dispatched method is a bare
      #                   access modifier
      def bare_access_modifier?
        macro? && bare_access_modifier_declaration?
      end

      # Checks whether the dispatched method is a non-bare access modifier that
      # affects only the method it receives.
      #
      # @return [Boolean] whether the dispatched method is a non-bare
      #                   access modifier
      def non_bare_access_modifier?
        macro? && non_bare_access_modifier_declaration?
      end

      # Checks whether the dispatched method is a bare `private` or `protected`
      # access modifier that affects all methods defined after the macro.
      #
      # @return [Boolean] whether the dispatched method is a bare
      #                    `private` or `protected` access modifier
      def special_modifier?
        bare_access_modifier? && SPECIAL_MODIFIERS.include?(source)
      end

      # Checks whether the name of the dispatched method matches the argument
      # and has an implicit receiver.
      #
      # @param [Symbol, String] name the method name to check for
      # @return [Boolean] whether the method name matches the argument
      def command?(name)
        !receiver && method?(name)
      end

      # Checks whether the dispatched method is a setter method.
      #
      # @return [Boolean] whether the dispatched method is a setter
      def setter_method?
        loc.respond_to?(:operator) && loc.operator
      end
      alias assignment? setter_method?

      # Checks whether the dispatched method uses a dot to connect the
      # receiver and the method name.
      #
      # This is useful for comparison operators, which can be called either
      # with or without a dot, i.e. `foo == bar` or `foo.== bar`.
      #
      # @return [Boolean] whether the method was called with a connecting dot
      def dot?
        loc.respond_to?(:dot) && loc.dot && loc.dot.is?('.')
      end

      # Checks whether the dispatched method uses a double colon to connect the
      # receiver and the method name.
      #
      # @return [Boolean] whether the method was called with a connecting dot
      def double_colon?
        loc.respond_to?(:dot) && loc.dot && loc.dot.is?('::')
      end

      # Checks whether the dispatched method uses a safe navigation operator to
      # connect the receiver and the method name.
      #
      # @return [Boolean] whether the method was called with a connecting dot
      def safe_navigation?
        loc.respond_to?(:dot) && loc.dot && loc.dot.is?('&.')
      end

      # Checks whether the *explicit* receiver of this method dispatch is
      # `self`.
      #
      # @return [Boolean] whether the receiver of this method dispatch is `self`
      def self_receiver?
        receiver&.self_type?
      end

      # Checks whether the *explicit* receiver of this method dispatch is a
      # `const` node.
      #
      # @return [Boolean] whether the receiver of this method dispatch
      #                   is a `const` node
      def const_receiver?
        receiver&.const_type?
      end

      # Checks whether the method dispatch is the implicit form of `#call`,
      # e.g. `foo.(bar)`.
      #
      # @return [Boolean] whether the method is the implicit form of `#call`
      def implicit_call?
        method?(:call) && !loc.selector
      end

      # Whether this method dispatch has an explicit block.
      #
      # @return [Boolean] whether the dispatched method has a block
      def block_literal?
        parent&.block_type? && eql?(parent.send_node)
      end

      # Checks whether this node is an arithmetic operation
      #
      # @return [Boolean] whether the dispatched method is an arithmetic
      #                   operation
      def arithmetic_operation?
        ARITHMETIC_OPERATORS.include?(method_name)
      end

      # Checks if this node is part of a chain of `def` modifiers.
      #
      # @example
      #
      #   private def foo; end
      #
      # @return [Boolean] whether the dispatched method is a `def` modifier
      def def_modifier?
        send_type? &&
          [self, *each_descendant(:send)].any?(&:adjacent_def_modifier?)
      end

      # Checks whether this is a lambda. Some versions of parser parses
      # non-literal lambdas as a method send.
      #
      # @return [Boolean] whether this method is a lambda
      def lambda?
        block_literal? && command?(:lambda)
      end

      # Checks whether this is a lambda literal (stabby lambda.)
      #
      # @example
      #
      #   -> (foo) { bar }
      #
      # @return [Boolean] whether this method is a lambda literal
      def lambda_literal?
        block_literal? && loc.expression && loc.expression.source == '->'
      end

      # Checks whether this is a unary operation.
      #
      # @example
      #
      #   -foo
      #
      # @return [Boolean] whether this method is a unary operation
      def unary_operation?
        return false unless loc.selector

        operator_method? && loc.expression.begin_pos == loc.selector.begin_pos
      end

      # Checks whether this is a binary operation.
      #
      # @example
      #
      #   foo + bar
      #
      # @return [Bookean] whether this method is a binary operation
      def binary_operation?
        return false unless loc.selector

        operator_method? && loc.expression.begin_pos != loc.selector.begin_pos
      end

      private

      def_node_matcher :macro_scope?, <<~PATTERN
        {^{({sclass class module block} ...) class_constructor?}
         ^^{({sclass class module block} ... ({begin if} ...)) class_constructor?}
         ^#macro_kwbegin_wrapper?
         #root_node?}
      PATTERN

      # Check if a node's parent is a kwbegin wrapper within a macro scope
      #
      # @param parent [Node] parent of the node being checked
      #
      # @return [Boolean] true if the parent is a kwbegin in a macro scope
      def macro_kwbegin_wrapper?(parent)
        parent.kwbegin_type? && macro_scope?(parent)
      end

      # Check if a node does not have a parent
      #
      # @param node [Node]
      #
      # @return [Boolean] if the parent is nil
      def root_node?(node)
        node.parent.nil?
      end

      def_node_matcher :adjacent_def_modifier?, <<~PATTERN
        (send nil? _ ({def defs} ...))
      PATTERN

      def_node_matcher :bare_access_modifier_declaration?, <<~PATTERN
        (send nil? {:public :protected :private :module_function})
      PATTERN

      def_node_matcher :non_bare_access_modifier_declaration?, <<~PATTERN
        (send nil? {:public :protected :private :module_function} _)
      PATTERN
    end
  end
end