docscribe 1.5.1 → 1.6.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.
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'docscribe/config'
5
+ require 'docscribe/cli/config_builder'
6
+ require 'docscribe/cli/options'
7
+
8
+ module Docscribe
9
+ module CLI
10
+ # Generate documentation coverage report for Ruby files.
11
+ module Coverage
12
+ BANNER = <<~TEXT
13
+ Usage: docscribe coverage [options] [paths]
14
+
15
+ Generate documentation coverage report.
16
+
17
+ Options:
18
+ TEXT
19
+
20
+ # @!attribute [rw] total_methods
21
+ # @return [Integer]
22
+ # @param [Integer] value
23
+ #
24
+ # @!attribute [rw] documented_methods
25
+ # @return [Integer]
26
+ # @param [Integer] value
27
+ #
28
+ # @!attribute [rw] total_params
29
+ # @return [Integer]
30
+ # @param [Integer] value
31
+ #
32
+ # @!attribute [rw] documented_params
33
+ # @return [Integer]
34
+ # @param [Integer] value
35
+ #
36
+ # @!attribute [rw] total_returns
37
+ # @return [Integer]
38
+ # @param [Integer] value
39
+ #
40
+ # @!attribute [rw] documented_returns
41
+ # @return [Integer]
42
+ # @param [Integer] value
43
+ CoverageStats = Struct.new(
44
+ :total_methods, :documented_methods,
45
+ :total_params, :documented_params,
46
+ :total_returns, :documented_returns,
47
+ keyword_init: true
48
+ )
49
+
50
+ # Struct tracking documentation coverage metrics per file.
51
+ class CoverageStats
52
+ # @return [Integer, Float]
53
+ def method_coverage
54
+ total_methods.zero? ? 100.0 : (documented_methods.to_f / total_methods * 100).round(1)
55
+ end
56
+
57
+ # @return [Integer, Float]
58
+ def param_coverage
59
+ total_params.zero? ? 100.0 : (documented_params.to_f / total_params * 100).round(1)
60
+ end
61
+
62
+ # @return [Integer, Float]
63
+ def return_coverage
64
+ total_returns.zero? ? 100.0 : (documented_returns.to_f / total_returns * 100).round(1)
65
+ end
66
+ end
67
+
68
+ class << self
69
+ # @param [Array<String>] argv
70
+ # @return [Integer]
71
+ def run(argv)
72
+ opts = parse_options(argv)
73
+ return 0 if opts[:help]
74
+
75
+ conf = Docscribe::Config.load(opts[:config])
76
+ paths = expand_paths(argv, conf)
77
+
78
+ stats = analyze_coverage(paths, conf)
79
+
80
+ print_report(stats, opts)
81
+ 0
82
+ end
83
+
84
+ private
85
+
86
+ # @private
87
+ # @param [Array<String>] argv
88
+ # @return [Hash<Symbol, Object>]
89
+ def parse_options(argv)
90
+ opts = { config: nil, format: 'text' }
91
+ build_parser(opts).parse!(argv)
92
+ opts
93
+ end
94
+
95
+ # @private
96
+ # @param [Hash<Symbol, Object>] opts
97
+ # @return [OptionParser]
98
+ def build_parser(opts)
99
+ OptionParser.new do |o|
100
+ o.banner = BANNER
101
+ o.on('--config PATH', 'Path to config file') { |v| opts[:config] = v }
102
+ o.on('--format FORMAT', 'Output format (text, json)') { |v| opts[:format] = v }
103
+ o.on('-h', '--help', 'Show help') do
104
+ opts[:help] = true
105
+ puts o
106
+ end
107
+ end
108
+ end
109
+
110
+ # @private
111
+ # @param [Array<String>] argv
112
+ # @param [Docscribe::Config] conf
113
+ # @return [Array<String>]
114
+ def expand_paths(argv, conf)
115
+ require 'pathname'
116
+ args = argv.empty? ? ['.'] : argv
117
+ files = expand_files(args)
118
+ files.uniq.sort.select { |p| conf.process_file?(p) }
119
+ end
120
+
121
+ # @private
122
+ # @param [Array<String>] args
123
+ # @return [Array<String>]
124
+ def expand_files(args)
125
+ files = [] #: Array[String]
126
+ args.each do |path|
127
+ if File.directory?(path)
128
+ files.concat(Dir.glob(File.join(path, '**', '*.rb')))
129
+ elsif File.file?(path)
130
+ files << path
131
+ end
132
+ end
133
+ files
134
+ end
135
+
136
+ # @private
137
+ # @param [Array<String>] paths
138
+ # @param [Docscribe::Config] _conf
139
+ # @return [Docscribe::CLI::Coverage::CoverageStats]
140
+ def analyze_coverage(paths, _conf)
141
+ require 'docscribe/parsing'
142
+ require 'parser/current'
143
+
144
+ stats = init_stats
145
+ paths.each { |path| analyze_file(path, stats) }
146
+ stats
147
+ end
148
+
149
+ # @private
150
+ # @return [Docscribe::CLI::Coverage::CoverageStats]
151
+ def init_stats
152
+ CoverageStats.new(
153
+ total_methods: 0, documented_methods: 0,
154
+ total_params: 0, documented_params: 0,
155
+ total_returns: 0, documented_returns: 0
156
+ )
157
+ end
158
+
159
+ # @private
160
+ # @param [String] path
161
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
162
+ # @raise [StandardError]
163
+ # @return [void]
164
+ # @return [nil] if StandardError
165
+ def analyze_file(path, stats)
166
+ src = File.read(path)
167
+ buffer = Parser::Source::Buffer.new(path, source: src)
168
+ ast = Docscribe::Parsing.parse_buffer(buffer)
169
+ analyze_node(ast, stats, src) if ast
170
+ rescue StandardError => e
171
+ warn "Skipping #{path}: #{e.message}" if ENV.fetch('DOCSCRIBE_DEBUG', false)
172
+ end
173
+
174
+ # @private
175
+ # @param [Object] node
176
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
177
+ # @param [String] src
178
+ # @return [void]
179
+ def analyze_node(node, stats, src)
180
+ return unless node.is_a?(Parser::AST::Node)
181
+
182
+ analyze_method_node(node, stats, src) if %i[def defs].include?(node.type)
183
+
184
+ node.children.each { |child| analyze_node(child, stats, src) if child.is_a?(Parser::AST::Node) }
185
+ end
186
+
187
+ # @private
188
+ # @param [Object] node
189
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
190
+ # @param [String] src
191
+ # @return [void]
192
+ def analyze_method_node(node, stats, src)
193
+ stats.total_methods += 1
194
+ doc_comment = extract_doc_comment(src, node.loc.expression.line)
195
+
196
+ if doc_comment
197
+ analyze_documented_method(node, stats, doc_comment)
198
+ else
199
+ stats.total_returns += 1
200
+ count_method_params(node, stats)
201
+ end
202
+ end
203
+
204
+ # @private
205
+ # @param [Object] node
206
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
207
+ # @param [String] doc_comment
208
+ # @return [void]
209
+ def analyze_documented_method(node, stats, doc_comment)
210
+ stats.documented_methods += 1
211
+ stats.documented_returns += 1 if doc_comment.match?(/@return\b/)
212
+ stats.total_returns += 1
213
+
214
+ param_matches = doc_comment.scan(/@param\b/)
215
+ param_count = count_params(node)
216
+ stats.total_params += param_count
217
+ stats.documented_params += [param_matches.size, param_count].min
218
+ end
219
+
220
+ # @private
221
+ # @param [Object] node
222
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
223
+ # @return [void]
224
+ def count_method_params(node, stats)
225
+ stats.total_params += count_params(node)
226
+ end
227
+
228
+ # @private
229
+ # @param [Object] node
230
+ # @return [Integer]
231
+ def count_params(node)
232
+ args_node = node.children[2] || node.children[1]
233
+ return 0 unless args_node.is_a?(Parser::AST::Node) && args_node.type == :args
234
+
235
+ args_node.children.count { |a| %i[arg optarg kwarg kwoptarg restarg].include?(a.type) }
236
+ end
237
+
238
+ # @private
239
+ # @param [String] src
240
+ # @param [Integer] method_line
241
+ # @return [String?]
242
+ def extract_doc_comment(src, method_line)
243
+ lines = src.lines
244
+ comment_lines = collect_comment_lines(lines, method_line)
245
+ comment_lines.empty? ? nil : comment_lines.join
246
+ end
247
+
248
+ # @private
249
+ # @param [Array<String>] lines
250
+ # @param [Integer] method_line
251
+ # @return [Array<String>]
252
+ def collect_comment_lines(lines, method_line)
253
+ comment_lines = [] #: Array[String]
254
+ idx = method_line - 2
255
+ while idx >= 0
256
+ line = lines[idx]
257
+ break unless line =~ /^\s*#/
258
+
259
+ comment_lines.unshift(line)
260
+ idx -= 1
261
+ end
262
+ comment_lines
263
+ end
264
+
265
+ # @private
266
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
267
+ # @param [Hash<Symbol, Object>] opts
268
+ # @return [void]
269
+ def print_report(stats, opts)
270
+ case opts[:format]
271
+ when 'json'
272
+ print_json_report(stats)
273
+ else
274
+ print_text_report(stats)
275
+ end
276
+ end
277
+
278
+ # @private
279
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
280
+ # @return [void]
281
+ def print_json_report(stats)
282
+ require 'json'
283
+ puts JSON.pretty_generate(
284
+ methods: report_entry(stats.total_methods, stats.documented_methods, stats.method_coverage),
285
+ params: report_entry(stats.total_params, stats.documented_params, stats.param_coverage),
286
+ returns: report_entry(stats.total_returns, stats.documented_returns, stats.return_coverage)
287
+ )
288
+ end
289
+
290
+ # @private
291
+ # @param [Integer] total
292
+ # @param [Integer] documented
293
+ # @param [Integer, Float] coverage
294
+ # @return [Hash<Symbol, Integer, Float>]
295
+ def report_entry(total, documented, coverage)
296
+ { total: total, documented: documented, coverage: coverage }
297
+ end
298
+
299
+ # @private
300
+ # @param [Docscribe::CLI::Coverage::CoverageStats] stats
301
+ # @return [void]
302
+ def print_text_report(stats)
303
+ puts 'Documentation Coverage Report'
304
+ puts '============================='
305
+ puts
306
+ puts "Methods: #{stats.documented_methods}/#{stats.total_methods} (#{stats.method_coverage}%)"
307
+ puts "Params: #{stats.documented_params}/#{stats.total_params} (#{stats.param_coverage}%)"
308
+ puts "Returns: #{stats.documented_returns}/#{stats.total_returns} (#{stats.return_coverage}%)"
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end
@@ -58,7 +58,7 @@ module Docscribe
58
58
  # @private
