docscribe 1.1.0 → 1.2.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +662 -187
  3. data/exe/docscribe +2 -126
  4. data/lib/docscribe/cli/config_builder.rb +62 -0
  5. data/lib/docscribe/cli/init.rb +58 -0
  6. data/lib/docscribe/cli/options.rb +204 -0
  7. data/lib/docscribe/cli/run.rb +415 -0
  8. data/lib/docscribe/cli.rb +31 -0
  9. data/lib/docscribe/config/defaults.rb +71 -0
  10. data/lib/docscribe/config/emit.rb +142 -0
  11. data/lib/docscribe/config/filtering.rb +160 -0
  12. data/lib/docscribe/config/loader.rb +59 -0
  13. data/lib/docscribe/config/rbs.rb +51 -0
  14. data/lib/docscribe/config/sorbet.rb +87 -0
  15. data/lib/docscribe/config/sorting.rb +23 -0
  16. data/lib/docscribe/config/template.rb +184 -0
  17. data/lib/docscribe/config/utils.rb +102 -0
  18. data/lib/docscribe/config.rb +20 -230
  19. data/lib/docscribe/infer/ast_walk.rb +28 -0
  20. data/lib/docscribe/infer/constants.rb +11 -0
  21. data/lib/docscribe/infer/literals.rb +55 -0
  22. data/lib/docscribe/infer/names.rb +43 -0
  23. data/lib/docscribe/infer/params.rb +62 -0
  24. data/lib/docscribe/infer/raises.rb +68 -0
  25. data/lib/docscribe/infer/returns.rb +171 -0
  26. data/lib/docscribe/infer.rb +104 -258
  27. data/lib/docscribe/inline_rewriter/collector.rb +845 -0
  28. data/lib/docscribe/inline_rewriter/doc_block.rb +383 -0
  29. data/lib/docscribe/inline_rewriter/doc_builder.rb +607 -0
  30. data/lib/docscribe/inline_rewriter/source_helpers.rb +228 -0
  31. data/lib/docscribe/inline_rewriter/tag_sorter.rb +244 -0
  32. data/lib/docscribe/inline_rewriter.rb +599 -428
  33. data/lib/docscribe/parsing.rb +55 -44
  34. data/lib/docscribe/types/provider_chain.rb +37 -0
  35. data/lib/docscribe/types/rbs/provider.rb +213 -0
  36. data/lib/docscribe/types/rbs/type_formatter.rb +132 -0
  37. data/lib/docscribe/types/signature.rb +65 -0
  38. data/lib/docscribe/types/sorbet/base_provider.rb +217 -0
  39. data/lib/docscribe/types/sorbet/rbi_provider.rb +35 -0
  40. data/lib/docscribe/types/sorbet/source_provider.rb +25 -0
  41. data/lib/docscribe/version.rb +1 -1
  42. metadata +37 -3
