summaryrefslogtreecommitdiff
path: root/rubocop/cop/module_with_instance_variables.rb
blob: 974a23bf70131d88b9aed3216e6518d97e3714bb (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
84
85
86
87
module RuboCop
  module Cop
    class ModuleWithInstanceVariables < RuboCop::Cop::Cop
      MSG = <<~EOL.freeze
        Do not use instance variables in a module. Please read this
        for the rationale behind it:

        doc/development/module_with_instance_variables.md

        If you think the use for this is fine, please just add:
        # rubocop:disable Cop/ModuleWithInstanceVariables
      EOL

      def on_module(node)
        return if
          rails_helper?(node) || rails_mailer?(node) || spec_helper?(node)

        check_method_definition(node)

        # Not sure why some module would have an extra begin wrapping around
        node.each_child_node(:begin) do |begin_node|
          check_method_definition(begin_node)
        end
      end

      private

      # We ignore Rails helpers right now because it's hard to workaround it
      def rails_helper?(node)
        node.source_range.source_buffer.name =~
          %r{app/helpers/\w+_helper.rb\z}
      end

      # We ignore Rails mailers right now because it's hard to workaround it
      def rails_mailer?(node)
        node.source_range.source_buffer.name =~
          %r{app/mailers/emails/}
      end

      # We ignore spec helpers because it usually doesn't matter
      def spec_helper?(node)
        node.source_range.source_buffer.name =~
          %r{spec/support/|features/steps/}
      end

      def check_method_definition(node)
        node.each_child_node(:def) do |definition|
          # We allow this pattern:
          #
          #     def f
          #       @f ||= true
          #     end
          if only_ivar_or_assignment?(definition)
            # We don't allow if any other ivar is used
            definition.each_descendant(:ivar) do |offense|
              add_offense(offense, :expression)
            end
          # We allow initialize method and single ivar
          elsif initialize_method?(definition) || single_ivar?(definition)
            next
          else
            definition.each_descendant(:ivar, :ivasgn) do |offense|
              add_offense(offense, :expression)
            end
          end
        end
      end

      def only_ivar_or_assignment?(definition)
        node = definition.child_nodes.last

        definition.child_nodes.size == 2 &&
          node.or_asgn_type? && node.child_nodes.first.ivasgn_type?
      end

      def single_ivar?(definition)
        node = definition.child_nodes.last

        definition.child_nodes.size == 2 && node.ivar_type?
      end

      def initialize_method?(definition)
        definition.children.first == :initialize
      end
    end
  end
end