docscribe 1.4.1 → 1.4.2
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/README.md +149 -0
- data/lib/docscribe/cli/config_builder.rb +125 -35
- data/lib/docscribe/cli/generate.rb +288 -117
- data/lib/docscribe/cli/init.rb +49 -13
- data/lib/docscribe/cli/options.rb +302 -127
- data/lib/docscribe/cli/run.rb +391 -135
- data/lib/docscribe/cli.rb +23 -5
- data/lib/docscribe/config/defaults.rb +11 -11
- data/lib/docscribe/config/emit.rb +1 -0
- data/lib/docscribe/config/filtering.rb +24 -11
- data/lib/docscribe/config/loader.rb +7 -4
- data/lib/docscribe/config/plugin.rb +1 -0
- data/lib/docscribe/config/rbs.rb +31 -22
- data/lib/docscribe/config/sorbet.rb +41 -15
- data/lib/docscribe/config/sorting.rb +1 -0
- data/lib/docscribe/config/template.rb +1 -0
- data/lib/docscribe/config/utils.rb +1 -0
- data/lib/docscribe/config.rb +1 -0
- data/lib/docscribe/infer/constants.rb +15 -0
- data/lib/docscribe/infer/literals.rb +43 -25
- data/lib/docscribe/infer/names.rb +24 -15
- data/lib/docscribe/infer/params.rb +52 -6
- data/lib/docscribe/infer/raises.rb +24 -14
- data/lib/docscribe/infer/returns.rb +365 -182
- data/lib/docscribe/infer.rb +10 -9
- data/lib/docscribe/inline_rewriter/collector.rb +766 -375
- data/lib/docscribe/inline_rewriter/doc_block.rb +217 -74
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1488 -602
- data/lib/docscribe/inline_rewriter/source_helpers.rb +100 -52
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +109 -48
- data/lib/docscribe/inline_rewriter.rb +1009 -595
- data/lib/docscribe/plugin/base/collector_plugin.rb +2 -3
- data/lib/docscribe/plugin/base/tag_plugin.rb +1 -1
- data/lib/docscribe/plugin/registry.rb +34 -7
- data/lib/docscribe/plugin.rb +48 -17
- data/lib/docscribe/types/rbs/collection_loader.rb +0 -1
- data/lib/docscribe/types/rbs/provider.rb +75 -26
- data/lib/docscribe/types/rbs/type_formatter.rb +127 -59
- data/lib/docscribe/types/sorbet/base_provider.rb +31 -12
- data/lib/docscribe/version.rb +1 -1
- metadata +2 -2
|
@@ -52,44 +52,17 @@ module Docscribe
|
|
|
52
52
|
# @param [Integer] def_bol_pos beginning-of-line position of the target def
|
|
53
53
|
# @return [Hash, nil]
|
|
54
54
|
def doc_comment_block_info(buffer, def_bol_pos)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# Skip blank lines directly above def
|
|
61
|
-
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
62
|
-
|
|
63
|
-
# Nearest non-blank line must be a comment
|
|
64
|
-
return nil unless i >= 0 && lines[i] =~ /^\s*#/
|
|
65
|
-
|
|
66
|
-
# Walk upward to include the entire contiguous comment block
|
|
67
|
-
end_idx = i
|
|
68
|
-
start_idx = i
|
|
69
|
-
start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
|
|
70
|
-
start_idx += 1
|
|
55
|
+
lines = buffer.source.lines
|
|
56
|
+
def_line_idx = (buffer.source[0...def_bol_pos] || '').count("\n")
|
|
57
|
+
block_range = find_comment_block_range(lines, def_line_idx)
|
|
58
|
+
return nil unless block_range
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
start_idx = block_range[:start_idx]
|
|
61
|
+
end_idx = block_range[:end_idx]
|
|
62
|
+
preserved_start_idx = find_preserved_start_idx(lines, start_idx, end_idx)
|
|
63
|
+
return nil unless doc_marker?(lines, preserved_start_idx..end_idx)
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
remaining = lines[removable_start_idx..end_idx]
|
|
79
|
-
return nil unless remaining.any? { |line| doc_marker_line?(line) }
|
|
80
|
-
|
|
81
|
-
start_pos = start_idx.positive? ? lines[0...start_idx].join.length : 0
|
|
82
|
-
doc_start_pos = removable_start_idx.positive? ? lines[0...removable_start_idx].join.length : 0
|
|
83
|
-
end_pos = lines[0..end_idx].join.length
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
lines: lines[start_idx..end_idx],
|
|
87
|
-
preserved_lines: lines[start_idx...removable_start_idx],
|
|
88
|
-
doc_lines: lines[removable_start_idx..end_idx],
|
|
89
|
-
start_pos: start_pos,
|
|
90
|
-
doc_start_pos: doc_start_pos,
|
|
91
|
-
end_pos: end_pos
|
|
92
|
-
}
|
|
65
|
+
build_block_info(lines, start_idx, preserved_start_idx, end_idx)
|
|
93
66
|
end
|
|
94
67
|
|
|
95
68
|
# Compute the removable range for an existing doc-like block above a method.
|
|
@@ -104,35 +77,109 @@ module Docscribe
|
|
|
104
77
|
def comment_block_removal_range(buffer, def_bol_pos)
|
|
105
78
|
src = buffer.source
|
|
106
79
|
lines = src.lines
|
|
107
|
-
def_line_idx = src[0...def_bol_pos].count("\n")
|
|
80
|
+
def_line_idx = (src[0...def_bol_pos] || '').count("\n")
|
|
81
|
+
block_range = find_comment_block_range(lines, def_line_idx)
|
|
82
|
+
return nil unless block_range
|
|
83
|
+
|
|
84
|
+
preserved_start_idx = find_preserved_start_idx(lines, block_range[:start_idx], block_range[:end_idx])
|
|
85
|
+
return nil unless doc_marker?(lines, preserved_start_idx..block_range[:end_idx])
|
|
86
|
+
|
|
87
|
+
compute_removal_range(buffer, lines, preserved_start_idx, def_bol_pos)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Find the range of a contiguous comment block directly above a method definition.
|
|
91
|
+
#
|
|
92
|
+
# Walks upward from def_line_idx, skipping blank lines, then includes all
|
|
93
|
+
# contiguous comment lines.
|
|
94
|
+
#
|
|
95
|
+
# @note module_function: when included, also defines #find_comment_block_range (instance visibility: private)
|
|
96
|
+
# @param [Array<String>] lines
|
|
97
|
+
# @param [Integer] def_line_idx
|
|
98
|
+
# @return [Hash{start_idx: Integer, end_idx: Integer}, nil]
|
|
99
|
+
def find_comment_block_range(lines, def_line_idx)
|
|
108
100
|
i = def_line_idx - 1
|
|
109
101
|
|
|
110
|
-
# Skip blank lines directly above def
|
|
111
102
|
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
112
|
-
|
|
113
|
-
# Nearest non-blank line must be a comment to remove anything
|
|
114
103
|
return nil unless i >= 0 && lines[i] =~ /^\s*#/
|
|
115
104
|
|
|
116
|
-
# Walk upward to include the entire contiguous comment block
|
|
117
105
|
start_idx = i
|
|
118
106
|
start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
|
|
119
107
|
start_idx += 1
|
|
120
108
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
removable_start_idx += 1 while removable_start_idx <= i && preserved_comment_line?(lines[removable_start_idx])
|
|
109
|
+
{ start_idx: start_idx, end_idx: i }
|
|
110
|
+
end
|
|
124
111
|
|
|
125
|
-
|
|
126
|
-
|
|
112
|
+
# Find the first index in a comment block after preserved directive-style lines.
|
|
113
|
+
#
|
|
114
|
+
# Preserved lines include RuboCop directives and Ruby magic comments.
|
|
115
|
+
#
|
|
116
|
+
# @note module_function: when included, also defines #find_preserved_start_idx (instance visibility: private)
|
|
117
|
+
# @param [Array<String>] lines
|
|
118
|
+
# @param [Integer] start_idx
|
|
119
|
+
# @param [Integer] end_idx
|
|
120
|
+
# @return [Integer]
|
|
121
|
+
def find_preserved_start_idx(lines, start_idx, end_idx)
|
|
122
|
+
idx = start_idx
|
|
123
|
+
idx += 1 while idx <= end_idx && preserved_comment_line?(lines[idx])
|
|
124
|
+
idx
|
|
125
|
+
end
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
# Whether a comment block range contains documentation markers.
|
|
128
|
+
#
|
|
129
|
+
# @note module_function: when included, also defines #doc_marker? (instance visibility: private)
|
|
130
|
+
# @param [Array<String>] lines
|
|
131
|
+
# @param [Range] range line index range
|
|
132
|
+
# @return [Boolean]
|
|
133
|
+
def doc_marker?(lines, range)
|
|
134
|
+
(lines[range] || []).any? { |line| doc_marker_line?(line) }
|
|
135
|
+
end
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
# Build block info hash from computed line ranges.
|
|
138
|
+
#
|
|
139
|
+
# @note module_function: when included, also defines #build_block_info (instance visibility: private)
|
|
140
|
+
# @param [Array<String>] lines
|
|
141
|
+
# @param [Integer] start_idx
|
|
142
|
+
# @param [Integer] preserved_start_idx
|
|
143
|
+
# @param [Integer] end_idx
|
|
144
|
+
# @return [Hash]
|
|
145
|
+
def build_block_info(lines, start_idx, preserved_start_idx, end_idx)
|
|
146
|
+
positions = compute_positions(lines, start_idx, preserved_start_idx, end_idx)
|
|
147
|
+
{
|
|
148
|
+
lines: lines[start_idx..end_idx],
|
|
149
|
+
preserved_lines: lines[start_idx...preserved_start_idx],
|
|
150
|
+
doc_lines: lines[preserved_start_idx..end_idx],
|
|
151
|
+
**positions
|
|
152
|
+
}
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Compute the removal range for preserved start position.
|
|
156
|
+
#
|
|
157
|
+
# @note module_function: when included, also defines #compute_removal_range (instance visibility: private)
|
|
158
|
+
# @param [Parser::Source::Buffer] buffer
|
|
159
|
+
# @param [Array<String>] lines
|
|
160
|
+
# @param [Integer] preserved_start_idx
|
|
161
|
+
# @param [Integer] def_bol_pos
|
|
162
|
+
# @return [Parser::Source::Range]
|
|
163
|
+
def compute_removal_range(buffer, lines, preserved_start_idx, def_bol_pos)
|
|
164
|
+
start_pos = preserved_start_idx.positive? ? (lines[0...preserved_start_idx] || []).join.length : 0
|
|
133
165
|
Parser::Source::Range.new(buffer, start_pos, def_bol_pos)
|
|
134
166
|
end
|
|
135
167
|
|
|
168
|
+
# Compute source positions for a comment block.
|
|
169
|
+
#
|
|
170
|
+
# @note module_function: when included, also defines #compute_positions (instance visibility: private)
|
|
171
|
+
# @param [Array<String>] lines
|
|
172
|
+
# @param [Integer] start_idx
|
|
173
|
+
# @param [Integer] doc_start_idx
|
|
174
|
+
# @param [Integer] end_pos_idx
|
|
175
|
+
# @return [Hash{start_pos: Integer, doc_start_pos: Integer, end_pos: Integer}]
|
|
176
|
+
def compute_positions(lines, start_idx, doc_start_idx, end_pos_idx)
|
|
177
|
+
start_pos = start_idx.positive? ? (lines[0...start_idx] || []).join.length : 0
|
|
178
|
+
doc_start_pos = doc_start_idx.positive? ? (lines[0...doc_start_idx] || []).join.length : 0
|
|
179
|
+
end_pos = (lines[0..end_pos_idx] || []).join.length
|
|
180
|
+
{ start_pos: start_pos, doc_start_pos: doc_start_pos, end_pos: end_pos }
|
|
181
|
+
end
|
|
182
|
+
|
|
136
183
|
# Whether a comment line should be preserved during aggressive replacement.
|
|
137
184
|
#
|
|
138
185
|
# Preserved lines include:
|
|
@@ -191,14 +238,15 @@ module Docscribe
|
|
|
191
238
|
#
|
|
192
239
|
# This helper is retained for compatibility/legacy behavior checks.
|
|
193
240
|
#
|
|
194
|
-
# @note module_function: when included, also defines #already_has_doc_immediately_above?
|
|
241
|
+
# @note module_function: when included, also defines #already_has_doc_immediately_above?
|
|
242
|
+
# (instance visibility: private)
|
|
195
243
|
# @param [Parser::Source::Buffer] buffer
|
|
196
244
|
# @param [Integer] insert_pos
|
|
197
245
|
# @return [Boolean]
|
|
198
246
|
def already_has_doc_immediately_above?(buffer, insert_pos)
|
|
199
247
|
src = buffer.source
|
|
200
248
|
lines = src.lines
|
|
201
|
-
current_line_index = src[0...insert_pos].count("\n")
|
|
249
|
+
current_line_index = (src[0...insert_pos] || '').count("\n")
|
|
202
250
|
i = current_line_index - 1
|
|
203
251
|
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
204
252
|
return false if i.negative?
|
|
@@ -64,26 +64,45 @@ module Docscribe
|
|
|
64
64
|
# @param [Array<String>] lines
|
|
65
65
|
# @return [Array<Hash>]
|
|
66
66
|
def parse_segments(lines)
|
|
67
|
-
segments = []
|
|
67
|
+
segments = [] #: Array[untyped]
|
|
68
68
|
i = 0
|
|
69
69
|
|
|
70
|
-
while i < lines.length
|
|
71
|
-
|
|
70
|
+
i = advance_parse(lines, i, segments) while i < lines.length
|
|
71
|
+
|
|
72
|
+
segments
|
|
73
|
+
end
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
# Parse the next line as either a tag run or non-tag content, appending to segments.
|
|
76
|
+
#
|
|
77
|
+
# @note module_function: when included, also defines #advance_parse (instance visibility: private)
|
|
78
|
+
# @param [Array<String>] lines comment block lines
|
|
79
|
+
# @param [Integer] idx current parse index
|
|
80
|
+
# @param [Array<Hash>] segments accumulated parsed segments
|
|
81
|
+
# @return [Integer] new index after processing
|
|
82
|
+
def advance_parse(lines, idx, segments)
|
|
83
|
+
if top_level_tag_line?(lines[idx])
|
|
84
|
+
consume_tag_run(lines, idx, segments)
|
|
85
|
+
else
|
|
86
|
+
segments << { type: :other, lines: [lines[idx]] }
|
|
87
|
+
idx + 1
|
|
84
88
|
end
|
|
89
|
+
end
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
# Consume a contiguous tag run and append to segments.
|
|
92
|
+
#
|
|
93
|
+
# @note module_function: when included, also defines #consume_tag_run (instance visibility: private)
|
|
94
|
+
# @param [Array<String>] lines
|
|
95
|
+
# @param [Integer] idx current index
|
|
96
|
+
# @param [Array<Hash>] segments accumulated segments
|
|
97
|
+
# @return [Integer] new index after consuming the run
|
|
98
|
+
def consume_tag_run(lines, idx, segments)
|
|
99
|
+
entries = [] #: Array[untyped]
|
|
100
|
+
while idx < lines.length && top_level_tag_line?(lines[idx])
|
|
101
|
+
entry, idx = consume_entry(lines, idx)
|
|
102
|
+
entries << entry
|
|
103
|
+
end
|
|
104
|
+
segments << { type: :tag_run, entries: entries }
|
|
105
|
+
idx
|
|
87
106
|
end
|
|
88
107
|
|
|
89
108
|
# Sort one parsed segment if it is a tag run.
|
|
@@ -124,29 +143,51 @@ module Docscribe
|
|
|
124
143
|
def consume_entry(lines, start_idx)
|
|
125
144
|
first = lines[start_idx]
|
|
126
145
|
tag = extract_tag_name(first)
|
|
127
|
-
entry_lines =
|
|
128
|
-
i =
|
|
146
|
+
entry_lines = collect_continuation_lines(lines, start_idx + 1)
|
|
147
|
+
i = entry_lines.length + start_idx
|
|
129
148
|
|
|
130
|
-
|
|
131
|
-
line = lines[i]
|
|
132
|
-
|
|
133
|
-
break if top_level_tag_line?(line)
|
|
134
|
-
break if blank_comment_line?(line)
|
|
135
|
-
break unless comment_line?(line)
|
|
149
|
+
entry = build_entry(tag, entry_lines, first, start_idx)
|
|
136
150
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
end
|
|
151
|
+
[entry, i]
|
|
152
|
+
end
|
|
140
153
|
|
|
141
|
-
|
|
154
|
+
# Build an Entry struct from parsed tag name, lines, and source line metadata.
|
|
155
|
+
#
|
|
156
|
+
# @note module_function: when included, also defines #build_entry (instance visibility: private)
|
|
157
|
+
# @param [String, nil] tag the extracted tag name
|
|
158
|
+
# @param [Array<String>] entry_lines all lines belonging to this entry
|
|
159
|
+
# @param [String] first the first (tag) line
|
|
160
|
+
# @param [Integer] start_idx original index of the first line
|
|
161
|
+
# @return [Entry]
|
|
162
|
+
def build_entry(tag, entry_lines, first, start_idx)
|
|
163
|
+
Entry.new(
|
|
142
164
|
tag: tag,
|
|
143
165
|
lines: entry_lines,
|
|
144
166
|
param_name: extract_param_name(first),
|
|
145
167
|
option_owner: extract_option_owner(first),
|
|
146
168
|
index: start_idx
|
|
147
169
|
)
|
|
170
|
+
end
|
|
148
171
|
|
|
149
|
-
|
|
172
|
+
# Collect continuation lines following a top-level tag entry.
|
|
173
|
+
#
|
|
174
|
+
# @note module_function: when included, also defines #collect_continuation_lines (instance visibility: private)
|
|
175
|
+
# @param [Array<String>] lines
|
|
176
|
+
# @param [Integer] start_idx
|
|
177
|
+
# @return [Array<String>]
|
|
178
|
+
def collect_continuation_lines(lines, start_idx)
|
|
179
|
+
result = [] #: Array[String]
|
|
180
|
+
i = start_idx
|
|
181
|
+
|
|
182
|
+
while i < lines.length
|
|
183
|
+
line = lines[i]
|
|
184
|
+
break if top_level_tag_line?(line) || blank_comment_line?(line) || !comment_line?(line)
|
|
185
|
+
|
|
186
|
+
result << line
|
|
187
|
+
i += 1
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
result
|
|
150
191
|
end
|
|
151
192
|
|
|
152
193
|
# Group entries so `@option` tags remain attached to their owning `@param`.
|
|
@@ -155,34 +196,54 @@ module Docscribe
|
|
|
155
196
|
# @param [Array<Entry>] entries
|
|
156
197
|
# @return [Array<Array<Entry>>]
|
|
157
198
|
def group_entries(entries)
|
|
158
|
-
groups = []
|
|
199
|
+
groups = [] #: Array[untyped]
|
|
159
200
|
i = 0
|
|
160
201
|
|
|
161
202
|
while i < entries.length
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if entry.tag == 'param'
|
|
165
|
-
group = [entry]
|
|
166
|
-
i += 1
|
|
167
|
-
|
|
168
|
-
while i < entries.length &&
|
|
169
|
-
entries[i].tag == 'option' &&
|
|
170
|
-
entries[i].option_owner &&
|
|
171
|
-
entries[i].option_owner == entry.param_name
|
|
172
|
-
group << entries[i]
|
|
173
|
-
i += 1
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
groups << group
|
|
177
|
-
else
|
|
178
|
-
groups << [entry]
|
|
179
|
-
i += 1
|
|
180
|
-
end
|
|
203
|
+
groups << group_entry(entries, i)
|
|
204
|
+
i += 1
|
|
181
205
|
end
|
|
182
206
|
|
|
183
207
|
groups
|
|
184
208
|
end
|
|
185
209
|
|
|
210
|
+
# Group a single entry, attaching any subsequent @option entries if it is a @param.
|
|
211
|
+
#
|
|
212
|
+
# @note module_function: when included, also defines #group_entry (instance visibility: private)
|
|
213
|
+
# @param [Array<Entry>] entries parsed tag entries
|
|
214
|
+
# @param [Integer] idx index of the entry to group
|
|
215
|
+
# @return [Array<Entry>] the entry group
|
|
216
|
+
def group_entry(entries, idx)
|
|
217
|
+
entry = entries[idx]
|
|
218
|
+
if entry.tag == 'param'
|
|
219
|
+
[entry] + collect_option_entries(entries, idx + 1, entry.param_name)
|
|
220
|
+
else
|
|
221
|
+
[entry]
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Collect `@option` entries belonging to the given param name.
|
|
226
|
+
#
|
|
227
|
+
# @note module_function: when included, also defines #collect_option_entries (instance visibility: private)
|
|
228
|
+
# @param [Array<Entry>] entries
|
|
229
|
+
# @param [Integer] start_idx
|
|
230
|
+
# @param [String] param_name
|
|
231
|
+
# @return [Array<Entry>]
|
|
232
|
+
def collect_option_entries(entries, start_idx, param_name)
|
|
233
|
+
result = [] #: Array[untyped]
|
|
234
|
+
i = start_idx
|
|
235
|
+
|
|
236
|
+
while i < entries.length &&
|
|
237
|
+
entries[i].tag == 'option' &&
|
|
238
|
+
entries[i].option_owner &&
|
|
239
|
+
entries[i].option_owner == param_name
|
|
240
|
+
result << entries[i]
|
|
241
|
+
i += 1
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
result
|
|
245
|
+
end
|
|
246
|
+
|
|
186
247
|
# Whether a line begins a top-level tag entry.
|
|
187
248
|
#
|
|
188
249
|
# @note module_function: when included, also defines #top_level_tag_line? (instance visibility: private)
|