# frozen_string_literal: true module DiverDown module Trace class IgnoredMethodIds def initialize(ignored_methods) # Ignore all methods in the module # Hash{ Module => Boolean } @ignored_modules = {} # Ignore all methods in the class # Hash{ Module => Hash{ Symbol => Boolean } } @ignored_class_method_id = Hash.new { |h, k| h[k] = {} } # Ignore all methods in the instance # Hash{ Module => Hash{ Symbol => Boolean } } @ignored_instance_method_id = Hash.new { |h, k| h[k] = {} } ignored_methods.each do |ignored_method| if ignored_method.include?('.') # instance method class_name, method_id = ignored_method.split('.') mod = DiverDown::Helper.constantize(class_name) @ignored_class_method_id[mod][method_id.to_sym] = true elsif ignored_method.include?('#') # class method class_name, method_id = ignored_method.split('#') mod = DiverDown::Helper.constantize(class_name) @ignored_instance_method_id[mod][method_id.to_sym] = true else # module mod = DiverDown::Helper.constantize(ignored_method) @ignored_modules[mod] = true end end end # @param mod [Module] # @param is_class [Boolean] class is true, instance is false # @param method_id [Symbol] # @return [Boolean] def ignored?(mod, is_class, method_id) ignored_module?(mod) || ignored_method?(mod, is_class, method_id) end private def ignored_module?(mod) unless @ignored_modules.key?(mod) dig_superclass(mod) end @ignored_modules.fetch(mod) end def ignored_method?(mod, is_class, method_id) store = if is_class # class methods @ignored_class_method_id else # instance methods @ignored_instance_method_id end begin dig_superclass_method_id(store, mod, method_id) unless store[mod].key?(method_id) rescue TypeError => e # https://github.com/ruby/ruby/blob/f42164e03700469a7000b4f00148a8ca01d75044/object.c#L2232 return false if e.message == 'uninitialized class' raise end store.fetch(mod).fetch(method_id) end def dig_superclass(mod) unless DiverDown::Helper.class?(mod) # NOTE: Do not lookup the ancestors if module given because of the complexity of implementation @ignored_modules[mod] = false return end stack = [] current = mod ignored = nil until current.nil? if @ignored_modules.key?(current) ignored = @ignored_modules.fetch(current) break else stack.push(current) current = current.superclass end end # Convert nil to boolean ignored = !!ignored stack.each do @ignored_modules[_1] = ignored end end def dig_superclass_method_id(store, mod, method_id) unless DiverDown::Helper.class?(mod) # NOTE: Do not lookup the ancestors if module given because of the complexity of implementation store[mod][method_id] = false return end stack = [] current = mod ignored = nil until current.nil? if store[current].key?(method_id) ignored = store[current].fetch(method_id) break else stack.push(current) current = current.superclass end end # Convert nil to boolean ignored = !!ignored stack.each do store[_1][method_id] = ignored end end end end end