bitclust-core 1.3.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +65 -0
  4. data/Rakefile +32 -0
  5. data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
  6. data/data/bitclust/searchpage/index.html +90 -0
  7. data/data/bitclust/template/class +69 -8
  8. data/data/bitclust/template/function +2 -2
  9. data/data/bitclust/template/layout +11 -3
  10. data/data/bitclust/template/method +2 -2
  11. data/data/bitclust/template/search +1 -1
  12. data/data/bitclust/template.epub/class +22 -1
  13. data/data/bitclust/template.epub/function +2 -2
  14. data/data/bitclust/template.epub/layout +1 -3
  15. data/data/bitclust/template.epub/method +2 -2
  16. data/data/bitclust/template.lillia/class +26 -5
  17. data/data/bitclust/template.lillia/layout +1 -3
  18. data/data/bitclust/template.lillia/method +2 -2
  19. data/data/bitclust/template.offline/class +48 -4
  20. data/data/bitclust/template.offline/function +2 -2
  21. data/data/bitclust/template.offline/layout +27 -3
  22. data/data/bitclust/template.offline/method +2 -2
  23. data/lib/bitclust/app.rb +16 -17
  24. data/lib/bitclust/capi_converter.rb +20 -0
  25. data/lib/bitclust/classentry.rb +67 -17
  26. data/lib/bitclust/compat.rb +2 -2
  27. data/lib/bitclust/completion.rb +58 -27
  28. data/lib/bitclust/crossrubyutils.rb +8 -5
  29. data/lib/bitclust/database.rb +28 -8
  30. data/lib/bitclust/doc_converter.rb +63 -0
  31. data/lib/bitclust/docentry.rb +1 -1
  32. data/lib/bitclust/entity_splitter.rb +221 -0
  33. data/lib/bitclust/entry.rb +47 -7
  34. data/lib/bitclust/functiondatabase.rb +13 -1
  35. data/lib/bitclust/functionentry.rb +5 -1
  36. data/lib/bitclust/functionreferenceparser.rb +4 -4
  37. data/lib/bitclust/generators/epub.rb +10 -3
  38. data/lib/bitclust/htmlutils.rb +1 -1
  39. data/lib/bitclust/include_graph.rb +375 -0
  40. data/lib/bitclust/include_pruner.rb +144 -0
  41. data/lib/bitclust/interface.rb +1 -1
  42. data/lib/bitclust/libraryentry.rb +8 -7
  43. data/lib/bitclust/lineinput.rb +7 -5
  44. data/lib/bitclust/link_checker.rb +179 -0
  45. data/lib/bitclust/markdown_bridge.rb +186 -0
  46. data/lib/bitclust/markdown_orchestrator.rb +267 -0
  47. data/lib/bitclust/markdown_to_rrd.rb +824 -0
  48. data/lib/bitclust/markdown_tree.rb +217 -0
  49. data/lib/bitclust/mdcompiler.rb +883 -0
  50. data/lib/bitclust/mdparser.rb +347 -0
  51. data/lib/bitclust/messagecatalog.rb +3 -3
  52. data/lib/bitclust/method_since_calculator.rb +117 -0
  53. data/lib/bitclust/methoddatabase.rb +124 -14
  54. data/lib/bitclust/methodentry.rb +125 -7
  55. data/lib/bitclust/methodid.rb +12 -11
  56. data/lib/bitclust/methodsignature.rb +10 -4
  57. data/lib/bitclust/nameutils.rb +41 -22
  58. data/lib/bitclust/preprocessor.rb +66 -29
  59. data/lib/bitclust/rdcompiler.rb +195 -95
  60. data/lib/bitclust/refsdatabase.rb +23 -8
  61. data/lib/bitclust/reloadable_request_handler.rb +72 -0
  62. data/lib/bitclust/requesthandler.rb +21 -16
  63. data/lib/bitclust/ridatabase.rb +10 -4
  64. data/lib/bitclust/rrd_to_markdown.rb +925 -0
  65. data/lib/bitclust/rrdparser.rb +196 -30
  66. data/lib/bitclust/runner.rb +17 -2
  67. data/lib/bitclust/screen.rb +80 -11
  68. data/lib/bitclust/search_index_generator.rb +288 -0
  69. data/lib/bitclust/searcher.rb +69 -20
  70. data/lib/bitclust/simplesearcher.rb +24 -12
  71. data/lib/bitclust/subcommand.rb +7 -1
  72. data/lib/bitclust/subcommands/ancestors_command.rb +9 -2
  73. data/lib/bitclust/subcommands/checklink_command.rb +54 -0
  74. data/lib/bitclust/subcommands/chm_command.rb +15 -7
  75. data/lib/bitclust/subcommands/classes_command.rb +5 -0
  76. data/lib/bitclust/subcommands/extract_command.rb +5 -0
  77. data/lib/bitclust/subcommands/htmlfile_command.rb +21 -7
  78. data/lib/bitclust/subcommands/init_command.rb +4 -1
  79. data/lib/bitclust/subcommands/list_command.rb +12 -4
  80. data/lib/bitclust/subcommands/lookup_command.rb +28 -9
  81. data/lib/bitclust/subcommands/methods_command.rb +10 -2
  82. data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
  83. data/lib/bitclust/subcommands/preproc_command.rb +5 -0
  84. data/lib/bitclust/subcommands/property_command.rb +2 -1
  85. data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
  86. data/lib/bitclust/subcommands/server_command.rb +10 -8
  87. data/lib/bitclust/subcommands/setup_command.rb +24 -10
  88. data/lib/bitclust/subcommands/statichtml_command.rb +129 -6
  89. data/lib/bitclust/subcommands/update_command.rb +110 -2
  90. data/lib/bitclust/syntax_highlighter.rb +48 -4
  91. data/lib/bitclust/textutils.rb +3 -3
  92. data/lib/bitclust/version.rb +1 -1
  93. data/lib/bitclust/version_badges.rb +90 -0
  94. data/lib/bitclust/whole_file_gate.rb +137 -0
  95. data/test/test_app.rb +269 -0
  96. data/test/test_bitclust.rb +2 -0
  97. data/test/test_capi_converter.rb +91 -0
  98. data/test/test_class_screen.rb +413 -0
  99. data/test/test_copy_doc_md.rb +53 -0
  100. data/test/test_doc_converter.rb +78 -0
  101. data/test/test_entity_splitter.rb +231 -0
  102. data/test/test_entry.rb +207 -2
  103. data/test/test_eol_warning.rb +43 -0
  104. data/test/test_functionreferenceparser.rb +11 -0
  105. data/test/test_include_graph.rb +640 -0
  106. data/test/test_include_pruner.rb +133 -0
  107. data/test/test_link_checker.rb +114 -0
  108. data/test/test_lookup_command.rb +135 -0
  109. data/test/test_markdown_bridge.rb +217 -0
  110. data/test/test_markdown_orchestrator.rb +478 -0
  111. data/test/test_markdown_to_rrd.rb +757 -0
  112. data/test/test_markdown_tree.rb +369 -0
  113. data/test/test_mdcompiler.rb +1276 -0
  114. data/test/test_mdparser.rb +538 -0
  115. data/test/test_method_screen.rb +166 -0
  116. data/test/test_method_since_calculator.rb +290 -0
  117. data/test/test_methoddatabase.rb +16 -0
  118. data/test/test_methodentry.rb +328 -0
  119. data/test/test_methodsignature.rb +3 -1
  120. data/test/test_methodsince_command.rb +168 -0
  121. data/test/test_nameutils.rb +22 -0
  122. data/test/test_preprocessor.rb +215 -0
  123. data/test/test_rdcompiler.rb +401 -30
  124. data/test/test_refsdatabase.rb +32 -0
  125. data/test/test_rrd_to_markdown.rb +968 -0
  126. data/test/test_rrdparser.rb +295 -2
  127. data/test/test_run_ruby_wasm.rb +54 -0
  128. data/test/test_run_worker_prelude.rb +83 -0
  129. data/test/test_runner.rb +51 -0
  130. data/test/test_search_index_generator.rb +513 -0
  131. data/test/test_search_screen.rb +48 -0
  132. data/test/test_searcher.rb +132 -0
  133. data/test/test_searchpage_command.rb +167 -0
  134. data/test/test_simplesearcher.rb +28 -0
  135. data/test/test_statichtml_command.rb +204 -0
  136. data/test/test_syntax_highlighter.rb +60 -0
  137. data/test/test_whole_file_gate.rb +175 -0
  138. data/theme/default/js/NOTICE +42 -0
  139. data/theme/default/js/run-worker.js +111 -0
  140. data/theme/default/js/run.js +350 -0
  141. data/theme/default/js/search_controller.js +134 -0
  142. data/theme/default/js/search_init.js +176 -0
  143. data/theme/default/js/search_navigation.js +110 -0
  144. data/theme/default/js/search_page.js +202 -0
  145. data/theme/default/js/search_ranker.js +244 -0
  146. data/theme/default/script.js +91 -18
  147. data/theme/default/search.css +149 -0
  148. data/theme/default/style.css +193 -21
  149. data/theme/default/syntax-highlight.css +8 -0
  150. data/theme/lillia/style.css +45 -1
  151. metadata +161 -20
  152. data/README +0 -32
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+
7
+ require 'bitclust/markdown_tree'
8
+
9
+ # MarkdownTree: 新パイプラインのファイル発見(MARKUP_SPEC §1.1)。
10
+ # refm/api/src/**/*.md を glob し、front matter と H1 から
11
+ # エンティティ / ライブラリ / 共有断片 に分類する。LIBRARIES は使わない。
12
+ #
13
+ # 検証(ビルド警告):
14
+ # - 孤児: エンティティ H1 も type: library も持たず、どの #@include からも参照されない
15
+ # - library の無いエンティティ(スコープ外サルベージ分は許容リストで扱う)
16
+ # - 未知の library を指すエンティティ
17
+ # - 関係リント: マルチエンティティファイルに front matter の関係キー、
18
+ # または本文 H1 直後の関係行があれば警告(関係は front matter が唯一の記述場所)
19
+ # - #@include 先の欠損
20
+ #
21
+ # テストリスト:
22
+ # [x] type: library → ライブラリ(名前 = パスから .md を除いたもの)
23
+ # [x] エンティティ H1 → エンティティ(名前・library を読む)
24
+ # [x] 両方持つ dual ファイル(pathname 型)は両方に数える
25
+ # [x] どちらでもない + 参照あり → 断片
26
+ # [x] どちらでもない + 参照なし → 孤児警告
27
+ # [x] コードフェンス内の「# class」風の行は H1 に数えない
28
+ # [x] #@include の解決: target / target.md / target の .rd → .md
29
+ # [x] 断片からの #@include も参照として辿る
30
+ # [x] library なしエンティティ → 警告
31
+ # [x] 未知 library 参照 → 警告
32
+ # [x] マルチエンティティ + front matter 関係キー → リント警告
33
+ # [x] マルチエンティティ + 本文 H1 直後の関係行 → リント警告
34
+ # [x] include 先欠損 → 警告
35
+ class TestMarkdownTree < Test::Unit::TestCase
36
+ def scan(files)
37
+ Dir.mktmpdir do |dir|
38
+ files.each do |path, content|
39
+ full = File.join(dir, path)
40
+ FileUtils.mkdir_p(File.dirname(full))
41
+ File.write(full, content)
42
+ end
43
+ return BitClust::MarkdownTree.scan(dir)
44
+ end
45
+ end
46
+
47
+ LIB = "---\ntype: library\ncategory: Cat\n---\n概要。\n"
48
+
49
+ def test_library_file
50
+ tree = scan("foo.md" => LIB)
51
+ assert_equal ["foo"], tree.libraries.keys
52
+ end
53
+
54
+ def test_entity_file
55
+ tree = scan(
56
+ "foo.md" => LIB,
57
+ "foo/Bar.md" => "---\nlibrary: foo\n---\n# class Bar < Object\n\n説明。\n"
58
+ )
59
+ assert_equal ["Bar"], tree.entities["foo/Bar.md"][:names]
60
+ assert_equal "foo", tree.entities["foo/Bar.md"][:library]
61
+ assert_equal [], tree.warnings
62
+ end
63
+
64
+ def test_scalar_library_is_normalized_to_memberships
65
+ tree = scan(
66
+ "foo.md" => LIB,
67
+ "foo/Bar.md" => "---\nlibrary: foo\n---\n# class Bar < Object\n"
68
+ )
69
+ assert_equal [{ library: "foo" }], tree.entities["foo/Bar.md"][:memberships]
70
+ end
71
+
72
+ def test_gated_library_list_memberships
73
+ # 多重所属(MARKUP_SPEC §1.2): ゲート付きリスト形式の library を
74
+ # memberships として読む。先頭が主所属(:library は後方互換の別名)
75
+ tree = scan(
76
+ "builtin.md" => LIB,
77
+ "thread.md" => LIB,
78
+ "thread/Mutex.md" =>
79
+ "---\n" \
80
+ "library:\n" \
81
+ " - builtin\n" \
82
+ "\#@until 1.9.1\n" \
83
+ " - thread\n" \
84
+ "\#@end\n" \
85
+ "---\n" \
86
+ "# class Mutex < Object\n\n説明。\n"
87
+ )
88
+ e = tree.entities["thread/Mutex.md"]
89
+ assert_equal "builtin", e[:library]
90
+ assert_equal [
91
+ { library: "builtin" },
92
+ { library: "thread", until: "1.9.1" },
93
+ ], e[:memberships]
94
+ assert_equal [], tree.warnings
95
+ end
96
+
97
+ def test_gated_library_list_with_both_bounds
98
+ tree = scan(
99
+ "builtin.md" => LIB,
100
+ "thread.md" => LIB,
101
+ "thread/CV.md" =>
102
+ "---\n" \
103
+ "library:\n" \
104
+ "\#@since 2.3.0\n" \
105
+ " - builtin\n" \
106
+ "\#@end\n" \
107
+ "\#@since 1.9.1\n" \
108
+ "\#@until 2.3.0\n" \
109
+ " - thread\n" \
110
+ "\#@end\n" \
111
+ "\#@end\n" \
112
+ "---\n" \
113
+ "# class ConditionVariable < Object\n"
114
+ )
115
+ assert_equal [
116
+ { library: "builtin", since: "2.3.0" },
117
+ { library: "thread", since: "1.9.1", until: "2.3.0" },
118
+ ], tree.entities["thread/CV.md"][:memberships]
119
+ assert_equal [], tree.warnings
120
+ end
121
+
122
+ def test_version_gated_body_relations_do_not_lint
123
+ # H1 自体が版分岐するファイル(rbconfig の Config⇔RbConfig 等)や
124
+ # ゲート付きヘッダ関係は body 残置が正しい(#@ ブロック内は
125
+ # ヘッダ関係扱いしない)。lint はゲート外の関係行だけを警告する
126
+ tree = scan(
127
+ "foo.md" => LIB,
128
+ "foo/Bar.md" =>
129
+ "---\nlibrary: foo\n---\n" \
130
+ "\#@since 1.9.1\n" \
131
+ "# module Bar\n" \
132
+ "\#@until 2.2.0\n" \
133
+ "alias Config\n" \
134
+ "\#@end\n" \
135
+ "説明。\n" \
136
+ "\#@else\n" \
137
+ "# module Config\n" \
138
+ "alias Bar\n" \
139
+ "\#@end\n"
140
+ )
141
+ assert tree.warnings.none? { |w| w.include?("relations in body") },
142
+ tree.warnings.inspect
143
+ end
144
+
145
+ def test_relation_after_gated_h1_block_does_not_lint
146
+ # 版分岐 H1 ペアの直後(ゲートの外)に両ブランチ共通の関係行が続く形
147
+ # (net/Net__HTTPURITooLong の alias)。H1 がゲート内にある間は
148
+ # ヘッダ領域全体を lint 対象から外す
149
+ tree = scan(
150
+ "foo.md" => LIB,
151
+ "foo/Bar.md" =>
152
+ "---\nlibrary: foo\n---\n" \
153
+ "\#@since 2.6.0\n" \
154
+ "# class Bar < Object\n" \
155
+ "alias NewName\n" \
156
+ "\#@else\n" \
157
+ "# class OldBar < Object\n" \
158
+ "\#@end\n" \
159
+ "alias CommonName\n" \
160
+ "説明。\n"
161
+ )
162
+ assert tree.warnings.none? { |w| w.include?("relations in body") },
163
+ tree.warnings.inspect
164
+ end
165
+
166
+ def test_gated_library_list_unknown_library_warns
167
+ tree = scan(
168
+ "foo.md" => LIB,
169
+ "foo/Bar.md" =>
170
+ "---\nlibrary:\n - foo\n\#@until 2.0.0\n - ghost\n\#@end\n---\n# class Bar < Object\n"
171
+ )
172
+ assert tree.warnings.any? { |w| w.include?("unknown library ghost") },
173
+ tree.warnings.inspect
174
+ end
175
+
176
+ def test_dual_library_entity_file
177
+ tree = scan("foo.md" => "---\ntype: library\n---\n# class Foo < Object\n説明。\n")
178
+ assert_equal ["foo"], tree.libraries.keys
179
+ assert_equal ["Foo"], tree.entities["foo.md"][:names]
180
+ end
181
+
182
+ def test_fragment_and_orphan
183
+ tree = scan(
184
+ "foo.md" => LIB + "\#@include(frag)\n",
185
+ "foo/orphan.md" => "誰からも参照されない断片。\n",
186
+ "frag.md" => "参照される断片。\n"
187
+ )
188
+ assert_equal ["frag.md"], tree.fragments
189
+ assert tree.warnings.any? { |w| w.include?("orphan") && w.include?("foo/orphan.md") },
190
+ tree.warnings.inspect
191
+ end
192
+
193
+ def test_code_fence_h1_is_not_entity
194
+ md = "---\nlibrary: foo\n---\n# class Bar < Object\n\n`````\n# class Fake < Object\n`````\n"
195
+ tree = scan("foo.md" => LIB, "foo/Bar.md" => md)
196
+ assert_equal ["Bar"], tree.entities["foo/Bar.md"][:names]
197
+ end
198
+
199
+ def test_include_resolution_variants
200
+ tree = scan(
201
+ "foo.md" => LIB + "\#@include(frag)\n\#@include(other.rd)\n\#@include(third.md)\n",
202
+ "frag.md" => "断片1。\n",
203
+ "other.md" => "断片2。\n",
204
+ "third.md" => "断片3。\n"
205
+ )
206
+ assert_equal ["frag.md", "other.md", "third.md"], tree.fragments.sort
207
+ assert_equal [], tree.warnings
208
+ end
209
+
210
+ def test_fragment_chain_is_followed
211
+ tree = scan(
212
+ "foo.md" => LIB + "\#@include(a)\n",
213
+ "a.md" => "断片A。\n\#@include(b)\n",
214
+ "b.md" => "断片B。\n"
215
+ )
216
+ assert_equal ["a.md", "b.md"], tree.fragments.sort
217
+ assert_equal [], tree.warnings
218
+ end
219
+
220
+ def test_referenced_file_without_front_matter_is_fragment_even_with_h1
221
+ # fiddle/2.0/types.rd 型: エンティティ H1 を含む断片が include 参照される
222
+ # (transclusion で取り込まれるので単独エンティティとしては数えない)
223
+ tree = scan(
224
+ "foo.md" => LIB + "\#@include(part)\n",
225
+ "part.md" => "断片。\n\n# reopen Fiddle\n### def win_types\n"
226
+ )
227
+ assert_equal ["part.md"], tree.fragments
228
+ assert_false tree.entities.key?("part.md")
229
+ assert_equal [], tree.warnings
230
+ end
231
+
232
+ def test_entity_kinds_are_recorded
233
+ tree = scan(
234
+ "foo.md" => LIB,
235
+ "foo/Bar.md" => "---\nlibrary: foo\n---\n# class Bar < Object\n\n# reopen Kernel\n"
236
+ )
237
+ assert_equal [%w[class Bar], %w[reopen Kernel]], tree.entities["foo/Bar.md"][:kinds]
238
+ end
239
+
240
+ def test_entity_without_library_warns
241
+ tree = scan("foo.md" => LIB, "Dead.md" => "# class Dead < Object\n説明。\n")
242
+ assert tree.warnings.any? { |w| w.include?("no library") && w.include?("Dead.md") },
243
+ tree.warnings.inspect
244
+ end
245
+
246
+ def test_unknown_library_reference_warns
247
+ tree = scan("Bar.md" => "---\nlibrary: ghost\n---\n# class Bar < Object\n")
248
+ assert tree.warnings.any? { |w| w.include?("unknown library") && w.include?("ghost") },
249
+ tree.warnings.inspect
250
+ end
251
+
252
+ def test_multi_entity_with_front_matter_relations_lints
253
+ md = "---\nlibrary: foo\ninclude:\n - Enumerable\n---\n# class A < Object\n\n# class B < Object\n"
254
+ tree = scan("foo.md" => LIB, "foo/AB.md" => md)
255
+ assert tree.warnings.any? { |w| w.include?("relation") && w.include?("foo/AB.md") },
256
+ tree.warnings.inspect
257
+ end
258
+
259
+ def test_multi_entity_with_body_relations_lints
260
+ md = "---\nlibrary: foo\n---\n# class A < Object\ninclude Enumerable\n\n# class B < Object\n"
261
+ tree = scan("foo.md" => LIB, "foo/AB.md" => md)
262
+ assert tree.warnings.any? { |w| w.include?("relation") && w.include?("foo/AB.md") },
263
+ tree.warnings.inspect
264
+ end
265
+
266
+ def test_single_entity_body_relation_also_lints
267
+ # O3 後の正しい形では本文関係行は存在しないはず(単一でも警告)
268
+ md = "---\nlibrary: foo\n---\n# class A < Object\ninclude Enumerable\n"
269
+ tree = scan("foo.md" => LIB, "foo/A.md" => md)
270
+ assert tree.warnings.any? { |w| w.include?("relation") && w.include?("foo/A.md") },
271
+ tree.warnings.inspect
272
+ end
273
+
274
+ def test_missing_include_target_warns
275
+ tree = scan("foo.md" => LIB + "\#@include(nothing)\n")
276
+ assert tree.warnings.any? { |w| w.include?("include target not found") },
277
+ tree.warnings.inspect
278
+ end
279
+
280
+ def test_library_name_front_matter_override
281
+ # 出力名の大文字小文字衝突回避で改名されたライブラリファイル
282
+ # (rdoc/rdoc → rdoc/rdoc.lib.md)は front matter の name: が名前になる
283
+ tree = scan(
284
+ "rdoc.md" => LIB,
285
+ "rdoc/rdoc.lib.md" => "---\ntype: library\nname: rdoc/rdoc\n---\n概要。\n"
286
+ )
287
+ assert tree.libraries.key?("rdoc/rdoc"), tree.libraries.keys.inspect
288
+ assert_equal "rdoc/rdoc.lib.md", tree.libraries["rdoc/rdoc"][:path]
289
+ assert_false tree.libraries.key?("rdoc/rdoc.lib")
290
+ end
291
+
292
+ def test_case_insensitive_filename_collision_warns
293
+ # 大文字小文字のみが異なる名前は macOS/Windows の case-insensitive FS で
294
+ # チェックアウト不能になるため、同一ディレクトリ内の衝突を警告する
295
+ tree = scan(
296
+ "x.md" => LIB,
297
+ "x/Foo.md" => "---\nlibrary: x\n---\n# class Foo < Object\nFoo。\n",
298
+ "x/foo.md" => "---\ntype: library\n---\n概要。\n"
299
+ )
300
+ assert tree.warnings.any? { |w|
301
+ w.include?("case-insensitive") && w.include?("x/Foo.md") && w.include?("x/foo.md")
302
+ }, tree.warnings.inspect
303
+ end
304
+
305
+ # bitclust#285: 新 prefix #% でも構造発見(所属ゲート・include・版ゲート深度)
306
+ # が同一に動くこと。旧 #@ との混在も許す
307
+ def test_percent_prefix_gated_memberships
308
+ tree = scan(
309
+ "builtin.md" => LIB,
310
+ "thread.md" => LIB,
311
+ "thread/Mutex.md" =>
312
+ "---\n" \
313
+ "library:\n" \
314
+ " - builtin\n" \
315
+ "#%until 1.9.1\n" \
316
+ " - thread\n" \
317
+ "#%end\n" \
318
+ "---\n" \
319
+ "# class Mutex < Object\n\n説明。\n"
320
+ )
321
+ e = tree.entities["thread/Mutex.md"]
322
+ assert_equal [
323
+ { library: "builtin" },
324
+ { library: "thread", until: "1.9.1" },
325
+ ], e[:memberships]
326
+ assert_equal [], tree.warnings
327
+ end
328
+
329
+ def test_percent_prefix_include
330
+ tree = scan(
331
+ "foo.md" => LIB + "#%include(frag)\n\#@include(other)\n",
332
+ "frag.md" => "断片1。\n",
333
+ "other.md" => "断片2。\n"
334
+ )
335
+ assert_equal ["frag.md", "other.md"], tree.fragments.sort
336
+ assert_equal [], tree.warnings
337
+ end
338
+
339
+ def test_percent_prefix_gated_h1_relations_do_not_lint
340
+ tree = scan(
341
+ "foo.md" => LIB,
342
+ "foo/Bar.md" =>
343
+ "---\nlibrary: foo\n---\n" \
344
+ "#%since 3.0\n" \
345
+ "# class Bar < Object\n" \
346
+ "include Enumerable\n" \
347
+ "#%end\n"
348
+ )
349
+ refute tree.entities["foo/Bar.md"][:body_relations],
350
+ "gated H1 relations must not be flagged (nil or false)"
351
+ assert_equal [], tree.warnings
352
+ end
353
+
354
+ def test_version_range_gated_h1_relations_do_not_lint
355
+ # #%version A...B(#285 の版範囲記法)も版ゲートとして深度に数える
356
+ tree = scan(
357
+ "foo.md" => LIB,
358
+ "foo/Bar.md" =>
359
+ "---\nlibrary: foo\n---\n" \
360
+ "#%version 3.0...4.0\n" \
361
+ "# class Bar < Object\n" \
362
+ "include Enumerable\n" \
363
+ "#%end\n"
364
+ )
365
+ refute tree.entities["foo/Bar.md"][:body_relations],
366
+ "version-range-gated H1 relations must not be flagged"
367
+ assert_equal [], tree.warnings
368
+ end
369
+ end