59
59
  # @return [Hash<Symbol, String, Boolean>]
60
60
  def default_init_options
61
- { config: 'docscribe.yml', force: false, stdout: false, help: false }
61
+ { config: 'docscribe.yml', force: false, stdout: false, help: false, pre_commit: false }
62
62
  end
63
63
 
64
64
  # Build and return an OptionParser for the init command.
@@ -69,9 +69,7 @@ module Docscribe
69
69
  def build_init_parser(opts)
70
70
  OptionParser.new do |o|
71
71
  o.banner = BANNER
72
- o.on('--config PATH', 'Where to write the config (default: docscribe.yml)') { |v| opts[:config] = v }
73
- o.on('-f', '--force', 'Overwrite if the file already exists') { opts[:force] = true }
74
- o.on('--stdout', 'Print config template to STDOUT instead of writing a file') { opts[:stdout] = true }
72
+ add_init_options(o, opts)
75
73
  o.on('-h', '--help', 'Show this help') do
76
74
  opts[:help] = true
77
75
  puts o
@@ -79,6 +77,19 @@ module Docscribe
79
77
  end
80
78
  end
81
79
 
80
+ # Add init-specific CLI options to the parser.
81
+ #
82
+ # @private
83
+ # @param [OptionParser] parser
84
+ # @param [Hash<Symbol, Object>] opts options hash
85
+ # @return [void]
86
+ def add_init_options(parser, opts)
87
+ parser.on('--config PATH', 'Where to write the config (default: docscribe.yml)') { |v| opts[:config] = v }
88
+ parser.on('-f', '--force', 'Overwrite if the file already exists') { opts[:force] = true }
89
+ parser.on('--stdout', 'Print config template to STDOUT instead of writing a file') { opts[:stdout] = true }
90
+ parser.on('--pre-commit', 'Install pre-commit hook for docscribe check') { opts[:pre_commit] = true }
91
+ end
92
+
82
93
  # Write the config template to a file.
