bitclust-core 1.6.1 → 1.7.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.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/data/bitclust/catalog/ja_JP.UTF-8 +4 -2
- data/data/bitclust/template/function-index +1 -0
- data/data/bitclust/template/layout +1 -0
- data/data/bitclust/template.offline/function-index +1 -0
- data/data/bitclust/template.offline/layout +3 -0
- data/lib/bitclust/classentry.rb +1 -1
- data/lib/bitclust/docentry.rb +1 -1
- data/lib/bitclust/functionentry.rb +1 -1
- data/lib/bitclust/irb.rb +68 -0
- data/lib/bitclust/markdown_exporter.rb +311 -0
- data/lib/bitclust/mdcompiler.rb +15 -1
- data/lib/bitclust/mdparser.rb +42 -1
- data/lib/bitclust/methodentry.rb +24 -0
- data/lib/bitclust/methodsignature.rb +1 -1
- data/lib/bitclust/preprocessor.rb +4 -1
- data/lib/bitclust/rbs_overload_matcher.rb +179 -0
- data/lib/bitclust/rbs_sig_importer.rb +276 -0
- data/lib/bitclust/rbs_signatures.rb +125 -0
- data/lib/bitclust/rdcompiler.rb +12 -1
- data/lib/bitclust/runner.rb +2 -0
- data/lib/bitclust/screen.rb +4 -0
- data/lib/bitclust/search_index_generator.rb +28 -10
- data/lib/bitclust/searcher.rb +12 -2
- data/lib/bitclust/subcommands/rbssig_command.rb +99 -0
- data/lib/bitclust/subcommands/setup_command.rb +4 -1
- data/lib/bitclust/subcommands/statichtml_command.rb +59 -0
- data/lib/bitclust/version.rb +1 -1
- data/lib/bitclust-irb.rb +2 -0
- data/test/test_irb_plugin.rb +118 -0
- data/test/test_link_checker.rb +1 -1
- data/test/test_markdown_exporter.rb +260 -0
- data/test/test_mdcompiler.rb +47 -5
- data/test/test_mdparser.rb +184 -4
- data/test/test_methodentry.rb +111 -0
- data/test/test_methodsignature.rb +3 -1
- data/test/test_preprocessor.rb +26 -0
- data/test/test_rbs_overload_matcher.rb +170 -0
- data/test/test_rbs_sig_importer.rb +237 -0
- data/test/test_rbssig_command.rb +186 -0
- data/test/test_rdcompiler.rb +99 -1
- data/test/test_search_index_generator.rb +81 -0
- data/test/test_setup_command.rb +21 -0
- data/test/test_statichtml_command.rb +137 -0
- data/theme/default/js/run.js +14 -0
- data/theme/default/js/search_init.js +3 -1
- data/theme/default/js/search_page.js +7 -1
- data/theme/default/js/version_switcher.js +147 -0
- data/theme/default/search.css +21 -0
- data/theme/default/style.css +83 -7
- data/theme/lillia/style.css +26 -0
- metadata +35 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test/unit'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require 'bitclust'
|
|
6
|
+
require 'bitclust/markdown_exporter'
|
|
7
|
+
require 'bitclust/mdparser'
|
|
8
|
+
require 'bitclust/methoddatabase'
|
|
9
|
+
require 'bitclust/functiondatabase'
|
|
10
|
+
require 'bitclust/subcommands/statichtml_command'
|
|
11
|
+
|
|
12
|
+
# MarkdownExporter: statichtml --markdown-output 用に、HTML の各ページと
|
|
13
|
+
# 対になる Markdown を前処理済みソースから組み立てる。
|
|
14
|
+
class TestMarkdownExporter < Test::Unit::TestCase
|
|
15
|
+
PARAMS = { "version" => "3.4" }
|
|
16
|
+
|
|
17
|
+
def setup
|
|
18
|
+
@urlmapper = BitClust::Subcommands::StatichtmlCommand::URLMapperEx.new(
|
|
19
|
+
:suffix => '.html',
|
|
20
|
+
:fs_casesensitive => true
|
|
21
|
+
)
|
|
22
|
+
@urlmapper.bitclust_html_base = '..'
|
|
23
|
+
@exporter = BitClust::MarkdownExporter.new(@urlmapper, '.html')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse_md(md, libname = "_builtin")
|
|
27
|
+
db = BitClust::MethodDatabase.dummy(PARAMS)
|
|
28
|
+
lib = BitClust::MDParser.new(db).parse(StringIO.new(md), libname, PARAMS)
|
|
29
|
+
[db, lib]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sub_test_case("convert_text") do
|
|
33
|
+
def test_bare_method_ref
|
|
34
|
+
assert_equal "[Array#index](../method/Array/i/index.md) を参照",
|
|
35
|
+
@exporter.convert_text("[m:Array#index] を参照")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_bare_class_ref
|
|
39
|
+
assert_equal "[String](../class/String.md)",
|
|
40
|
+
@exporter.convert_text("[c:String]")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_bare_library_ref
|
|
44
|
+
assert_equal "[csv](../library/csv.md)",
|
|
45
|
+
@exporter.convert_text("[lib:csv]")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_bare_doc_ref
|
|
49
|
+
assert_equal "[spec/literal](../doc/spec=2fliteral.md)",
|
|
50
|
+
@exporter.convert_text("[d:spec/literal]")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_bare_function_ref
|
|
54
|
+
assert_equal "[rb_ary_new](../function/rb_ary_new.md)",
|
|
55
|
+
@exporter.convert_text("[f:rb_ary_new]")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_module_function_ref
|
|
59
|
+
# md 表記の ?. は内部表記 .# に正規化して URL を引く(ラベルは書かれたまま)
|
|
60
|
+
assert_equal "[Kernel?.puts](../method/Kernel/m/puts.md)",
|
|
61
|
+
@exporter.convert_text("[m:Kernel?.puts]")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_method_ref_with_escaped_brackets
|
|
65
|
+
# [m:Array#\[\]] のようにエスケープされた参照。URL はエスケープを解いた
|
|
66
|
+
# メソッド名から引き、ラベルは書かれたまま(md として正しい形)を保つ
|
|
67
|
+
assert_equal "[Array#\\[\\]](../method/Array/i/=5b=5d.md)",
|
|
68
|
+
@exporter.convert_text("[m:Array#\\[\\]]")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_labeled_ref
|
|
72
|
+
assert_equal "[個数](../method/Array/i/size.md)",
|
|
73
|
+
@exporter.convert_text("[個数](m:Array#size)")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_labeled_ref_with_code_span_label
|
|
77
|
+
assert_equal "[`Array#size`](../method/Array/i/size.md)",
|
|
78
|
+
@exporter.convert_text("[`Array#size`](m:Array#size)")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_markdown_link_kept
|
|
82
|
+
assert_equal "[Ruby](https://www.ruby-lang.org/)",
|
|
83
|
+
@exporter.convert_text("[Ruby](https://www.ruby-lang.org/)")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_anchor_link_kept
|
|
87
|
+
assert_equal "[前述の例](#example)",
|
|
88
|
+
@exporter.convert_text("[前述の例](#example)")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_url_ref
|
|
92
|
+
assert_equal "[https://www.ruby-lang.org/](https://www.ruby-lang.org/)",
|
|
93
|
+
@exporter.convert_text("[url:https://www.ruby-lang.org/]")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_ref_in_page
|
|
97
|
+
assert_equal "[overview](#overview)",
|
|
98
|
+
@exporter.convert_text("[ref:overview]")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_ref_cross_page
|
|
102
|
+
assert_equal "[spec/literal#num](../doc/spec=2fliteral.md#num)",
|
|
103
|
+
@exporter.convert_text("[ref:d:spec/literal#num]")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_code_span_is_not_converted
|
|
107
|
+
assert_equal "`[m:Array#index]`",
|
|
108
|
+
@exporter.convert_text("`[m:Array#index]`")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def test_code_fence_is_not_converted
|
|
112
|
+
src = "```ruby\n# [m:Array#index]\n```\n[m:Array#index]\n"
|
|
113
|
+
expected = "```ruby\n# [m:Array#index]\n```\n[Array#index](../method/Array/i/index.md)\n"
|
|
114
|
+
assert_equal expected, @exporter.convert_text(src)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_unknown_ref_type_is_kept
|
|
118
|
+
assert_equal "[man:printf(3)]", @exporter.convert_text("[man:printf(3)]")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_unresolvable_method_spec_is_kept
|
|
122
|
+
# クラス名とメソッド名に分割できない指定はそのまま残す
|
|
123
|
+
assert_equal "[m:broken spec]", @exporter.convert_text("[m:broken spec]")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_escaped_bracket_is_kept
|
|
127
|
+
assert_equal "\\[m:Array#index]", @exporter.convert_text("\\[m:Array#index]")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
sub_test_case("pages") do
|
|
132
|
+
CLASS_MD = <<~MD
|
|
133
|
+
---
|
|
134
|
+
library: _builtin
|
|
135
|
+
---
|
|
136
|
+
# class Array < Object
|
|
137
|
+
|
|
138
|
+
配列クラス。[c:Enumerable] も参照。
|
|
139
|
+
|
|
140
|
+
## Class Methods
|
|
141
|
+
|
|
142
|
+
### def self.new(size) -> Array
|
|
143
|
+
|
|
144
|
+
生成。
|
|
145
|
+
|
|
146
|
+
## Instance Methods
|
|
147
|
+
|
|
148
|
+
### def index(val) -> Integer
|
|
149
|
+
### def find_index(val) -> Integer
|
|
150
|
+
|
|
151
|
+
位置を返します。
|
|
152
|
+
MD
|
|
153
|
+
|
|
154
|
+
def test_class_page
|
|
155
|
+
_, lib = parse_md(CLASS_MD)
|
|
156
|
+
md = @exporter.class_page(lib.classes.first)
|
|
157
|
+
assert_equal <<~MD, md
|
|
158
|
+
# class Array < Object
|
|
159
|
+
|
|
160
|
+
配列クラス。[Enumerable](../class/Enumerable.md) も参照。
|
|
161
|
+
|
|
162
|
+
## Class Methods
|
|
163
|
+
|
|
164
|
+
- [new](../method/Array/s/new.md)
|
|
165
|
+
|
|
166
|
+
## Instance Methods
|
|
167
|
+
|
|
168
|
+
- [find_index](../method/Array/i/find_index.md)
|
|
169
|
+
- [index](../method/Array/i/index.md)
|
|
170
|
+
MD
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def test_module_page_heading
|
|
174
|
+
md = <<~MD
|
|
175
|
+
---
|
|
176
|
+
library: _builtin
|
|
177
|
+
---
|
|
178
|
+
# module Comparable
|
|
179
|
+
|
|
180
|
+
比較。
|
|
181
|
+
MD
|
|
182
|
+
_, lib = parse_md(md)
|
|
183
|
+
page = @exporter.class_page(lib.classes.first)
|
|
184
|
+
assert page.start_with?("# module Comparable\n"), page
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_method_page
|
|
188
|
+
_, lib = parse_md(CLASS_MD)
|
|
189
|
+
entry = lib.classes.first.entries.find { |e| e.names.include?("index") }
|
|
190
|
+
@urlmapper.bitclust_html_base = '../../..'
|
|
191
|
+
md = @exporter.method_page("Array#index", [entry])
|
|
192
|
+
assert_equal <<~MD, md
|
|
193
|
+
# Array#index
|
|
194
|
+
|
|
195
|
+
### def index(val) -> Integer
|
|
196
|
+
### def find_index(val) -> Integer
|
|
197
|
+
|
|
198
|
+
位置を返します。
|
|
199
|
+
MD
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def test_method_page_normalizes_module_function_name
|
|
203
|
+
_, lib = parse_md(CLASS_MD)
|
|
204
|
+
entry = lib.classes.first.entries.first
|
|
205
|
+
md = @exporter.method_page("Kernel.#puts", [entry])
|
|
206
|
+
assert md.start_with?("# Kernel?.puts\n"), md
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def test_library_page
|
|
210
|
+
md = <<~MD
|
|
211
|
+
---
|
|
212
|
+
type: library
|
|
213
|
+
category: FileFormat
|
|
214
|
+
---
|
|
215
|
+
CSV を扱うライブラリです。
|
|
216
|
+
MD
|
|
217
|
+
_, lib = parse_md(md, "csv")
|
|
218
|
+
page = @exporter.library_page(lib)
|
|
219
|
+
assert_equal <<~MD, page
|
|
220
|
+
# library csv
|
|
221
|
+
|
|
222
|
+
CSV を扱うライブラリです。
|
|
223
|
+
MD
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_doc_page
|
|
227
|
+
db = BitClust::MethodDatabase.dummy(PARAMS)
|
|
228
|
+
entry = BitClust::DocEntry.new(db, "help")
|
|
229
|
+
entry.title = "ヘルプ"
|
|
230
|
+
entry.source = "\n[c:String] を参照。\n"
|
|
231
|
+
page = @exporter.doc_page(entry)
|
|
232
|
+
assert_equal <<~MD, page
|
|
233
|
+
# ヘルプ
|
|
234
|
+
|
|
235
|
+
[String](../class/String.md) を参照。
|
|
236
|
+
MD
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def test_function_page
|
|
240
|
+
require 'bitclust/functiondatabase'
|
|
241
|
+
md = <<~MD
|
|
242
|
+
### VALUE rb_ary_new()
|
|
243
|
+
|
|
244
|
+
空の配列を返します。
|
|
245
|
+
MD
|
|
246
|
+
db = BitClust::FunctionDatabase.dummy(PARAMS)
|
|
247
|
+
io = StringIO.new(md)
|
|
248
|
+
def io.path = "array.c.md"
|
|
249
|
+
BitClust::MDFunctionParser.new(db).parse(io, "array.c", PARAMS)
|
|
250
|
+
page = @exporter.function_page(db.functions.first)
|
|
251
|
+
assert_equal <<~MD, page
|
|
252
|
+
# rb_ary_new
|
|
253
|
+
|
|
254
|
+
### VALUE rb_ary_new()
|
|
255
|
+
|
|
256
|
+
空の配列を返します。
|
|
257
|
+
MD
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
data/test/test_mdcompiler.rb
CHANGED
|
@@ -23,7 +23,7 @@ class TestMDCompiler < Test::Unit::TestCase
|
|
|
23
23
|
@rd = BitClust::RDCompiler.new(@u, 1, { :database => @db })
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def compile_method(compiler, src)
|
|
26
|
+
def compile_method(compiler, src, rbs_signature_overloads: nil)
|
|
27
27
|
method_entry = Object.new
|
|
28
28
|
mock(method_entry).source { src }
|
|
29
29
|
mock(method_entry).index_id.any_times { "dummy" }
|
|
@@ -32,6 +32,7 @@ class TestMDCompiler < Test::Unit::TestCase
|
|
|
32
32
|
mock(method_entry).names.any_times { [] }
|
|
33
33
|
mock(method_entry).since_map.any_times { {} }
|
|
34
34
|
mock(method_entry).until_map.any_times { {} }
|
|
35
|
+
mock(method_entry).rbs_signature_overloads.any_times { rbs_signature_overloads }
|
|
35
36
|
compiler.compile_method(method_entry)
|
|
36
37
|
end
|
|
37
38
|
|
|
@@ -58,6 +59,47 @@ class TestMDCompiler < Test::Unit::TestCase
|
|
|
58
59
|
|
|
59
60
|
# ---- メソッドエントリ ----
|
|
60
61
|
|
|
62
|
+
# RBS シグネチャ表示: MDCompiler も entry_chunk をオーバーライドしている
|
|
63
|
+
# ので、rd 経路と同じ位置(説明 <dd> の直前)に同じ <dt> が出ること
|
|
64
|
+
def test_rbs_signatures_dt_matches_rd_output
|
|
65
|
+
rd_src = "--- index(val) -> Integer\n\n説明\n"
|
|
66
|
+
overloads = [{'segments' => [['t', '() -> void']]}]
|
|
67
|
+
md_src = BitClust::RRDToMarkdown.convert(rd_src)
|
|
68
|
+
rd_html = compile_method(@rd, rd_src, rbs_signature_overloads: overloads)
|
|
69
|
+
md_html = compile_method(@md, md_src, rbs_signature_overloads: overloads)
|
|
70
|
+
assert_include md_html,
|
|
71
|
+
'<dt class="rbs-signature"><code>() → void</code></dt>'
|
|
72
|
+
assert_equal rd_html, md_html
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# チャンク振り分けは md 側のシグネチャ行(### def ...)でも rd と同じに
|
|
76
|
+
# 動くこと(MDCompiler は行を "--- " に正規化してから照合する)
|
|
77
|
+
def test_rbs_signatures_chunk_assignment_matches_rd_output
|
|
78
|
+
rd_src = <<~RD
|
|
79
|
+
--- index(sub) -> Integer | nil
|
|
80
|
+
|
|
81
|
+
文字列で探す。
|
|
82
|
+
|
|
83
|
+
--- index(pattern) {|s| ... } -> Integer | nil
|
|
84
|
+
|
|
85
|
+
ブロックで探す。
|
|
86
|
+
RD
|
|
87
|
+
overloads = [
|
|
88
|
+
{'segments' => [['t', '(String sub) -> Integer?']],
|
|
89
|
+
'params' => ['sub'], 'arity' => [1, 1]},
|
|
90
|
+
{'segments' => [['t', '(Regexp pattern) { (String) -> bool } -> Integer?']],
|
|
91
|
+
'params' => ['pattern'], 'arity' => [1, 1], 'block' => 'req'},
|
|
92
|
+
]
|
|
93
|
+
md_src = BitClust::RRDToMarkdown.convert(rd_src)
|
|
94
|
+
rd_html = compile_method(@rd, rd_src, rbs_signature_overloads: overloads)
|
|
95
|
+
md_html = compile_method(@md, md_src, rbs_signature_overloads: overloads)
|
|
96
|
+
assert_match(
|
|
97
|
+
%r!<code>index\(pattern\).*?</dt>\n<dt class="rbs-signature"><code>\(Regexp pattern\) \{ \(String\) → bool \} → Integer\?</code></dt>\n<dd!m,
|
|
98
|
+
md_html)
|
|
99
|
+
assert_equal 2, md_html.scan('rbs-signature').size
|
|
100
|
+
assert_equal rd_html, md_html
|
|
101
|
+
end
|
|
102
|
+
|
|
61
103
|
def test_method_signature_and_paragraph
|
|
62
104
|
assert_equivalent_method <<~RD
|
|
63
105
|
--- index(val) -> Integer
|
|
@@ -752,10 +794,10 @@ class TestMDCompiler < Test::Unit::TestCase
|
|
|
752
794
|
def test_gfm_table_alignment
|
|
753
795
|
md = "# T\n\n| a | b | c |\n|:--|:-:|--:|\n| 1 | 2 | 3 |\n"
|
|
754
796
|
html = gfm_compiler.compile(md)
|
|
755
|
-
assert_include html, '<th
|
|
756
|
-
assert_include html, '<th
|
|
757
|
-
assert_include html, '<th
|
|
758
|
-
assert_include html, '<td
|
|
797
|
+
assert_include html, '<th style="text-align:left">a</th>'
|
|
798
|
+
assert_include html, '<th style="text-align:center">b</th>'
|
|
799
|
+
assert_include html, '<th style="text-align:right">c</th>'
|
|
800
|
+
assert_include html, '<td style="text-align:right">3</td>'
|
|
759
801
|
end
|
|
760
802
|
|
|
761
803
|
def test_gfm_pipe_line_without_delimiter_is_paragraph
|
data/test/test_mdparser.rb
CHANGED
|
@@ -82,7 +82,7 @@ class TestMDParser < Test::Unit::TestCase
|
|
|
82
82
|
|
|
83
83
|
## Class Methods
|
|
84
84
|
|
|
85
|
-
### def new -> Comparable
|
|
85
|
+
### def Comparable.new -> Comparable
|
|
86
86
|
|
|
87
87
|
生成。
|
|
88
88
|
MD
|
|
@@ -181,7 +181,7 @@ class TestMDParser < Test::Unit::TestCase
|
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
def test_unsupported_signature_forms_are_rejected
|
|
184
|
-
# RBS 形式(MARKUP_SPEC §3.2
|
|
184
|
+
# RBS 形式(MARKUP_SPEC §3.2 で構想)は描画側
|
|
185
185
|
# (MethodSignature.parse)が受理できないため、パース時に拒否する
|
|
186
186
|
base = <<~MD
|
|
187
187
|
---
|
|
@@ -197,12 +197,83 @@ class TestMDParser < Test::Unit::TestCase
|
|
|
197
197
|
|
|
198
198
|
説明。
|
|
199
199
|
MD
|
|
200
|
-
['### def each: () { (untyped) -> void } -> self'
|
|
201
|
-
'### def self.new(size = 0) -> Foo'].each do |sig|
|
|
200
|
+
['### def each: () { (untyped) -> void } -> self'].each do |sig|
|
|
202
201
|
assert_raise(BitClust::ParseError, sig) { parse_md(base % sig) }
|
|
203
202
|
end
|
|
204
203
|
end
|
|
205
204
|
|
|
205
|
+
def test_self_and_class_name_prefixed_signatures
|
|
206
|
+
# クラスメソッドは `def self.name` / `def Klass.name` 形式でも書ける。
|
|
207
|
+
# どちらも singleton method として登録され、名前にプレフィクスは含まない
|
|
208
|
+
md = <<~MD
|
|
209
|
+
---
|
|
210
|
+
library: _builtin
|
|
211
|
+
---
|
|
212
|
+
# class Foo < Object
|
|
213
|
+
|
|
214
|
+
テスト。
|
|
215
|
+
|
|
216
|
+
## Class Methods
|
|
217
|
+
|
|
218
|
+
### def self.build(x) -> Foo
|
|
219
|
+
|
|
220
|
+
self. 形式。
|
|
221
|
+
|
|
222
|
+
### def Foo.create(x) -> Foo
|
|
223
|
+
|
|
224
|
+
Klass. 形式。
|
|
225
|
+
MD
|
|
226
|
+
_, lib = parse_md(md)
|
|
227
|
+
entries = lib.classes.first.entries
|
|
228
|
+
assert_equal [["build"], ["create"]], entries.map(&:names)
|
|
229
|
+
assert_equal [:singleton_method, :singleton_method], entries.map(&:type)
|
|
230
|
+
# source には書かれたままの md が入る
|
|
231
|
+
assert_include entries[0].source, "### def self.build(x) -> Foo"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_main_prefixed_signature_on_object_page
|
|
235
|
+
# object main のページでは main. プレフィクスで特異メソッドを書ける
|
|
236
|
+
# (main は fatal・ARGF.class と同様に文法上クラス名として扱われる)
|
|
237
|
+
md = <<~MD
|
|
238
|
+
---
|
|
239
|
+
library: _builtin
|
|
240
|
+
---
|
|
241
|
+
# object main
|
|
242
|
+
|
|
243
|
+
テスト。
|
|
244
|
+
|
|
245
|
+
## Class Methods
|
|
246
|
+
|
|
247
|
+
### def main.include(*modules) -> self
|
|
248
|
+
|
|
249
|
+
main. 形式。
|
|
250
|
+
MD
|
|
251
|
+
_, lib = parse_md(md)
|
|
252
|
+
entries = lib.classes.first.entries
|
|
253
|
+
assert_equal [["include"]], entries.map(&:names)
|
|
254
|
+
assert_equal [:singleton_method], entries.map(&:type)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def test_self_prefixed_signature_in_instance_methods_section_is_rejected
|
|
258
|
+
# self. は singleton 確定なので Instance Methods セクションとは矛盾する
|
|
259
|
+
md = <<~MD
|
|
260
|
+
---
|
|
261
|
+
library: _builtin
|
|
262
|
+
---
|
|
263
|
+
# class Foo < Object
|
|
264
|
+
|
|
265
|
+
テスト。
|
|
266
|
+
|
|
267
|
+
## Instance Methods
|
|
268
|
+
|
|
269
|
+
### def self.bar -> nil
|
|
270
|
+
|
|
271
|
+
矛盾。
|
|
272
|
+
MD
|
|
273
|
+
error = assert_raise(BitClust::ParseError) { parse_md(md) }
|
|
274
|
+
assert_include error.message, "signature crash"
|
|
275
|
+
end
|
|
276
|
+
|
|
206
277
|
def test_library_file_with_category
|
|
207
278
|
md = <<~MD
|
|
208
279
|
---
|
|
@@ -570,4 +641,113 @@ class TestMDParser < Test::Unit::TestCase
|
|
|
570
641
|
assert_equal('2.0.0', entry.since_of('fuga'))
|
|
571
642
|
assert_equal('3.0', entry.since_of('fuga2'))
|
|
572
643
|
end
|
|
644
|
+
|
|
645
|
+
def test_entry_style_bare_def_in_singleton_section_is_rejected
|
|
646
|
+
# MARKUP_SPEC §3.1: 特異メソッドはプレフィクス必須(def 行だけで
|
|
647
|
+
# メソッド種別が分かるようにする)
|
|
648
|
+
md = <<~MD
|
|
649
|
+
---
|
|
650
|
+
library: _builtin
|
|
651
|
+
---
|
|
652
|
+
# class Foo < Object
|
|
653
|
+
|
|
654
|
+
テスト。
|
|
655
|
+
|
|
656
|
+
## Class Methods
|
|
657
|
+
|
|
658
|
+
### def build(x) -> Foo
|
|
659
|
+
|
|
660
|
+
プレフィクスなし。
|
|
661
|
+
MD
|
|
662
|
+
error = assert_raise(BitClust::ParseError) { parse_md(md) }
|
|
663
|
+
assert_include error.message, "singleton method entry must be written as `def Foo.name' or `def self.name'"
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def test_entry_style_keyword_section_mismatch_is_rejected
|
|
667
|
+
# rurema/doctree#3291 型の分類間違い: キーワードとセクションの不一致
|
|
668
|
+
base = <<~MD
|
|
669
|
+
---
|
|
670
|
+
library: _builtin
|
|
671
|
+
---
|
|
672
|
+
# class Foo < Object
|
|
673
|
+
|
|
674
|
+
テスト。
|
|
675
|
+
|
|
676
|
+
## %s
|
|
677
|
+
|
|
678
|
+
### %s
|
|
679
|
+
|
|
680
|
+
不一致。
|
|
681
|
+
MD
|
|
682
|
+
[
|
|
683
|
+
# Class Methods 配下の const(#3291 の実例)
|
|
684
|
+
['Class Methods', 'const CURRENT -> Foo', "entry keyword `const' does not match this section (expected `def')"],
|
|
685
|
+
# Constants 配下の gvar
|
|
686
|
+
['Constants', 'gvar $foo -> Foo', "entry keyword `gvar' does not match this section (expected `const')"],
|
|
687
|
+
# Instance Methods 配下の const
|
|
688
|
+
['Instance Methods', 'const BAR -> Integer', "entry keyword `const' does not match this section (expected `def')"],
|
|
689
|
+
].each do |section, sig, message|
|
|
690
|
+
error = assert_raise(BitClust::ParseError, "#{section} / #{sig}") do
|
|
691
|
+
parse_md(base % [section, sig])
|
|
692
|
+
end
|
|
693
|
+
assert_include error.message, message
|
|
694
|
+
end
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def test_entry_style_module_functions_require_keyword
|
|
698
|
+
md = <<~MD
|
|
699
|
+
---
|
|
700
|
+
library: _builtin
|
|
701
|
+
---
|
|
702
|
+
# module Foo
|
|
703
|
+
|
|
704
|
+
テスト。
|
|
705
|
+
|
|
706
|
+
## Module Functions
|
|
707
|
+
|
|
708
|
+
### def bar -> nil
|
|
709
|
+
|
|
710
|
+
module_function キーワードなし。
|
|
711
|
+
MD
|
|
712
|
+
error = assert_raise(BitClust::ParseError) { parse_md(md) }
|
|
713
|
+
assert_include error.message, "entry keyword `def' does not match this section (expected `module_function def')"
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def test_entry_style_accepts_consistent_forms
|
|
717
|
+
md = <<~MD
|
|
718
|
+
---
|
|
719
|
+
library: _builtin
|
|
720
|
+
---
|
|
721
|
+
# class Foo < Object
|
|
722
|
+
|
|
723
|
+
テスト。
|
|
724
|
+
|
|
725
|
+
## Class Methods
|
|
726
|
+
|
|
727
|
+
### def Foo.build(x) -> Foo
|
|
728
|
+
|
|
729
|
+
Klass 形式。
|
|
730
|
+
|
|
731
|
+
### def self.create(x) -> Foo
|
|
732
|
+
|
|
733
|
+
self 形式。
|
|
734
|
+
|
|
735
|
+
## Instance Methods
|
|
736
|
+
|
|
737
|
+
### def each {|x| ... } -> self
|
|
738
|
+
|
|
739
|
+
素の def。
|
|
740
|
+
|
|
741
|
+
## Constants
|
|
742
|
+
|
|
743
|
+
### const BAR -> Integer
|
|
744
|
+
|
|
745
|
+
定数。
|
|
746
|
+
MD
|
|
747
|
+
_, lib = parse_md(md)
|
|
748
|
+
entries = lib.classes.first.entries
|
|
749
|
+
assert_equal [["build"], ["create"], ["each"], ["BAR"]], entries.map(&:names)
|
|
750
|
+
assert_equal [:singleton_method, :singleton_method, :instance_method, :constant],
|
|
751
|
+
entries.map(&:type)
|
|
752
|
+
end
|
|
573
753
|
end
|
data/test/test_methodentry.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'bitclust'
|
|
|
3
3
|
require 'bitclust/methoddatabase'
|
|
4
4
|
require 'tmpdir'
|
|
5
5
|
require 'fileutils'
|
|
6
|
+
require 'json'
|
|
6
7
|
|
|
7
8
|
class TestMethodEntryTitleLabels < Test::Unit::TestCase
|
|
8
9
|
def test_title_labels_matches_label_when_no_alias
|
|
@@ -326,3 +327,113 @@ class TestMethodEntrySinceUntil < Test::Unit::TestCase
|
|
|
326
327
|
File.write(path, lines.join)
|
|
327
328
|
end
|
|
328
329
|
end
|
|
330
|
+
|
|
331
|
+
# rbs_sig property(RBS シグネチャ表示)。オーバーロード配列
|
|
332
|
+
# ({"segments" => 行, "params"/"arity"/"block" => メタ情報} の配列)を
|
|
333
|
+
# {"overloads": [...]} の JSON 1 行で 'String' 型 property に格納する。
|
|
334
|
+
# JSON は改行を含まず、値中の '=' や ',' も property ファイル
|
|
335
|
+
# (key=value 1 行形式)で安全。
|
|
336
|
+
#
|
|
337
|
+
# テストリスト:
|
|
338
|
+
# [x] 既定は nil で rbs_signature_overloads も nil
|
|
339
|
+
# [x] JSON を入れて save → reload で round-trip する
|
|
340
|
+
# [x] 旧形式(セグメント行列そのものの配列)は segments だけの
|
|
341
|
+
# オーバーロード列に読み替える(#322 以前の DB)
|
|
342
|
+
# [x] property 行が無い古い DB でも nil(後方互換)
|
|
343
|
+
# [x] 空文字列(シグネチャ無しで save された値)も nil 扱い
|
|
344
|
+
# [x] 壊れた JSON は nil 扱い(描画を落とさない)
|
|
345
|
+
class TestMethodEntryRbsSig < Test::Unit::TestCase
|
|
346
|
+
OVERLOADS = [
|
|
347
|
+
{'segments' => [['t', '('], ['c', 'Integer'], ['t', ') -> '], ['c', 'String']],
|
|
348
|
+
'params' => ['base'], 'arity' => [1, 1]},
|
|
349
|
+
{'segments' => [['t', '() -> void']],
|
|
350
|
+
'params' => [], 'arity' => [0, 0], 'block' => 'req'},
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
def setup
|
|
354
|
+
@dir = Dir.mktmpdir
|
|
355
|
+
@db = BitClust::MethodDatabase.new(@dir)
|
|
356
|
+
@db.init
|
|
357
|
+
@db.transaction do
|
|
358
|
+
@db.propset('version', '4.0')
|
|
359
|
+
@db.propset('encoding', 'utf-8')
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def teardown
|
|
364
|
+
FileUtils.rm_rf(@dir)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# 未保存エントリの String 型 property は "(uninitialized)" センチネル
|
|
368
|
+
# なので、rbs_sig そのものではなく overloads が nil であることを見る
|
|
369
|
+
def test_default_has_no_overloads
|
|
370
|
+
m = build_entry('sig')
|
|
371
|
+
assert_nil m.rbs_signature_overloads
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def test_roundtrip
|
|
375
|
+
m = build_entry('sig')
|
|
376
|
+
m.rbs_sig = JSON.generate({'overloads' => OVERLOADS})
|
|
377
|
+
m.save
|
|
378
|
+
|
|
379
|
+
reloaded = reload_entry(m.id)
|
|
380
|
+
assert_equal(OVERLOADS, reloaded.rbs_signature_overloads)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def test_old_array_format_is_read_as_segments_only
|
|
384
|
+
m = build_entry('sig')
|
|
385
|
+
m.rbs_sig = JSON.generate(OVERLOADS.map {|o| o['segments'] })
|
|
386
|
+
m.save
|
|
387
|
+
|
|
388
|
+
reloaded = reload_entry(m.id)
|
|
389
|
+
assert_equal(OVERLOADS.map {|o| {'segments' => o['segments']} },
|
|
390
|
+
reloaded.rbs_signature_overloads)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def test_missing_property_is_backward_compatible
|
|
394
|
+
m = build_entry('sig')
|
|
395
|
+
m.save
|
|
396
|
+
strip_property_line(m.id, 'rbs_sig')
|
|
397
|
+
|
|
398
|
+
reloaded = reload_entry(m.id)
|
|
399
|
+
assert_nil reloaded.rbs_sig
|
|
400
|
+
assert_nil reloaded.rbs_signature_overloads
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def test_saved_without_signature_is_treated_as_absent
|
|
404
|
+
m = build_entry('sig')
|
|
405
|
+
m.save
|
|
406
|
+
|
|
407
|
+
reloaded = reload_entry(m.id)
|
|
408
|
+
assert_nil reloaded.rbs_signature_overloads
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def test_broken_json_is_treated_as_absent
|
|
412
|
+
m = build_entry('sig')
|
|
413
|
+
m.rbs_sig = '{broken'
|
|
414
|
+
assert_nil m.rbs_signature_overloads
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
private
|
|
418
|
+
|
|
419
|
+
def build_entry(name)
|
|
420
|
+
id = BitClust::NameUtils.build_method_id('_builtin', 'Foo', :instance_method, name)
|
|
421
|
+
m = BitClust::MethodEntry.new(@db, id)
|
|
422
|
+
m.names = [name]
|
|
423
|
+
m.visibility = :public
|
|
424
|
+
m.kind = :defined
|
|
425
|
+
m.source = "説明\n"
|
|
426
|
+
m
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def reload_entry(id)
|
|
430
|
+
fresh_db = BitClust::MethodDatabase.new(@dir)
|
|
431
|
+
BitClust::MethodEntry.new(fresh_db, id)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def strip_property_line(id, key)
|
|
435
|
+
path = File.join(@dir, BitClust::NameUtils.encodeid("method/#{id}"))
|
|
436
|
+
lines = File.readlines(path).reject {|l| l.start_with?("#{key}=") }
|
|
437
|
+
File.write(path, lines.join)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
@@ -6,7 +6,9 @@ class TestMethodSignature < Test::Unit::TestCase
|
|
|
6
6
|
data("special var" => ["$_ -> String | nil", "--- $_ -> String | nil"],
|
|
7
7
|
"backquote" => ["`command` -> String", "--- `(command) -> String"],
|
|
8
8
|
"aset" => ["self[key] = value", "--- []=(key, value)"],
|
|
9
|
-
"aset splat" => ["[]=(*idxary)", "--- []=(*idxary)"]
|
|
9
|
+
"aset splat" => ["[]=(*idxary)", "--- []=(*idxary)"],
|
|
10
|
+
"self prefix" => ["build(x) -> Foo", "--- self.build(x) -> Foo"],
|
|
11
|
+
"class prefix" => ["build(x) -> Foo", "--- Foo.build(x) -> Foo"])
|
|
10
12
|
def test_friendlyname(data)
|
|
11
13
|
friendly_string, method_signature = data
|
|
12
14
|
assert_equal(friendly_string, BitClust::MethodSignature.parse(method_signature).friendly_string)
|