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) =>
`
`
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