83
94
  #
84
95
  # @private
@@ -86,6 +97,8 @@ module Docscribe
86
97
  # @param [String] yaml config template content
87
98
  # @return [Integer] exit code
88
99
  def write_init_config(opts, yaml)
100
+ return install_pre_commit_hook(opts) if opts[:pre_commit]
101
+
89
102
  path = opts[:config]
90
103
  if File.exist?(path) && !opts[:force]
91
104
  warn "Config already exists: #{path} (use --force to overwrite)"
@@ -96,6 +109,70 @@ module Docscribe
96
109
  puts "Created: #{path}"
97
110
  0
98
111
  end
112
+
113
+ # @private
114
+ # @param [Hash<Symbol, Object>] opts
115
+ # @return [Integer]
116
+ def install_pre_commit_hook(opts)
117
+ hook_dir = File.join('.git', 'hooks')
118
+ hook_path = File.join(hook_dir, 'pre-commit')
119
+
120
+ return 1 unless hook_preconditions_met?(hook_dir, hook_path, opts)
121
+
122
+ File.write(hook_path, pre_commit_hook_script)
123
+ File.chmod(0o755, hook_path)
124
+ puts "Installed pre-commit hook: #{hook_path}"
125
+ 0
126
+ end
127
+
128
+ # Check pre-commit hook installation preconditions.
129
+ #
130
+ # @private
131
+ # @param [String] hook_dir
132
+ # @param [String] hook_path
133
+ # @param [Hash<Symbol, Object>] opts
134
+ # @return [Boolean]
135
+ def hook_preconditions_met?(hook_dir, hook_path, opts)
136
+ unless Dir.exist?(hook_dir)
137
+ warn 'No .git/hooks directory found. Are you in a git repository?'
138
+ return false
139
+ end
140
+
141
+ if File.exist?(hook_path) && !opts[:force]
142
+ warn "Pre-commit hook already exists: #{hook_path} (use --force to overwrite)"
143
+ return false
144
+ end
145
+
146
+ true
147
+ end
148
+
149
+ # Generate the pre-commit hook shell script content.
150
+ #
151
+ # @private
152
+ # @return [String]
153
+ def pre_commit_hook_script
154
+ <<~HOOK
155
+ #!/bin/sh
156
+ # Docscribe pre-commit hook
157
+ # Runs docscribe check on staged Ruby files
158
+
159
+ STAGED_RUBY_FILES=$(git diff --cached --name-only --diff-filter=ACM -- '*.rb')
160
+ if [ -z "$STAGED_RUBY_FILES" ]; then
161
+ exit 0
162
+ fi
163
+
164
+ echo "Checking documentation with docscribe..."
165
+ bundle exec docscribe check $STAGED_RUBY_FILES
166
+ RESULT=$?
167
+
168
+ if [ $RESULT -ne 0 ]; then
169
+ echo "Documentation check failed. Please add documentation before committing."
170
+ exit 1
171
+ fi
172
+
173
+ exit 0
174
+ HOOK
175
+ end
99
176
  end
