docscribe 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +149 -0
- data/lib/docscribe/cli/config_builder.rb +125 -35
- data/lib/docscribe/cli/generate.rb +288 -117
- data/lib/docscribe/cli/init.rb +49 -13
- data/lib/docscribe/cli/options.rb +302 -127
- data/lib/docscribe/cli/run.rb +391 -135
- data/lib/docscribe/cli.rb +23 -5
- data/lib/docscribe/config/defaults.rb +11 -11
- data/lib/docscribe/config/emit.rb +1 -0
- data/lib/docscribe/config/filtering.rb +24 -11
- data/lib/docscribe/config/loader.rb +7 -4
- data/lib/docscribe/config/plugin.rb +1 -0
- data/lib/docscribe/config/rbs.rb +31 -22
- data/lib/docscribe/config/sorbet.rb +41 -15
- data/lib/docscribe/config/sorting.rb +1 -0
- data/lib/docscribe/config/template.rb +1 -0
- data/lib/docscribe/config/utils.rb +1 -0
- data/lib/docscribe/config.rb +1 -0
- data/lib/docscribe/infer/constants.rb +15 -0
- data/lib/docscribe/infer/literals.rb +43 -25
- data/lib/docscribe/infer/names.rb +24 -15
- data/lib/docscribe/infer/params.rb +52 -6
- data/lib/docscribe/infer/raises.rb +24 -14
- data/lib/docscribe/infer/returns.rb +365 -182
- data/lib/docscribe/infer.rb +10 -9
- data/lib/docscribe/inline_rewriter/collector.rb +766 -375
- data/lib/docscribe/inline_rewriter/doc_block.rb +217 -74
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1488 -602
- data/lib/docscribe/inline_rewriter/source_helpers.rb +100 -52
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +109 -48
- data/lib/docscribe/inline_rewriter.rb +1009 -595
- data/lib/docscribe/plugin/base/collector_plugin.rb +2 -3
- data/lib/docscribe/plugin/base/tag_plugin.rb +1 -1
- data/lib/docscribe/plugin/registry.rb +34 -7
- data/lib/docscribe/plugin.rb +48 -17
- data/lib/docscribe/types/rbs/collection_loader.rb +0 -1
- data/lib/docscribe/types/rbs/provider.rb +75 -26
- data/lib/docscribe/types/rbs/type_formatter.rb +127 -59
- data/lib/docscribe/types/sorbet/base_provider.rb +31 -12
- data/lib/docscribe/version.rb +1 -1
- metadata +2 -2
data/lib/docscribe/cli/run.rb
CHANGED
|
@@ -17,6 +17,19 @@ module Docscribe
|
|
|
17
17
|
# - process exit status
|
|
18
18
|
module Run
|
|
19
19
|
class << self
|
|
20
|
+
INITIAL_RUN_STATE = {
|
|
21
|
+
changed: false,
|
|
22
|
+
had_errors: false,
|
|
23
|
+
checked_ok: 0,
|
|
24
|
+
checked_fail: 0,
|
|
25
|
+
corrected: 0,
|
|
26
|
+
fail_paths: [], #: Array[String]
|
|
27
|
+
fail_changes: {}, #: Hash[String, untyped]
|
|
28
|
+
error_paths: [], #: Array[String]
|
|
29
|
+
error_messages: {}, #: Hash[String, String]
|
|
30
|
+
type_mismatch_paths: [], #: Array[String]
|
|
31
|
+
type_mismatch_changes: {} #: Hash[String, untyped]
|
|
32
|
+
}.freeze
|
|
20
33
|
# Run Docscribe for files or STDIN using the selected mode and strategy.
|
|
21
34
|
#
|
|
22
35
|
# Modes:
|
|
@@ -32,23 +45,27 @@ module Docscribe
|
|
|
32
45
|
# @param [Array<String>] argv remaining path arguments
|
|
33
46
|
# @return [Integer] process exit code
|
|
34
47
|
def run(options:, argv:)
|
|
35
|
-
conf =
|
|
36
|
-
conf = Docscribe::CLI::ConfigBuilder.build(conf, options)
|
|
37
|
-
conf.load_plugins!
|
|
48
|
+
conf = build_config(options)
|
|
38
49
|
|
|
39
50
|
return run_stdin(options: options, conf: conf) if options[:mode] == :stdin
|
|
40
51
|
|
|
41
|
-
paths =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if paths.empty?
|
|
45
|
-
warn 'No files found. Pass files or directories (e.g. `docscribe lib`).'
|
|
46
|
-
return 1
|
|
47
|
-
end
|
|
52
|
+
paths = filtered_paths(argv, conf)
|
|
53
|
+
return no_files_found unless paths.any?
|
|
48
54
|
|
|
49
55
|
run_files(options: options, conf: conf, paths: paths)
|
|
50
56
|
end
|
|
51
57
|
|
|
58
|
+
# Load and build the effective config from CLI options.
|
|
59
|
+
#
|
|
60
|
+
# @param [Hash] options parsed CLI options
|
|
61
|
+
# @return [Docscribe::Config] effective config with plugins loaded
|
|
62
|
+
def build_config(options)
|
|
63
|
+
conf = Docscribe::Config.load(options[:config])
|
|
64
|
+
conf = Docscribe::CLI::ConfigBuilder.build(conf, options)
|
|
65
|
+
conf.load_plugins!
|
|
66
|
+
conf
|
|
67
|
+
end
|
|
68
|
+
|
|
52
69
|
# Rewrite code from STDIN using the selected strategy and print the
|
|
53
70
|
# result.
|
|
54
71
|
#
|
|
@@ -57,18 +74,50 @@ module Docscribe
|
|
|
57
74
|
# @raise [StandardError]
|
|
58
75
|
# @return [Integer] process exit code
|
|
59
76
|
def run_stdin(options:, conf:)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
puts stdin_rewrite_result(options, conf)[:output]
|
|
78
|
+
0
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
warn "Docscribe: Error processing stdin: #{e.class}: #{e.message}"
|
|
81
|
+
1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Rewrite STDIN input and return the result report.
|
|
85
|
+
#
|
|
86
|
+
# @param [Hash] options parsed CLI options
|
|
87
|
+
# @param [Docscribe::Config] conf effective config
|
|
88
|
+
# @return [Hash] rewrite result with :output key
|
|
89
|
+
def stdin_rewrite_result(options, conf)
|
|
90
|
+
Docscribe::InlineRewriter.rewrite_with_report(
|
|
91
|
+
$stdin.read,
|
|
63
92
|
strategy: options[:strategy],
|
|
64
93
|
config: conf,
|
|
65
|
-
core_rbs_provider: conf
|
|
94
|
+
core_rbs_provider: core_rbs_provider_for(conf),
|
|
66
95
|
file: '(stdin)'
|
|
67
96
|
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Return the core RBS provider from the config if available.
|
|
100
|
+
#
|
|
101
|
+
# @param [Docscribe::Config] conf effective config
|
|
102
|
+
# @return [Object, nil] core RBS provider or nil
|
|
103
|
+
def core_rbs_provider_for(conf)
|
|
104
|
+
conf.respond_to?(:core_rbs_provider) ? conf.core_rbs_provider : nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Expand CLI path arguments and filter through config file patterns.
|
|
108
|
+
#
|
|
109
|
+
# @param [Array<String>] argv CLI path arguments
|
|
110
|
+
# @param [Docscribe::Config] conf effective config
|
|
111
|
+
# @return [Array<String>] filtered Ruby file paths
|
|
112
|
+
def filtered_paths(argv, conf)
|
|
113
|
+
expand_paths(argv).select { |path| conf.process_file?(path) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Warn and return exit code when no matching files were found.
|
|
117
|
+
#
|
|
118
|
+
# @return [Integer] exit code 1
|
|
119
|
+
def no_files_found
|
|
120
|
+
warn 'No files found. Pass files or directories (e.g. `docscribe lib`).'
|
|
72
121
|
1
|
|
73
122
|
end
|
|
74
123
|
|
|
@@ -80,22 +129,31 @@ module Docscribe
|
|
|
80
129
|
# @param [Array<String>] args file and/or directory arguments
|
|
81
130
|
# @return [Array<String>] unique sorted Ruby file paths
|
|
82
131
|
def expand_paths(args)
|
|
83
|
-
files = []
|
|
132
|
+
files = [] #: Array[String]
|
|
84
133
|
args = ['.'] if args.empty?
|
|
85
134
|
|
|
86
135
|
args.each do |path|
|
|
87
|
-
|
|
88
|
-
files.concat(Dir.glob(File.join(path, '**', '*.rb')))
|
|
89
|
-
elsif File.file?(path)
|
|
90
|
-
files << path
|
|
91
|
-
else
|
|
92
|
-
warn "Skipping missing path: #{path}"
|
|
93
|
-
end
|
|
136
|
+
append_expanded_path(files, path)
|
|
94
137
|
end
|
|
95
138
|
|
|
96
139
|
files.uniq.sort
|
|
97
140
|
end
|
|
98
141
|
|
|
142
|
+
# Append a file or recursively expand a directory into the files array.
|
|
143
|
+
#
|
|
144
|
+
# @param [Array<String>] files mutable file path accumulator
|
|
145
|
+
# @param [String] path file or directory path to expand
|
|
146
|
+
# @return [void]
|
|
147
|
+
def append_expanded_path(files, path)
|
|
148
|
+
if File.directory?(path)
|
|
149
|
+
files.concat(Dir.glob(File.join(path, '**', '*.rb')))
|
|
150
|
+
elsif File.file?(path)
|
|
151
|
+
files << path
|
|
152
|
+
else
|
|
153
|
+
warn "Skipping missing path: #{path}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
99
157
|
# Process file paths in inspect or write mode.
|
|
100
158
|
#
|
|
101
159
|
# In inspect mode:
|
|
@@ -120,38 +178,46 @@ module Docscribe
|
|
|
120
178
|
process_one_file(path, options: options, conf: conf, pwd: pwd, state: state)
|
|
121
179
|
end
|
|
122
180
|
|
|
181
|
+
finalize_run(options, state)
|
|
182
|
+
|
|
183
|
+
run_exit_code(options, state)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
# Print the check or write summary at the end of a run.
|
|
189
|
+
#
|
|
190
|
+
# @private
|
|
191
|
+
# @param [Hash] options CLI options
|
|
192
|
+
# @param [Hash] state shared processing state
|
|
193
|
+
# @return [void]
|
|
194
|
+
def finalize_run(options, state)
|
|
123
195
|
if options[:mode] == :check
|
|
124
196
|
print_check_summary(state: state, options: options)
|
|
125
197
|
elsif options[:mode] == :write
|
|
126
198
|
print_write_summary(state: state)
|
|
127
199
|
end
|
|
200
|
+
end
|
|
128
201
|
|
|
202
|
+
# Determine the process exit code based on run state and mode.
|
|
203
|
+
#
|
|
204
|
+
# @private
|
|
205
|
+
# @param [Hash] options CLI options
|
|
206
|
+
# @param [Hash] state shared processing state
|
|
207
|
+
# @return [Integer] exit code 0 or 1
|
|
208
|
+
def run_exit_code(options, state)
|
|
129
209
|
return 1 if state[:had_errors]
|
|
130
210
|
return 1 if options[:mode] == :check && state[:changed]
|
|
131
211
|
|
|
132
212
|
0
|
|
133
213
|
end
|
|
134
214
|
|
|
135
|
-
private
|
|
136
|
-
|
|
137
215
|
# Initialize the shared state hash used throughout a run.
|
|
138
216
|
#
|
|
139
217
|
# @private
|
|
140
218
|
# @return [Hash] initial state with counters and tracking arrays
|
|
141
219
|
def initial_run_state
|
|
142
|
-
|
|
143
|
-
changed: false,
|
|
144
|
-
had_errors: false,
|
|
145
|
-
checked_ok: 0,
|
|
146
|
-
checked_fail: 0,
|
|
147
|
-
corrected: 0,
|
|
148
|
-
fail_paths: [],
|
|
149
|
-
fail_changes: {},
|
|
150
|
-
error_paths: [],
|
|
151
|
-
error_messages: {},
|
|
152
|
-
type_mismatch_paths: [],
|
|
153
|
-
type_mismatch_changes: {}
|
|
154
|
-
}
|
|
220
|
+
Marshal.load(Marshal.dump(INITIAL_RUN_STATE))
|
|
155
221
|
end
|
|
156
222
|
|
|
157
223
|
# Process a single file: read, rewrite, and dispatch to check/write handler.
|
|
@@ -169,33 +235,28 @@ module Docscribe
|
|
|
169
235
|
src = read_source_for_path(path, display_path: display_path, options: options, state: state)
|
|
170
236
|
return unless src
|
|
171
237
|
|
|
172
|
-
|
|
173
|
-
|
|
238
|
+
ctx = { conf: conf, display_path: display_path, options: options, state: state }
|
|
239
|
+
result = rewrite_result_for_path(path, src: src, ctx: ctx)
|
|
174
240
|
return unless result
|
|
175
241
|
|
|
176
|
-
out
|
|
177
|
-
|
|
242
|
+
dispatch_file_result(path, src: src, out: result[:output], file_changes: result[:changes] || [],
|
|
243
|
+
display_path: display_path, options: options, state: state)
|
|
244
|
+
end
|
|
178
245
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
out: out,
|
|
194
|
-
file_changes: file_changes,
|
|
195
|
-
display_path: display_path,
|
|
196
|
-
options: options,
|
|
197
|
-
state: state
|
|
198
|
-
)
|
|
246
|
+
# Dispatch the rewrite result to the check or write handler based on mode.
|
|
247
|
+
#
|
|
248
|
+
# @private
|
|
249
|
+
# @param [String] path file path
|
|
250
|
+
# @param [String] src original source code
|
|
251
|
+
# @param [String] out rewritten source code
|
|
252
|
+
# @param [Array<Hash>] file_changes structured change records
|
|
253
|
+
# @param [Hash] ctx context hash with :options, :state, :display_path, :conf
|
|
254
|
+
# @return [void]
|
|
255
|
+
def dispatch_file_result(path, src:, out:, file_changes:, **ctx)
|
|
256
|
+
if ctx[:options][:mode] == :check
|
|
257
|
+
handle_check_result(path, src: src, out: out, file_changes: file_changes, **ctx)
|
|
258
|
+
elsif ctx[:options][:mode] == :write
|
|
259
|
+
handle_write_result(path, src: src, out: out, file_changes: file_changes, **ctx)
|
|
199
260
|
end
|
|
200
261
|
end
|
|
201
262
|
|
|
@@ -249,23 +310,42 @@ module Docscribe
|
|
|
249
310
|
# @param [String] display_path path shown in CLI output
|
|
250
311
|
# @param [Hash] options CLI options
|
|
251
312
|
# @param [Hash] state shared processing state
|
|
313
|
+
# @param [Hash] ctx context hash with :conf, :display_path, :options, :state keys
|
|
252
314
|
# @raise [StandardError]
|
|
253
315
|
# @return [Hash, nil] rewrite result or nil on error
|
|
254
|
-
def rewrite_result_for_path(path, src:,
|
|
255
|
-
|
|
316
|
+
def rewrite_result_for_path(path, src:, ctx:)
|
|
317
|
+
conf = ctx[:conf]
|
|
318
|
+
|
|
319
|
+
core_rbs_provider =
|
|
320
|
+
conf.respond_to?(:core_rbs_provider) ? conf.core_rbs_provider : nil
|
|
321
|
+
|
|
256
322
|
Docscribe::InlineRewriter.rewrite_with_report(
|
|
257
|
-
src,
|
|
258
|
-
strategy: options[:strategy],
|
|
259
|
-
config: conf,
|
|
260
|
-
core_rbs_provider: core_rbs_provider,
|
|
261
|
-
file: path
|
|
323
|
+
src, strategy: ctx[:options][:strategy], config: conf, core_rbs_provider: core_rbs_provider, file: path
|
|
262
324
|
)
|
|
263
325
|
rescue StandardError => e
|
|
326
|
+
record_rewrite_error(path, e, ctx)
|
|
327
|
+
nil
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Record a rewrite error in the shared state and print an error indicator.
|
|
331
|
+
#
|
|
332
|
+
# @private
|
|
333
|
+
# @param [String] path file path that caused the error
|
|
334
|
+
# @param [StandardError] error the exception raised during rewriting
|
|
335
|
+
# @param [Hash] ctx context hash with :state, :options, :display_path
|
|
336
|
+
# @return [void]
|
|
337
|
+
def record_rewrite_error(path, error, ctx)
|
|
338
|
+
state = ctx[:state]
|
|
339
|
+
|
|
264
340
|
state[:had_errors] = true
|
|
265
341
|
state[:error_paths] << path
|
|
266
|
-
state[:error_messages][path] = "#{
|
|
267
|
-
|
|
268
|
-
|
|
342
|
+
state[:error_messages][path] = "#{error.class}: #{error.message}"
|
|
343
|
+
|
|
344
|
+
if ctx[:options][:verbose]
|
|
345
|
+
warn "ERR #{ctx[:display_path]}: #{state[:error_messages][path]}"
|
|
346
|
+
else
|
|
347
|
+
print('E')
|
|
348
|
+
end
|
|
269
349
|
end
|
|
270
350
|
|
|
271
351
|
# Handle the result of an inspect (check) run.
|
|
@@ -278,30 +358,64 @@ module Docscribe
|
|
|
278
358
|
# @param [String] display_path path shown in CLI output
|
|
279
359
|
# @param [Hash] options CLI options
|
|
280
360
|
# @param [Hash] state shared processing state
|
|
361
|
+
# @param [Hash] ctx context hash with :display_path, :options, :state keys
|
|
281
362
|
# @return [void]
|
|
282
|
-
def handle_check_result(path, src:, out:, file_changes:,
|
|
283
|
-
type_mismatches = file_changes
|
|
363
|
+
def handle_check_result(path, src:, out:, file_changes:, **ctx)
|
|
364
|
+
type_mismatches = type_mismatch_changes(file_changes)
|
|
284
365
|
has_real_changes = file_changes.any? { |c| !%i[updated_param updated_return].include?(c[:type]) }
|
|
285
366
|
|
|
286
367
|
if out == src && !has_real_changes
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
state[:type_mismatch_changes][path] = type_mismatches
|
|
290
|
-
options[:verbose] ? puts("MT #{display_path}") : print('M')
|
|
291
|
-
else
|
|
292
|
-
state[:checked_ok] += 1
|
|
293
|
-
options[:verbose] ? puts("OK #{display_path}") : print('.')
|
|
294
|
-
end
|
|
368
|
+
handle_check_no_changes(path, type_mismatches: type_mismatches, display_path: ctx[:display_path],
|
|
369
|
+
options: ctx[:options], state: ctx[:state])
|
|
295
370
|
return
|
|
296
371
|
end
|
|
297
372
|
|
|
373
|
+
handle_check_failed(path, file_changes: file_changes, display_path: ctx[:display_path],
|
|
374
|
+
options: ctx[:options], state: ctx[:state])
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Extract type mismatch changes from file_changes.
|
|
378
|
+
#
|
|
379
|
+
# @private
|
|
380
|
+
# @param [Array<Hash>] file_changes
|
|
381
|
+
# @return [Array<Hash>]
|
|
382
|
+
def type_mismatch_changes(file_changes)
|
|
383
|
+
file_changes.select { |c| %i[updated_param updated_return].include?(c[:type]) }
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Handle check result when there are no real changes.
|
|
387
|
+
#
|
|
388
|
+
# @private
|
|
389
|
+
# @param [String] path
|
|
390
|
+
# @param [Array<Hash>] type_mismatches
|
|
391
|
+
# @param [String] display_path
|
|
392
|
+
# @param [Hash] options
|
|
393
|
+
# @param [Hash] state
|
|
394
|
+
# @return [void]
|
|
395
|
+
def handle_check_no_changes(path, type_mismatches:, display_path:, options:, state:)
|
|
396
|
+
if type_mismatches.any?
|
|
397
|
+
state[:type_mismatch_paths] << path
|
|
398
|
+
state[:type_mismatch_changes][path] = type_mismatches
|
|
399
|
+
log_check_verdict('MT', display_path, options)
|
|
400
|
+
else
|
|
401
|
+
state[:checked_ok] += 1
|
|
402
|
+
log_check_verdict('OK', display_path, options)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Handle a failed check (file needs updates).
|
|
407
|
+
#
|
|
408
|
+
# @private
|
|
409
|
+
# @param [String] path
|
|
410
|
+
# @param [Array<Hash>] file_changes
|
|
411
|
+
# @param [String] display_path
|
|
412
|
+
# @param [Hash] options
|
|
413
|
+
# @param [Hash] state
|
|
414
|
+
# @return [void]
|
|
415
|
+
def handle_check_failed(path, file_changes:, display_path:, options:, state:)
|
|
298
416
|
if options[:verbose]
|
|
299
417
|
puts("FAIL #{display_path}")
|
|
300
|
-
|
|
301
|
-
file_changes.each do |change|
|
|
302
|
-
puts(" - #{format_change_reason(change)}")
|
|
303
|
-
end
|
|
304
|
-
end
|
|
418
|
+
print_check_explanations(file_changes, options)
|
|
305
419
|
else
|
|
306
420
|
print('F')
|
|
307
421
|
end
|
|
@@ -322,33 +436,87 @@ module Docscribe
|
|
|
322
436
|
# @param [String] display_path path shown in CLI output
|
|
323
437
|
# @param [Hash] options CLI options
|
|
324
438
|
# @param [Hash] state shared processing state
|
|
439
|
+
# @param [Hash] ctx context hash with :display_path, :options, :state keys
|
|
325
440
|
# @raise [StandardError]
|
|
326
441
|
# @return [void]
|
|
327
|
-
def handle_write_result(path, src:, out:, file_changes:,
|
|
442
|
+
def handle_write_result(path, src:, out:, file_changes:, **ctx)
|
|
328
443
|
if out == src
|
|
329
|
-
|
|
444
|
+
log_check_verdict('OK', ctx[:display_path], ctx[:options])
|
|
330
445
|
return
|
|
331
446
|
end
|
|
332
447
|
|
|
333
448
|
File.write(path, out)
|
|
449
|
+
log_write_verdict('CHANGED', ctx[:display_path], file_changes, ctx[:options])
|
|
450
|
+
ctx[:state][:corrected] += 1
|
|
451
|
+
rescue StandardError => e
|
|
452
|
+
record_write_error(path, e, display_path: ctx[:display_path], options: ctx[:options], state: ctx[:state])
|
|
453
|
+
end
|
|
334
454
|
|
|
455
|
+
# Log a write-mode verdict.
|
|
456
|
+
#
|
|
457
|
+
# @private
|
|
458
|
+
# @param [String] verdict
|
|
459
|
+
# @param [String] display_path
|
|
460
|
+
# @param [Array<Hash>] file_changes
|
|
461
|
+
# @param [Hash] options
|
|
462
|
+
# @return [void]
|
|
463
|
+
def log_write_verdict(verdict, display_path, file_changes, options)
|
|
335
464
|
if options[:verbose]
|
|
336
|
-
puts("
|
|
337
|
-
|
|
338
|
-
file_changes.each do |change|
|
|
339
|
-
puts(" - #{format_change_reason(change)}")
|
|
340
|
-
end
|
|
341
|
-
end
|
|
465
|
+
puts("#{verdict} #{display_path}")
|
|
466
|
+
print_check_explanations(file_changes, options)
|
|
342
467
|
else
|
|
343
468
|
print('C')
|
|
344
469
|
end
|
|
470
|
+
end
|
|
345
471
|
|
|
346
|
-
|
|
347
|
-
|
|
472
|
+
# Print explanations for file changes.
|
|
473
|
+
#
|
|
474
|
+
# @private
|
|
475
|
+
# @param [Array<Hash>] file_changes
|
|
476
|
+
# @param [Hash] options
|
|
477
|
+
# @return [void]
|
|
478
|
+
def print_check_explanations(file_changes, options)
|
|
479
|
+
return unless options[:explain]
|
|
480
|
+
|
|
481
|
+
file_changes.each do |change|
|
|
482
|
+
puts(" - #{format_change_reason(change)}")
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Record a write error in state.
|
|
487
|
+
#
|
|
488
|
+
# @private
|
|
489
|
+
# @param [String] path
|
|
490
|
+
# @param [StandardError] e
|
|
491
|
+
# @param [String] display_path
|
|
492
|
+
# @param [Hash] options
|
|
493
|
+
# @param [Hash] state
|
|
494
|
+
# @param [StandardError] error the exception raised during file write
|
|
495
|
+
# @return [void]
|
|
496
|
+
def record_write_error(path, error, display_path:, options:, state:)
|
|
348
497
|
state[:had_errors] = true
|
|
349
498
|
state[:error_paths] << path
|
|
350
|
-
state[:error_messages][path] = "#{
|
|
351
|
-
|
|
499
|
+
state[:error_messages][path] = "#{error.class}: #{error.message}"
|
|
500
|
+
log_check_verdict('ERR', display_path, options)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Log a per-file check verdict.
|
|
504
|
+
#
|
|
505
|
+
# @private
|
|
506
|
+
# @param [String] verdict
|
|
507
|
+
# @param [String] display_path
|
|
508
|
+
# @param [Hash] options
|
|
509
|
+
# @return [void]
|
|
510
|
+
def log_check_verdict(verdict, display_path, options)
|
|
511
|
+
if options[:verbose]
|
|
512
|
+
puts("#{verdict} #{display_path}")
|
|
513
|
+
else
|
|
514
|
+
print(if verdict == 'FAIL'
|
|
515
|
+
'F'
|
|
516
|
+
else
|
|
517
|
+
verdict == 'MT' ? 'M' : '.'
|
|
518
|
+
end)
|
|
519
|
+
end
|
|
352
520
|
end
|
|
353
521
|
|
|
354
522
|
# Print the check-mode summary (files OK / need updates / errors).
|
|
@@ -359,25 +527,75 @@ module Docscribe
|
|
|
359
527
|
# @return [void]
|
|
360
528
|
def print_check_summary(state:, options:)
|
|
361
529
|
puts
|
|
530
|
+
print_check_status_line(state)
|
|
531
|
+
print_fail_paths(state, options)
|
|
532
|
+
print_type_mismatch_paths(state, options)
|
|
533
|
+
print_error_paths(state)
|
|
534
|
+
end
|
|
362
535
|
|
|
536
|
+
# Print the check-mode status line.
|
|
537
|
+
#
|
|
538
|
+
# @private
|
|
539
|
+
# @param [Hash] state
|
|
540
|
+
# @return [void]
|
|
541
|
+
def print_check_status_line(state)
|
|
363
542
|
checked_error = state[:error_paths].size
|
|
364
543
|
type_mismatch_count = state[:type_mismatch_paths].size
|
|
365
544
|
|
|
366
|
-
if state
|
|
545
|
+
if all_fine?(state, checked_error, type_mismatch_count)
|
|
367
546
|
puts "Docscribe: OK (#{state[:checked_ok]} files checked)"
|
|
368
|
-
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
if state[:checked_fail].zero? && checked_error.zero?
|
|
547
|
+
elsif mismatch_only?(state, checked_error)
|
|
372
548
|
puts "Docscribe: OK (#{state[:checked_ok]} files checked, #{type_mismatch_count} with type mismatches)"
|
|
373
549
|
else
|
|
374
|
-
|
|
375
|
-
parts << "#{type_mismatch_count} type mismatches" if type_mismatch_count.positive?
|
|
376
|
-
parts << "#{checked_error} errors"
|
|
377
|
-
parts << "#{state[:checked_ok]} ok"
|
|
378
|
-
puts "Docscribe: FAILED (#{parts.join(', ')})"
|
|
550
|
+
puts build_failure_line(state, type_mismatch_count, checked_error)
|
|
379
551
|
end
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Whether no failures, errors, or type mismatches occurred.
|
|
555
|
+
#
|
|
556
|
+
# @private
|
|
557
|
+
# @param [Hash] state shared processing state
|
|
558
|
+
# @param [Integer] checked_error number of files with errors
|
|
559
|
+
# @param [Integer] type_mismatch_count number of files with type mismatches
|
|
560
|
+
# @return [Boolean]
|
|
561
|
+
def all_fine?(state, checked_error, type_mismatch_count)
|
|
562
|
+
state[:checked_fail].zero? && checked_error.zero? && type_mismatch_count.zero?
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
# Whether type mismatches exist but no failures or errors.
|
|
566
|
+
#
|
|
567
|
+
# @private
|
|
568
|
+
# @param [Hash] state shared processing state
|
|
569
|
+
# @param [Integer] checked_error number of files with errors
|
|
570
|
+
# @return [Boolean]
|
|
571
|
+
def mismatch_only?(state, checked_error)
|
|
572
|
+
state[:checked_fail].zero? && checked_error.zero?
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# Build the human-readable failure summary line for check output.
|
|
576
|
+
#
|
|
577
|
+
# @private
|
|
578
|
+
# @param [Hash] state shared processing state
|
|
579
|
+
# @param [Integer] type_mismatch_count number of files with type mismatches
|
|
580
|
+
# @param [Integer] checked_error number of files with errors
|
|
581
|
+
# @return [String]
|
|
582
|
+
def build_failure_line(state, type_mismatch_count, checked_error)
|
|
583
|
+
parts = ["#{state[:checked_fail]} need updates"]
|
|
584
|
+
parts << "#{type_mismatch_count} type mismatches" if type_mismatch_count.positive?
|
|
585
|
+
parts << "#{checked_error} errors"
|
|
586
|
+
parts << "#{state[:checked_ok]} ok"
|
|
587
|
+
"Docscribe: FAILED (#{parts.join(', ')})"
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
public
|
|
380
591
|
|
|
592
|
+
# Print fail paths from check summary.
|
|
593
|
+
#
|
|
594
|
+
# @private
|
|
595
|
+
# @param [Hash] state
|
|
596
|
+
# @param [Hash] options
|
|
597
|
+
# @return [void]
|
|
598
|
+
def print_fail_paths(state, options)
|
|
381
599
|
state[:fail_paths].each do |p|
|
|
382
600
|
warn "Would update docs: #{p}"
|
|
383
601
|
next unless options[:explain] && !options[:verbose]
|
|
@@ -386,19 +604,22 @@ module Docscribe
|
|
|
386
604
|
warn " - #{format_change_reason(change)}"
|
|
387
605
|
end
|
|
388
606
|
end
|
|
607
|
+
end
|
|
389
608
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
609
|
+
# Print type mismatch paths from check summary.
|
|
610
|
+
#
|
|
611
|
+
# @private
|
|
612
|
+
# @param [Hash] state
|
|
613
|
+
# @param [Hash] options
|
|
614
|
+
# @return [void]
|
|
615
|
+
def print_type_mismatch_paths(state, options)
|
|
616
|
+
return unless options[:verbose] || options[:explain]
|
|
398
617
|
|
|
399
|
-
state[:
|
|
400
|
-
warn "
|
|
401
|
-
|
|
618
|
+
state[:type_mismatch_paths].each do |p|
|
|
619
|
+
warn "Type mismatches: #{p}"
|
|
620
|
+
Array(state[:type_mismatch_changes][p]).each do |change|
|
|
621
|
+
warn " - #{format_change_reason(change)}"
|
|
622
|
+
end
|
|
402
623
|
end
|
|
403
624
|
end
|
|
404
625
|
|
|
@@ -408,18 +629,44 @@ module Docscribe
|
|
|
408
629
|
# @param [Hash] change structured change produced by the inline rewriter
|
|
409
630
|
# @return [String] human-readable explanation line
|
|
410
631
|
def format_change_reason(change)
|
|
411
|
-
line = change
|
|
412
|
-
method = change
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
632
|
+
line = change_line_suffix(change)
|
|
633
|
+
method = change_method_suffix(change)
|
|
634
|
+
|
|
635
|
+
return "unsorted tags#{line}" if change[:type] == :unsorted_tags
|
|
636
|
+
return "#{change[:message]}#{method}#{line}" if direct_message_change?(change)
|
|
637
|
+
|
|
638
|
+
"#{change[:message] || change[:type].to_s.tr('_', ' ')}#{method}#{line}"
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Format the line number suffix for a change reason string.
|
|
642
|
+
#
|
|
643
|
+
# @param [Hash] change structured change record
|
|
644
|
+
# @return [String] " at line N" or empty
|
|
645
|
+
def change_line_suffix(change)
|
|
646
|
+
change[:line] ? " at line #{change[:line]}" : ''
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# Format the method name suffix for a change reason string.
|
|
650
|
+
#
|
|
651
|
+
# @param [Hash] change structured change record
|
|
652
|
+
# @return [String] " for method_name" or empty
|
|
653
|
+
def change_method_suffix(change)
|
|
654
|
+
change[:method] ? " for #{change[:method]}" : ''
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# Whether a change type uses its own :message field directly as the reason.
|
|
658
|
+
#
|
|
659
|
+
# @param [Hash] change structured change record
|
|
660
|
+
# @return [Boolean]
|
|
661
|
+
def direct_message_change?(change)
|
|
662
|
+
%i[
|
|
663
|
+
missing_param
|
|
664
|
+
missing_return
|
|
665
|
+
missing_raise
|
|
666
|
+
missing_visibility
|
|
667
|
+
missing_module_function_note
|
|
668
|
+
insert_full_doc_block
|
|
669
|
+
].include?(change[:type])
|
|
423
670
|
end
|
|
424
671
|
|
|
425
672
|
# Print the write-mode summary (files corrected, errors).
|
|
@@ -434,6 +681,15 @@ module Docscribe
|
|
|
434
681
|
return unless state[:had_errors]
|
|
435
682
|
|
|
436
683
|
warn "Docscribe: #{state[:error_paths].size} file(s) had errors"
|
|
684
|
+
print_error_paths(state)
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
# Print error paths from check summary.
|
|
688
|
+
#
|
|
689
|
+
# @private
|
|
690
|
+
# @param [Hash] state
|
|
691
|
+
# @return [void]
|
|
692
|
+
def print_error_paths(state)
|
|
437
693
|
state[:error_paths].each do |p|
|
|
438
694
|
warn "Error processing: #{p}"
|
|
439
695
|
warn " #{state[:error_messages][p]}" if state[:error_messages][p]
|