lib/rubocop/cop/style/open_struct_use.rb
# 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