100
177
  end
101
178
  end
@@ -27,6 +27,7 @@ module Docscribe
27
27
  keep_descriptions: false,
28
28
  no_boilerplate: false,
29
29
  progress: false,
30
+ parallel: false,
30
31
  server: false
31
32
  }.freeze
32
33
 
@@ -76,6 +77,8 @@ module Docscribe
76
77
  -q, --quiet Only show status, no details
77
78
  --format FORMAT Output format: text (default), json, or sarif
78
79
 
80
+ Performance:
81
+ --parallel Process files in parallel (default: sequential)
79
82
 
80
83
  Other:
81
84
  -v, --version Print version and exit
@@ -335,6 +338,7 @@ module Docscribe
335
338
  def define_output_options(opts, options)
336
339
  define_verbose_option(opts, options)
337
340
  define_progress_option(opts, options)
341
+ define_parallel_option(opts, options)
338
342
  define_explain_option(opts, options)
339
343
  define_quiet_option(opts, options)
340
344
  define_format_option(opts, options)
@@ -367,6 +371,18 @@ module Docscribe
367
371
  end
368
372
  end
369
373
 
374
+ # Define parallel option
375
+ #
376
+ # @note module_function: defines #define_parallel_option (visibility: private)
377
+ # @param [Hash<Symbol, Object>] opts the option parser to configure
378
+ # @param [Hash<Symbol, Object>] options mutable parsed options hash
379
+ # @return [void]
380
+ def define_parallel_option(opts, options)
381
+ opts.on('--parallel', 'Process files in parallel (default: sequential)') do
382
+ options[:parallel] = true
383
+ end
384
+ end
385
+
370
386
  # Define explain option
