docscribe 1.6.0 → 1.6.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -193
  3. data/exe/docscribe-client +26 -7
  4. data/lib/docscribe/cli/config_builder.rb +37 -2
  5. data/lib/docscribe/cli/coverage.rb +5 -5
  6. data/lib/docscribe/cli/formatters/json.rb +74 -29
  7. data/lib/docscribe/cli/formatters/sarif.rb +20 -3
  8. data/lib/docscribe/cli/options.rb +17 -2
  9. data/lib/docscribe/cli/rbs_gen.rb +4 -4
  10. data/lib/docscribe/cli/run.rb +107 -24
  11. data/lib/docscribe/cli/update_types.rb +61 -17
  12. data/lib/docscribe/cli.rb +19 -13
  13. data/lib/docscribe/config/defaults.rb +1 -0
  14. data/lib/docscribe/config/rbs.rb +22 -1
  15. data/lib/docscribe/config/template.rb +3 -0
  16. data/lib/docscribe/config/validation.rb +19 -0
  17. data/lib/docscribe/config.rb +1 -0
  18. data/lib/docscribe/infer/behavior.rb +13 -13
  19. data/lib/docscribe/infer/params.rb +2 -2
  20. data/lib/docscribe/infer/raises.rb +5 -6
  21. data/lib/docscribe/infer/returns.rb +1612 -151
  22. data/lib/docscribe/infer.rb +7 -7
  23. data/lib/docscribe/inline_rewriter/doc_builder.rb +485 -102
  24. data/lib/docscribe/inline_rewriter.rb +263 -97
  25. data/lib/docscribe/plugin/registry.rb +1 -0
  26. data/lib/docscribe/server/base.rb +255 -0
  27. data/lib/docscribe/server/client.rb +95 -0
  28. data/lib/docscribe/server/daemon.rb +678 -0
  29. data/lib/docscribe/server/protocol.rb +50 -0
  30. data/lib/docscribe/server.rb +4 -835
  31. data/lib/docscribe/types/primitive.rb +160 -0
  32. data/lib/docscribe/types/sorbet/base_provider.rb +33 -1
  33. data/lib/docscribe/types/yard/formatter.rb +35 -6
  34. data/lib/docscribe/types/yard/parser.rb +25 -20
  35. data/lib/docscribe/types/yard/validator.rb +131 -0
  36. data/lib/docscribe/validator/generic_compatibility.rb +698 -0
  37. data/lib/docscribe/validator/type_mismatch_validator.rb +287 -0
  38. data/lib/docscribe/version.rb +1 -1
  39. metadata +12 -3
@@ -20,7 +20,8 @@ module Docscribe
20
20
  insert_full_doc_block: 'convention',
21
21
  unsorted_tags: 'convention',
22
22
  updated_param: 'warning',
23
- updated_return: 'warning'
23
+ updated_return: 'warning',
24
+ invalid_type: 'warning'
24
25
  }.freeze
25
26
 
26
27
  COP_NAME_MAP = {
@@ -32,7 +33,8 @@ module Docscribe
32
33
  insert_full_doc_block: 'Docscribe/MissingDocBlock',
33
34
  unsorted_tags: 'Docscribe/UnsortedTags',
34
35
  updated_param: 'Docscribe/UpdatedParam',
35
- updated_return: 'Docscribe/UpdatedReturn'
36
+ updated_return: 'Docscribe/UpdatedReturn',
37
+ invalid_type: 'Docscribe/InvalidType'
36
38
  }.freeze
37
39
 
38
40
  # Output JSON check summary.
@@ -60,7 +62,7 @@ module Docscribe
60
62
  # @private
61
63
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
62
64
  # @param [Docscribe::CLI::Formatters::opts] _options runtime options hash
63
- # @return [Hash<Symbol, Object>]
65
+ # @return [Docscribe::CLI::Formatters::Json::json_document]
64
66
  def build_document(state, _options)
65
67
  document_hash(build_files(state), state)
66
68
  end
@@ -68,9 +70,9 @@ module Docscribe
68
70
  # Build document hash structure.
69
71
  #
70
72
  # @private
