# frozen_string_literal: true module RuboCop module Cop module Style # Flags uses of OpenStruct, as it is now officially discouraged # to be used for performance, version compatibility, and potential security issues. # # @safety # # Note that this cop may flag false positives; for instance, the following legal # use of a hand-rolled `OpenStruct` type would be considered an offense: # # ``` # module MyNamespace # class OpenStruct # not the OpenStruct we're looking for # end # # def new_struct # OpenStruct.new # resolves to MyNamespace::OpenStruct # end # end # ``` # # @example # # # bad # point = OpenStruct.new(x: 0, y: 1) # # # good # Point = Struct.new(:x, :y) # point = Point.new(0, 1) # # # also good # point = { x: 0, y: 1 } # # # bad # test_double = OpenStruct.new(a: 'b') # # # good (assumes test using rspec-mocks) # test_double = double # allow(test_double).to receive(:a).and_return('b') # class OpenStructUse < Base MSG = 'Avoid using `OpenStruct`; use `Struct`, `Hash`, a class or test doubles instead.' # @!method uses_open_struct?(node) def_node_matcher :uses_open_struct?, <<~PATTERN (const {nil? (cbase)} :OpenStruct) PATTERN def on_const(node) return unless uses_open_struct?(node) return if custom_class_or_module_definition?(node) add_offense(node) end private def custom_class_or_module_definition?(node) parent = node.parent (parent.class_type? || parent.module_type?) && node.left_siblings.empty? end end end end end