Skip to content

Commit

Permalink
Delay looking up the module for an include statement until all files …
Browse files Browse the repository at this point in the history
…are parsed. This papers over a worst-case behavior for RDoc::Include#module
  • Loading branch information
drbrain committed Oct 7, 2011
1 parent 738e654 commit 50d3bc0
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 15 deletions.
3 changes: 3 additions & 0 deletions History.rdoc
Expand Up @@ -94,6 +94,9 @@
Andrea Singh.
* The method parameter coverage report no longer includes parameter default
values. Issue #77 by Jake Goulding.
* The module for an include is not looked up until parsed all the files are
parsed. Unless your project includes nonexistent modules this avoids
worst-case behavior (<tt>O(n!)</tt>) of RDoc::Include#module.

=== 3.9.2 / 2011-08-11

Expand Down
2 changes: 2 additions & 0 deletions Rakefile
Expand Up @@ -2,6 +2,8 @@ $:.unshift File.expand_path 'lib'
require 'rdoc'
require 'hoe'

ENV['BENCHMARK'] = 'yes'

task :docs => :generate
task :test => :generate

Expand Down
4 changes: 4 additions & 0 deletions lib/rdoc/class_module.rb
Expand Up @@ -374,6 +374,8 @@ def merge class_module
end
end

@includes.uniq! # clean up

merge_collections method_list, cm.method_list, other_files do |add, meth|
if add then
add_method meth
Expand Down Expand Up @@ -609,6 +611,8 @@ def update_includes
mod = include.module
!(String === mod) && RDoc::TopLevel.all_modules_hash[mod.full_name].nil?
end

includes.uniq!
end

end
Expand Down
3 changes: 1 addition & 2 deletions lib/rdoc/context.rb
Expand Up @@ -398,8 +398,7 @@ def add_constant constant
# Adds included module +include+ which should be an RDoc::Include

def add_include include
add_to @includes, include unless
@includes.map { |i| i.full_name }.include? include.full_name
add_to @includes, include

include
end
Expand Down
18 changes: 15 additions & 3 deletions lib/rdoc/include.rb
Expand Up @@ -15,7 +15,7 @@ def initialize(name, comment)
super()
@name = name
self.comment = comment
@module = nil # cache for module if found
@module = nil # cache for module if found
end

##
Expand All @@ -28,10 +28,11 @@ def <=> other
end

def == other # :nodoc:
self.class == other.class and
self.name == other.name
self.class === other and @name == other.name
end

alias eql? ==

##
# Full name based on #module

Expand All @@ -40,6 +41,10 @@ def full_name
RDoc::ClassModule === m ? m.full_name : @name
end

def hash # :nodoc:
[@name, self.module].hash
end

def inspect # :nodoc:
"#<%s:0x%x %s.include %s>" % [
self.class,
Expand All @@ -57,6 +62,13 @@ def inspect # :nodoc:
# - if not found, look into the children of included modules,
# in reverse inclusion order;
# - if still not found, go up the hierarchy of names.
#
# This method has <code>O(n!)</code> behavior when the module calling
# include is referencing nonexistent modules. Avoid calling #module until
# after all the files are parsed. This behavior is due to ruby's constant
# lookup behavior.
#
# As of the beginning of October, 2011, no gem includes nonexistent modules.

def module
return @module if @module
Expand Down
1 change: 1 addition & 0 deletions lib/rdoc/test_case.rb
@@ -1,5 +1,6 @@
require 'rubygems'
require 'minitest/autorun'
require 'minitest/benchmark' if ENV['BENCHMARK']

require 'fileutils'
require 'pp'
Expand Down
13 changes: 13 additions & 0 deletions test/test_rdoc_class_module.rb
Expand Up @@ -774,6 +774,19 @@ def test_update_includes
assert_equal [a, c], @c1.includes
end

def test_update_includes_trim
a = RDoc::Include.new 'D::M', nil
b = RDoc::Include.new 'D::M', nil

@c1.add_include a
@c1.add_include b
@c1.ancestors # cache included modules

@c1.update_includes

assert_equal [a], @c1.includes
end

def test_update_includes_with_colons
a = RDoc::Include.new 'M1', nil
b = RDoc::Include.new 'M1::M2', nil
Expand Down
20 changes: 10 additions & 10 deletions test/test_rdoc_context.rb
Expand Up @@ -183,16 +183,6 @@ def test_add_include
assert_equal [incl], @context.includes
end

def test_add_include_twice
incl1 = RDoc::Include.new 'Name', 'comment'
@context.add_include incl1

incl2 = RDoc::Include.new 'Name', 'comment'
@context.add_include incl2

assert_equal [incl1], @context.includes
end

def test_add_method
meth = RDoc::AnyMethod.new nil, 'old_name'
meth.visibility = nil
Expand Down Expand Up @@ -334,6 +324,16 @@ def test_add_to_done_documenting
refute_includes arr, incl
end

def bench_add_include
cm = RDoc::ClassModule.new 'Klass'

assert_performance_linear 0.9 do |count|
count.times do |i|
cm.add_include RDoc::Include.new("N::M#{i}", nil)
end
end
end

def test_child_name
assert_equal 'C1::C1', @c1.child_name('C1')
end
Expand Down

0 comments on commit 50d3bc0

Please sign in to comment.