-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support
ignored_method_ids
to ignore specific method_id
- Loading branch information
Showing
8 changed files
with
325 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe DiverDown::Trace::IgnoredMethodIds do | ||
describe 'InstanceMethods' do | ||
describe '#ignored?' do | ||
it 'returns false if ignored_methods are blank' do | ||
stub_const('A', Class.new) | ||
|
||
ignored_method = described_class.new([]) | ||
|
||
expect(ignored_method.ignored?(A, true, 'new')).to be(false) | ||
end | ||
|
||
it 'returns true if class is matched' do | ||
stub_const('A', Class.new) | ||
stub_const('B', Class.new) | ||
stub_const('C', Class.new(A)) | ||
|
||
ignored_method = described_class.new( | ||
[ | ||
'A', | ||
] | ||
) | ||
|
||
expect(ignored_method.ignored?(A, true, :new)).to be(true) | ||
expect(ignored_method.ignored?(B, true, :new)).to be(false) | ||
expect(ignored_method.ignored?(C, true, :new)).to be(true) | ||
end | ||
|
||
it 'does not lookup the ancestors if module given because of the complexity of implementation' do | ||
stub_const('A', Module.new) | ||
stub_const('B', Module.new) | ||
stub_const('C', Module.new.tap { _1.extend(A) }) | ||
|
||
ignored_method = described_class.new( | ||
[ | ||
'A.name', | ||
] | ||
) | ||
|
||
expect(ignored_method.ignored?(A, true, :name)).to be(true) | ||
expect(ignored_method.ignored?(B, true, :name)).to be(false) | ||
expect(ignored_method.ignored?(C, true, :name)).to be(false) | ||
end | ||
|
||
it 'returns true if class method is matched' do | ||
stub_const('A', Class.new) | ||
stub_const('B', Class.new) | ||
stub_const('C', Class.new(A)) | ||
|
||
ignored_method = described_class.new( | ||
[ | ||
'A.new', | ||
] | ||
) | ||
|
||
expect(ignored_method.ignored?(A, true, :new)).to be(true) | ||
expect(ignored_method.ignored?(B, true, :new)).to be(false) | ||
expect(ignored_method.ignored?(C, true, :new)).to be(true) | ||
end | ||
|
||
it 'returns true if instance method is matched' do | ||
stub_const('A', Class.new) | ||
stub_const('B', Class.new) | ||
stub_const('C', Class.new(A)) | ||
|
||
ignored_method = described_class.new( | ||
[ | ||
'A#initialize', | ||
] | ||
) | ||
|
||
expect(ignored_method.ignored?(A, false, :initialize)).to be(true) | ||
expect(ignored_method.ignored?(B, false, :initialize)).to be(false) | ||
expect(ignored_method.ignored?(C, false, :initialize)).to be(true) | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.