71
- # @param [Array<Hash<Symbol, Object>>] files files offenses array
73
+ # @param [Array<Docscribe::CLI::Formatters::Json::json_file>] files files offenses array
72
74
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
73
- # @return [Hash<Symbol, Object>]
75
+ # @return [Docscribe::CLI::Formatters::Json::json_document]
74
76
  def document_hash(files, state)
75
77
  {
76
78
  metadata: metadata_hash,
@@ -82,7 +84,7 @@ module Docscribe
82
84
  # Build tool metadata hash.
83
85
  #
84
86
  # @private
85
- # @return [Hash<Symbol, String>]
87
+ # @return [Docscribe::CLI::Formatters::Json::json_metadata]
86
88
  def metadata_hash
87
89
  {
88
90
  docscribe_version: Docscribe::VERSION,
@@ -93,9 +95,9 @@ module Docscribe
93
95
  # Build summary statistics hash.
94
96
  #
95
97
  # @private
96
- # @param [Array<Hash<Symbol, Object>>] files files offenses array
98
+ # @param [Array<Docscribe::CLI::Formatters::Json::json_file>] files files offenses array
97
99
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
98
- # @return [Hash<Symbol, Integer>]
100
+ # @return [Docscribe::CLI::Formatters::Json::json_summary]
99
101
  def summary_hash(files, state)
100
102
  {
101
103
  offense_count: files.sum { |f| f[:offenses].size },
@@ -109,7 +111,7 @@ module Docscribe
109
111
  #
110
112
  # @private
111
113
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
112
- # @return [Array<Hash<Symbol, Object>>]
114
+ # @return [Array<Docscribe::CLI::Formatters::Json::json_file>]
113
115
  def build_files(state)
114
116
  files = [] #: Array[Hash[untyped, untyped]]
115
117
 
@@ -124,15 +126,16 @@ module Docscribe
124
126
  #
125
127
  # @private
126
128
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
127
- # @param [Array<Hash<Symbol, Object>>] files files offenses array
129
+ # @param [Array<Docscribe::CLI::Formatters::Json::json_file>] files files offenses array
128
130
  # @return [void]
129
131
  def append_check_files(state, files)
130
132
  state[:fail_paths].each do |path|
131
- files << file_entry(path, state[:fail_changes][path] || [])
133
+ merge_or_append(files, path, build_offenses(state[:fail_changes][path] || []))
132
134
  end
133
135
 
134
136
  state[:type_mismatch_paths].each do |path|
135
- files << file_entry(path, state[:type_mismatch_changes][path] || [], severity: 'warning')
137
+ changes = state[:type_mismatch_changes][path] || []
138
+ merge_or_append(files, path, build_offenses(changes, severity: 'warning'))
136
139
  end
137
140
  end
138
141
 
@@ -140,7 +143,7 @@ module Docscribe
140
143
  #
141
144
  # @private
142
145
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
143
- # @param [Array<Hash<Symbol, Object>>] files files offenses array
146
+ # @param [Array<Docscribe::CLI::Formatters::Json::json_file>] files files offenses array
144
147
  # @return [void]
145
148
  def append_corrected_files(state, files)
146
149
  state[:corrected_paths].each do |path|
@@ -152,7 +155,7 @@ module Docscribe
152
155
  #
153
156
  # @private
154
157
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
155
- # @param [Array<Hash<Symbol, Object>>] files files offenses array
158
+ # @param [Array<Docscribe::CLI::Formatters::Json::json_file>] files files offenses array
156
159
  # @return [void]
157
160
  def append_error_files(state, files)
158
161
  state[:error_paths].each do |path|
@@ -163,12 +166,12 @@ module Docscribe
163
166
  # Merge or append file offenses.
164
167
  #
165
168
  # @private
166
- # @param [Array<Hash<Symbol, Object>>] files files offenses array
169
+ # @param [Array<Docscribe::CLI::Formatters::Json::json_file>] files files offenses array
167
170
  # @param [String] path file path string
168
- # @param [Array<Hash<Symbol, Object>>] offenses offense objects array
171
+ # @param [Array<Docscribe::CLI::Formatters::Json::json_offense>] offenses offense objects array
169
172
  # @return [void]
170
173
  def merge_or_append(files, path, offenses)
171
- existing = files.find { |f| f[:path] == path }
174
+ existing = files.find { |f| normalize_path(f[:path]) == normalize_path(path) }
172
175
 
173
176
  if existing
174
177
  existing[:offenses].concat(offenses)
@@ -177,15 +180,18 @@ module Docscribe
177
180
  end
178
181
  end
179
182
 
180
- # Build single file entry hash.
183
+ # Normalize a path for dedup comparison, resolving symlinks
184
+ # (e.g. /tmp vs /private/tmp on macOS).
181
185
  #
182
186
  # @private
183
187
  # @param [String] path file path string
184
- # @param [Array<Docscribe::CLI::Formatters::change>] changes changes info array
185
- # @param [String?] severity offense severity level
186
- # @return [Hash<Symbol, Object>]
187
- def file_entry(path, changes, severity: nil)
188
- { path: path, offenses: build_offenses(changes, severity: severity) }
188
+ # @raise [SystemCallError]
189
+ # @raise [ArgumentError]
190
+ # @return [String]
191
+ def normalize_path(path)
192
+ File.realpath(path)
193
+ rescue SystemCallError, ArgumentError
194
+ File.expand_path(path)
189
195
  end
190
196
 
191
197
  # Build error offense entry.
@@ -193,7 +199,7 @@ module Docscribe
193
199
  # @private
194
200
  # @param [Docscribe::CLI::Formatters::state] state formatter state hash
195
201
  # @param [String] path file path string
196
- # @return [Hash<Symbol, Object>]
202
+ # @return [Docscribe::CLI::Formatters::Json::json_offense]
197
203
  def error_offense(state, path)
198
204
  error_offense_hash(state[:error_messages][path] || 'Unknown error')
199
205
  end
@@ -202,7 +208,7 @@ module Docscribe
202
208
  #
203
209
  # @private
204
210
  # @param [String] message error message string
205
- # @return [Hash<Symbol, Object>]
211
+ # @return [Docscribe::CLI::Formatters::Json::json_offense]
206
212
  def error_offense_hash(message)
207
213
  { severity: 'fatal', cop_name: 'Docscribe/ProcessingError', message: message,
208
214
  corrected: false, correctable: false, location: default_location }
@@ -211,7 +217,7 @@ module Docscribe
211
217
  # Default location hash value.
212
218
  #
213
219
  # @private
214
- # @return [Hash<Symbol, Integer>]
220
+ # @return [Docscribe::CLI::Formatters::Json::json_location]
215
221
  def default_location
216
222
  { start_line: 1, start_column: 1, last_line: 1, last_column: 1 }
217
223
  end
@@ -221,7 +227,7 @@ module Docscribe
221
227
  # @private
222
228
  # @param [Array<Docscribe::CLI::Formatters::change>] changes changes info array
223
229
  # @param [String?] severity offense severity level
224
- # @return [Array<Hash<Symbol, Object>>]
230
+ # @return [Array<Docscribe::CLI::Formatters::Json::json_offense>]
225
231
  def build_offenses(changes, severity: nil)
226
232
  changes.map { |change| build_offense(change, severity) }
227
233
  end
@@ -231,8 +237,18 @@ module Docscribe
231
237
  # @private
232
238
  # @param [Docscribe::CLI::Formatters::change] change change info hash
233
239
  # @param [String?] severity offense severity level
234
- # @return [Hash<Symbol, Object>]
240
+ # @return [Docscribe::CLI::Formatters::Json::json_offense]
235
241
  def build_offense(change, severity)
242
+ offense = base_offense(change, severity)
243
+ attach_source(offense, change)
244
+ attach_type(offense, change)
245
+ end
246
+
247
+ # @private
248
+ # @param [Docscribe::CLI::Formatters::change] change
249
+ # @param [String?] severity
250
+ # @return [Docscribe::CLI::Formatters::Json::json_offense]
251
+ def base_offense(change, severity)
236
252
  {
237
253
  severity: severity || SEVERITY_MAP[change[:type]] || 'convention',
238
254
  cop_name: COP_NAME_MAP[change[:type]] || cop_name_fallback(change),
@@ -243,11 +259,40 @@ module Docscribe
243
259
  }
244
260
  end
245
261
 
262
+ # @private
263
+ # @param [Docscribe::CLI::Formatters::Json::json_offense] offense
264
+ # @param [Docscribe::CLI::Formatters::change] change
265
+ # @return [Docscribe::CLI::Formatters::Json::json_offense]
266
+ def attach_source(offense, change)
267
+ source = offense_source(change)
268
+ offense[:source] = source if source
269
+ offense
270
+ end
271
+
272
+ # Attach machine-readable change type when present.
273
+ #
274
+ # @private
275
+ # @param [Docscribe::CLI::Formatters::Json::json_offense] offense offense hash being built
276
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
277
+ # @return [Docscribe::CLI::Formatters::Json::json_offense]
278
+ def attach_type(offense, change)
279
+ type = change[:type] || change['type']
280
+ offense[:type] = type.to_s if type
281
+ offense
282
+ end
283
+
284
+ # @private
285
+ # @param [Docscribe::CLI::Formatters::change] change
286
+ # @return [String, nil]
287
+ def offense_source(change)
288
+ change[:source] || change['source']
289
+ end
290
+
246
291
  # Build location hash from change.
247
292
  #
248
293
  # @private
249
294
  # @param [Docscribe::CLI::Formatters::change] change change info hash
250
- # @return [Hash<Symbol, Integer>]
295
+ # @return [Docscribe::CLI::Formatters::Json::json_location]
251
296
  def location_for(change)
252
297
  line = change[:line] || 1
253
298
  { start_line: line, start_column: 1, last_line: line, last_column: 1 }
@@ -24,7 +24,8 @@ module Docscribe
24
24
  insert_full_doc_block: 'note',
25
25
  unsorted_tags: 'note',
26
26
  updated_param: 'warning',
27
- updated_return: 'warning'
27
+ updated_return: 'warning',
28
+ invalid_type: 'warning'
28
29
  }.freeze
29
30
 
30
31
  COP_NAME_MAP = {
@@ -36,7 +37,8 @@ module Docscribe
36
37
  insert_full_doc_block: 'Docscribe/MissingDocBlock',
37
38
  unsorted_tags: 'Docscribe/UnsortedTags',
38
39
  updated_param: 'Docscribe/UpdatedParam',
39
- updated_return: 'Docscribe/UpdatedReturn'
40
+ updated_return: 'Docscribe/UpdatedReturn',
41
+ invalid_type: 'Docscribe/InvalidType'
40
42
  }.freeze
41
43
 
42
44
  SARIF_SCHEMA = 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/' \
@@ -159,12 +161,27 @@ module Docscribe
159
161
  # @param [String?] level
160
162
  # @return [Hash<Symbol, Object>]
161
163
  def build_result(change, path, level: nil)
162
- {
164
+ result = {
163
165
  ruleId: cop_name_for(change),
164
166
  level: level || SEVERITY_MAP[change[:type]] || 'note',
165
167
  message: { text: message_for(change) },
166
168
  locations: [location(path, change[:line] || 1)]
167
169
  }
170
+ properties = result_properties(change)
171
+ result[:properties] = properties unless properties.empty?
172
+ result
173
+ end
174
+
175
+ # @private
176
+ # @param [Docscribe::CLI::Formatters::change] change
177
+ # @return [Hash<Symbol, String>]
178
+ def result_properties(change)
179
+ source = change[:source] || change['source']
180
+ type = change[:type] || change['type']
181
+ properties = {}
182
+ properties[:source] = source if source
183
+ properties[:type] = type.to_s if type
184
+ properties
168
185
  end
169
186
 
170
187
  # @private
@@ -28,7 +28,8 @@ module Docscribe
28
28
  no_boilerplate: false,
29
29
  progress: false,
30
30
  parallel: false,
31
- server: false
31
+ server: false,
32
+ validate_types: nil #: true or false when --[no-]validate-types is passed, nil otherwise
32
33
  }.freeze
33
34
 
34
35
  module_function
@@ -39,7 +40,7 @@ module Docscribe
39
40
  docscribe generate <type> <name> [options]
40
41
  docscribe sigs [options] [files...]
41
42
  docscribe rbs [options] [files...]
42
- docscribe update_types [directory]
43
+ docscribe update_types [directory|file]
43
44
  docscribe check_for_comments [paths...]
44
45
 
45
46
  Default behavior:
@@ -63,6 +64,7 @@ module Docscribe
63
64
  --sorbet Use Sorbet signatures from inline sigs / RBI files when available
64
65
  --rbi-dir DIR Add a Sorbet RBI directory (repeatable). Implies --sorbet.
65
66
  --rbs-collection Auto-discover RBS collection from rbs_collection.lock.yaml. Implies --rbs.
67
+ --validate-types Validate YARD types against inferred/RBS types and report mismatches
66
68
 
67
69
  Filtering:
68
70
  --include PATTERN Include PATTERN (method id or file path; glob or /regex/)
@@ -202,6 +204,7 @@ module Docscribe
202
204
  define_sorbet_option(opts, options)
203
205
  define_rbi_dir_option(opts, options)
204
206
  define_rbs_collection_option(opts, options)
207
+ define_validate_types_option(opts, options)
205
208
  end
206
209
 
207
210
  # Define rbs option
@@ -267,6 +270,18 @@ module Docscribe
267
270
  end
268
271
  end
269
272
 
273
+ # Define validate types option
274
+ #
275
+ # @note module_function: defines #define_validate_types_option (visibility: private)
276
+ # @param [OptionParser] opts the option parser to configure
277
+ # @param [Hash<Symbol, Object>] options mutable parsed options hash
278
+ # @return [void]
279
+ def define_validate_types_option(opts, options)
280
+ opts.on('--[no-]validate-types', 'Validate YARD types against inferred/RBS types and report mismatches') do |value|
281
+ options[:validate_types] = value
282
+ end
283
+ end
284
+
270
285
  # Define filter options
271
286
  #
272
287
  # @note module_function: defines #define_filter_options (visibility: private)
@@ -29,7 +29,7 @@ module Docscribe
29
29
  # @!attribute [rw] options
30
30
  # @return [Array<Docscribe::CLI::RbsGen::ParamTag>]
31
31
  # @param [Array<Docscribe::CLI::RbsGen::ParamTag>] value
32
- YardTags = Struct.new(:params, :return_type, :options, keyword_init: true)
32
+ YardTags = Struct.new(:params, :return_type, :options, keyword_init: true) #: Class[YardTags]
33
33
  # @!attribute [rw] name
34
34
  # @return [String]
35
35
  # @param [String] value
@@ -37,7 +37,7 @@ module Docscribe
37
37
  # @!attribute [rw] type
38
38
  # @return [String]
39
39
  # @param [String] value
40
- ParamTag = Struct.new(:name, :type, keyword_init: true)
40
+ ParamTag = Struct.new(:name, :type, keyword_init: true) #: Class[ParamTag]
41
41
  # @!attribute [rw] name
42
42
  # @return [Symbol]
43
43
  # @param [Symbol] value
@@ -61,7 +61,7 @@ module Docscribe
61
61
  # @!attribute [rw] yard_tags
62
62
  # @return [Docscribe::CLI::RbsGen::YardTags?]
63
63
  # @param [Docscribe::CLI::RbsGen::YardTags?] value
64
- MethodDef = Struct.new(:name, :scope, :container, :file, :line, :yard_tags, keyword_init: true)
64
+ MethodDef = Struct.new(:name, :scope, :container, :file, :line, :yard_tags, keyword_init: true) #: Class[MethodDef]
65
65
  # @!attribute [rw] containers
66
66
  # @return [Array<String>]
67
67
  # @param [Array<String>] value
@@ -86,7 +86,7 @@ module Docscribe
86
86
  # @return [Boolean]
87
87
  # @param [Boolean] value
88
88
  WalkContext = Struct.new(:containers, :method_defs, :path, :comment_map, :src_lines, :inside_sclass,
89
- keyword_init: true)
89
+ keyword_init: true) #: Class[WalkContext]
90
90
 
91
91
  class << self
92
92
  # @param [Array<String>] argv
@@ -42,6 +42,7 @@ module Docscribe
42
42
  include exclude include_file exclude_file
43
43
  rbs rbs_collection sig_dirs
44
44
  sorbet rbi_dirs
45
+ validate_types
45
46
  ].freeze