@@ -0,0 +1,415 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ require 'docscribe/cli/config_builder'
6
+ require 'docscribe/inline_rewriter'
7
+
8
+ module Docscribe
9
+ module CLI
10
+ # Execute Docscribe from parsed CLI options.
11
+ #
12
+ # This module handles:
13
+ # - config loading and CLI overrides
14
+ # - stdin mode
15
+ # - file expansion / filtering
16
+ # - inspect vs write behavior
17
+ # - process exit status
18
+ module Run
19
+ class << self
20
+ # Run Docscribe for files or STDIN using the selected mode and strategy.
21
+ #
22
+ # Modes:
23
+ # - :check => inspect what the selected strategy would change
24
+ # - :write => apply the selected strategy in place
25
+ # - :stdin => rewrite STDIN and print to STDOUT
26
+ #
27
+ # Strategies:
28
+ # - :safe => merge/add/normalize non-destructively
29
+ # - :aggressive => rebuild existing doc blocks
30
+ #
31
+ # @param [Hash] options parsed CLI options
32
+ # @param [Array<String>] argv remaining path arguments
33
+ # @return [Integer] process exit code
34
+ def run(options:, argv:)
35
+ conf = Docscribe::Config.load(options[:config])
36
+ conf = Docscribe::CLI::ConfigBuilder.build(conf, options)
37
+
38
+ return run_stdin(options: options, conf: conf) if options[:mode] == :stdin
39
+
40
+ paths = expand_paths(argv)
41
+ paths = paths.select { |p| conf.process_file?(p) }
42
+
43
+ if paths.empty?
44
+ warn 'No files found. Pass files or directories (e.g. `docscribe lib`).'
45
+ return 1
46
+ end
47
+
48
+ run_files(options: options, conf: conf, paths: paths)
49
+ end
50
+
51
+ # Rewrite code from STDIN using the selected strategy and print the
52
+ # result.
53
+ #
54
+ # @param [Hash] options parsed CLI options
55
+ # @param [Docscribe::Config] conf effective config
56
+ # @raise [StandardError]
57
+ # @return [Integer] process exit code
58
+ def run_stdin(options:, conf:)
59
+ code = $stdin.read
60
+ result = Docscribe::InlineRewriter.rewrite_with_report(
61
+ code,
62
+ strategy: options[:strategy],
63
+ config: conf,
64
+ file: '(stdin)'
65
+ )
66
+ puts result[:output]
67
+ 0
68
+ rescue StandardError => e
69
+ warn "Docscribe: Error processing stdin: #{e.class}: #{e.message}"
70
+ 1
71
+ end
72
+
73
+ # Expand CLI path arguments into a sorted list of Ruby files.
74
+ #
75
+ # Directories are expanded recursively to `**/*.rb`.
76
+ # If no arguments are provided, the current directory is used.
77
+ #
78
+ # @param [Array<String>] args file and/or directory arguments
79
+ # @return [Array<String>] unique sorted Ruby file paths
80
+ def expand_paths(args)
81
+ files = []
82
+ args = ['.'] if args.empty?
83
+
84
+ args.each do |path|
85
+ if File.directory?(path)
86
+ files.concat(Dir.glob(File.join(path, '**', '*.rb')))
87
+ elsif File.file?(path)
88
+ files << path
89
+ else
90
+ warn "Skipping missing path: #{path}"
91
+ end
92
+ end
93
+
94
+ files.uniq.sort
95
+ end
96
+
97
+ # Process file paths in inspect or write mode.
98
+ #
99
+ # In inspect mode:
100
+ # - prints progress/status
101
+ # - exits non-zero if any file would change or if any errors occurred
102
+ #
103
+ # In write mode:
104
+ # - rewrites changed files in place
105
+ # - exits non-zero only if errors occurred
106
+ #
107
+ # @param [Hash] options parsed CLI options
108
+ # @param [Docscribe::Config] conf effective config
109
+ # @param [Array<String>] paths Ruby file paths to process
110
+ # @return [Integer] process exit code
111
+ def run_files(options:, conf:, paths:)
112
+ $stdout.sync = true
113
+
114
+ state = initial_run_state
115
+ pwd = Pathname.pwd
116
+
117
+ paths.each do |path|
118
+ process_one_file(path, options: options, conf: conf, pwd: pwd, state: state)
119
+ end
120
+
121
+ if options[:mode] == :check
122
+ print_check_summary(state: state, options: options)
123
+ elsif options[:mode] == :write
124
+ print_write_summary(state: state)
125
+ end
126
+
127
+ return 1 if state[:had_errors]
128
+ return 1 if options[:mode] == :check && state[:changed]
129
+
130
+ 0
131
+ end
132
+
133
+ private
134
+
135
+ # Method documentation.
136
+ #
137
+ # @private
138
+ # @return [Hash]
139
+ def initial_run_state
140
+ {
141
+ changed: false,
142
+ had_errors: false,
143
+ checked_ok: 0,
144
+ checked_fail: 0,
145
+ corrected: 0,
146
+ fail_paths: [],
147
+ fail_changes: {},
148
+ error_paths: [],
149
+ error_messages: {}
150
+ }
151
+ end
152
+
153
+ # Method documentation.
154
+ #
155
+ # @private
156
+ # @param [Object] path Param documentation.
157
+ # @param [Hash] options Param documentation.
158
+ # @param [Object] conf Param documentation.
159
+ # @param [Object] pwd Param documentation.
160
+ # @param [Object] state Param documentation.
161
+ # @return [Object]
162
+ def process_one_file(path, options:, conf:, pwd:, state:)
163
+ display_path = display_path_for(path, pwd: pwd)
164
+
165
+ src = read_source_for_path(path, display_path: display_path, options: options, state: state)
166
+ return unless src
167
+
168
+ result = rewrite_result_for_path(path, src: src, conf: conf, display_path: display_path, options: options,
169
+ state: state)
170
+ return unless result
171
+
172
+ out = result[:output]
173
+ file_changes = result[:changes] || []
174
+
175
+ if options[:mode] == :check
176
+ handle_check_result(
177
+ path,
178
+ src: src,
179
+ out: out,
180
+ file_changes: file_changes,
181
+ display_path: display_path,
182
+ options: options,
183
+ state: state
184
+ )
185
+ elsif options[:mode] == :write
186
+ handle_write_result(
187
+ path,
188
+ src: src,
189
+ out: out,
190
+ file_changes: file_changes,
191
+ display_path: display_path,
192
+ options: options,
193
+ state: state
194
+ )
195
+ end
196
+ end
197
+
198
+ # Prefer a relative display path when the file is under the current working directory.
199
+ #
200
+ # Falls back to basename for files outside the project root or when relative path
201
+ # computation fails.
202
+ #
203
+ # @private
204
+ # @param [String] path file path to display
205
+ # @param [Pathname] pwd current working directory
206
+ # @raise [StandardError]
207
+ # @return [String] path shown in CLI output
208
+ def display_path_for(path, pwd:)
209
+ abs = Pathname.new(path).expand_path
210
+
211
+ pwd_str = pwd.to_s
212
+ abs_str = abs.to_s
213
+ return abs.relative_path_from(pwd).to_s if abs_str.start_with?(pwd_str + File::SEPARATOR)
214
+
215
+ File.basename(abs_str)
216
+ rescue StandardError
217
+ File.basename(path.to_s)
218
+ end
219
+
220
+ # Method documentation.
221
+ #
222
+ # @private
223
+ # @param [Object] path Param documentation.
224
+ # @param [Object] display_path Param documentation.
225
+ # @param [Hash] options Param documentation.
226
+ # @param [Object] state Param documentation.
227
+ # @raise [StandardError]
228
+ # @return [Object]
229
+ # @return [nil] if StandardError
230
+ def read_source_for_path(path, display_path:, options:, state:)
231
+ File.read(path)
232
+ rescue StandardError => e
233
+ state[:had_errors] = true
234
+ state[:error_paths] << path
235
+ state[:error_messages][path] = "#{e.class}: #{e.message}"
236
+ options[:verbose] ? warn("ERR #{display_path}: #{state[:error_messages][path]}") : print('E')
237
+ nil
238
+ end
239
+
240
+ # Method documentation.
241
+ #
242
+ # @private
243
+ # @param [Object] path Param documentation.
244
+ # @param [Object] src Param documentation.
245
+ # @param [Object] conf Param documentation.
246
+ # @param [Object] display_path Param documentation.
247
+ # @param [Hash] options Param documentation.
248
+ # @param [Object] state Param documentation.
249
+ # @raise [StandardError]
250
+ # @return [Object]
251
+ # @return [nil] if StandardError
252
+ def rewrite_result_for_path(path, src:, conf:, display_path:, options:, state:)
253
+ Docscribe::InlineRewriter.rewrite_with_report(
254
+ src,
255
+ strategy: options[:strategy],
256
+ config: conf,
257
+ file: path
258
+ )
259
+ rescue StandardError => e
260
+ state[:had_errors] = true
261
+ state[:error_paths] << path
262
+ state[:error_messages][path] = "#{e.class}: #{e.message}"
263
+ options[:verbose] ? warn("ERR #{display_path}: #{state[:error_messages][path]}") : print('E')
264
+ nil
265
+ end
266
+
267
+ # Method documentation.
268
+ #
269
+ # @private
270
+ # @param [Object] path Param documentation.
271
+ # @param [Object] src Param documentation.
272
+ # @param [Object] out Param documentation.
273
+ # @param [Object] file_changes Param documentation.
274
+ # @param [Object] display_path Param documentation.
275
+ # @param [Hash] options Param documentation.
276
+ # @param [Object] state Param documentation.
277
+ # @return [Object]
278
+ def handle_check_result(path, src:, out:, file_changes:, display_path:, options:, state:)
279
+ if out == src
280
+ options[:verbose] ? puts("OK #{display_path}") : print('.')
281
+ state[:checked_ok] += 1
282
+ return
283
+ end
284
+
285
+ if options[:verbose]
286
+ puts("FAIL #{display_path}")
287
+ if options[:explain]
288
+ file_changes.each do |change|
289
+ puts(" - #{format_change_reason(change)}")
290
+ end
291
+ end
292
+ else
293
+ print('F')
294
+ end
295
+
296
+ state[:checked_fail] += 1
297
+ state[:changed] = true
298
+ state[:fail_paths] << path
299
+ state[:fail_changes][path] = file_changes
300
+ end
301
+
302
+ # Method documentation.
303
+ #
304
+ # @private
305
+ # @param [Object] path Param documentation.
306
+ # @param [Object] src Param documentation.
307
+ # @param [Object] out Param documentation.
308
+ # @param [Object] file_changes Param documentation.
309
+ # @param [Object] display_path Param documentation.
310
+ # @param [Hash] options Param documentation.
311
+ # @param [Object] state Param documentation.
312
+ # @raise [StandardError]
313
+ # @return [Object]
314
+ # @return [Object] if StandardError
315
+ def handle_write_result(path, src:, out:, file_changes:, display_path:, options:, state:)
316
+ if out == src
317
+ options[:verbose] ? puts("OK #{display_path}") : print('.')
318
+ return
319
+ end
320
+
321
+ File.write(path, out)
322
+
323
+ if options[:verbose]
324
+ puts("CHANGED #{display_path}")
325
+ if options[:explain]
326
+ file_changes.each do |change|
327
+ puts(" - #{format_change_reason(change)}")
328
+ end
329
+ end
330
+ else
331
+ print('C')
332
+ end
333
+
334
+ state[:corrected] += 1
335
+ rescue StandardError => e
336
+ state[:had_errors] = true
337
+ state[:error_paths] << path
338
+ state[:error_messages][path] = "#{e.class}: #{e.message}"
339
+ options[:verbose] ? warn("ERR #{display_path}: #{state[:error_messages][path]}") : print('E')
340
+ end
341
+
342
+ # Method documentation.
343
+ #
344
+ # @private
345
+ # @param [Object] state Param documentation.
346
+ # @param [Hash] options Param documentation.
347
+ # @return [Object]
348
+ def print_check_summary(state:, options:)
349
+ puts
350
+
351
+ checked_error = state[:error_paths].size
352
+
353
+ if state[:checked_fail].zero? && checked_error.zero?
354
+ puts "Docscribe: OK (#{state[:checked_ok]} files checked)"
355
+ return
356
+ end
357
+
358
+ puts "Docscribe: FAILED (#{state[:checked_fail]} files need updates, #{checked_error} errors, #{state[:checked_ok]} ok)"
359
+
360
+ state[:fail_paths].each do |p|
361
+ warn "Would update docs: #{p}"
362
+ next unless options[:explain] && !options[:verbose]
363
+
364
+ Array(state[:fail_changes][p]).each do |change|
365
+ warn " - #{format_change_reason(change)}"
366
+ end
367
+ end
368
+
369
+ state[:error_paths].each do |p|
370
+ warn "Error processing: #{p}"
371
+ warn " #{state[:error_messages][p]}" if state[:error_messages][p]
372
+ end
373
+ end
374
+
375
+ # Format a structured change record into human-readable CLI output.
376
+ #
377
+ # @private
378
+ # @param [Hash] change structured change produced by the inline rewriter
379
+ # @return [String] human-readable explanation line
380
+ def format_change_reason(change)
381
+ line = change[:line] ? " at line #{change[:line]}" : ''
382
+ method = change[:method] ? " for #{change[:method]}" : ''
383
+
384
+ case change[:type]
385
+ when :unsorted_tags
386
+ "unsorted tags#{line}"
387
+ when :missing_param, :missing_return, :missing_raise, :missing_visibility, :missing_module_function_note,
388
+ :insert_full_doc_block
389
+ "#{change[:message]}#{method}#{line}"
390
+ else
391
+ "#{change[:message] || change[:type].to_s.tr('_', ' ')}#{method}#{line}"
392
+ end
393
+ end
394
+
395
+ # Method documentation.
396
+ #
397
+ # @private
398
+ # @param [Object] state Param documentation.
399
+ # @return [Object]
400
+ def print_write_summary(state:)
401
+ puts
402
+ puts "Docscribe: updated #{state[:corrected]} file(s)" if state[:corrected].positive?
403
+
404
+ return unless state[:had_errors]
405
+
406
+ warn "Docscribe: #{state[:error_paths].size} file(s) had errors"
407
+ state[:error_paths].each do |p|
408
+ warn "Error processing: #{p}"
409
+ warn " #{state[:error_messages][p]}" if state[:error_messages][p]
410
+ end
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'docscribe/cli/init'
4
+ require 'docscribe/cli/options'
5
+ require 'docscribe/cli/run'
6
+
7
+ module Docscribe
8
+ module CLI
9
+ class << self
10
+ # Main CLI entry point.
11
+ #
12
+ # Dispatches:
13
+ # - `docscribe init ...` to the config-template generator
14
+ # - all other commands to the main option parser and runner
15
+ #
16
+ # @param [Array<String>] argv raw command-line arguments
17
+ # @return [Integer] process exit code
18
+ def run(argv)
19
+ argv = argv.dup
20
+
21
+ if argv.first == 'init'
22
+ argv.shift
23
+ return Docscribe::CLI::Init.run(argv)
24
+ end
25
+
26
+ options = Docscribe::CLI::Options.parse!(argv)
27
+ Docscribe::CLI::Run.run(options: options, argv: argv)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docscribe
4
+ class Config
5
+ # Default configuration values used when no `docscribe.yml` is present or
6
+ # when specific keys are missing from user config.
7
+ #
8
+ # These defaults define:
9
+ # - which documentation tags are emitted
10
+ # - default generated text
11
+ # - type inference behavior
12
+ # - method / file filtering
13
+ # - optional RBS integration
14
+ # - optional Sorbet integration
15
+ DEFAULT = {
16
+ 'emit' => {
17
+ 'header' => true,
18
+ 'param_tags' => true,
19
+ 'return_tag' => true,
20
+ 'visibility_tags' => true,
21
+ 'raise_tags' => true,
22
+ 'rescue_conditional_returns' => true,
23
+ 'attributes' => false
24
+ },
25
+ 'doc' => {
26
+ 'default_message' => 'Method documentation.',
27
+ 'param_tag_style' => 'type_name',
28
+ 'param_documentation' => 'Param documentation.',
29
+ 'sort_tags' => true,
30
+ 'tag_order' => %w[todo note api private protected param option yieldparam raise return]
31
+ },
32
+ 'methods' => {
33
+ 'instance' => {
34
+ 'public' => {},
35
+ 'protected' => {},
36
+ 'private' => {}
37
+ },
38
+ 'class' => {
39
+ 'public' => {},
40
+ 'protected' => {},
41
+ 'private' => {}
42
+ }
43
+ },
44
+ 'inference' => {
45
+ 'fallback_type' => 'Object',
46
+ 'nil_as_optional' => true,
47
+ 'treat_options_keyword_as_hash' => true
48
+ },
49
+ 'filter' => {
50
+ 'visibilities' => %w[public protected private],
51
+ 'scopes' => %w[instance class],
52
+ 'include' => [],
53
+ 'exclude' => [],
54
+ 'files' => {
55
+ 'include' => [],
56
+ 'exclude' => []
57
+ }
58
+ },
59
+ 'rbs' => {
60
+ 'enabled' => false,
61
+ 'sig_dirs' => ['sig'],
62
+ 'collapse_generics' => false
63
+ },
64
+ 'sorbet' => {
65
+ 'enabled' => false,
66
+ 'rbi_dirs' => ['sorbet/rbi', 'rbi'],
67
+ 'collapse_generics' => false
68
+ }
69
+ }.freeze
70
+ end
71
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docscribe
4
+ class Config
5
+ # Whether to emit method header lines such as:
6
+ # # +MyClass#foo+ -> Integer
7
+ #
8
+ # @return [Boolean]
9
+ def emit_header?
10
+ fetch_bool(%w[emit header], true)
11
+ end
12
+
13
+ # Whether to emit `@param` tags.
14
+ #
15
+ # @return [Boolean]
16
+ def emit_param_tags?
17
+ fetch_bool(%w[emit param_tags], true)
18
+ end
19
+
20
+ # Whether to emit visibility tags such as `@private` and `@protected`.
21
+ #
22
+ # @return [Boolean]
23
+ def emit_visibility_tags?
24
+ fetch_bool(%w[emit visibility_tags], true)
25
+ end
26
+
27
+ # Whether to emit inferred `@raise` tags.
28
+ #
29
+ # @return [Boolean]
30
+ def emit_raise_tags?
31
+ fetch_bool(%w[emit raise_tags], true)
32
+ end
33
+
34
+ # Whether to emit conditional rescue-return tags like:
35
+ # # @return [String] if FooError
36
+ #
37
+ # @return [Boolean]
38
+ def emit_rescue_conditional_returns?
39
+ fetch_bool(%w[emit rescue_conditional_returns], true)
40
+ end
41
+
42
+ # Whether to emit YARD `@!attribute` docs for `attr_*` macros.
43
+ #
44
+ # @return [Boolean]
45
+ def emit_attributes?
46
+ fetch_bool(%w[emit attributes], false)
47
+ end
48
+
49
+ # Whether to emit the `@return` tag for a method, taking per-scope and
50
+ # per-visibility overrides into account.
51
+ #
52
+ # @param [Symbol] scope :instance or :class
53
+ # @param [Symbol] visibility :public, :protected, or :private
54
+ # @return [Boolean]
55
+ def emit_return_tag?(scope, visibility)
56
+ method_override_bool(
57
+ scope,
58
+ visibility,
59
+ 'return_tag',
60
+ default: fetch_bool(%w[emit return_tag], true)
61
+ )
62
+ end
63
+
64
+ # Default text inserted into generated doc blocks, taking per-scope and
65
+ # per-visibility overrides into account.
66
+ #
67
+ # @param [Symbol] scope :instance or :class
68
+ # @param [Symbol] visibility :public, :protected, or :private
69
+ # @return [String]
70
+ def default_message(scope, visibility)
71
+ method_override_str(
72
+ scope,
73
+ visibility,
74
+ 'default_message',
75
+ default: raw.dig('doc', 'default_message') ||
76
+ DEFAULT.dig('doc', 'default_message') ||
77
+ 'Method documentation.'
78
+ )
79
+ end
80
+
81
+ # Fallback type used when inference cannot determine a more specific type.
82
+ #
83
+ # @return [String]
84
+ def fallback_type
85
+ raw.dig('inference', 'fallback_type') ||
86
+ DEFAULT.dig('inference', 'fallback_type') ||
87
+ 'Object'
88
+ end
89
+
90
+ # Whether unions involving nil should be rendered as optional types.
91
+ #
92
+ # For example, `String, nil` may become `String?` depending on formatter
93
+ # behavior.
94
+ #
95
+ # @return [Boolean]
96
+ def nil_as_optional?
97
+ fetch_bool(%w[inference nil_as_optional], true)
98
+ end
99
+
100
+ # Whether keyword arguments named `options` / `options:` should be treated
101
+ # specially as Hash values during inference.
102
+ #
103
+ # @return [Boolean]
104
+ def treat_options_keyword_as_hash?
105
+ fetch_bool(%w[inference treat_options_keyword_as_hash], true)
106
+ end
107
+
108
+ # Param tag syntax style.
109
+ #
110
+ # Supported values:
111
+ # - `"type_name"` => `@param [String] name`
112
+ # - `"name_type"` => `@param name [String]`
113
+ #
114
+ # @return [String]
115
+ def param_tag_style
116
+ raw.dig('doc', 'param_tag_style') || DEFAULT.dig('doc', 'param_tag_style')
117
+ end
118
+
119
+ # Default generated parameter description text.
120
+ #
121
+ # @return [String]
122
+ def param_documentation
123
+ raw.dig('doc', 'param_documentation') || DEFAULT.dig('doc', 'param_documentation')
124
+ end
125
+
126
+ # Whether to include the default placeholder line:
127
+ # # Method documentation.
128
+ #
129
+ # @return [Boolean]
130
+ def include_default_message?
131
+ fetch_bool(%w[emit include_default_message], true)
132
+ end
133
+
134
+ # Whether to append placeholder text to generated @param tags:
135
+ # # @param [String] name Param documentation.
136
+ #
137
+ # @return [Boolean]
138
+ def include_param_documentation?
139
+ fetch_bool(%w[emit include_param_documentation], true)
140
+ end
141
+ end
142
+ end