summaryrefslogtreecommitdiff
path: root/rubocop/cop/api/grape_array_missing_coerce.rb
blob: 5cb80e113a4332f6206e3e2a9ffcb274718a1c49 (plain)
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
# frozen_string_literal: true

module RuboCop
  module Cop
    module API
      class GrapeArrayMissingCoerce < RuboCop::Cop::Base
        # This cop checks that Grape API parameters using an Array type
        # implement a coerce_with method:
        #
        # https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions
        #
        # @example
        #
        # # bad
        # requires :values, type: Array[String]
        #
        # # good
        # requires :values, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce
        #
        # end
        MSG = 'This Grape parameter defines an Array but is missing a coerce_with definition. ' \
          'For more details, see https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions'

        def_node_matcher :grape_api_instance?, <<~PATTERN
          (class
            (const _ _)
            (const
              (const
                (const nil? :Grape) :API) :Instance)
            ...
          )
        PATTERN

        def_node_matcher :grape_api_param_block?, <<~PATTERN
          (send _ {:requires :optional}
            (sym _)
            $_)
        PATTERN

        def_node_matcher :grape_type_def?, <<~PATTERN
           (sym :type)
        PATTERN

        def_node_matcher :grape_array_type?, <<~PATTERN
           (send
             (const nil? :Array) :[]
             (const nil? _))
        PATTERN

        def_node_matcher :grape_coerce_with?, <<~PATTERN
          (sym :coerce_with)
        PATTERN

        def on_class(node)
          @grape_api ||= grape_api_instance?(node)
        end

        def on_send(node)
          return unless @grape_api

          match = grape_api_param_block?(node)

          return unless match.is_a?(RuboCop::AST::HashNode)

          is_array_type = false
          has_coerce_method = false

          match.each_pair do |first, second|
            has_coerce_method ||= grape_coerce_with?(first)

            if grape_type_def?(first) && grape_array_type?(second)
              is_array_type = true
            end
          end

          if is_array_type && !has_coerce_method
            add_offense(node)
          end
        end
      end
    end
  end
end