371
387
  #
372
388
  # @note module_function: defines #define_explain_option (visibility: private)
@@ -85,7 +85,8 @@ module Docscribe
85
85
  # @!attribute [rw] inside_sclass
86
86
  # @return [Boolean]
87
87
  # @param [Boolean] value
88
- WalkContext = Struct.new(:containers, :method_defs, :path, :comment_map, :src_lines, :inside_sclass, keyword_init: true)
88
+ WalkContext = Struct.new(:containers, :method_defs, :path, :comment_map, :src_lines, :inside_sclass,
89
+ keyword_init: true)
89
90
 
90
91
  class << self
91
92
  # @param [Array<String>] argv
@@ -167,7 +168,7 @@ module Docscribe
167
168
  # @return [Boolean] if StandardError
168
169
  def generate_for_file(path, options)
169
170
  process_source?(File.read(path), path, options)
170
- rescue Parser::SyntaxError => e # steep:ignore
171
+ rescue Parser::SyntaxError => e
171
172
  warn "Syntax error in #{path}: #{e.message}"
172
173
  false
173
174
  rescue StandardError => e
@@ -35,6 +35,8 @@ module Docscribe
35
35
  processed: 0
36
36
  }.freeze
37
37
 
38
+ PARALLEL_DEFAULT_THREADS = 4
39
+
38
40
  CLI_OVERRIDE_KEYS = %i[
39
41
  keep_descriptions no_boilerplate
40
42
  include exclude include_file exclude_file
@@ -382,6 +384,8 @@ module Docscribe
382
384
  # @param [Array<String>] paths Ruby file paths to process
383
385
  # @return [Integer] process exit code
384
386
  def run_files(options:, conf:, paths:)
387
+ return run_files_parallel(options: options, conf: conf, paths: paths) if options[:parallel] && paths.size > 1
388
+
385
389
  $stdout.sync = true
386
390
 
387
391
  state = initial_run_state
@@ -397,6 +401,136 @@ module Docscribe
397
401
  run_exit_code(options, state)
398
402
  end
399
403
 
404
+ # Process files in parallel using a Thread pool.
405
+ #
406
+ # @param [Docscribe::CLI::Formatters::opts] options parsed CLI options
407
+ # @param [Docscribe::Config] conf effective config
408
+ # @param [Array<String>] paths Ruby file paths to process
409
+ # @return [Integer] process exit code
410
+ def run_files_parallel(options:, conf:, paths:)
411
+ $stdout.sync = true
412
+ state = initial_run_state
413
+ state[:total] = paths.size
414
+ run_parallel_workers(options, conf, paths, state)
415
+ finalize_run(options, state)
416
+ run_exit_code(options, state)
417
+ end
418
+
419
+ # @param [Object] options
420
+ # @param [Object] conf
421
+ # @param [Array<String>] paths
422
+ # @param [Docscribe::CLI::Formatters::state] state
423
+ # @return [void]
424
+ def run_parallel_workers(options, conf, paths, state)
425
+ pool = {
426
+ paths: paths, state: state, options: options, conf: conf,
427
+ pwd: Pathname.pwd, mutex: Mutex.new, error_mutex: Mutex.new
428
+ }
429
+ thread_count = [ENV.fetch('DOCSCRIBE_THREADS', PARALLEL_DEFAULT_THREADS).to_i, 1].max
430
+ thread_count.times.map { parallel_worker(pool) }.each(&:join)
431
+ end
432
+
433
+ # @param [Hash<Symbol, Object>] pool
434
+ # @return [Thread]
435
+ def parallel_worker(pool)
436
+ Thread.new do
437
+ loop do
438
+ path = fetch_work(pool[:paths], pool[:mutex]) or break
439
+ run_and_merge(path, pool)
440
+ end
441
+ end
442
+ end
443
+
444
+ # @param [Array<String>] paths
445
+ # @param [Thread::Mutex] mutex
446
+ # @return [String?]
447
+ def fetch_work(paths, mutex)
448
+ path = nil
449
+ mutex.synchronize { path = paths.shift }
450
+ path
451
+ end
452
+
453
+ # @param [String] path
454
+ # @param [Hash<Symbol, Object>] pool
455
+ # @raise [StandardError]
456
+ # @return [void]
457
+ # @return [Object] if StandardError
458
+ def run_and_merge(path, pool)
459
+ local_state = initial_run_state
460
+ process_one_file(path, options: pool[:options], conf: pool[:conf], pwd: pool[:pwd], state: local_state)
461
+ merge_state(pool[:state], local_state, pool[:error_mutex])
462
+ rescue StandardError => e
463
+ handle_worker_error(e, path, pool[:state], pool[:error_mutex])
464
+ end
465
+
466
+ # @param [StandardError] exception
467
+ # @param [String] path
468
+ # @param [Docscribe::CLI::Formatters::state] state
469
+ # @param [Thread::Mutex] error_mutex
470
+ # @return [void]
471
+ def handle_worker_error(exception, path, state, error_mutex)
472
+ error_mutex.synchronize do
473
+ state[:had_errors] = true
474
+ state[:error_paths] << path
475
+ state[:error_messages][path] = "#{exception.class}: #{exception.message}"
476
+ end
477
+ $stderr.print('E')
478
+ end
479
+
480
+ # Merge a thread-local state into the shared state under mutex protection.
481
+ #
482
+ # @param [Docscribe::CLI::Formatters::state] target shared state to merge into
483
+ # @param [Docscribe::CLI::Formatters::state] source thread-local state to merge from
484
+ # @param [Thread::Mutex] mutex mutex protecting the target state
485
+ # @return [void]
486
+ def merge_state(target, source, mutex)
487
+ mutex.synchronize do
488
+ merge_state_flags(target, source)
489
+ merge_state_counts(target, source)
490
+ merge_state_arrays(target, source)
491
+ merge_state_hashes(target, source)
492
+ end
493
+ end
494
+
495
+ # @param [Docscribe::CLI::Formatters::state] target
496
+ # @param [Docscribe::CLI::Formatters::state] source
497
+ # @return [void]
498
+ def merge_state_flags(target, source)
499
+ target[:changed] ||= source[:changed]
500
+ target[:had_errors] ||= source[:had_errors]
501
+ end
502
+
503
+ # @param [Docscribe::CLI::Formatters::state] target
504
+ # @param [Docscribe::CLI::Formatters::state] source
505
+ # @return [void]
506
+ def merge_state_counts(target, source)
507
+ target[:checked_ok] += source[:checked_ok]
508
+ target[:checked_fail] += source[:checked_fail]
509
+ target[:corrected] += source[:corrected]
510
+ target[:processed] += source[:processed]
511
+ end
512
+
513
+ # @param [Docscribe::CLI::Formatters::state] target
514
+ # @param [Docscribe::CLI::Formatters::state] source
515
+ # @return [void]
516
+ def merge_state_arrays(target, source)
517
+ target[:corrected_paths].concat(source[:corrected_paths])
518
+ target[:fail_paths].concat(source[:fail_paths])
519
+ target[:error_paths].concat(source[:error_paths])
520
+ target[:type_mismatch_paths].concat(source[:type_mismatch_paths])
521
+ end
522
+
523
+ # @param [Docscribe::CLI::Formatters::state] target
524
+ # @param [Docscribe::CLI::Formatters::state] source
525
+ # @return [void]
526
+ def merge_state_hashes(target, source)
527
+ %i[corrected_changes fail_changes error_messages type_mismatch_changes].each do |key|
528
+ t = target[key] #: Hash[String, untyped]
529
+ s = source[key] #: Hash[String, untyped]
530
+ s.each { |k, v| t[k] = v }
531
+ end
532
+ end
533
+
400
534
  # @param [Docscribe::CLI::Formatters::opts] options
401
535
  # @return [Hash<String, Object>]
402
536
  def extract_cli_overrides(options)
@@ -169,7 +169,7 @@ module Docscribe
169
169
  return unless ast
170
170
 
171
171
  walk_for_methods(ast, [], methods, path)
172
- rescue Parser::SyntaxError => e # steep:ignore
172
+ rescue Parser::SyntaxError => e
173
173
  warn "Syntax error in #{path}: #{e.message}"
174
174
  rescue StandardError => e
175
175
  warn "Error parsing #{path}: #{e.class}: #{e.message}"
data/lib/docscribe/cli.rb CHANGED
@@ -18,13 +18,15 @@ module Docscribe
18
18
  end
19
19
 
20
20
  COMMANDS = {
21
- 'init' => :Init,
21
+ 'check_for_comments' => :CheckForComments,
22
+ 'config' => :ConfigDump,
23
+ 'coverage' => :Coverage,
22
24
  'generate' => :Generate,
23
- 'sigs' => :Sigs,
25
+ 'init' => :Init,
24
26
  'rbs' => :RbsGen,
25
- 'update_types' => :UpdateTypes,
26
- 'check_for_comments' => :CheckForComments,
27
- 'server' => :ServerCmd
27
+ 'server' => :ServerCmd,
28
+ 'sigs' => :Sigs,
29
+ 'update_types' => :UpdateTypes
28
30
  }.freeze
29
31
 
30
32
  private
@@ -26,6 +26,16 @@ module Docscribe
26
26
  @raw = deep_merge(DEFAULT, raw)
27
27
  @config_path = config_path
28
28
  end
29
+
30
+ # @return [Object]
31
+ def to_h
32
+ hash = {} #: Hash[String, Object]
33
+ instance_variables.each do |var|
34
+ key = var.to_s.sub('@', '')
35
+ hash[key] = instance_variable_get(var)
36
+ end
37
+ hash
38
+ end
29
39
  end
30
40
  end
31
41