diff --git a/CHANGELOG.md b/CHANGELOG.md index 242f3538..3e70b745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Main (3.0.0.alpha) * [#345](https://github.com/rails/sdoc/pull/345) Version explicit links to `api.rubyonrails.org` [@jonathanhefner](https://github.com/jonathanhefner) * [#356](https://github.com/rails/sdoc/pull/356) Redesign "Constants" section [@jonathanhefner](https://github.com/jonathanhefner) * [#357](https://github.com/rails/sdoc/pull/357) Support permalinking constants [@jonathanhefner](https://github.com/jonathanhefner) +* [#358](https://github.com/rails/sdoc/pull/358) Add constants to search index [@jonathanhefner](https://github.com/jonathanhefner) 2.6.1 ===== diff --git a/lib/rdoc/generator/template/rails/resources/css/main.css b/lib/rdoc/generator/template/rails/resources/css/main.css index 2ec512c9..06563f21 100644 --- a/lib/rdoc/generator/template/rails/resources/css/main.css +++ b/lib/rdoc/generator/template/rails/resources/css/main.css @@ -504,7 +504,7 @@ html { } /* Hide TIP when one of the first three search results is a method. */ -.panel__results:has(.results__result:nth-child(n+1):nth-child(-n+3) .result__method:not(:empty))::before { +.panel__results:has(.results__result:nth-child(n+1):nth-child(-n+3) .result__member:not(:empty))::before { display: none; } @@ -542,11 +542,11 @@ html { flex-direction: column; } -.result__method { +.result__member { display: flex; } -.result__method::before { +.result__member::before { content: "\221F"; font-size: 1.25em; margin: -0.3em 0.2em; @@ -557,7 +557,7 @@ html { margin: var(--space-xs) 0 0 0; } -:is(.result__method, .result__summary):empty { +:is(.result__member, .result__summary):empty { display: none; } diff --git a/lib/rdoc/generator/template/rails/resources/js/main.js b/lib/rdoc/generator/template/rails/resources/js/main.js index 51a5228f..0081a902 100644 --- a/lib/rdoc/generator/template/rails/resources/js/main.js +++ b/lib/rdoc/generator/template/rails/resources/js/main.js @@ -3,11 +3,11 @@ import { Search } from "./search.js"; document.addEventListener("turbo:load", () => { const searchInput = document.getElementById("search"); const searchOutput = document.getElementById("results"); - const search = new Search(searchInput, searchOutput, (url, module, method, summary) => + const search = new Search(searchInput, searchOutput, (url, module, member, summary) => `
${module.replaceAll("::", "::")} - ${method || ""} + ${member || ""}

${summary || ""}

` diff --git a/lib/sdoc/search_index.rb b/lib/sdoc/search_index.rb index 71179928..58526940 100644 --- a/lib/sdoc/search_index.rb +++ b/lib/sdoc/search_index.rb @@ -13,32 +13,47 @@ def to_json(*) end def generate(rdoc_modules) - # RDoc duplicates RDoc::MethodAttr instances when modules are aliased by - # assigning to a constant. For example, `MyBar = Foo::Bar` will duplicate - # all of Foo::Bar's RDoc::MethodAttr instances. - rdoc_objects = rdoc_modules + rdoc_modules.flat_map(&:method_list).uniq + rdoc_objects = rdoc_modules + rdoc_modules.flat_map(&:constants) + rdoc_modules.flat_map(&:method_list) + + # RDoc duplicates member instances when modules are aliased by assigning to + # a constant. For example, `MyBar = Foo::Bar` will duplicate all of + # Foo::Bar's RDoc::Constant and RDoc::MethodAttr instances. + rdoc_objects.uniq! ngram_sets = rdoc_objects.map { |rdoc_object| derive_ngrams(rdoc_object.full_name) } ngram_bit_positions = compile_ngrams(ngram_sets) bit_weights = compute_bit_weights(ngram_bit_positions) entries = rdoc_objects.zip(ngram_sets).map do |rdoc_object, ngrams| - rdoc_module, rdoc_method = rdoc_object.is_a?(RDoc::ClassModule) ? [rdoc_object] : [rdoc_object.parent, rdoc_object] - description = rdoc_object.description - - [ - generate_fingerprint(ngrams, ngram_bit_positions), - compute_tiebreaker_bonus(rdoc_module.full_name, rdoc_method&.name, description), - rdoc_object.path, - rdoc_module.full_name, - (signature_for(rdoc_method) if rdoc_method), - *truncate_description(description, 130), - ] + fingerprint = generate_fingerprint(ngrams, ngram_bit_positions) + + case rdoc_object + when RDoc::ClassModule + build_entry(rdoc_object, fingerprint) + when RDoc::Constant + build_entry(rdoc_object, fingerprint, "::#{rdoc_object.name}") + when RDoc::MethodAttr + build_entry(rdoc_object, fingerprint, signature_for(rdoc_object)) + end end { "ngrams" => ngram_bit_positions, "weights" => bit_weights, "entries" => entries } end + def build_entry(rdoc_object, fingerprint, member_label = nil) + rdoc_module = member_label ? rdoc_object.parent : rdoc_object + description = rdoc_object.description + + [ + fingerprint, + compute_tiebreaker_bonus(rdoc_module.full_name, (rdoc_object.name if member_label), description), + rdoc_object.path, + rdoc_module.full_name, + member_label, + *truncate_description(description, 130), + ] + end + def derive_ngrams(name) if name.match?(/:[^:A-Z]|#/) # Example: "ActiveModel::Name::new" => ["ActiveModel", "Name", ":new"] @@ -100,15 +115,15 @@ def compute_bit_weights(ngram_bit_positions) Uint8Array.new(weights) end - def compute_tiebreaker_bonus(module_name, method_name, description) + def compute_tiebreaker_bonus(module_name, member_name, description) # Give bonus in proportion to documentation length, but scale up extremely # slowly. Bonus is per matching ngram so it must be small enough to not # outweigh points from other matches. bonus = (description.length + 1) ** 0.01 / 100 # Reduce bonus in proportion to name length. This favors short names over - # long names. Notably, this will often favor methods over modules since - # method names are usually shorter than fully qualified module names. - bonus /= (method_name&.length || module_name.length) ** 0.1 + # long names. Notably, this will often favor members over modules since + # member names are usually shorter than fully qualified module names. + bonus /= (member_name&.length || module_name.length) ** 0.1 end def signature_for(rdoc_method) diff --git a/spec/search_index_spec.rb b/spec/search_index_spec.rb index 3025519f..34c55d34 100644 --- a/spec/search_index_spec.rb +++ b/spec/search_index_spec.rb @@ -2,16 +2,22 @@ describe SDoc::SearchIndex do describe "#generate" do - it "generates a search index for the given modules and their methods" do + it "generates a search index for the given modules and their members" do top_level = rdoc_top_level_for <<~RUBY # This is FooBar. class FooBar + # This is +BAZ_QUX+. + BAZ_QUX = true + # This is #hoge_fuga. def hoge_fuga; end end RUBY - ngrams = SDoc::SearchIndex.derive_ngrams("FooBar") | SDoc::SearchIndex.derive_ngrams("FooBar#hoge_fuga") + ngrams = + SDoc::SearchIndex.derive_ngrams("FooBar") | + SDoc::SearchIndex.derive_ngrams("FooBar::BAZ_QUX") | + SDoc::SearchIndex.derive_ngrams("FooBar#hoge_fuga") search_index = SDoc::SearchIndex.generate(top_level.classes_and_modules) @@ -20,29 +26,29 @@ def hoge_fuga; end _(search_index["ngrams"].keys.sort).must_equal ngrams.sort _(search_index["ngrams"].values.max).must_equal search_index["weights"].length - 1 - _(search_index["entries"].length).must_equal 2 + _(search_index["entries"].length).must_equal 3 search_index["entries"].each do |entry| _(entry.length).must_be :<=, 6 _(entry[0]).must_be_kind_of Array # Fingerprint _(entry[1]).must_be :<, 1.0 # Tiebreaker bonus + _(entry[3]).must_equal "FooBar" # Module name end - module_entry, method_entry = search_index["entries"].sort_by { |entry| entry[4] ? 1 : 0 } + module_entry, method_entry, constant_entry = search_index["entries"].sort_by { |entry| entry[4].to_s } # URL _(module_entry[2]).must_equal "classes/FooBar.html" + _(constant_entry[2]).must_equal "classes/FooBar.html#constant-BAZ_QUX" _(method_entry[2]).must_equal "classes/FooBar.html#method-i-hoge_fuga" - # Module name - _(module_entry[3]).must_equal "FooBar" - _(method_entry[3]).must_equal "FooBar" - - # Method signature + # Member label _(module_entry[4]).must_be_nil + _(constant_entry[4]).must_equal "::BAZ_QUX" _(method_entry[4]).must_equal "#hoge_fuga()" # Description _(module_entry[5]).must_equal "This is FooBar." + _(constant_entry[5]).must_equal "This is BAZ_QUX." _(method_entry[5]).must_equal "This is hoge_fuga." end end