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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +465 -130
  3. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  4. data/lib/docscribe/cli/config_builder.rb +107 -53
  5. data/lib/docscribe/cli/formatters/json.rb +294 -0
  6. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  7. data/lib/docscribe/cli/formatters/text.rb +208 -0
  8. data/lib/docscribe/cli/formatters.rb +26 -0
  9. data/lib/docscribe/cli/generate.rb +45 -45
  10. data/lib/docscribe/cli/init.rb +14 -6
  11. data/lib/docscribe/cli/options.rb +190 -88
  12. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  13. data/lib/docscribe/cli/run.rb +210 -152
  14. data/lib/docscribe/cli/sigs.rb +366 -0
  15. data/lib/docscribe/cli/update_types.rb +103 -0
  16. data/lib/docscribe/cli.rb +21 -13
  17. data/lib/docscribe/config/defaults.rb +5 -1
  18. data/lib/docscribe/config/emit.rb +17 -0
  19. data/lib/docscribe/config/filtering.rb +18 -25
  20. data/lib/docscribe/config/loader.rb +15 -11
  21. data/lib/docscribe/config/plugin.rb +1 -1
  22. data/lib/docscribe/config/rbs.rb +41 -9
  23. data/lib/docscribe/config/sorbet.rb +9 -12
  24. data/lib/docscribe/config/sorting.rb +1 -1
  25. data/lib/docscribe/config/template.rb +9 -1
  26. data/lib/docscribe/config/utils.rb +11 -9
  27. data/lib/docscribe/config.rb +2 -4
  28. data/lib/docscribe/infer/ast_walk.rb +1 -1
  29. data/lib/docscribe/infer/literals.rb +6 -11
  30. data/lib/docscribe/infer/names.rb +2 -3
  31. data/lib/docscribe/infer/params.rb +15 -17
  32. data/lib/docscribe/infer/raises.rb +3 -5
  33. data/lib/docscribe/infer/returns.rb +542 -140
  34. data/lib/docscribe/infer.rb +22 -23
  35. data/lib/docscribe/inline_rewriter/collector.rb +159 -164
  36. data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
  37. data/lib/docscribe/inline_rewriter/doc_builder.rb +1026 -723
  38. data/lib/docscribe/inline_rewriter/source_helpers.rb +49 -49
  39. data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
  40. data/lib/docscribe/inline_rewriter.rb +495 -492
  41. data/lib/docscribe/parsing.rb +29 -10
  42. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
  43. data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
  44. data/lib/docscribe/plugin/context.rb +28 -18
  45. data/lib/docscribe/plugin/registry.rb +26 -27
  46. data/lib/docscribe/plugin/tag.rb +9 -14
  47. data/lib/docscribe/plugin.rb +17 -16
  48. data/lib/docscribe/types/provider_chain.rb +4 -2
  49. data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
  50. data/lib/docscribe/types/rbs/provider.rb +60 -44
  51. data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
  52. data/lib/docscribe/types/signature.rb +22 -42
  53. data/lib/docscribe/types/sorbet/base_provider.rb +24 -19
  54. data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
  55. data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
  56. data/lib/docscribe/types/yard/formatter.rb +100 -0
  57. data/lib/docscribe/types/yard/parser.rb +240 -0
  58. data/lib/docscribe/types/yard/types.rb +52 -0
  59. data/lib/docscribe/version.rb +1 -1
  60. 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, nil]
47
+ # @param [Array<String>] argv raw command-line arguments
48
+ # @return [Integer]
41
49
  def dispatch_subcommand(argv)
42
- case argv.first
43
- when 'init'
44
- argv.shift
45
- Docscribe::CLI::Init.run(argv)
46
- when 'generate'
47
- argv.shift
48
- Docscribe::CLI::Generate.run(argv)
49
- else
50
- 0
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
- # @private
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
- # @private
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
- # @private
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
- # @private
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
- # @private
110
- # @param [Array<String>] patterns
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
- # @private
125
- # @param [String] pattern
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') || DEFAULT.dig('filter', 'visibilities')).map(&:to_s)
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') || DEFAULT.dig('filter', 'exclude')).map(&:to_s).reject(&:empty?)
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') || DEFAULT.dig('filter', 'include')).map(&:to_s).reject(&:empty?)
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