docscribe 1.4.2 → 1.5.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/README.md +465 -130
- data/lib/docscribe/cli/check_for_comments.rb +183 -0
- data/lib/docscribe/cli/config_builder.rb +107 -53
- data/lib/docscribe/cli/formatters/json.rb +294 -0
- data/lib/docscribe/cli/formatters/sarif.rb +235 -0
- data/lib/docscribe/cli/formatters/text.rb +208 -0
- data/lib/docscribe/cli/formatters.rb +26 -0
- data/lib/docscribe/cli/generate.rb +45 -45
- data/lib/docscribe/cli/init.rb +14 -6
- data/lib/docscribe/cli/options.rb +190 -88
- data/lib/docscribe/cli/rbs_gen.rb +529 -0
- data/lib/docscribe/cli/run.rb +210 -152
- data/lib/docscribe/cli/sigs.rb +366 -0
- data/lib/docscribe/cli/update_types.rb +103 -0
- data/lib/docscribe/cli.rb +21 -13
- data/lib/docscribe/config/defaults.rb +5 -1
- data/lib/docscribe/config/emit.rb +17 -0
- data/lib/docscribe/config/filtering.rb +18 -25
- data/lib/docscribe/config/loader.rb +15 -11
- data/lib/docscribe/config/plugin.rb +1 -1
- data/lib/docscribe/config/rbs.rb +41 -9
- data/lib/docscribe/config/sorbet.rb +9 -12
- data/lib/docscribe/config/sorting.rb +1 -1
- data/lib/docscribe/config/template.rb +9 -1
- data/lib/docscribe/config/utils.rb +11 -9
- data/lib/docscribe/config.rb +2 -4
- data/lib/docscribe/infer/ast_walk.rb +1 -1
- data/lib/docscribe/infer/literals.rb +6 -11
- data/lib/docscribe/infer/names.rb +2 -3
- data/lib/docscribe/infer/params.rb +15 -17
- data/lib/docscribe/infer/raises.rb +3 -5
- data/lib/docscribe/infer/returns.rb +542 -140
- data/lib/docscribe/infer.rb +22 -23
- data/lib/docscribe/inline_rewriter/collector.rb +159 -164
- data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1026 -723
- data/lib/docscribe/inline_rewriter/source_helpers.rb +49 -49
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
- data/lib/docscribe/inline_rewriter.rb +495 -492
- data/lib/docscribe/parsing.rb +29 -10
- data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
- data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
- data/lib/docscribe/plugin/context.rb +28 -18
- data/lib/docscribe/plugin/registry.rb +26 -27
- data/lib/docscribe/plugin/tag.rb +9 -14
- data/lib/docscribe/plugin.rb +17 -16
- data/lib/docscribe/types/provider_chain.rb +4 -2
- data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
- data/lib/docscribe/types/rbs/provider.rb +60 -44
- data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
- data/lib/docscribe/types/signature.rb +22 -42
- data/lib/docscribe/types/sorbet/base_provider.rb +24 -19
- data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
- data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
- data/lib/docscribe/types/yard/formatter.rb +100 -0
- data/lib/docscribe/types/yard/parser.rb +240 -0
- data/lib/docscribe/types/yard/types.rb +52 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +33 -1
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'docscribe/parsing'
|
|
5
|
+
require 'docscribe/types/rbs/provider'
|
|
6
|
+
|
|
7
|
+
module Docscribe
|
|
8
|
+
module CLI
|
|
9
|
+
# Check RBS signature coverage for Ruby source files.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# docscribe sigs [options] [files...]
|
|
13
|
+
#
|
|
14
|
+
# Parses Ruby source files, extracts method definitions, and checks
|
|
15
|
+
# each method against the configured RBS signature directories.
|
|
16
|
+
# Reports methods that lack RBS type signatures.
|
|
17
|
+
module Sigs
|
|
18
|
+
BANNER = <<~TEXT
|
|
19
|
+
Usage: docscribe sigs [options] [files...]
|
|
20
|
+
|
|
21
|
+
Check RBS signature coverage for Ruby source files.
|
|
22
|
+
|
|
23
|
+
TEXT
|
|
24
|
+
|
|
25
|
+
EXIT_CODES = "\nExit codes:\n " \
|
|
26
|
+
"0 - all methods have signatures\n " \
|
|
27
|
+
"1 - some methods lack signatures\n " \
|
|
28
|
+
'2 - error occurred'
|
|
29
|
+
|
|
30
|
+
# @!attribute [rw] name
|
|
31
|
+
# @return [Symbol]
|
|
32
|
+
# @param [Symbol] value
|
|
33
|
+
#
|
|
34
|
+
# @!attribute [rw] scope
|
|
35
|
+
# @return [Symbol]
|
|
36
|
+
# @param [Symbol] value
|
|
37
|
+
#
|
|
38
|
+
# @!attribute [rw] container
|
|
39
|
+
# @return [String?]
|
|
40
|
+
# @param [String?] value
|
|
41
|
+
#
|
|
42
|
+
# @!attribute [rw] file
|
|
43
|
+
# @return [String]
|
|
44
|
+
# @param [String] value
|
|
45
|
+
#
|
|
46
|
+
# @!attribute [rw] line
|
|
47
|
+
# @return [Integer]
|
|
48
|
+
# @param [Integer] value
|
|
49
|
+
MethodDef = Struct.new(:name, :scope, :container, :file, :line, keyword_init: true)
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
# @param [Array<String>] argv
|
|
53
|
+
# @return [Integer]
|
|
54
|
+
def run(argv)
|
|
55
|
+
warn_ruby_version
|
|
56
|
+
options = parse_options(argv)
|
|
57
|
+
paths = expand_paths(argv)
|
|
58
|
+
return no_files_found if paths.empty?
|
|
59
|
+
|
|
60
|
+
run_with(options, extract_methods(paths))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# @private
|
|
66
|
+
# @return [void]
|
|
67
|
+
def warn_ruby_version
|
|
68
|
+
return unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0')
|
|
69
|
+
|
|
70
|
+
warn 'Warning: docscribe sigs requires Ruby 3.0+ for RBS support. ' \
|
|
71
|
+
"You are running Ruby #{RUBY_VERSION}."
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @private
|
|
75
|
+
# @param [Array<String>] argv
|
|
76
|
+
# @return [Hash<Symbol, Object>]
|
|
77
|
+
def parse_options(argv)
|
|
78
|
+
options = { sig_dirs: ['sig'], rbs_collection: false, verbose: false }
|
|
79
|
+
|
|
80
|
+
parser = OptionParser.new do |opts|
|
|
81
|
+
opts.banner = BANNER
|
|
82
|
+
register_sig_options(opts, options)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
parser.parse!(argv)
|
|
86
|
+
options
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @private
|
|
90
|
+
# @param [OptionParser] opts
|
|
91
|
+
# @param [Hash<Symbol, Object>] options
|
|
92
|
+
# @return [void]
|
|
93
|
+
def register_sig_options(opts, options)
|
|
94
|
+
opts.on('-s', '--sig-dir DIR', 'Add RBS signature directory (repeatable)') { |d| options[:sig_dirs] << d }
|
|
95
|
+
opts.on('--rbs-collection', 'Use RBS collection') { options[:rbs_collection] = true }
|
|
96
|
+
opts.on('--verbose', 'Print methods that have signatures too') { options[:verbose] = true }
|
|
97
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
98
|
+
puts opts, EXIT_CODES
|
|
99
|
+
exit 0
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @private
|
|
104
|
+
# @param [Array<String>] args
|
|
105
|
+
# @return [Array<String>]
|
|
106
|
+
def expand_paths(args)
|
|
107
|
+
files = [] #: Array[String]
|
|
108
|
+
args = ['.'] if args.empty?
|
|
109
|
+
args.each { |path| expand_single_path(files, path) }
|
|
110
|
+
files.uniq.sort
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @private
|
|
114
|
+
# @param [Array<String>] files
|
|
115
|
+
# @param [String] path
|
|
116
|
+
# @return [void]
|
|
117
|
+
def expand_single_path(files, path)
|
|
118
|
+
if File.directory?(path)
|
|
119
|
+
files.concat(Dir.glob(File.join(path, '**', '*.rb')))
|
|
120
|
+
elsif File.file?(path)
|
|
121
|
+
files << path
|
|
122
|
+
else
|
|
123
|
+
warn "Skipping missing path: #{path}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @private
|
|
128
|
+
# @return [Integer]
|
|
129
|
+
def no_files_found
|
|
130
|
+
warn 'No files found. Pass files or directories (e.g. `docscribe sigs lib`).'
|
|
131
|
+
2
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @private
|
|
135
|
+
# @param [Hash<Symbol, Object>] options
|
|
136
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
137
|
+
# @return [Integer]
|
|
138
|
+
def run_with(options, methods)
|
|
139
|
+
return 0 if methods.empty?
|
|
140
|
+
|
|
141
|
+
provider = build_provider(options)
|
|
142
|
+
return 2 unless provider
|
|
143
|
+
|
|
144
|
+
missing = check_sigs(methods, provider, verbose: options[:verbose])
|
|
145
|
+
report_results(methods, missing)
|
|
146
|
+
missing.empty? ? 0 : 1
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @private
|
|
150
|
+
# @param [Array<String>] paths
|
|
151
|
+
# @return [Array<Docscribe::CLI::Sigs::MethodDef>]
|
|
152
|
+
def extract_methods(paths)
|
|
153
|
+
methods = [] #: Array[MethodDef]
|
|
154
|
+
paths.each { |path| extract_methods_from_file(path, methods) }
|
|
155
|
+
methods
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @private
|
|
159
|
+
# @param [String] path
|
|
160
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
161
|
+
# @raise [Parser::SyntaxError]
|
|
162
|
+
# @raise [StandardError]
|
|
163
|
+
# @return [void] if StandardError
|
|
164
|
+
# @return [Object] if Parser::SyntaxError
|
|
165
|
+
# @return [Object] if StandardError
|
|
166
|
+
def extract_methods_from_file(path, methods)
|
|
167
|
+
src = File.read(path)
|
|
168
|
+
ast = Docscribe::Parsing.parse(src, file: path)
|
|
169
|
+
return unless ast
|
|
170
|
+
|
|
171
|
+
walk_for_methods(ast, [], methods, path)
|
|
172
|
+
rescue Parser::SyntaxError => e # steep:ignore
|
|
173
|
+
warn "Syntax error in #{path}: #{e.message}"
|
|
174
|
+
rescue StandardError => e
|
|
175
|
+
warn "Error parsing #{path}: #{e.class}: #{e.message}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @private
|
|
179
|
+
# @param [Parser::AST::Node] node
|
|
180
|
+
# @param [Array<String>] containers
|
|
181
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
182
|
+
# @param [String] path
|
|
183
|
+
# @param [Boolean] inside_sclass
|
|
184
|
+
# @return [void]
|
|
185
|
+
def walk_for_methods(node, containers, methods, path, inside_sclass: false)
|
|
186
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
187
|
+
|
|
188
|
+
case node.type
|
|
189
|
+
when :class, :module then walk_class_module(node, containers, methods, path)
|
|
190
|
+
when :sclass then walk_sclass(node, containers, methods, path)
|
|
191
|
+
when :def then collect_def(node, containers, methods, path, inside_sclass: inside_sclass)
|
|
192
|
+
when :defs then collect_defs(node, containers, methods, path)
|
|
193
|
+
else walk_children(node, containers, methods, path, inside_sclass: inside_sclass)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @private
|
|
198
|
+
# @param [Parser::AST::Node] node
|
|
199
|
+
# @param [Array<String>] containers
|
|
200
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
201
|
+
# @param [String] path
|
|
202
|
+
# @return [void]
|
|
203
|
+
def walk_class_module(node, containers, methods, path)
|
|
204
|
+
containers.push(const_name(node.children[0]))
|
|
205
|
+
node.children.drop(1).each { |c| walk_for_methods(c, containers, methods, path) }
|
|
206
|
+
containers.pop
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @private
|
|
210
|
+
# @param [Parser::AST::Node] node
|
|
211
|
+
# @param [Array<String>] containers
|
|
212
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
213
|
+
# @param [String] path
|
|
214
|
+
# @return [void]
|
|
215
|
+
def walk_sclass(node, containers, methods, path)
|
|
216
|
+
node.children.drop(1).each { |c| walk_for_methods(c, containers, methods, path, inside_sclass: true) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# @private
|
|
220
|
+
# @param [Parser::AST::Node] node
|
|
221
|
+
# @param [Array<String>] containers
|
|
222
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
223
|
+
# @param [String] path
|
|
224
|
+
# @param [Boolean] inside_sclass
|
|
225
|
+
# @return [void]
|
|
226
|
+
def walk_children(node, containers, methods, path, inside_sclass: false)
|
|
227
|
+
node.children.each { |c| walk_for_methods(c, containers, methods, path, inside_sclass: inside_sclass) }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# @private
|
|
231
|
+
# @param [Parser::AST::Node] node
|
|
232
|
+
# @param [Array<String>] containers
|
|
233
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
234
|
+
# @param [String] path
|
|
235
|
+
# @param [Boolean] inside_sclass
|
|
236
|
+
# @return [void]
|
|
237
|
+
def collect_def(node, containers, methods, path, inside_sclass: false)
|
|
238
|
+
methods << MethodDef.new(
|
|
239
|
+
name: node.children[0],
|
|
240
|
+
scope: inside_sclass ? :class : :instance,
|
|
241
|
+
container: container_name(containers),
|
|
242
|
+
file: path,
|
|
243
|
+
line: node.loc&.line || 1
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# @private
|
|
248
|
+
# @param [Parser::AST::Node] node
|
|
249
|
+
# @param [Array<String>] containers
|
|
250
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
251
|
+
# @param [String] path
|
|
252
|
+
# @return [void]
|
|
253
|
+
def collect_defs(node, containers, methods, path)
|
|
254
|
+
methods << MethodDef.new(
|
|
255
|
+
name: node.children[1],
|
|
256
|
+
scope: :class,
|
|
257
|
+
container: container_name(containers),
|
|
258
|
+
file: path,
|
|
259
|
+
line: node.loc&.line || 1
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# @private
|
|
264
|
+
# @param [Array<String>] containers
|
|
265
|
+
# @return [String?]
|
|
266
|
+
def container_name(containers)
|
|
267
|
+
containers.empty? ? nil : containers.join('::')
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# @private
|
|
271
|
+
# @param [Parser::AST::Node] node
|
|
272
|
+
# @return [String]
|
|
273
|
+
def const_name(node)
|
|
274
|
+
return node.to_s unless node.is_a?(Parser::AST::Node)
|
|
275
|
+
return node.children[1].to_s if node.type == :const
|
|
276
|
+
|
|
277
|
+
node.children.map { |c| c.is_a?(Parser::AST::Node) ? const_name(c) : c.to_s }.join('::')
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# @private
|
|
281
|
+
# @param [Hash<Symbol, Object>] options
|
|
282
|
+
# @raise [LoadError]
|
|
283
|
+
# @raise [StandardError]
|
|
284
|
+
# @return [Docscribe::Types::RBS::Provider?] if StandardError
|
|
285
|
+
# @return [nil] if LoadError
|
|
286
|
+
# @return [nil] if StandardError
|
|
287
|
+
def build_provider(options)
|
|
288
|
+
dirs = options[:rbs_collection] ? load_collection_dirs : [] #: Array[String]
|
|
289
|
+
Docscribe::Types::RBS::Provider.new(sig_dirs: options[:sig_dirs], collection_dirs: dirs)
|
|
290
|
+
rescue LoadError
|
|
291
|
+
warn 'Docscribe: rbs gem is not installed. Add `gem "rbs"` to your Gemfile ' \
|
|
292
|
+
'or run `bundle exec rbs collection install`.'
|
|
293
|
+
nil
|
|
294
|
+
rescue StandardError => e
|
|
295
|
+
warn "Docscribe: Failed to initialize RBS provider: #{e.class}: #{e.message}"
|
|
296
|
+
nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# @private
|
|
300
|
+
# @raise [StandardError]
|
|
301
|
+
# @return [Array<String>] if StandardError
|
|
302
|
+
# @return [Array] if StandardError
|
|
303
|
+
def load_collection_dirs
|
|
304
|
+
dir = Docscribe::Types::RBS::CollectionLoader.resolve
|
|
305
|
+
dir ? [dir] : []
|
|
306
|
+
rescue StandardError => e
|
|
307
|
+
warn "Docscribe: Failed to load RBS collection: #{e.class}: #{e.message}"
|
|
308
|
+
[]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# @private
|
|
312
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
313
|
+
# @param [Docscribe::Types::RBS::Provider] provider
|
|
314
|
+
# @param [Boolean] verbose
|
|
315
|
+
# @return [Array<Docscribe::CLI::Sigs::MethodDef>]
|
|
316
|
+
def check_sigs(methods, provider, verbose:)
|
|
317
|
+
missing = [] #: Array[MethodDef]
|
|
318
|
+
methods.each do |m|
|
|
319
|
+
sig = lookup_signature(m, provider)
|
|
320
|
+
puts " OK #{format_method(m)} (#{m.file}:#{m.line})" if sig && verbose
|
|
321
|
+
puts " MISS #{format_method(m)} (#{m.file}:#{m.line})" unless sig
|
|
322
|
+
missing << m unless sig
|
|
323
|
+
end
|
|
324
|
+
missing
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# @private
|
|
328
|
+
# @param [Docscribe::CLI::Sigs::MethodDef] method_def
|
|
329
|
+
# @param [Docscribe::Types::RBS::Provider] provider
|
|
330
|
+
# @return [Object, nil]
|
|
331
|
+
def lookup_signature(method_def, provider)
|
|
332
|
+
container = method_def.container
|
|
333
|
+
return nil unless container
|
|
334
|
+
|
|
335
|
+
provider.signature_for(
|
|
336
|
+
container: container,
|
|
337
|
+
scope: method_def.scope,
|
|
338
|
+
name: method_def.name
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# @private
|
|
343
|
+
# @param [Docscribe::CLI::Sigs::MethodDef] method_def
|
|
344
|
+
# @return [String]
|
|
345
|
+
def format_method(method_def)
|
|
346
|
+
prefix = method_def.scope == :class ? 'self.' : ''
|
|
347
|
+
container = method_def.container ? "#{method_def.container}#" : ''
|
|
348
|
+
"#{container}#{prefix}#{method_def.name}"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# @private
|
|
352
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] methods
|
|
353
|
+
# @param [Array<Docscribe::CLI::Sigs::MethodDef>] missing
|
|
354
|
+
# @return [void]
|
|
355
|
+
def report_results(methods, missing)
|
|
356
|
+
puts
|
|
357
|
+
if missing.empty?
|
|
358
|
+
puts "Docscribe: All #{methods.size} methods have RBS signatures"
|
|
359
|
+
else
|
|
360
|
+
puts "Docscribe: #{missing.size}/#{methods.size} methods missing RBS signatures"
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
|
|
5
|
+
require 'docscribe/cli/options'
|
|
6
|
+
require 'docscribe/cli/run'
|
|
7
|
+
|
|
8
|
+
module Docscribe
|
|
9
|
+
module CLI
|
|
10
|
+
# Two-pass update: rebuild docs then re-merge with RBS types.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# docscribe update_types [directory]
|
|
14
|
+
#
|
|
15
|
+
# Pass 1: `-AkB --rbs-collection <dir>` — aggressive rebuild, keep descriptions,
|
|
16
|
+
# no boilerplate, using RBS collection signatures.
|
|
17
|
+
# Pass 2: `-aB --rbs-collection <dir>` — safe merge cleanup, no boilerplate,
|
|
18
|
+
# using RBS collection signatures.
|
|
19
|
+
module UpdateTypes
|
|
20
|
+
BANNER = <<~TEXT
|
|
21
|
+
Usage: docscribe update_types [directory]
|
|
22
|
+
|
|
23
|
+
Two-pass type-aware documentation update.
|
|
24
|
+
|
|
25
|
+
Pass 1 (aggressive): docscribe -AkB --rbs-collection <dir>
|
|
26
|
+
rebuild doc blocks, keep descriptions, no boilerplate
|
|
27
|
+
|
|
28
|
+
Pass 2 (safe): docscribe -aB --rbs-collection <dir>
|
|
29
|
+
safe merge cleanup, no boilerplate
|
|
30
|
+
|
|
31
|
+
TEXT
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
# @param [Array<String>] argv
|
|
35
|
+
# @return [Integer]
|
|
36
|
+
def run(argv)
|
|
37
|
+
options = parse_options(argv)
|
|
38
|
+
dir = options[:dir]
|
|
39
|
+
|
|
40
|
+
announce_start
|
|
41
|
+
|
|
42
|
+
exit1 = run_first_pass(dir)
|
|
43
|
+
return exit1 unless exit1.zero?
|
|
44
|
+
|
|
45
|
+
exit2 = run_second_pass(dir)
|
|
46
|
+
return exit2 unless exit2.zero?
|
|
47
|
+
|
|
48
|
+
announce_complete
|
|
49
|
+
0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# @private
|
|
55
|
+
# @param [Array<String>] argv
|
|
56
|
+
# @return [Hash<Symbol, Object>]
|
|
57
|
+
def parse_options(argv)
|
|
58
|
+
options = { dir: '.' }
|
|
59
|
+
OptionParser.new(BANNER) do |opts|
|
|
60
|
+
opts.on('-h', '--help', 'Show this help') { puts opts or exit 0 }
|
|
61
|
+
opts.parse!(argv)
|
|
62
|
+
end
|
|
63
|
+
options[:dir] = argv.first if argv.any?
|
|
64
|
+
options
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @private
|
|
68
|
+
# @return [void]
|
|
69
|
+
def announce_start
|
|
70
|
+
puts 'Docscribe: Running type-aware documentation update...'
|
|
71
|
+
puts
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @private
|
|
75
|
+
# @param [String] dir
|
|
76
|
+
# @return [Integer]
|
|
77
|
+
def run_first_pass(dir)
|
|
78
|
+
puts 'Pass 1: Aggressive rebuild with RBS collection...'
|
|
79
|
+
argv1 = ['-AkB', '--rbs-collection', dir]
|
|
80
|
+
options1 = Docscribe::CLI::Options.parse!(argv1)
|
|
81
|
+
Docscribe::CLI::Run.run(options: options1, argv: [dir])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @private
|
|
85
|
+
# @param [String] dir
|
|
86
|
+
# @return [Integer]
|
|
87
|
+
def run_second_pass(dir)
|
|
88
|
+
puts 'Pass 2: Safe merge with RBS collection...'
|
|
89
|
+
argv2 = ['-aB', '--rbs-collection', dir]
|
|
90
|
+
options2 = Docscribe::CLI::Options.parse!(argv2)
|
|
91
|
+
Docscribe::CLI::Run.run(options: options2, argv: [dir])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @private
|
|
95
|
+
# @return [void]
|
|
96
|
+
def announce_complete
|
|
97
|
+
puts
|
|
98
|
+
puts 'Docscribe: Type-aware documentation update complete.'
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
data/lib/docscribe/cli.rb
CHANGED
|
@@ -4,6 +4,10 @@ require 'docscribe/cli/init'
|
|
|
4
4
|
require 'docscribe/cli/generate'
|
|
5
5
|
require 'docscribe/cli/options'
|
|
6
6
|
require 'docscribe/cli/run'
|
|
7
|
+
require 'docscribe/cli/sigs'
|
|
8
|
+
require 'docscribe/cli/rbs_gen'
|
|
9
|
+
require 'docscribe/cli/update_types'
|
|
10
|
+
require 'docscribe/cli/check_for_comments'
|
|
7
11
|
|
|
8
12
|
module Docscribe
|
|
9
13
|
# CLI entry point and command dispatch.
|
|
@@ -28,26 +32,30 @@ module Docscribe
|
|
|
28
32
|
|
|
29
33
|
private
|
|
30
34
|
|
|
35
|
+
# Subcommand
|
|
36
|
+
#
|
|
31
37
|
# @private
|
|
32
|
-
# @param [String] cmd
|
|
38
|
+
# @param [String?] cmd potential subcommand name
|
|
33
39
|
# @return [Boolean]
|
|
34
40
|
def subcommand?(cmd)
|
|
35
|
-
%w[init generate].include?(cmd)
|
|
41
|
+
%w[init generate sigs rbs update_types check_for_comments].include?(cmd)
|
|
36
42
|
end
|
|
37
43
|
|
|
44
|
+
# Dispatch subcommand
|
|
45
|
+
#
|
|
38
46
|
# @private
|
|
39
|
-
# @param [Array<String>] argv
|
|
40
|
-
# @return [Integer
|
|
47
|
+
# @param [Array<String>] argv raw command-line arguments
|
|
48
|
+
# @return [Integer]
|
|
41
49
|
def dispatch_subcommand(argv)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
when '
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
cmd = argv.shift
|
|
51
|
+
case cmd
|
|
52
|
+
when 'init' then Docscribe::CLI::Init.run(argv)
|
|
53
|
+
when 'generate' then Docscribe::CLI::Generate.run(argv)
|
|
54
|
+
when 'sigs' then Docscribe::CLI::Sigs.run(argv)
|
|
55
|
+
when 'rbs' then Docscribe::CLI::RbsGen.run(argv)
|
|
56
|
+
when 'update_types' then Docscribe::CLI::UpdateTypes.run(argv)
|
|
57
|
+
when 'check_for_comments' then Docscribe::CLI::CheckForComments.run(argv)
|
|
58
|
+
else 0
|
|
51
59
|
end
|
|
52
60
|
end
|
|
53
61
|
end
|
|
@@ -63,13 +63,17 @@ module Docscribe
|
|
|
63
63
|
'collection' => false,
|
|
64
64
|
'sig_dirs' => ['sig'],
|
|
65
65
|
'collection_dirs' => [], #: Array[String]
|
|
66
|
-
'collapse_generics' => false
|
|
66
|
+
'collapse_generics' => false,
|
|
67
|
+
'warn_missing_collection' => true,
|
|
68
|
+
'collapse_object_generics' => false
|
|
67
69
|
},
|
|
68
70
|
'sorbet' => {
|
|
69
71
|
'enabled' => false,
|
|
70
72
|
'rbi_dirs' => ['sorbet/rbi', 'rbi'],
|
|
71
73
|
'collapse_generics' => false
|
|
72
74
|
},
|
|
75
|
+
'keep_descriptions' => false,
|
|
76
|
+
'skip_anonymous_block_params' => false,
|
|
73
77
|
'plugins' => {
|
|
74
78
|
'require' => [] #: Array[String]
|
|
75
79
|
}
|
|
@@ -139,5 +139,22 @@ module Docscribe
|
|
|
139
139
|
def include_param_documentation?
|
|
140
140
|
fetch_bool(%w[emit include_param_documentation], true)
|
|
141
141
|
end
|
|
142
|
+
|
|
143
|
+
# Whether to preserve existing @param/@return descriptions in aggressive mode.
|
|
144
|
+
#
|
|
145
|
+
# @return [Boolean]
|
|
146
|
+
def keep_descriptions?
|
|
147
|
+
fetch_bool(%w[keep_descriptions], false)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Whether to skip @param generation for anonymous block arguments (&).
|
|
151
|
+
#
|
|
152
|
+
# Ruby 3.2+ allows `def foo(&)`. When enabled, no @param is generated
|
|
153
|
+
# for anonymous block parameters since they have no name to reference.
|
|
154
|
+
#
|
|
155
|
+
# @return [Boolean]
|
|
156
|
+
def skip_anonymous_block_params?
|
|
157
|
+
fetch_bool(%w[skip_anonymous_block_params], false)
|
|
158
|
+
end
|
|
142
159
|
end
|
|
143
160
|
end
|
|
@@ -9,7 +9,6 @@ module Docscribe
|
|
|
9
9
|
# Exclude rules win. If no include rules are configured, files are included by default.
|
|
10
10
|
#
|
|
11
11
|
# @param [String] path file path to test
|
|
12
|
-
# @raise [StandardError]
|
|
13
12
|
# @return [Boolean]
|
|
14
13
|
def process_file?(path)
|
|
15
14
|
include_patterns, exclude_patterns = load_file_patterns
|
|
@@ -23,8 +22,7 @@ module Docscribe
|
|
|
23
22
|
|
|
24
23
|
# Load normalized file include/exclude patterns from config.
|
|
25
24
|
#
|
|
26
|
-
# @
|
|
27
|
-
# @return [Array(Array<String>, Array<String>)] include_patterns, exclude_patterns
|
|
25
|
+
# @return [(Array<String>, Array<String>)]
|
|
28
26
|
def load_file_patterns
|
|
29
27
|
files = raw.dig('filter', 'files') || {}
|
|
30
28
|
[normalize_file_patterns(files['include']), normalize_file_patterns(files['exclude'])]
|
|
@@ -32,10 +30,10 @@ module Docscribe
|
|
|
32
30
|
|
|
33
31
|
# Compute the relative path for filtering.
|
|
34
32
|
#
|
|
35
|
-
# @
|
|
36
|
-
# @param [String] path
|
|
33
|
+
# @param [String] path file path to test
|
|
37
34
|
# @raise [StandardError]
|
|
38
|
-
# @return [String]
|
|
35
|
+
# @return [String] if StandardError
|
|
36
|
+
# @return [Object] if StandardError
|
|
39
37
|
def relative_path(path)
|
|
40
38
|
Pathname.new(path).expand_path.relative_path_from(Pathname.pwd).cleanpath.to_s
|
|
41
39
|
rescue StandardError
|
|
@@ -76,8 +74,7 @@ module Docscribe
|
|
|
76
74
|
# - remove empties
|
|
77
75
|
# - expand shorthand directory forms
|
|
78
76
|
#
|
|
79
|
-
# @
|
|
80
|
-
# @param [Array<String>, nil] list raw pattern list
|
|
77
|
+
# @param [Array<String>?] list raw pattern list
|
|
81
78
|
# @return [Array<String>]
|
|
82
79
|
def normalize_file_patterns(list)
|
|
83
80
|
Array(list).compact.map(&:to_s).reject(&:empty?).flat_map { |pat| expand_directory_shorthand(pat) }.uniq
|
|
@@ -89,8 +86,7 @@ module Docscribe
|
|
|
89
86
|
# - `"spec/"` => `"spec/**/*"`
|
|
90
87
|
# - `"spec"` => `"spec/**/*"` if `spec` exists as a directory
|
|
91
88
|
#
|
|
92
|
-
# @
|
|
93
|
-
# @param [String] pattern
|
|
89
|
+
# @param [String] pattern file pattern to expand
|
|
94
90
|
# @return [Array<String>]
|
|
95
91
|
def expand_directory_shorthand(pattern)
|
|
96
92
|
pat = pattern.dup
|
|
@@ -106,9 +102,8 @@ module Docscribe
|
|
|
106
102
|
|
|
107
103
|
# Check whether a file path matches any configured file pattern.
|
|
108
104
|
#
|
|
109
|
-
# @
|
|
110
|
-
# @param [
|
|
111
|
-
# @param [String] path
|
|
105
|
+
# @param [Array<String>] patterns file filter patterns
|
|
106
|
+
# @param [String] path file path to test
|
|
112
107
|
# @return [Boolean]
|
|
113
108
|
def file_matches_any?(patterns, path)
|
|
114
109
|
patterns.any? { |pat| file_match_pattern?(pat, path) }
|
|
@@ -121,13 +116,12 @@ module Docscribe
|
|
|
121
116
|
# - globs
|
|
122
117
|
# - recursive glob shorthand normalization
|
|
123
118
|
#
|
|
124
|
-
# @
|
|
125
|
-
# @param [String]
|
|
126
|
-
# @param [String] path
|
|
119
|
+
# @param [String] pattern file filter pattern
|
|
120
|
+
# @param [String] path file path to test
|
|
127
121
|
# @return [Boolean]
|
|
128
122
|
def file_match_pattern?(pattern, path)
|
|
129
123
|
if pattern.start_with?('/') && pattern.end_with?('/') && pattern.length >= 2
|
|
130
|
-
return Regexp.new(pattern[1..-2]).match?(path)
|
|
124
|
+
return Regexp.new(pattern[1..-2]).match?(path) # steep:ignore
|
|
131
125
|
end
|
|
132
126
|
|
|
133
127
|
patterns_to_try = [pattern]
|
|
@@ -140,34 +134,33 @@ module Docscribe
|
|
|
140
134
|
|
|
141
135
|
# Allowed method scopes from config/defaults.
|
|
142
136
|
#
|
|
143
|
-
# @private
|
|
144
137
|
# @return [Array<String>]
|
|
145
138
|
def filter_scopes
|
|
146
|
-
Array(raw.dig('filter', 'scopes') || DEFAULT.dig('filter', 'scopes')).map(&:to_s)
|
|
139
|
+
Array(raw.dig('filter', 'scopes') || DEFAULT.dig('filter', 'scopes')).map(&:to_s) # steep:ignore
|
|
147
140
|
end
|
|
148
141
|
|
|
149
142
|
# Allowed method visibilities from config/defaults.
|
|
150
143
|
#
|
|
151
|
-
# @private
|
|
152
144
|
# @return [Array<String>]
|
|
153
145
|
def filter_visibilities
|
|
154
|
-
Array(raw.dig('filter', 'visibilities') ||
|
|
146
|
+
Array(raw.dig('filter', 'visibilities') ||
|
|
147
|
+
DEFAULT.dig('filter', 'visibilities')).map(&:to_s) # steep:ignore
|
|
155
148
|
end
|
|
156
149
|
|
|
157
150
|
# Exclude method filter patterns.
|
|
158
151
|
#
|
|
159
|
-
# @private
|
|
160
152
|
# @return [Array<String>]
|
|
161
153
|
def filter_exclude_patterns
|
|
162
|
-
Array(raw.dig('filter', 'exclude') ||
|
|
154
|
+
Array(raw.dig('filter', 'exclude') ||
|
|
155
|
+
DEFAULT.dig('filter', 'exclude')).map(&:to_s).reject(&:empty?) # steep:ignore
|
|
163
156
|
end
|
|
164
157
|
|
|
165
158
|
# Include method filter patterns.
|
|
166
159
|
#
|
|
167
|
-
# @private
|
|
168
160
|
# @return [Array<String>]
|
|
169
161
|
def filter_include_patterns
|
|
170
|
-
Array(raw.dig('filter', 'include') ||
|
|
162
|
+
Array(raw.dig('filter', 'include') ||
|
|
163
|
+
DEFAULT.dig('filter', 'include')).map(&:to_s).reject(&:empty?) # steep:ignore
|
|
171
164
|
end
|
|
172
165
|
end
|
|
173
166
|
end
|