46
47
 
47
48
  # Run Docscribe for files or STDIN using the selected mode and strategy.
@@ -145,7 +146,7 @@ module Docscribe
145
146
  #
146
147
  # @param [Docscribe::CLI::Formatters::opts] options parsed CLI options
147
148
  # @param [Docscribe::Config] conf effective config
148
- # @return [Hash<Symbol, Object>] rewrite result with :output key
149
+ # @return [Docscribe::CLI::Run::rewrite_result] rewrite result with :output key
149
150
  def stdin_rewrite_result(options, conf)
150
151
  Docscribe::InlineRewriter.rewrite_with_report(
151
152
  $stdin.read,
@@ -243,7 +244,7 @@ module Docscribe
243
244
  # @param [Hash<String, Object>] result server result with :changed and :changes keys
244
245
  # @param [Array<Docscribe::CLI::Formatters::change>] file_changes change records
245
246
  # @param [String] path file path
246
- # @param [Object] ctx context hash with :display_path, :options, :state keys
247
+ # @param [Docscribe::CLI::Run::run_ctx] ctx context hash with :display_path, :options, :state keys
247
248
  # @return [void]
248
249
  def dispatch_server_result(result, file_changes, path, **ctx)
249
250
  if ctx[:options][:mode] == :check
@@ -326,14 +327,17 @@ module Docscribe
326
327
  #
327
328
  # @param [Hash<String, Object>] change change record from server
328
329
  # @return [Docscribe::CLI::Formatters::change]
329
- def symbolize_change(change)
330
- {
330
+ def symbolize_change(change) # steep:ignore
331
+ hash = {
331
332
  type: change['type'].to_sym,
332
333
  file: change['file'],
333
334
  line: change['line'],
334
335
  method: change['method'],
335
- message: change['message']
336
+ message: change['message'],
337
+ source: change['source']
336
338
  }
339
+ hash[:param] = change['param'] if change['param']
340
+ hash.compact
337
341
  end
338
342
 
339
343
  # Expand CLI path arguments into a sorted list of Ruby files.
@@ -416,8 +420,8 @@ module Docscribe
416
420
  run_exit_code(options, state)
417
421
  end
418
422
 
419
- # @param [Object] options
420
- # @param [Object] conf
423
+ # @param [Docscribe::CLI::Formatters::opts] options
424
+ # @param [Docscribe::Config] conf
421
425
  # @param [Array<String>] paths
422
426
  # @param [Docscribe::CLI::Formatters::state] state
423
427
  # @return [void]
@@ -430,7 +434,7 @@ module Docscribe
430
434
  thread_count.times.map { parallel_worker(pool) }.each(&:join)
431
435
  end
432
436
 
433
- # @param [Hash<Symbol, Object>] pool
437
+ # @param [Docscribe::CLI::Run::pool] pool
434
438
  # @return [Thread]
435
439
  def parallel_worker(pool)
436
440
  Thread.new do
@@ -451,7 +455,7 @@ module Docscribe
451
455
  end
452
456
 
453
457
  # @param [String] path
454
- # @param [Hash<Symbol, Object>] pool
458
+ # @param [Docscribe::CLI::Run::pool] pool
455
459
  # @raise [StandardError]
456
460
  # @return [void]
457
461
  # @return [Object] if StandardError
@@ -605,7 +609,7 @@ module Docscribe
605
609
  return unless result
606
610
 
607
611
  dispatch_file_result(path, src: src, out: result[:output], file_changes: result[:changes] || [],
608
- display_path: display_path, options: options, state: state)
612
+ display_path: display_path, options: options, state: state, conf: conf)
609
613
  end
610
614
 
611
615
  # Print progress indicator to stderr when --progress is active.
@@ -629,7 +633,7 @@ module Docscribe
629
633
  # @param [String] src original source code
630
634
  # @param [String] out rewritten source code
631
635
  # @param [Array<Docscribe::CLI::Formatters::change>] file_changes structured change records
632
- # @param [Object] ctx context hash with :options, :state, :display_path, :conf
636
+ # @param [Docscribe::CLI::Run::run_ctx] ctx context hash with :options, :state, :display_path, :conf
633
637
  # @return [void]
634
638
  def dispatch_file_result(path, src:, out:, file_changes:, **ctx)
635
639
  if ctx[:options][:mode] == :check
@@ -687,9 +691,9 @@ module Docscribe
687
691
  # @private
688
692
  # @param [String] path file path
689
693
  # @param [String] src source code
690
- # @param [Hash<Symbol, Object>] ctx context hash with :conf, :display_path, :options, :state keys
694
+ # @param [Docscribe::CLI::Run::run_ctx] ctx context hash with :conf, :display_path, :options, :state keys
691
695
  # @raise [StandardError]
692
- # @return [Hash<Symbol, Object>, nil]
696
+ # @return [Docscribe::CLI::Run::rewrite_result, nil]
693
697
  # @return [nil] if StandardError
694
698
  def rewrite_result_for_path(path, src:, ctx:)
695
699
  conf = ctx[:conf]
@@ -710,7 +714,7 @@ module Docscribe
710
714
  # @private
711
715
  # @param [String] path file path that caused the error
712
716
  # @param [StandardError] error the exception raised during rewriting
713
- # @param [Hash<Symbol, Object>] ctx context hash with :state, :options, :display_path
717
+ # @param [Docscribe::CLI::Run::run_ctx] ctx context hash with :state, :options, :display_path
714
718
  # @return [void]
715
719
  def record_rewrite_error(path, error, ctx)
716
720
  state = ctx[:state]
@@ -733,18 +737,97 @@ module Docscribe
733
737
  # @param [String] src original source code
734
738
  # @param [String] out rewritten source code
735
739
  # @param [Array<Docscribe::CLI::Formatters::change>] file_changes structured change records
736
- # @param [Object] ctx context hash with :display_path, :options, :state keys
740
+ # @param [Docscribe::CLI::Run::run_ctx] ctx context hash with :display_path, :options, :state keys
737
741
  # @return [void]
738
742
  def handle_check_result(path, src:, out:, file_changes:, **ctx)
739
743
  type_mismatches = type_mismatch_changes(file_changes)
740
- has_real_changes = file_changes.any? { |c| !%i[updated_param updated_return].include?(c[:type]) }
744
+ has_real_changes = real_changes?(file_changes)
745
+ return handle_validated_type_mismatch(path, file_changes, type_mismatches, ctx) if validated_mismatch?(ctx, type_mismatches, out, src, has_real_changes)
746
+ return handle_no_changes(path, type_mismatches, ctx) if no_real_changes?(out, src, has_real_changes)
741
747
 
742
- if out == src && !has_real_changes
743
- handle_check_no_changes(path, type_mismatches: type_mismatches, display_path: ctx[:display_path],
744
- options: ctx[:options], state: ctx[:state])
745
- return
746
- end
748
+ handle_failed_check(path, file_changes, ctx)
749
+ end
750
+
751
+ # @private
752
+ # @param [Array<Docscribe::CLI::Formatters::change>] file_changes
753
+ # @return [Boolean]
754
+ def real_changes?(file_changes)
755
+ file_changes.any? { |c| !%i[updated_param updated_return invalid_type].include?(c[:type]) }
756
+ end
757
+
758
+ # @private
759
+ # @param [Docscribe::CLI::Run::run_ctx] ctx
760
+ # @param [Array<Docscribe::CLI::Formatters::change>] type_mismatches
761
+ # @param [String] out
762
+ # @param [String] src
763
+ # @param [Boolean] has_real_changes
764
+ # @return [Boolean]
765
+ def validated_mismatch?(ctx, type_mismatches, out, src, has_real_changes)
766
+ validate_types_enabled?(ctx) && type_mismatches.any? && out == src && !has_real_changes
767
+ end
768
+
769
+ # @private
770
+ # @param [Docscribe::CLI::Run::run_ctx] ctx
771
+ # @return [Boolean]
772
+ def validate_types_enabled?(ctx)
773
+ return true if ctx[:options][:validate_types] == true
774
+ return false if ctx[:options][:validate_types] == false
747
775
 
776
+ ctx[:conf].respond_to?(:validate_types?) && ctx[:conf].validate_types?
777
+ end
778
+
779
+ # @private
780
+ # @param [String] out
781
+ # @param [String] src
782
+ # @param [Boolean] has_real_changes
783
+ # @return [Boolean]
784
+ def no_real_changes?(out, src, has_real_changes)
785
+ out == src && !has_real_changes
786
+ end
787
+
788
+ # @private
789
+ # @param [String] path
790
+ # @param [Array<Docscribe::CLI::Formatters::change>] file_changes
791
+ # @param [Array<Docscribe::CLI::Formatters::change>] type_mismatches
792
+ # @param [Docscribe::CLI::Run::run_ctx] ctx
793
+ # @return [void]
794
+ def handle_validated_type_mismatch(path, file_changes, type_mismatches, ctx)
795
+ handle_failed_check(path, file_changes, ctx)
796
+ # Drop the records the tracker already holds: the same mismatch must
797
+ # not appear (and count) twice in JSON output.
798
+ stored = ctx[:state][:fail_changes][path] || []
799
+ ctx[:state][:fail_changes][path] = stored - type_mismatches
800
+ track_validated_mismatch(type_mismatches, ctx, path)
801
+ end
802
+
803
+ # @private
804
+ # @param [Array<Docscribe::CLI::Formatters::change>] type_mismatches
805
+ # @param [Docscribe::CLI::Run::run_ctx] ctx
806
+ # @param [String] path raw file path, used as the map key so fail and
807
+ # mismatch entries merge in JSON output
808
+ # @return [void]
809
+ def track_validated_mismatch(type_mismatches, ctx, path)
810
+ state = ctx[:state]
811
+ state[:type_mismatch_paths] << path unless state[:type_mismatch_paths].include?(path)
812
+ state[:type_mismatch_changes][path] = type_mismatches
813
+ end
814
+
815
+ # @private
816
+ # @param [String] path
817
+ # @param [Array<Docscribe::CLI::Formatters::change>] type_mismatches
818
+ # @param [Docscribe::CLI::Run::run_ctx] ctx
819
+ # @return [void]
820
+ def handle_no_changes(path, type_mismatches, ctx)
821
+ handle_check_no_changes(path, type_mismatches: type_mismatches, display_path: ctx[:display_path],
822
+ options: ctx[:options], state: ctx[:state])
823
+ end
824
+
825
+ # @private
826
+ # @param [String] path
827
+ # @param [Array<Docscribe::CLI::Formatters::change>] file_changes
828
+ # @param [Docscribe::CLI::Run::run_ctx] ctx
829
+ # @return [void]
830
+ def handle_failed_check(path, file_changes, ctx)
748
831
  handle_check_failed(path, file_changes: file_changes, display_path: ctx[:display_path],
749
832
  options: ctx[:options], state: ctx[:state])
750
833
  end
@@ -755,7 +838,7 @@ module Docscribe
755
838
  # @param [Array<Docscribe::CLI::Formatters::change>] file_changes structured change records
756
839
  # @return [Array<Docscribe::CLI::Formatters::change>]
757
840
  def type_mismatch_changes(file_changes)
758
- file_changes.select { |c| %i[updated_param updated_return].include?(c[:type]) }
841
+ file_changes.select { |c| %i[updated_param updated_return invalid_type].include?(c[:type]) }
759
842
  end
760
843
 
761
844
  # Handle check result when there are no real changes.
@@ -809,7 +892,7 @@ module Docscribe
809
892
  # @param [String] src original source code
810
893
  # @param [String] out rewritten source code
811
894
  # @param [Array<Docscribe::CLI::Formatters::change>] file_changes structured change records
812
- # @param [Object] ctx context hash with :display_path, :options, :state keys
895
+ # @param [Docscribe::CLI::Run::run_ctx] ctx context hash with :display_path, :options, :state keys
813
896
  # @raise [StandardError]
814
897
  # @return [void]
815
898
  # @return [Object] if StandardError
@@ -827,7 +910,7 @@ module Docscribe
827
910
  # @param [String] path file path
828
911
  # @param [String] out rewritten source code
829
912
  # @param [Array<Docscribe::CLI::Formatters::change>] file_changes structured change records
830
- # @param [Hash<Symbol, Object>] ctx context hash with :display_path, :options, :state keys
913
+ # @param [Docscribe::CLI::Run::run_ctx] ctx context hash with :display_path, :options, :state keys
831
914
  # @return [void]
832
915
  def apply_correction(path, out, file_changes, ctx)
833
916
  File.write(path, out)