bitclust-core 1.6.0 → 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/methoddatabase.rb +4 -0
- 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 +219 -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
|
@@ -159,6 +159,7 @@ module BitClust
|
|
|
159
159
|
# 版範囲の省略記法(#285): #%version A...B は半開区間 [A, B)。
|
|
160
160
|
# Ruby の終端排他 Range リテラルに合わせて3点ドット(.. は終端を含むと
|
|
161
161
|
# 誤解しやすいため受け付けない)。A... は A 以上、...B は B 未満。
|
|
162
|
+
# ドットなしの単一版 V はその版のみ(version == "V")。
|
|
162
163
|
# 版はダブルクォート付きでもよい
|
|
163
164
|
VERSION_LITERAL = /"(\d+(?:\.\d+)*)"|(\d+(?:\.\d+)*)/
|
|
164
165
|
def build_cond_by_range(line)
|
|
@@ -170,8 +171,10 @@ module BitClust
|
|
|
170
171
|
%Q(version >= "#{$1 || $2}")
|
|
171
172
|
when /\A\.\.\.#{VERSION_LITERAL}\z/o
|
|
172
173
|
%Q(version < "#{$1 || $2}")
|
|
174
|
+
when /\A#{VERSION_LITERAL}\z/o
|
|
175
|
+
%Q(version == "#{$1 || $2}")
|
|
173
176
|
else
|
|
174
|
-
parse_error "wrong version range (expected A...B / A... / ...B)", line
|
|
177
|
+
parse_error "wrong version range (expected V / A...B / A... / ...B)", line
|
|
175
178
|
end
|
|
176
179
|
end
|
|
177
180
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# bitclust/rbs_overload_matcher.rb
|
|
4
|
+
#
|
|
5
|
+
# RBS のオーバーロードを、引数パターンごとに分かれた説明チャンク
|
|
6
|
+
# (Array.new / Array#[] のように同名メソッドの `---` 見出し+説明の組が
|
|
7
|
+
# 複数あるもの)へ振り分ける。RBS の引数名は rdoc の call-seq 由来で
|
|
8
|
+
# rurema のシグネチャの引数名とよく一致するため、
|
|
9
|
+
# 「ブロック有無 → 引数名の一致 → アリティ範囲の重なり」のスコアで
|
|
10
|
+
# 最も近いチャンクを選ぶ。1:1 対応は原理的に保証されない
|
|
11
|
+
# (RBS の () と (int size, ?Elem val) が rurema では同一チャンク等)ので、
|
|
12
|
+
# 多対 1 の割り当てとし、確実な対応が取れないオーバーロードは先頭チャンクに
|
|
13
|
+
# 寄せる(どのチャンクにも重複しては出さない)。
|
|
14
|
+
#
|
|
15
|
+
# オーバーロードのメタ情報(params/arity/block)は RbsSigImporter が
|
|
16
|
+
# rbs_sig property の JSON に書き込んだものをそのまま(文字列キーの Hash で)
|
|
17
|
+
# 受け取る。チャンク側は正規化済みシグネチャ行("--- " 始まり)を
|
|
18
|
+
# MethodSignature.parse で解析する。
|
|
19
|
+
|
|
20
|
+
require 'bitclust/methodsignature'
|
|
21
|
+
require 'bitclust/exception'
|
|
22
|
+
|
|
23
|
+
module BitClust
|
|
24
|
+
|
|
25
|
+
module RbsOverloadMatcher
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# overloads: rbs_sig JSON の overloads 配列(文字列キー Hash の配列)
|
|
30
|
+
# chunks: チャンクごとの正規化済みシグネチャ行("--- " 始まり)の配列
|
|
31
|
+
# 返り値: チャンクごとのオーバーロード添字の配列(chunks と同じ長さ)
|
|
32
|
+
def assign(overloads, chunks)
|
|
33
|
+
result = Array.new(chunks.size) { [] } #: Array[Array[Integer]]
|
|
34
|
+
return result if chunks.empty?
|
|
35
|
+
if chunks.size == 1
|
|
36
|
+
result[0] = (0...overloads.size).to_a
|
|
37
|
+
return result
|
|
38
|
+
end
|
|
39
|
+
features = chunks.map {|lines| chunk_features(lines) }
|
|
40
|
+
overloads.each_index do |i|
|
|
41
|
+
ov = overload_features(overloads[i])
|
|
42
|
+
result[ov ? best_chunk(ov, features) : 0] << i
|
|
43
|
+
end
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# チャンクのシグネチャ行群から照合用の特徴を合算する。
|
|
48
|
+
# 1 行も解析できなければ nil(振り分け先にしない)
|
|
49
|
+
def chunk_features(sig_lines)
|
|
50
|
+
names = [] #: Array[String]
|
|
51
|
+
min = nil #: Integer?
|
|
52
|
+
max = 0 #: Integer?
|
|
53
|
+
block = false
|
|
54
|
+
parsed = false
|
|
55
|
+
sig_lines.each do |line|
|
|
56
|
+
begin
|
|
57
|
+
sig = MethodSignature.parse(line)
|
|
58
|
+
rescue ParseError
|
|
59
|
+
next
|
|
60
|
+
end
|
|
61
|
+
parsed = true
|
|
62
|
+
block ||= !sig.block.nil?
|
|
63
|
+
pmin, pmax, pnames, pblock = params_features(sig.params)
|
|
64
|
+
block ||= pblock
|
|
65
|
+
names.concat(pnames)
|
|
66
|
+
min = pmin if min.nil? || pmin < min
|
|
67
|
+
max = (max && pmax) ? [max, pmax].max : nil
|
|
68
|
+
end
|
|
69
|
+
return nil unless parsed
|
|
70
|
+
{ names: names, min: min || 0, max: max, block: block }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# rbs_sig JSON のオーバーロード 1 個からスコア用の特徴を取り出す。
|
|
74
|
+
# メタ情報が無いもの(#322 以前の旧形式・attr・(?) 型)は nil
|
|
75
|
+
def overload_features(overload)
|
|
76
|
+
arity = overload['arity']
|
|
77
|
+
params = overload['params']
|
|
78
|
+
block = overload['block']
|
|
79
|
+
return nil unless arity || params || block
|
|
80
|
+
{ names: params || [],
|
|
81
|
+
min: arity && arity[0],
|
|
82
|
+
max: arity && arity[1],
|
|
83
|
+
block: block }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# ---- 以下は実装詳細(module_function なので呼べてしまうが非公開扱い) ----
|
|
87
|
+
|
|
88
|
+
def best_chunk(overload, features)
|
|
89
|
+
best = 0
|
|
90
|
+
best_score = nil #: Integer?
|
|
91
|
+
features.each_with_index do |chunk, i|
|
|
92
|
+
next unless chunk
|
|
93
|
+
s = score(overload, chunk)
|
|
94
|
+
if best_score.nil? || s > best_score
|
|
95
|
+
best = i
|
|
96
|
+
best_score = s
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
best
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# ブロックの一致(または矛盾)を最優先に、引数名の一致 > アリティ範囲の
|
|
103
|
+
# 重なりで加点する。同点は先頭チャンク優先(best_chunk が > で更新)
|
|
104
|
+
def score(overload, chunk)
|
|
105
|
+
s = 0
|
|
106
|
+
case overload[:block]
|
|
107
|
+
when 'req'
|
|
108
|
+
s += chunk[:block] ? 2 : -3
|
|
109
|
+
when 'opt'
|
|
110
|
+
s += 1
|
|
111
|
+
else
|
|
112
|
+
s += chunk[:block] ? -3 : 2
|
|
113
|
+
end
|
|
114
|
+
s += 2 * (overload[:names] & chunk[:names]).size
|
|
115
|
+
if overload[:min] && chunk[:min]
|
|
116
|
+
omax = overload[:max] || Float::INFINITY
|
|
117
|
+
cmax = chunk[:max] || Float::INFINITY
|
|
118
|
+
s += (overload[:min] <= cmax && chunk[:min] <= omax) ? 1 : -2
|
|
119
|
+
end
|
|
120
|
+
s
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# シグネチャの引数リスト文字列 → [最小アリティ, 最大アリティ(nil=無制限),
|
|
124
|
+
# 引数名(位置引数+キーワード名。**kwrest 名も含む), ブロック有無(&引数)]
|
|
125
|
+
def params_features(params)
|
|
126
|
+
return [0, 0, [], false] if params.nil? || params.strip.empty?
|
|
127
|
+
required = 0
|
|
128
|
+
optional = 0
|
|
129
|
+
rest = false
|
|
130
|
+
block = false
|
|
131
|
+
names = [] #: Array[String]
|
|
132
|
+
split_top_level(params).each do |token|
|
|
133
|
+
token = token.strip
|
|
134
|
+
next if token.empty?
|
|
135
|
+
name = token[/[A-Za-z_]\w*/]
|
|
136
|
+
case token
|
|
137
|
+
when /\A&/
|
|
138
|
+
block = true
|
|
139
|
+
when /\A\*\*/
|
|
140
|
+
names << name if name
|
|
141
|
+
when /\A\*/, '...'
|
|
142
|
+
rest = true
|
|
143
|
+
names << name if name
|
|
144
|
+
when /\A[A-Za-z_]\w*:/
|
|
145
|
+
names << (name || raise)
|
|
146
|
+
when /=/
|
|
147
|
+
optional += 1
|
|
148
|
+
names << name if name
|
|
149
|
+
else
|
|
150
|
+
required += 1
|
|
151
|
+
names << name if name
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
[required, rest ? nil : required + optional, names, block]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# 括弧((), [], {})のネストを無視して最上位の ',' で分割する
|
|
158
|
+
# (デフォルト値の中の ',' で切らないため)
|
|
159
|
+
def split_top_level(str)
|
|
160
|
+
tokens = [+''] #: Array[String]
|
|
161
|
+
depth = 0
|
|
162
|
+
str.each_char do |c|
|
|
163
|
+
case c
|
|
164
|
+
when '(', '[', '{' then depth += 1
|
|
165
|
+
when ')', ']', '}' then depth -= 1
|
|
166
|
+
when ','
|
|
167
|
+
if depth == 0
|
|
168
|
+
tokens << +''
|
|
169
|
+
next
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
tokens.last << c
|
|
173
|
+
end
|
|
174
|
+
tokens
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# bitclust/rbs_sig_importer.rb
|
|
4
|
+
#
|
|
5
|
+
# RBS 型シグネチャ(.rbs)を読み込み、"Class#method"/"Class.method" キー →
|
|
6
|
+
# オーバーロード配列の対応表を作って MethodEntry の rbs_sig property に
|
|
7
|
+
# 書き込む(rbssig サブコマンドが DB 構築後に呼ぶ)。
|
|
8
|
+
#
|
|
9
|
+
# オーバーロードは Hash 1 個: "segments" = [種別, テキスト] の組の配列
|
|
10
|
+
# (種別 "t" は素のテキスト、"c" はクラス/モジュール名で、描画側=
|
|
11
|
+
# RbsSignatures が DB に存在するものだけリンク化する。テキストを連結すると
|
|
12
|
+
# 元のシグネチャ文字列に戻る)に加え、説明チャンクへの振り分け
|
|
13
|
+
# (RbsOverloadMatcher)用のメタ情報 "params"(引数名)・"arity"
|
|
14
|
+
# ([最小, 最大|nil])・"block"("req"/"opt")を持つ。型名の位置は RBS
|
|
15
|
+
# パーサの location で厳密に取る。rbs gem が必要なのは書き込み側のこの
|
|
16
|
+
# ファイルだけで、描画側は property を読むだけで動く。
|
|
17
|
+
|
|
18
|
+
require 'rbs'
|
|
19
|
+
require 'json'
|
|
20
|
+
require 'pathname'
|
|
21
|
+
|
|
22
|
+
module BitClust
|
|
23
|
+
|
|
24
|
+
class RbsSigImporter
|
|
25
|
+
|
|
26
|
+
# core_root: ruby/rbs チェックアウトの core/(組み込みクラスの型)
|
|
27
|
+
# repository_root: 同 stdlib/(core_root を与えると rbs が stringio を
|
|
28
|
+
# 暗黙に要求するので、core_root とセットで渡す)
|
|
29
|
+
# sig_dirs: 素の .rbs ディレクトリ(テスト・追加分)
|
|
30
|
+
def initialize(core_root: nil, repository_root: nil, sig_dirs: [])
|
|
31
|
+
@core_root = core_root
|
|
32
|
+
@repository_root = repository_root
|
|
33
|
+
@sig_dirs = sig_dirs
|
|
34
|
+
@signatures = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# "Class#method"/"Class.method" → オーバーロード配列
|
|
38
|
+
def signatures
|
|
39
|
+
@signatures ||= build_signatures
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# bitclust の typechar('i'/'s'/'m')と名前の並び(別名)で対応表を引く。
|
|
43
|
+
# モジュール関数(m)は def self?. 由来の # キーを先に、new は
|
|
44
|
+
# Class.new → Class#initialize の順で引く。見つからなければ nil
|
|
45
|
+
def lookup(class_name, typechar, names)
|
|
46
|
+
names.each do |name|
|
|
47
|
+
case typechar
|
|
48
|
+
when 'i'
|
|
49
|
+
sig = signatures["#{class_name}##{name}"]
|
|
50
|
+
when 's'
|
|
51
|
+
sig = signatures["#{class_name}.#{name}"]
|
|
52
|
+
sig ||= signatures["#{class_name}#initialize"] if name == 'new'
|
|
53
|
+
when 'm'
|
|
54
|
+
sig = signatures["#{class_name}##{name}"] || signatures["#{class_name}.#{name}"]
|
|
55
|
+
else
|
|
56
|
+
return nil
|
|
57
|
+
end
|
|
58
|
+
return sig if sig
|
|
59
|
+
end
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# DB の全メソッドエントリに対応表を引き、ヒットしたものへ rbs_sig
|
|
64
|
+
# property(JSON 1 行)を書き込む。書き方は MethodSinceCalculator#apply と
|
|
65
|
+
# 同じ(変更があったエントリだけ save)
|
|
66
|
+
def apply(db)
|
|
67
|
+
stats = { entries_updated: 0, sigs_matched: 0, methods_missed: 0 } #: stats
|
|
68
|
+
db.classes.each do |c|
|
|
69
|
+
c.entries.each do |m|
|
|
70
|
+
next if m.kind == :undefined
|
|
71
|
+
next unless %w[i s m].include?(m.typechar)
|
|
72
|
+
overloads = lookup(c.name, m.typechar, m.names)
|
|
73
|
+
unless overloads
|
|
74
|
+
stats[:methods_missed] += 1
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
stats[:sigs_matched] += 1
|
|
78
|
+
json = JSON.generate({'overloads' => overloads})
|
|
79
|
+
next if m.rbs_sig == json
|
|
80
|
+
m.rbs_sig = json
|
|
81
|
+
m.save
|
|
82
|
+
stats[:entries_updated] += 1
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
stats
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def build_signatures
|
|
91
|
+
env = load_environment
|
|
92
|
+
sigs = {} #: signatures
|
|
93
|
+
aliases = [] #: Array[[String, untyped]]
|
|
94
|
+
env.class_decls.each do |type_name, entry|
|
|
95
|
+
class_name = type_name.to_s.delete_prefix('::')
|
|
96
|
+
each_decl(entry) do |decl|
|
|
97
|
+
decl.members.each do |member|
|
|
98
|
+
case member
|
|
99
|
+
when RBS::AST::Members::MethodDefinition
|
|
100
|
+
overloads = member.overloads.map {|o| method_type_overload(o.method_type.to_s) }
|
|
101
|
+
method_keys(class_name, member.name, member.kind).each do |key|
|
|
102
|
+
sigs[key] ||= overloads
|
|
103
|
+
end
|
|
104
|
+
when RBS::AST::Members::AttrReader
|
|
105
|
+
sigs[attr_key(class_name, member, member.name)] ||= [attr_overload(member.type.to_s)]
|
|
106
|
+
when RBS::AST::Members::AttrWriter
|
|
107
|
+
sigs[attr_key(class_name, member, "#{member.name}=")] ||= [attr_overload(member.type.to_s)]
|
|
108
|
+
when RBS::AST::Members::AttrAccessor
|
|
109
|
+
overloads = [attr_overload(member.type.to_s)]
|
|
110
|
+
sigs[attr_key(class_name, member, member.name)] ||= overloads
|
|
111
|
+
sigs[attr_key(class_name, member, "#{member.name}=")] ||= overloads
|
|
112
|
+
when RBS::AST::Members::Alias
|
|
113
|
+
aliases << [class_name, member]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
resolve_aliases(sigs, aliases)
|
|
119
|
+
sigs
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def load_environment
|
|
123
|
+
repository = RBS::Repository.new(no_stdlib: true)
|
|
124
|
+
if (repository_root = @repository_root)
|
|
125
|
+
repository.add(Pathname(repository_root))
|
|
126
|
+
end
|
|
127
|
+
core_root = @core_root
|
|
128
|
+
loader = RBS::EnvironmentLoader.new(
|
|
129
|
+
core_root: core_root && Pathname(core_root),
|
|
130
|
+
repository: repository)
|
|
131
|
+
@sig_dirs.each {|dir| loader.add(path: Pathname(dir)) }
|
|
132
|
+
env = RBS::Environment.new
|
|
133
|
+
loader.load(env: env)
|
|
134
|
+
env
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# rbs 3.x は MultiEntry#decls、rbs 4.x は entry.each_decl
|
|
138
|
+
def each_decl(entry, &block)
|
|
139
|
+
if entry.respond_to?(:each_decl)
|
|
140
|
+
entry.each_decl(&block)
|
|
141
|
+
else
|
|
142
|
+
entry.decls.each {|d| block.call(d.decl) }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# def self?.(kind :singleton_instance)は Class.name と Class#name の
|
|
147
|
+
# 両方を定義する(bitclust のモジュール関数に対応)ので両キーに登録する
|
|
148
|
+
def method_keys(class_name, name, kind)
|
|
149
|
+
case kind
|
|
150
|
+
when :singleton
|
|
151
|
+
["#{class_name}.#{name}"]
|
|
152
|
+
when :singleton_instance
|
|
153
|
+
["#{class_name}.#{name}", "#{class_name}##{name}"]
|
|
154
|
+
else
|
|
155
|
+
["#{class_name}##{name}"]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def attr_key(class_name, member, name)
|
|
160
|
+
member.kind == :singleton ? "#{class_name}.#{name}" : "#{class_name}##{name}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# alias メンバーは元メソッドのシグネチャを共有する。alias が alias を
|
|
164
|
+
# 指すこともあるので、解決が進まなくなるまで繰り返す
|
|
165
|
+
def resolve_aliases(sigs, aliases)
|
|
166
|
+
until aliases.empty?
|
|
167
|
+
resolved, aliases = aliases.partition {|class_name, member|
|
|
168
|
+
sigs.key?(alias_key(class_name, member, member.old_name))
|
|
169
|
+
}
|
|
170
|
+
break if resolved.empty?
|
|
171
|
+
resolved.each do |class_name, member|
|
|
172
|
+
sigs[alias_key(class_name, member, member.new_name)] ||=
|
|
173
|
+
sigs[alias_key(class_name, member, member.old_name)]
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def alias_key(class_name, member, name)
|
|
179
|
+
member.kind == :singleton ? "#{class_name}.#{name}" : "#{class_name}##{name}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# メソッド型シグネチャ 1 行をオーバーロード(segments+メタ情報)にする
|
|
183
|
+
def method_type_overload(sig)
|
|
184
|
+
method_type = RBS::Parser.parse_method_type(sig, require_eof: true)
|
|
185
|
+
locs = [] #: Array[[String, Integer]]
|
|
186
|
+
collect_function_type_names(method_type.type, locs)
|
|
187
|
+
collect_function_type_names(method_type.block.type, locs) if method_type.block
|
|
188
|
+
overload = {'segments' => build_segments(sig, locs)} #: overload
|
|
189
|
+
add_overload_meta(overload, method_type)
|
|
190
|
+
overload
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# attr 用: 型だけの文字列をオーバーロード(segments のみ)にする
|
|
194
|
+
def attr_overload(sig)
|
|
195
|
+
type = RBS::Parser.parse_type(sig, require_eof: true)
|
|
196
|
+
locs = [] #: Array[[String, Integer]]
|
|
197
|
+
collect_type_names(type, locs)
|
|
198
|
+
{'segments' => build_segments(sig, locs)}
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# チャンク振り分け(RbsOverloadMatcher)用のメタ情報。引数名は位置引数
|
|
202
|
+
# (rest 含む)+キーワード名。アリティは位置引数の [最小, 最大] で
|
|
203
|
+
# rest があれば最大 nil(無制限)。`(?)`(UntypedFunction)には位置引数の
|
|
204
|
+
# 概念が無いので params/arity は付けない
|
|
205
|
+
def add_overload_meta(overload, method_type)
|
|
206
|
+
fun = method_type.type
|
|
207
|
+
if fun.respond_to?(:required_positionals)
|
|
208
|
+
names = [] #: Array[String]
|
|
209
|
+
positionals = fun.required_positionals + fun.optional_positionals +
|
|
210
|
+
fun.trailing_positionals
|
|
211
|
+
positionals.each {|p| names << p.name.to_s if p.name }
|
|
212
|
+
rest = fun.rest_positionals
|
|
213
|
+
names << rest.name.to_s if rest && rest.name
|
|
214
|
+
fun.required_keywords.each_key {|k| names << k.to_s }
|
|
215
|
+
fun.optional_keywords.each_key {|k| names << k.to_s }
|
|
216
|
+
required = fun.required_positionals.size + fun.trailing_positionals.size
|
|
217
|
+
overload['params'] = names
|
|
218
|
+
overload['arity'] =
|
|
219
|
+
[required, rest ? nil : required + fun.optional_positionals.size]
|
|
220
|
+
end
|
|
221
|
+
if (block = method_type.block)
|
|
222
|
+
overload['block'] = block.required ? 'req' : 'opt'
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def collect_function_type_names(fun, locs)
|
|
227
|
+
fun.each_param {|param| collect_type_names(param.type, locs) }
|
|
228
|
+
collect_type_names(fun.return_type, locs)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# クラス/モジュール参照(ClassInstance)の名前とその開始位置を集める。
|
|
232
|
+
# location の :name 子は "::String" のように名前空間プレフィクスを含む
|
|
233
|
+
# ので、end_pos から正規化名の長さを引いて名前本体の開始位置にする
|
|
234
|
+
# (:: はテキストセグメント側に残る)
|
|
235
|
+
def collect_type_names(type, locs)
|
|
236
|
+
case type
|
|
237
|
+
when RBS::Types::ClassInstance
|
|
238
|
+
name = type.name.to_s.delete_prefix('::')
|
|
239
|
+
if (loc = type.location)
|
|
240
|
+
name_loc = loc[:name] || loc
|
|
241
|
+
locs << [name, name_loc.end_pos - name.length]
|
|
242
|
+
end
|
|
243
|
+
type.args.each {|t| collect_type_names(t, locs) }
|
|
244
|
+
when RBS::Types::Union, RBS::Types::Intersection, RBS::Types::Tuple
|
|
245
|
+
type.types.each {|t| collect_type_names(t, locs) }
|
|
246
|
+
when RBS::Types::Optional
|
|
247
|
+
collect_type_names(type.type, locs)
|
|
248
|
+
when RBS::Types::Record
|
|
249
|
+
type.all_fields.each_value {|t, _required| collect_type_names(t, locs) }
|
|
250
|
+
when RBS::Types::Alias
|
|
251
|
+
# エイリアス型(string, hash 等)自体はクラスではないのでテキストの
|
|
252
|
+
# ままにするが、ジェネリクス引数の中のクラス名は拾う
|
|
253
|
+
type.args.each {|t| collect_type_names(t, locs) }
|
|
254
|
+
when RBS::Types::Proc
|
|
255
|
+
collect_function_type_names(type.type, locs)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def build_segments(sig, locs)
|
|
260
|
+
segments = [] #: segment_line
|
|
261
|
+
pos = 0
|
|
262
|
+
locs.sort_by {|_name, start| start }.each do |name, start|
|
|
263
|
+
# 位置が既出セグメントと重なる・実文字列と一致しない型は
|
|
264
|
+
# リンク化を諦めてテキストのまま残す(取りこぼしても表示は壊れない)
|
|
265
|
+
next if start < pos || sig[start, name.length] != name
|
|
266
|
+
segments << ['t', sig[pos...start] || raise] if start > pos
|
|
267
|
+
segments << ['c', name]
|
|
268
|
+
pos = start + name.length
|
|
269
|
+
end
|
|
270
|
+
segments << ['t', sig[pos..] || raise] if pos < sig.length
|
|
271
|
+
segments
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# bitclust/rbs_signatures.rb
|
|
4
|
+
#
|
|
5
|
+
# メソッドエントリの RBS 型シグネチャ(rbs_sig property。RbsSigImporter が
|
|
6
|
+
# DB 構築後に書き込む)を、シグネチャ見出し(<dt> 群)の直後・説明 <dd> の
|
|
7
|
+
# 直前に 1 オーバーロード 1 行の <dt class="rbs-signature"> として描画する。
|
|
8
|
+
# dd だと字下げで説明の一部に見え、pre だと theme/script.js が COPY ボタンを
|
|
9
|
+
# 付けてしまうため、見出しと同じ dt+code の並びにする。property が無い
|
|
10
|
+
# エントリでは何も出さないので、出力は従来とバイト一致のまま。
|
|
11
|
+
#
|
|
12
|
+
# Array.new / Array#[] のように引数パターンごとに説明チャンクが分かれる
|
|
13
|
+
# メソッドでは、compile_method 冒頭でソースのシグネチャ行を走査して
|
|
14
|
+
# チャンク列を作り、RbsOverloadMatcher で各オーバーロードを最も近い
|
|
15
|
+
# チャンクへ振り分けておく(entry_chunk は自分の番のぶんだけ描画する)。
|
|
16
|
+
#
|
|
17
|
+
# RDCompiler(md は MDCompiler が継承)に include される。escape_html /
|
|
18
|
+
# class_link(いずれも HTMLUtils)と @option[:database] にだけ依存し、
|
|
19
|
+
# rbs gem は使わない(型名の位置は property 側で解決済み)。
|
|
20
|
+
|
|
21
|
+
require 'bitclust/rbs_overload_matcher'
|
|
22
|
+
|
|
23
|
+
module BitClust
|
|
24
|
+
|
|
25
|
+
module RbsSignatures
|
|
26
|
+
|
|
27
|
+
# 振り分け状態はメソッドのコンパイル専用(compile_method が
|
|
28
|
+
# prepare_rbs_signatures で作り直す)なので、他のコンパイルへ
|
|
29
|
+
# 持ち越さないよう setup(全コンパイル共通)からリセットする
|
|
30
|
+
def reset_rbs_signatures
|
|
31
|
+
@rbs_sig_by_chunk = nil
|
|
32
|
+
@rbs_chunk_index = 0
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# compile_method 冒頭で呼ぶ。rbs_sig property があれば、source の
|
|
36
|
+
# シグネチャ行からチャンク列を作りオーバーロードを振り分けておく
|
|
37
|
+
def prepare_rbs_signatures(entry, source)
|
|
38
|
+
reset_rbs_signatures
|
|
39
|
+
overloads = entry.rbs_signature_overloads
|
|
40
|
+
return unless overloads
|
|
41
|
+
chunks = rbs_scan_signature_chunks(source)
|
|
42
|
+
if chunks.empty?
|
|
43
|
+
no_signatures = [] #: Array[String]
|
|
44
|
+
chunks = [no_signatures]
|
|
45
|
+
end
|
|
46
|
+
assignment = RbsOverloadMatcher.assign(overloads, chunks)
|
|
47
|
+
@rbs_sig_by_chunk = assignment.map {|indexes|
|
|
48
|
+
indexes.filter_map {|i| overloads[i]['segments'] }
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 現在のチャンクに割り当てられたオーバーロードの <dt> 行(改行連結)を
|
|
53
|
+
# 返し、チャンクカウンタを進める。メソッド以外のコンパイル
|
|
54
|
+
# (prepare が呼ばれていない)や割り当てが無いチャンクでは nil
|
|
55
|
+
def rbs_signature_dts_for_chunk
|
|
56
|
+
by_chunk = @rbs_sig_by_chunk
|
|
57
|
+
return nil unless by_chunk
|
|
58
|
+
lines = by_chunk[@rbs_chunk_index]
|
|
59
|
+
@rbs_chunk_index += 1
|
|
60
|
+
return nil unless lines && !lines.empty?
|
|
61
|
+
lines.map {|line|
|
|
62
|
+
html = line.map {|kind, text| rbs_segment_html(kind, text) }.join
|
|
63
|
+
%Q(<dt class="rbs-signature"><code>#{html}</code></dt>)
|
|
64
|
+
}.join("\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# entry_chunk と同じ規則でシグネチャ行の並び(チャンク)を数える。
|
|
70
|
+
# 属性行({: ...})はシグネチャの並びを切らない
|
|
71
|
+
def rbs_scan_signature_chunks(source)
|
|
72
|
+
chunks = [] #: Array[Array[String]]
|
|
73
|
+
current = nil #: Array[String]?
|
|
74
|
+
source.each_line do |line|
|
|
75
|
+
line = line.chomp
|
|
76
|
+
if (sig = rbs_signature_line(line))
|
|
77
|
+
if current
|
|
78
|
+
current << sig
|
|
79
|
+
else
|
|
80
|
+
fresh = [sig] #: Array[String]
|
|
81
|
+
current = fresh
|
|
82
|
+
chunks << fresh
|
|
83
|
+
end
|
|
84
|
+
elsif current && RDCompiler::METHOD_ATTRIBUTE_LINE_RE =~ line
|
|
85
|
+
# シグネチャの並びを継続
|
|
86
|
+
else
|
|
87
|
+
current = nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
chunks
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# シグネチャ行なら "--- " 始まりの正規形にして返す(rd はそのまま)。
|
|
94
|
+
# md のシグネチャ行(### def ...)は MDCompiler がオーバーライドする
|
|
95
|
+
def rbs_signature_line(line)
|
|
96
|
+
line if line.start_with?('---')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def rbs_segment_html(kind, text)
|
|
100
|
+
if kind == 'c' and rbs_known_class?(text)
|
|
101
|
+
class_link(text)
|
|
102
|
+
else
|
|
103
|
+
escape_html(text).gsub('->', '→')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# DB に存在するクラス/モジュールだけリンク化する(自動生成リンクで
|
|
108
|
+
# 死リンクを作らないため。interface や型変数もここで自然に落ちる)。
|
|
109
|
+
# 同じページで同じ型名を何度も引くのでメモ化する
|
|
110
|
+
def rbs_known_class?(name)
|
|
111
|
+
@rbs_known_classes ||= {} #: Hash[String, bool]
|
|
112
|
+
@rbs_known_classes.fetch(name) {
|
|
113
|
+
db = @option[:database]
|
|
114
|
+
@rbs_known_classes[name] =
|
|
115
|
+
begin
|
|
116
|
+
db ? (db.fetch_class(name) && true) : false
|
|
117
|
+
rescue ClassNotFound
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
data/lib/bitclust/rdcompiler.rb
CHANGED
|
@@ -15,6 +15,7 @@ require 'bitclust/textutils'
|
|
|
15
15
|
require 'bitclust/messagecatalog'
|
|
16
16
|
require 'bitclust/syntax_highlighter'
|
|
17
17
|
require 'bitclust/version_badges'
|
|
18
|
+
require 'bitclust/rbs_signatures'
|
|
18
19
|
require 'rouge'
|
|
19
20
|
require 'stringio'
|
|
20
21
|
|
|
@@ -27,6 +28,7 @@ module BitClust
|
|
|
27
28
|
include TextUtils
|
|
28
29
|
include Translatable
|
|
29
30
|
include VersionBadges
|
|
31
|
+
include RbsSignatures
|
|
30
32
|
|
|
31
33
|
def initialize(urlmapper, hlevel = 1, opt = {})
|
|
32
34
|
@urlmapper = urlmapper
|
|
@@ -59,7 +61,9 @@ module BitClust
|
|
|
59
61
|
@opt = opt
|
|
60
62
|
@type = :method
|
|
61
63
|
@method = m
|
|
62
|
-
|
|
64
|
+
source = m.source
|
|
65
|
+
setup(source, m) {
|
|
66
|
+
prepare_rbs_signatures(m, source)
|
|
63
67
|
entry
|
|
64
68
|
}
|
|
65
69
|
ensure
|
|
@@ -71,6 +75,7 @@ module BitClust
|
|
|
71
75
|
def setup(src, entry = nil)
|
|
72
76
|
@f = LineInput.new(StringIO.new(src), entry)
|
|
73
77
|
@out = StringIO.new
|
|
78
|
+
reset_rbs_signatures
|
|
74
79
|
yield
|
|
75
80
|
@out.string
|
|
76
81
|
end
|
|
@@ -135,6 +140,12 @@ module BitClust
|
|
|
135
140
|
k, v = line.sub(/\A:/, '').split(':', 2)
|
|
136
141
|
props[k&.strip] = v&.strip
|
|
137
142
|
end if @type == :method
|
|
143
|
+
# RBS シグネチャは見出し <dt> 群の直後・説明 <dd> の直前に、この
|
|
144
|
+
# チャンクへ振り分けられたぶんだけ <dt> 行として出す(rbs_sig
|
|
145
|
+
# property が無ければ何も出ない)
|
|
146
|
+
if (rbs_dts = rbs_signature_dts_for_chunk())
|
|
147
|
+
@out.puts rbs_dts
|
|
148
|
+
end
|
|
138
149
|
@out.puts %Q(<dd class="#{@type.to_s}-description">)
|
|
139
150
|
undef_message if attrs.include?('undef')
|
|
140
151
|
while @f.next?
|
data/lib/bitclust/runner.rb
CHANGED
|
@@ -62,6 +62,7 @@ Subcommands(for developers):
|
|
|
62
62
|
classes Display defined classes for all ruby.
|
|
63
63
|
methods Display defined methods for all ruby.
|
|
64
64
|
methodsince Fill per-name since/until from a version ladder of DBs.
|
|
65
|
+
rbssig Fill RBS type signatures into a 4.0+ DB from .rbs files.
|
|
65
66
|
checklink Report broken [[c:]]/[[m:]]/[[lib:]]/[[d:]]/[[f:]] links.
|
|
66
67
|
|
|
67
68
|
Subcommands(for packagers):
|
|
@@ -110,6 +111,7 @@ Global Options:
|
|
|
110
111
|
'classes' => BitClust::Subcommands::ClassesCommand.new,
|
|
111
112
|
'methods' => BitClust::Subcommands::MethodsCommand.new,
|
|
112
113
|
'methodsince' => BitClust::Subcommands::MethodsinceCommand.new,
|
|
114
|
+
'rbssig' => BitClust::Subcommands::RbssigCommand.new,
|
|
113
115
|
'checklink' => BitClust::Subcommands::ChecklinkCommand.new,
|
|
114
116
|
}
|
|
115
117
|
end
|