From 95e28e45ab793405cf09e15d387ca4ad13f7a4c8 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Fri, 29 Mar 2024 16:39:01 +0900 Subject: [PATCH] User can use `include: []` instead of modules --- lib/diver_down/helper.rb | 10 ++- lib/diver_down/trace/module_set.rb | 76 +++++++++++-------- ...{array_strategy.rb => array_module_set.rb} | 19 ++--- .../trace/module_set/base_strategy.rb | 21 ----- .../const_source_location_module_set.rb | 28 +++++++ .../const_source_location_strategy.rb | 43 ----------- lib/diver_down/trace/tracer.rb | 8 +- spec/diver_down/helper_spec.rb | 19 +++++ spec/diver_down/trace/module_set_spec.rb | 14 ++-- 9 files changed, 114 insertions(+), 124 deletions(-) rename lib/diver_down/trace/module_set/{array_strategy.rb => array_module_set.rb} (64%) delete mode 100644 lib/diver_down/trace/module_set/base_strategy.rb create mode 100644 lib/diver_down/trace/module_set/const_source_location_module_set.rb delete mode 100644 lib/diver_down/trace/module_set/const_source_location_strategy.rb diff --git a/lib/diver_down/helper.rb b/lib/diver_down/helper.rb index 123d2ea..8cb1ec2 100644 --- a/lib/diver_down/helper.rb +++ b/lib/diver_down/helper.rb @@ -45,12 +45,18 @@ def self.resolve_singleton_class(obj) end end - # @param obj [Object, Module, Class] - # @return ['instance', 'class'] + # @param obj [Object] + # @return [Boolean] def self.module?(obj) Module === obj end + # @param obj [Object] + # @return [Boolean] + def self.class?(obj) + Class === obj + end + # @param str [String] # @return [Module] def self.constantize(str) diff --git a/lib/diver_down/trace/module_set.rb b/lib/diver_down/trace/module_set.rb index 39e06be..0847ee1 100644 --- a/lib/diver_down/trace/module_set.rb +++ b/lib/diver_down/trace/module_set.rb @@ -4,56 +4,72 @@ module DiverDown module Trace # A class to quickly determine if a TracePoint is a module to be traced. class ModuleSet - require 'diver_down/trace/module_set/base_strategy' - require 'diver_down/trace/module_set/array_strategy' - require 'diver_down/trace/module_set/const_source_location_strategy' + require 'diver_down/trace/module_set/array_module_set' + require 'diver_down/trace/module_set/const_source_location_module_set' - # @param [DiverDown::Trace::ModuleSet::BaseStrategy] strategy - def initialize(strategy) - @strategy = strategy + # @param [Array, Set, nil] modules + # @param [Array] include + def initialize(modules: nil, include: nil) + @cache = {} + @array_module_set = DiverDown::Trace::ModuleSet::ArrayModuleSet.new(modules) unless modules.nil? + @const_source_location_module_set = DiverDown::Trace::ModuleSet::ConstSourceLocationModuleSet.new(include:) unless include.nil? end # @param [Module] mod # @return [Boolean] def include?(mod) - result = @strategy[mod] + unless @cache.key?(mod) + if DiverDown::Helper.class?(mod) + # class + begin + dig_superclass(mod) + rescue TypeError => e + # https://github.com/ruby/ruby/blob/f42164e03700469a7000b4f00148a8ca01d75044/object.c#L2232 + return false if e.message == 'uninitialized class' - if result.nil? - # If the strategy doesn't know, check the superclass - superclass_include(mod) - else - result + raise + end + else + # module + @cache[mod] = !!_include?(mod) + end end + + @cache.fetch(mod) end private - def superclass_include(mod) - return false unless Class === mod + def _include?(mod) + @array_module_set&.include?(mod) || + @const_source_location_module_set&.include?(mod) + end + + def dig_superclass(mod) + stack = [] + current = mod + included = nil - stack = [mod] - current = mod.superclass - found = nil + until current.nil? + if @cache.key?(current) + included = @cache.fetch(current) + break + else + stack.push(current) + included = _include?(current) - while current && found.nil? - found = @strategy[current] # nil or boolean - stack.push(current) - current = current.superclass + break if included + + current = current.superclass + end end # Convert nil to boolean - found = !!found + included = !!included stack.each do - @strategy[_1] = found + @cache[_1] = included unless @cache.key?(_1) end - - found - rescue TypeError => e - # https://github.com/ruby/ruby//blob/f42164e03700469a7000b4f00148a8ca01d75044/object.c#L2232 - return false if e.message == 'uninitialized class' - - raise end end end diff --git a/lib/diver_down/trace/module_set/array_strategy.rb b/lib/diver_down/trace/module_set/array_module_set.rb similarity index 64% rename from lib/diver_down/trace/module_set/array_strategy.rb rename to lib/diver_down/trace/module_set/array_module_set.rb index 187db19..fd41e6a 100644 --- a/lib/diver_down/trace/module_set/array_strategy.rb +++ b/lib/diver_down/trace/module_set/array_module_set.rb @@ -3,12 +3,10 @@ module DiverDown module Trace class ModuleSet - class ArrayStrategy < BaseStrategy + class ArrayModuleSet # @param [Array, #each] modules def initialize(modules) - super() - - @set = {} + @map = {} modules.each do mod = if DiverDown::Helper.module?(_1) @@ -18,21 +16,14 @@ def initialize(modules) DiverDown::Helper.constantize(_1) end - self[mod] = true + @map[mod] = true end end # @param [Module] mod # @return [Boolean, nil] - def [](mod) - @set[mod] - end - - # @param [Module] mod - # @param value [Boolean] - # @return [void] - def []=(mod, value) - @set[mod] = value + def include?(mod) + @map[mod] end end end diff --git a/lib/diver_down/trace/module_set/base_strategy.rb b/lib/diver_down/trace/module_set/base_strategy.rb deleted file mode 100644 index dcb3c7b..0000000 --- a/lib/diver_down/trace/module_set/base_strategy.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module DiverDown - module Trace - class ModuleSet - class BaseStrategy - # @param [Module, String] mod_or_module_name - # @return [Boolean, nil] nil means mod_or_module_name is not stored yet - def [](mod_or_module_name) - raise NotImplementedError, '[] must be implemented in subclass' - end - - # @param [Module, String] mod_or_module_name - # @param [Boolean] value - def []=(mod_or_module_name, value) - raise NotImplementedError, '[] must be implemented in subclass' - end - end - end - end -end diff --git a/lib/diver_down/trace/module_set/const_source_location_module_set.rb b/lib/diver_down/trace/module_set/const_source_location_module_set.rb new file mode 100644 index 0000000..88a3278 --- /dev/null +++ b/lib/diver_down/trace/module_set/const_source_location_module_set.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module DiverDown + module Trace + class ModuleSet + class ConstSourceLocationModuleSet + # @param [Array, Set] include + def initialize(include: []) + @include = include.to_set + end + + # @param [Module] mod + # @return [Boolean] + def include?(mod) + module_name = DiverDown::Helper.normalize_module_name(mod) + + path, = begin + Object.const_source_location(module_name) + rescue NameError, TypeError + nil + end + + path && @include.include?(path) + end + end + end + end +end diff --git a/lib/diver_down/trace/module_set/const_source_location_strategy.rb b/lib/diver_down/trace/module_set/const_source_location_strategy.rb deleted file mode 100644 index da5ed35..0000000 --- a/lib/diver_down/trace/module_set/const_source_location_strategy.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module DiverDown - module Trace - class ModuleSet - class ConstSourceLocationStrategy < BaseStrategy - # @param [Array, Set] include - # @param [Array, Set] exclude - def initialize(include: [], exclude: []) - super() - - @cache = {} - - @include = include.to_set - @exclude = exclude.to_set - end - - # @param [Module, String] mod - # @return [Boolean, nil] - def [](mod) - unless @cache.key?(mod) - module_name = DiverDown::Helper.normalize_module_name(mod) - - path, = begin - Object.const_source_location(module_name) - rescue NameError, TypeError - nil - end - - @cache[mod] = @include.include?(path) && !@exclude.include?(path) - end - - @cache.fetch(mod) - end - - # @param [Module] mod - def []=(mod, value) - @set[mod] = value - end - end - end - end -end diff --git a/lib/diver_down/trace/tracer.rb b/lib/diver_down/trace/tracer.rb index 00b860d..4449742 100644 --- a/lib/diver_down/trace/tracer.rb +++ b/lib/diver_down/trace/tracer.rb @@ -27,12 +27,10 @@ def initialize(module_set: [], target_files: nil, filter_method_id_path: nil, mo @module_set = if module_set.is_a?(DiverDown::Trace::ModuleSet) module_set - elsif module_set.is_a?(DiverDown::Trace::ModuleSet::BaseStrategy) - DiverDown::Trace::ModuleSet.new(module_set) + elsif module_set.respond_to?(:to_set) + DiverDown::Trace::ModuleSet.new(modules: module_set) else - DiverDown::Trace::ModuleSet.new( - DiverDown::Trace::ModuleSet::ArrayStrategy.new(module_set) - ) + DiverDown::Trace::ModuleSet.new(**module_set) end @target_file_set = target_files&.to_set diff --git a/spec/diver_down/helper_spec.rb b/spec/diver_down/helper_spec.rb index 21a8397..efd27c6 100644 --- a/spec/diver_down/helper_spec.rb +++ b/spec/diver_down/helper_spec.rb @@ -145,6 +145,25 @@ def self.name end end + describe '.class?' do + it 'returns true given class' do + expect(described_class.class?(Class.new)).to be(true) + end + + it 'returns true given singleton class' do + expect(described_class.class?(Class.new.singleton_class)).to be(true) + end + + it 'returns false given module' do + expect(described_class.class?(Module.new)).to be(false) + end + + it 'returns false given instance' do + klass = Class.new + expect(described_class.class?(klass.new)).to be(false) + end + end + describe '.constantize' do it 'returns constant given string' do expect(described_class.constantize('String')).to eq(String) diff --git a/spec/diver_down/trace/module_set_spec.rb b/spec/diver_down/trace/module_set_spec.rb index b7e6afe..f6d66b0 100644 --- a/spec/diver_down/trace/module_set_spec.rb +++ b/spec/diver_down/trace/module_set_spec.rb @@ -3,13 +3,13 @@ RSpec.describe DiverDown::Trace::ModuleSet do describe 'InstanceMethods' do describe '#include?' do - context 'array strategy' do + context 'with modules' do it 'checks module or module name' do stub_const('A', Module.new) stub_const('B', Module.new) set = described_class.new( - DiverDown::Trace::ModuleSet::ArrayStrategy.new([A]) + modules: [A] ) expect(set.include?(A)).to be(true) @@ -22,7 +22,7 @@ stub_const('C', Class.new(B)) set = described_class.new( - DiverDown::Trace::ModuleSet::ArrayStrategy.new([B]) + modules: [B] ) expect(set.include?(A)).to be(false) @@ -48,9 +48,7 @@ class ::A # rubocop:enable Lint/ConstantDefinitionInBlock set = described_class.new( - DiverDown::Trace::ModuleSet::ConstSourceLocationStrategy.new( - include: [__FILE__] - ) + include: [__FILE__] ) expect(set.include?(A)).to be(true) @@ -66,9 +64,7 @@ class ::B < ::A; end # rubocop:enable Lint/ConstantDefinitionInBlock set = described_class.new( - DiverDown::Trace::ModuleSet::ConstSourceLocationStrategy.new( - include: [__FILE__] - ) + include: [__FILE__] ) expect(set.include?(A)).to be(true)