diff --git a/History.rdoc b/History.rdoc
index bf1abef987..f44dc37d2b 100644
--- a/History.rdoc
+++ b/History.rdoc
@@ -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 (O(n!)) of RDoc::Include#module.
=== 3.9.2 / 2011-08-11
diff --git a/Rakefile b/Rakefile
index 9f074e3629..9a7fbee002 100644
--- a/Rakefile
+++ b/Rakefile
@@ -2,6 +2,8 @@ $:.unshift File.expand_path 'lib'
require 'rdoc'
require 'hoe'
+ENV['BENCHMARK'] = 'yes'
+
task :docs => :generate
task :test => :generate
diff --git a/lib/rdoc/class_module.rb b/lib/rdoc/class_module.rb
index 4e119837fa..52258b5bf1 100644
--- a/lib/rdoc/class_module.rb
+++ b/lib/rdoc/class_module.rb
@@ -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
@@ -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
diff --git a/lib/rdoc/context.rb b/lib/rdoc/context.rb
index e290ba4d45..3691fc60b3 100644
--- a/lib/rdoc/context.rb
+++ b/lib/rdoc/context.rb
@@ -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
diff --git a/lib/rdoc/include.rb b/lib/rdoc/include.rb
index c40fecf4dd..bbedce0472 100644
--- a/lib/rdoc/include.rb
+++ b/lib/rdoc/include.rb
@@ -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
##
@@ -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
@@ -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,
@@ -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 O(n!)
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
diff --git a/lib/rdoc/test_case.rb b/lib/rdoc/test_case.rb
index e5fdce2b90..3cf0eefe67 100644
--- a/lib/rdoc/test_case.rb
+++ b/lib/rdoc/test_case.rb
@@ -1,5 +1,6 @@
require 'rubygems'
require 'minitest/autorun'
+require 'minitest/benchmark' if ENV['BENCHMARK']
require 'fileutils'
require 'pp'
diff --git a/test/test_rdoc_class_module.rb b/test/test_rdoc_class_module.rb
index d931a22037..54caaec85a 100644
--- a/test/test_rdoc_class_module.rb
+++ b/test/test_rdoc_class_module.rb
@@ -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
diff --git a/test/test_rdoc_context.rb b/test/test_rdoc_context.rb
index 4dcac5a119..192a287b8c 100644
--- a/test/test_rdoc_context.rb
+++ b/test/test_rdoc_context.rb
@@ -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
@@ -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