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