112 lines
2.8 KiB
Ruby
112 lines
2.8 KiB
Ruby
|
module Gitlab
|
||
|
module Utils
|
||
|
module Override
|
||
|
class Extension
|
||
|
def self.verify_class!(klass, method_name)
|
||
|
instance_method_defined?(klass, method_name) ||
|
||
|
raise(
|
||
|
NotImplementedError.new(
|
||
|
"#{klass}\##{method_name} doesn't exist!"))
|
||
|
end
|
||
|
|
||
|
def self.instance_method_defined?(klass, name, include_super: true)
|
||
|
klass.instance_methods(include_super).include?(name) ||
|
||
|
klass.private_instance_methods(include_super).include?(name)
|
||
|
end
|
||
|
|
||
|
attr_reader :subject
|
||
|
|
||
|
def initialize(subject)
|
||
|
@subject = subject
|
||
|
end
|
||
|
|
||
|
def add_method_name(method_name)
|
||
|
method_names << method_name
|
||
|
end
|
||
|
|
||
|
def add_class(klass)
|
||
|
classes << klass
|
||
|
end
|
||
|
|
||
|
def verify!
|
||
|
classes.each do |klass|
|
||
|
index = klass.ancestors.index(subject)
|
||
|
parents = klass.ancestors.drop(index + 1)
|
||
|
|
||
|
method_names.each do |method_name|
|
||
|
parents.any? do |parent|
|
||
|
self.class.instance_method_defined?(
|
||
|
parent, method_name, include_super: false)
|
||
|
end ||
|
||
|
raise(
|
||
|
NotImplementedError.new(
|
||
|
"#{klass}\##{method_name} doesn't exist!"))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def method_names
|
||
|
@method_names ||= []
|
||
|
end
|
||
|
|
||
|
def classes
|
||
|
@classes ||= []
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Instead of writing patterns like this:
|
||
|
#
|
||
|
# def f
|
||
|
# raise NotImplementedError unless defined?(super)
|
||
|
#
|
||
|
# true
|
||
|
# end
|
||
|
#
|
||
|
# We could write it like:
|
||
|
#
|
||
|
# extend ::Gitlab::Utils::Override
|
||
|
#
|
||
|
# override :f
|
||
|
# def f
|
||
|
# true
|
||
|
# end
|
||
|
#
|
||
|
# This would make sure we're overriding something. See:
|
||
|
# https://gitlab.com/gitlab-org/gitlab-ee/issues/1819
|
||
|
def override(method_name)
|
||
|
return unless ENV['STATIC_VERIFICATION']
|
||
|
|
||
|
if is_a?(Class)
|
||
|
Extension.verify_class!(self, method_name)
|
||
|
else # We delay the check for modules
|
||
|
Override.extensions[self] ||= Extension.new(self)
|
||
|
Override.extensions[self].add_method_name(method_name)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def included(base = nil)
|
||
|
return super if base.nil? # Rails concern, ignoring it
|
||
|
|
||
|
super
|
||
|
|
||
|
if base.is_a?(Class) # We could check for Class in `override`
|
||
|
# This could be `nil` if `override` was never called
|
||
|
Override.extensions[self]&.add_class(base)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
alias_method :prepended, :included
|
||
|
|
||
|
def self.extensions
|
||
|
@extensions ||= {}
|
||
|
end
|
||
|
|
||
|
def self.verify!
|
||
|
extensions.values.each(&:verify!)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|