<% specs.each do |s| %>
-<%= method_link(s.to_s, (s.type == '#' ? s.method : s.type + s.method)) %>
+<%= method_link(s.to_s, (s.type == '#' ? s.method : display_typemark(s.type, @target_version) + s.method)) %>
<% end %>
@@ -11,7 +11,7 @@ <%= search_form() %> <% headline_init %> -<%= headline("#{@entry.type_label} #{@entry.label}") %> +<%= headline("#{@entry.type_label} #{@entry.display_label}") %>
-
<%=h @entry.header %>diff --git a/data/bitclust/template/method b/data/bitclust/template/method index 4d563fd..967ba52 100644 --- a/data/bitclust/template/method +++ b/data/bitclust/template/method @@ -1,6 +1,6 @@ <% entry = @entries.sort.first - @title = "#{entry.type_label} #{entry.title_labels.join(', ')}" + @title = "#{entry.type_label} #{entry.display_title_labels.join(', ')}" @description = entry.description %>@@ -15,7 +15,7 @@ <% headline_init %> -<%= headline("#{entry.type_label} #{entry.label}") %> +<%= headline("#{entry.type_label} #{entry.display_label}") %> <% headline_push %>
' - string @method.klass.name + @method.typemark if @opt + string @method.klass.name + @method.display_typemark if @opt string escape_html(sig.friendly_string) string '' string heading_version_badges(@method, sig.name, first) if @method diff --git a/lib/bitclust/search_index_generator.rb b/lib/bitclust/search_index_generator.rb index 2c65104..e38ff80 100644 --- a/lib/bitclust/search_index_generator.rb +++ b/lib/bitclust/search_index_generator.rb @@ -122,7 +122,10 @@ def method_entries(db) next unless type cname = entry.klass.name - tmark = entry.typemark + # bitclust#250: 検索結果に出る表示ラベル(name/full_name)は 4.0 + # 以降のドキュメントでは module function を "?." で表示する。path + # は識別子なので typechar(常に不変)のまま + tmark = entry.display_typemark tchar = entry.typechar entry.names.each do |mname| path = "method/#{encode(cname)}/#{tchar}/#{encode(mname)}#{@suffix}" diff --git a/lib/bitclust/subcommands/chm_command.rb b/lib/bitclust/subcommands/chm_command.rb index f363cdb..f729205 100644 --- a/lib/bitclust/subcommands/chm_command.rb +++ b/lib/bitclust/subcommands/chm_command.rb @@ -207,8 +207,10 @@ def exec(argv, options) name = e.typename == :special_variable ? "$#{e_name}" : e_name @index_contents << Sitemap::Content.new("#{name} (#{e.library.name} - #{e.klass.name})", filename) + # bitclust#250: CHM の索引(.hhk)に出る表示ラベル。4.0 以降の + # ドキュメントでは module function を "?." で表示する @index_contents << - Sitemap::Content.new("#{e.klass.name}#{e.typemark}#{name} (#{e.library.name})", filename) + Sitemap::Content.new("#{e.klass.name}#{e.display_typemark}#{name} (#{e.library.name})", filename) end end pb.title = align_progress_bar_title(e.name) diff --git a/sig/bitclust/functionentry.rbs b/sig/bitclust/functionentry.rbs index 49efead..0acce0e 100644 --- a/sig/bitclust/functionentry.rbs +++ b/sig/bitclust/functionentry.rbs @@ -20,6 +20,7 @@ module BitClust attr_accessor source_location: Location? alias label id + alias display_label label alias macro? macro alias private? private diff --git a/sig/bitclust/methodentry.rbs b/sig/bitclust/methodentry.rbs index 306ecad..d61f78e 100644 --- a/sig/bitclust/methodentry.rbs +++ b/sig/bitclust/methodentry.rbs @@ -28,6 +28,8 @@ module BitClust def typechar: () -> NameUtils::typechar + def display_typemark: () -> String + def type_label: () -> String def library: () -> LibraryEntry @@ -54,14 +56,20 @@ module BitClust def label: () -> String + def display_label: () -> String + def short_label: () -> String + def display_short_label: () -> String + def index_id: () -> String def labels: () -> Array[String] def title_labels: () -> Array[String] + def display_title_labels: () -> Array[String] + def name?: (String name) -> bool def since_of: (String name) -> String? diff --git a/sig/bitclust/nameutils.rbs b/sig/bitclust/nameutils.rbs index 1b42e50..38f8d79 100644 --- a/sig/bitclust/nameutils.rbs +++ b/sig/bitclust/nameutils.rbs @@ -97,6 +97,10 @@ module BitClust def self?.typemark2char: (typemark mark) -> typechar + # bitclust#250: 表示専用の変換。戻り値は '?.' も取り得るので typemark + # 型ではなく String にする(識別子として使ってはいけない)。 + def self?.display_typemark: (typemark mark, String? version) -> String + def self?.functionname?: (String n) -> bool # string -> case-sensitive ID diff --git a/sig/bitclust/rdcompiler.rbs b/sig/bitclust/rdcompiler.rbs index 4bf58ba..fde70e7 100644 --- a/sig/bitclust/rdcompiler.rbs +++ b/sig/bitclust/rdcompiler.rbs @@ -32,7 +32,7 @@ module BitClust type opt = bool? - # method_signature で `string @method.klass.name + @method.typemark` を実行するかどうか + # method_signature で `string @method.klass.name + @method.display_typemark` を実行するかどうか @opt: opt @f: LineInput diff --git a/test/test_class_screen.rb b/test/test_class_screen.rb index 48a13ac..5015801 100644 --- a/test/test_class_screen.rb +++ b/test/test_class_screen.rb @@ -327,3 +327,87 @@ def test_per_signature_badges_attach_to_their_own_alias_line 'the until badge must attach after alias_two') end end + +# bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を +# 独自の「.#」から「?.」に変える(表示のみ、識別子は変えない)。 +# +# data/bitclust/template/class の「継承しているメソッド」一覧は +# @entry.inherited_method_specs(MethodSpec、@db を持たない値オブジェクト)を +# 直接文字列化する数少ない箇所なので、上の TestClassScreenDefaultTemplateVersionBadges +# と同じ理由で実ディスク DB を使う +class TestClassScreenModuleFunctionDisplay < Test::Unit::TestCase + SRC = <<'HERE' += module Mixin +mixin module +== Module Functions +--- mf +mixin module function += class Host < Object +include Mixin +host class +== Instance Methods +--- own_method +own method +HERE + + def render(version) + datadir = File.expand_path('../data/bitclust', __dir__) + dbdir = Dir.mktmpdir('bitclust-module-function-display-db') + srcdir = Dir.mktmpdir('bitclust-module-function-display-src') + begin + src_path = File.join(srcdir, 'testlib.rd') + File.write(src_path, SRC) + + db = BitClust::MethodDatabase.new(dbdir) + db.init + db.transaction do + db.propset('version', version) + db.propset('encoding', 'utf-8') + end + db.transaction do + db.update_by_file(src_path, 'testlib') + end + + manager = BitClust::ScreenManager.new( + :templatedir => "#{datadir}/template", + :catalogdir => "#{datadir}/catalog", + :encoding => 'utf-8', + :default_encoding => 'utf-8', + :base_url => '', + :target_version => version + ) + # See TestClassScreenDefaultTemplateVersionBadges above for the general + # background: Screen#run_template caches the compiled ERB body on the + # *class*, keyed only by method name. A plain anonymous subclass is not + # sufficient isolation here, though: if BitClust::ClassScreen itself + # was already compiled for a different templatedir (offline) by some + # other test earlier in this same process -- order-dependent, since + # TestClassScreen above uses BitClust::ClassScreen directly, with no + # subclass -- then `respond_to?(:class_template)` is already true via + # *inheritance*, and the subclass would silently reuse that stale + # compiled method instead of compiling its own. So compile directly + # onto screen_class ourselves, before Screen#run_template ever gets a + # chance to check: our own method then always wins by normal Ruby + # method resolution, regardless of what BitClust::ClassScreen has. + screen_class = Class.new(BitClust::ClassScreen) + repo = BitClust::TemplateRepository.new("#{datadir}/template") + ERB.new(repo.load('class')).def_method(screen_class, 'class_template', 'class.erb') + manager.send(:new_screen, screen_class, db.get_class('Host'), :database => db).body + ensure + FileUtils.rm_r(srcdir, :force => true) + FileUtils.rm_r(dbdir, :force => true) + end + end + + def test_inherited_module_function_shows_dot_hash_before_4_0 + html = render('3.4') + assert_include(html, '.#mf') + assert_not_include(html, '?.mf') + end + + def test_inherited_module_function_switches_to_question_dot_at_4_0 + html = render('4.0') + assert_include(html, '?.mf') + assert_not_include(html, '.#mf') + end +end diff --git a/test/test_method_screen.rb b/test/test_method_screen.rb index de961e5..697b447 100644 --- a/test/test_method_screen.rb +++ b/test/test_method_screen.rb @@ -121,3 +121,46 @@ def test_since_badge_is_rendered_after_fill_since assert_include(html, 'Ruby 3.2 から') end end + +# bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を +# 独自の「.#」から「?.」に変える(表示のみ、識別子は変えない)。この +# メソッドページの見出し・がまさに issue に挙がっている +# 「module function Kernel.#open」の例そのもの +class TestMethodScreenModuleFunctionDisplay < Test::Unit::TestCase + SRC = <<'HERE' += module Kernel +description +== Module Functions +--- mf + +説明 +HERE + + def render(version) + _lib, db = BitClust::RRDParser.parse(SRC, 'testlib', {'version' => version}) + entry = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf')) + datadir = File.expand_path('../data/bitclust', __dir__) + manager = BitClust::ScreenManager.new( + :templatedir => "#{datadir}/template.offline", + :catalogdir => "#{datadir}/catalog", + :encoding => 'utf-8', + :default_encoding => 'utf-8', + :base_url => '', + :target_version => version + ) + manager.method_screen([entry], :database => db).body + end + + def test_headline_and_title_show_dot_hash_before_4_0 + html = render('3.4') + assert_match(%r{ module function Kernel\.#mf
}, html) + assert_include(html, 'Kernel.#mf') + end + + def test_headline_and_title_switch_to_question_dot_at_4_0 + html = render('4.0') + assert_match(%r{ module function Kernel\?\.mf
}, html) + assert_include(html, 'Kernel?.mf') + assert_not_include(html, 'Kernel.#mf') + end +end diff --git a/test/test_methodentry.rb b/test/test_methodentry.rb index cfaaa97..2072d7a 100644 --- a/test/test_methodentry.rb +++ b/test/test_methodentry.rb @@ -69,6 +69,85 @@ def test_title_labels_matches_label_for_single_special_variable end end +# bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を +# 独自の「.#」から「?.」に変える(表示のみ。識別子は変えない)。 +# +# label/short_label/labels/title_labels 自身は refsdatabase.rb の +# [[a:...]] アンカー解決キー(labels)や `bitclust methods --diff` の +# expand_mf マッチング(labels)が literal ".#" を前提にしているため、 +# 一切変更しない。表示用には display_label/display_short_label/ +# display_title_labels/display_typemark を新設し、テンプレート側の +# 実際の表示箇所だけがそちらを呼ぶようにする。 +class TestMethodEntryDisplayTypemark < Test::Unit::TestCase + SRC = < IO + +標準出力。 +HERE + + def build(version) + _lib, db = BitClust::RRDParser.parse(SRC, 'testlib', {'version' => version}) + db + end + + def test_module_function_display_stays_dot_hash_before_4_0 + db = build('3.4') + m = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf')) + assert_equal('.#', m.display_typemark) + assert_equal('Kernel.#mf', m.display_label) + assert_equal('.#mf', m.display_short_label) + assert_equal(['Kernel.#mf', 'Kernel.#mf2'], m.display_title_labels) + end + + def test_module_function_display_switches_to_question_dot_at_4_0 + db = build('4.0') + m = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf')) + assert_equal('?.', m.display_typemark) + assert_equal('Kernel?.mf', m.display_label) + assert_equal('?.mf', m.display_short_label) + assert_equal(['Kernel?.mf', 'Kernel?.mf2'], m.display_title_labels) + end + + # 識別子として使われる label/labels/short_label/title_labels は、 + # 表示版とは無関係に version が 4.0 以降でも従来どおり ".#" のまま + def test_identity_methods_are_unaffected_by_version + db = build('4.0') + m = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf')) + assert_equal('.#', m.typemark) + assert_equal('Kernel.#mf', m.label) + assert_equal('.#mf', m.short_label) + assert_equal(['Kernel.#mf', 'Kernel.#mf2'], m.title_labels) + assert_equal(['Kernel.#mf', 'Kernel.#mf2'], m.labels) + end + + def test_instance_method_and_special_variable_display_are_unaffected_by_version + db = build('4.0') + im = db.get_method(BitClust::MethodSpec.parse('Kernel#im')) + assert_equal('#', im.display_typemark) + assert_equal('Kernel#im', im.display_label) + assert_equal('im', im.display_short_label) + + sv = db.get_method(BitClust::MethodSpec.parse('Kernel$stdout')) + assert_equal('$', sv.display_typemark) + assert_equal('$stdout', sv.display_label) + assert_equal('$stdout', sv.display_short_label) + end +end + # メソッド名別の since/until (bitclust#132 P1)。 # # テストリスト: diff --git a/test/test_nameutils.rb b/test/test_nameutils.rb index c1250e3..12daf39 100644 --- a/test/test_nameutils.rb +++ b/test/test_nameutils.rb @@ -306,6 +306,28 @@ def test_typemark2char(data) assert_equal(expected, typemark2char(target)) end + # bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を + # 独自の「.#」から「?.」に変える。表示だけの変換であり、識別子(typemark + # そのもの・typemark2char 等)は一切変えないので、対象は '.#' のみ。 + # バージョン比較は文字列比較ではなく Gem::Version で行う(例: "10.0" を + # 文字列比較すると "4.0" より小さく見えてしまう)。 + data("module function before 4.0 stays as-is" => ['.#', '.#', '3.4'], + "module function at exactly 4.0 switches" => ['?.', '.#', '4.0'], + "module function after 4.0 switches" => ['?.', '.#', '4.1'], + "module function 4.0.0 counts as 4.0" => ['?.', '.#', '4.0.0'], + "module function double-digit major (10.0)" => ['?.', '.#', '10.0'], + "module function just under 4.0 stays as-is" => ['.#', '.#', '3.99'], + "module function with nil version stays as-is" => ['.#', '.#', nil], + "module function with empty version stays" => ['.#', '.#', ''], + "singleton method mark is never rewritten" => ['.', '.', '4.0'], + "instance method mark is never rewritten" => ['#', '#', '4.0'], + "constant mark is never rewritten" => ['::', '::', '4.0'], + "special variable mark is never rewritten" => ['$', '$', '4.0']) + def test_display_typemark(data) + expected, mark, version = data + assert_equal(expected, display_typemark(mark, version)) + end + data("s" => [".", "s"], "i" => ["#", "i"], "m" => [".#", "m"], diff --git a/test/test_rdcompiler.rb b/test/test_rdcompiler.rb index 362c66e..ae074c6 100644 --- a/test/test_rdcompiler.rb +++ b/test/test_rdcompiler.rb @@ -214,6 +214,47 @@ def test_method assert_compiled_method_source(expected, src) end + # bitclust#250: template.lillia/library だけが compile_method(m, true) で + # 第2引数(@opt)を渡し、見出し先頭に「クラス名+typemark」を付ける。4.0 + # 以降のドキュメントではこの prefix も "?." 表示に切り替わる(識別子である + # typemark ではなく、表示専用の display_typemark を経由する) + def test_method_with_opt_prefixes_class_name_and_display_typemark + klass = Object.new + mock(klass).name { 'Kernel' } + + method_entry = Object.new + mock(method_entry).source { "--- mf\n\nfoo\n" } + mock(method_entry).index_id.any_times { 'dummy' } + mock(method_entry).defined?.any_times { true } + mock(method_entry).id.any_times { 'Kernel/m.mf._builtin' } + mock(method_entry).names.any_times { ['mf'] } + mock(method_entry).since_map.any_times { {} } + mock(method_entry).until_map.any_times { {} } + mock(method_entry).klass.any_times { klass } + mock(method_entry).display_typemark.any_times { '?.' } + + html = @c.compile_method(method_entry, true) + assert_include(html, ' Kernel?.mf') + end + + # opt を渡さない通常経路(class/method/function 各テンプレート)は + # klass/display_typemark に一切触れず、prefix なしのまま(回帰防止: + # klass/display_typemark をスタブしていないので、誤って参照すると + # 未定義呼び出しとして失敗する) + def test_method_without_opt_omits_the_class_and_typemark_prefix + method_entry = Object.new + mock(method_entry).source { "--- mf\n\nfoo\n" } + mock(method_entry).index_id.any_times { 'dummy' } + mock(method_entry).defined?.any_times { true } + mock(method_entry).id.any_times { 'Kernel/m.mf._builtin' } + mock(method_entry).names.any_times { ['mf'] } + mock(method_entry).since_map.any_times { {} } + mock(method_entry).until_map.any_times { {} } + + html = @c.compile_method(method_entry) + assert_include(html, 'mf') + end + def test_method_with_emlist src = <<'HERE' --- <=> diff --git a/test/test_search_index_generator.rb b/test/test_search_index_generator.rb index 796f113..a00abb3 100644 --- a/test/test_search_index_generator.rb +++ b/test/test_search_index_generator.rb @@ -69,6 +69,41 @@ def test_module_function_is_class_method assert_equal 'method/-kernel/m/at_exit.html', e[:path] end + # bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を + # 独自の「.#」から「?.」に変える(表示のみ)。full_name は検索結果に + # そのまま出る表示ラベルなので変換対象だが、path(識別子)は変えない + def test_module_function_full_name_switches_to_question_dot_at_4_0 + prefix = 'db_si_40' + base = 'tree_si_40' + root = "#{base}/refm/api/src" + FileUtils.mkdir_p("#{root}/_builtin") + File.write("#{root}/LIBRARIES", "_builtin\n") + File.write("#{root}/_builtin.rd", <<~RD) + description + + = module Kernel + description + == Module Functions + --- at_exit{ ... } -> Proc + aaa + RD + db = BitClust::MethodDatabase.new(prefix) + db.init + db.transaction do + [%w[version 4.0], %w[encoding utf-8]].each { |k, v| db.propset(k, v) } + end + db.transaction { db.update_by_stdlibtree(root) } + + index = BitClust::SearchIndexGenerator.new.build_index(db) + e = index.find { |x| x[:path] == 'method/-kernel/m/at_exit.html' } + assert_not_nil e + assert_equal 'at_exit', e[:name] + assert_equal 'class_method', e[:type] + assert_equal 'Kernel?.at_exit', e[:full_name] + ensure + FileUtils.rm_r([prefix, base], :force => true) + end + def test_constant_entry index = @gen.build_index(@db) e = find_entry(index, 'Foo::AAA') diff --git a/test/test_search_screen.rb b/test/test_search_screen.rb new file mode 100644 index 0000000..3c862cc --- /dev/null +++ b/test/test_search_screen.rb @@ -0,0 +1,48 @@ +require 'test/unit' +require 'bitclust' +require 'bitclust/screen' + +# Covers data/bitclust/template/search: the dynamic CGI search-results page +# rendered by `bitclust server`'s /search endpoint (SearchScreen). Distinct +# from the static client-side search index (SearchIndexGenerator, covered by +# test_search_index_generator.rb) -- this one builds its "Kernel.#mf"-style +# label directly in the template from klass.name + typemark + signature. +# +# bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を +# 独自の「.#」から「?.」に変える(表示のみ、識別子は変えない)。 +class TestSearchScreenModuleFunctionDisplay < Test::Unit::TestCase + SRC = <<'HERE' += module Kernel +description +== Module Functions +--- mf + +説明 +HERE + + def render(version) + _lib, db = BitClust::RRDParser.parse(SRC, 'testlib', {'version' => version}) + entry = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf')) + datadir = File.expand_path('../data/bitclust', __dir__) + manager = BitClust::ScreenManager.new( + :templatedir => "#{datadir}/template", + :catalogdir => "#{datadir}/catalog", + :encoding => 'utf-8', + :default_encoding => 'utf-8', + :base_url => '', + :target_version => version + ) + manager.search_screen([entry], :database => db, :q => 'mf', :elapsed_time => 0.0).body + end + + def test_module_function_shows_dot_hash_before_4_0 + html = render('3.4') + assert_include(html, 'Kernel.#mf') + end + + def test_module_function_switches_to_question_dot_at_4_0 + html = render('4.0') + assert_include(html, 'Kernel?.mf') + assert_not_include(html, 'Kernel.#mf') + end +end
-
<% @entries.sort.each do |ent| %>
diff --git a/data/bitclust/template/search b/data/bitclust/template/search
index f6452db..8c5f6b3 100644
--- a/data/bitclust/template/search
+++ b/data/bitclust/template/search
@@ -39,7 +39,7 @@
else
sigs.map {|sig|
method_link(e.spec_string,
- e.klass.name + e.typemark + sig.to_s)
+ e.klass.name + e.display_typemark + sig.to_s)
}
end.join("
\n") %>