docscribe 1.6.1 → 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 (38) 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 +46 -15
  27. data/lib/docscribe/server/client.rb +20 -11
  28. data/lib/docscribe/server/daemon.rb +200 -23
  29. data/lib/docscribe/server/protocol.rb +4 -4
  30. data/lib/docscribe/types/primitive.rb +160 -0
  31. data/lib/docscribe/types/sorbet/base_provider.rb +33 -1
  32. data/lib/docscribe/types/yard/formatter.rb +35 -6
  33. data/lib/docscribe/types/yard/parser.rb +25 -20
  34. data/lib/docscribe/types/yard/validator.rb +131 -0
  35. data/lib/docscribe/validator/generic_compatibility.rb +698 -0
  36. data/lib/docscribe/validator/type_mismatch_validator.rb +287 -0
  37. data/lib/docscribe/version.rb +1 -1
  38. metadata +8 -3
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'docscribe/types/yard/validator'
4
+ require 'docscribe/infer/constants'
5
+ require 'docscribe/validator/generic_compatibility'
6
+
7
+ module Docscribe
8
+ module Validator
9
+ # Compares YARD-documented types against inferred / external types.
10
+ #
11
+ # Lightweight, in-process checks only:
12
+ # - last expression / literal inference (already in `Infer`)
13
+ # - `core_rbs_provider` for stdlib sends (`String#to_i` etc.)
14
+ # - `external_sig` (RBS/Sorbet) when available and `--validate-types` is on
15
+ #
16
+ # Heavy inter-procedural `send("foo")` graph is out of scope for this MVP.
17
+ # When `expected` is `Object` (fallback) we silence to avoid false positives.
18
+ class TypeMismatchValidator
19
+ # @!attribute [rw] type
20
+ # @return [Symbol]
21
+ # @param [Symbol] value
22
+ #
23
+ # @!attribute [rw] yard_type
24
+ # @return [String?]
25
+ # @param [String?] value
26
+ #
27
+ # @!attribute [rw] expected_type
28
+ # @return [String?]
29
+ # @param [String?] value
30
+ #
31
+ # @!attribute [rw] message
32
+ # @return [String]
33
+ # @param [String] value
34
+ #
35
+ # @!attribute [rw] source
36
+ # @return [String?]
37
+ # @param [String?] value
38
+ Result = Struct.new(:type, :yard_type, :expected_type, :message, :source, keyword_init: true)
39
+
40
+ # @param [String] fallback_type value of `inference.fallback_type` (default `Object`)
41
+ # @return [void]
42
+ def initialize(fallback_type: Infer::FALLBACK_TYPE)
43
+ @fallback_type = fallback_type.to_s
44
+ end
45
+
46
+ # Whether a documented param type mismatches the expected one.
47
+ #
48
+ # @param [String, nil] yard_type type from `@param [...]`
49
+ # @param [String, nil] expected_type type from `external_sig` or `Infer`
50
+ # @param [String, Symbol, nil] method_name method name for void compatibility
51
+ # @return [Boolean]
52
+ def mismatched_param?(yard_type, expected_type, method_name: nil)
53
+ mismatched_return?(yard_type, expected_type, method_name: method_name)
54
+ end
55
+
56
+ # Whether a documented return type mismatches the expected one.
57
+ #
58
+ # @param [String, nil] yard_type type from `@return [...]`
59
+ # @param [String, nil] expected_type inferred or external `normal_type`
60
+ # @param [String, Symbol, nil] method_name method name for void compatibility
61
+ # @return [Boolean]
62
+ def mismatched_return?(yard_type, expected_type, method_name: nil)
63
+ return false if blank_type?(yard_type)
64
+ return false if blank_type?(expected_type)
65
+ return false if expected_suppressed?(expected_type)
66
+ return false if yard_compatible?(yard_type, expected_type, method_name: method_name)
67
+ return false if types_normalized_equal?(yard_type, expected_type)
68
+
69
+ !normalized_equal?(yard_type, expected_type)
70
+ end
71
+
72
+ # Whether YARD type is generic compatible with expected (e.g. Hash vs Hash<Symbol, String>).
73
+ # Delegates to GenericCompatibility service for dynamic, map-dispatched checks.
74
+ #
75
+ # @param [String, nil] yard_type
76
+ # @param [String, nil] expected_type
77
+ # @param [String, Symbol, nil] method_name method name for void compatibility threading
78
+ # @return [Boolean]
79
+ def generic_compatible?(yard_type, expected_type, method_name: nil)
80
+ GenericCompatibility.compatible?(yard_type, expected_type, fallback_type: @fallback_type, method_name: method_name)
81
+ end
82
+
83
+ # Whether yard type is included in expected union.
84
+ #
85
+ # @param [String, nil] yard_type
86
+ # @param [String, nil] expected_type
87
+ # @return [Boolean]
88
+ def yard_in_expected_union?(yard_type, expected_type)
89
+ return false if yard_type.nil? || expected_type.nil?
90
+
91
+ normalized_yard = normalize(yard_type)
92
+ expected_type.split(',').any? { |part| normalize(part) == normalized_yard }
93
+ end
94
+
95
+ # Whether void YARD type is compatible with fallback union or initialize/setup dynamic.
96
+ #
97
+ # @param [String, nil] yard_type
98
+ # @param [String, nil] expected_type
99
+ # @param [String, Symbol, nil] method_name method name for dynamic check
100
+ # @return [Boolean]
101
+ def void_compatible?(yard_type, expected_type, method_name: nil)
102
+ GenericCompatibility.void_compatible?(yard_type, expected_type, @fallback_type, method_name: method_name)
103
+ end
104
+
105
+ # Whether a type string is a union of only fallback types (with optional `?`).
106
+ #
107
+ # @param [String, nil] type_str
108
+ # @return [Boolean]
109
+ def fallback_union?(type_str)
110
+ return false if type_str.nil? || type_str.strip.empty?
111
+
112
+ fallback_norm = normalize(@fallback_type)
113
+ parts = type_str.to_s.split(',').map { |p| normalize(p.strip.delete_suffix('?').strip) }
114
+ parts.all? { |p| p == fallback_norm || p.empty? }
115
+ end
116
+
117
+ # Whether a YARD type string has invalid syntax (e.g. `Sym bol` leftover).
118
+ #
119
+ # @param [String, nil] yard_type
120
+ # @return [Boolean]
121
+ def invalid_syntax?(yard_type) # rubocop:disable SortedMethodsByCall/Waterfall
122
+ return false if yard_type.nil? || yard_type.strip.empty?
123
+
124
+ !Types::Yard::Validator.valid?(yard_type)
125
+ end
126
+
127
+ # Build a Result for a return mismatch, or nil if no mismatch.
128
+ #
129
+ # @param [String, nil] yard_type
130
+ # @param [String, nil] expected_type
131
+ # @param [String] source source of expected type: "rbs" or "infer"
132
+ # @param [String, Symbol, nil] method_name method name for void compatibility
133
+ # @return [Docscribe::Validator::TypeMismatchValidator::Result, nil]
134
+ def check_return(yard_type, expected_type, source: 'infer', method_name: nil)
135
+ return invalid_return_result(yard_type, expected_type) if invalid_syntax?(yard_type)
136
+ return unless mismatched_return?(yard_type, expected_type, method_name: method_name)
137
+
138
+ mismatch_return_result(yard_type, expected_type, source: source)
139
+ end
140
+
141
+ # Build a Result for a param mismatch, or nil if no mismatch.
142
+ #
143
+ # @param [String] param_name
144
+ # @param [String, nil] yard_type
145
+ # @param [String, nil] expected_type
146
+ # @param [String] source source of expected type: "rbs" or "infer"
147
+ # @param [String, Symbol, nil] method_name method name for void compatibility
148
+ # @return [Docscribe::Validator::TypeMismatchValidator::Result, nil]
149
+ def check_param(param_name, yard_type, expected_type, source: 'infer', method_name: nil)
150
+ return invalid_param_result(param_name, yard_type, expected_type) if invalid_syntax?(yard_type)
151
+ return unless mismatched_param?(yard_type, expected_type, method_name: method_name)
152
+
153
+ mismatch_param_result(param_name, yard_type, expected_type, source: source)
154
+ end
155
+
156
+ # @param [String, nil] yard_type
157
+ # @param [String, nil] expected_type
158
+ # @return [Docscribe::Validator::TypeMismatchValidator::Result]
159
+ def invalid_return_result(yard_type, expected_type)
160
+ Result.new(
161
+ type: :invalid_syntax,
162
+ yard_type: yard_type,
163
+ expected_type: expected_type,
164
+ message: "invalid YARD type [#{yard_type}]#{" expected [#{expected_type}]" if expected_type && expected_type != @fallback_type}",
165
+ source: 'syntax'
166
+ )
167
+ end
168
+
169
+ # @param [String, nil] yard_type
170
+ # @param [String, nil] expected_type
171
+ # @param [String] source
172
+ # @return [Docscribe::Validator::TypeMismatchValidator::Result]
173
+ def mismatch_return_result(yard_type, expected_type, source: 'infer')
174
+ Result.new(
175
+ type: :type_mismatch_return,
176
+ yard_type: yard_type,
177
+ expected_type: expected_type,
178
+ message: "updated @return from #{yard_type} to #{expected_type}",
179
+ source: source
180
+ )
181
+ end
182
+
183
+ # @param [String] param_name
184
+ # @param [String, nil] yard_type
185
+ # @param [String, nil] expected_type
186
+ # @return [Docscribe::Validator::TypeMismatchValidator::Result]
187
+ def invalid_param_result(param_name, yard_type, expected_type)
188
+ Result.new(
189
+ type: :invalid_syntax,
190
+ yard_type: yard_type,
191
+ expected_type: expected_type,
192
+ message: "invalid YARD type [#{yard_type}] for @param #{param_name}#{" expected [#{expected_type}]" if expected_type && expected_type != @fallback_type}",
193
+ source: 'syntax'
194
+ )
195
+ end
196
+
197
+ # @param [String] param_name
198
+ # @param [String, nil] yard_type
199
+ # @param [String, nil] expected_type
200
+ # @param [String] source
201
+ # @return [Docscribe::Validator::TypeMismatchValidator::Result]
202
+ def mismatch_param_result(param_name, yard_type, expected_type, source: 'infer')
203
+ Result.new(
204
+ type: :type_mismatch_param,
205
+ yard_type: yard_type,
206
+ expected_type: expected_type,
207
+ message: "updated @param #{param_name} from #{yard_type} to #{expected_type}",
208
+ source: source
209
+ )
210
+ end
211
+
212
+ private
213
+
214
+ # Whether type string blank (nil or whitespace).
215
+ #
216
+ # @private
217
+ # @param [String, nil] type_str
218
+ # @return [Boolean]
219
+ def blank_type?(type_str)
220
+ type_str.nil? || type_str.strip.empty?
221
+ end
222
+
223
+ # Whether expected suppressed as fallback (uncertain).
224
+ #
225
+ # @private
226
+ # @param [String, nil] expected_type
227
+ # @return [Boolean]
228
+ def expected_suppressed?(expected_type)
229
+ normalize(expected_type) == @fallback_type || fallback_union?(expected_type)
230
+ end
231
+
232
+ # Whether yard compatible with expected via void/union/generic.
233
+ #
234
+ # @private
235
+ # @param [String, nil] yard_type
236
+ # @param [String, nil] expected_type
237
+ # @param [String, Symbol, nil] method_name method name for void compatibility
238
+ # @return [Boolean]
239
+ def yard_compatible?(yard_type, expected_type, method_name: nil)
240
+ void_compatible?(yard_type, expected_type, method_name: method_name) ||
241
+ yard_in_expected_union?(yard_type, expected_type) ||
242
+ generic_compatible?(yard_type, expected_type, method_name: method_name)
243
+ end
244
+
245
+ # Whether types equal after normalization (including optional "?").
246
+ #
247
+ # @private
248
+ # @param [String, nil] yard_type
249
+ # @param [String, nil] expected_type
250
+ # @return [Boolean]
251
+ def types_normalized_equal?(yard_type, expected_type)
252
+ normalized_equal?(yard_type, expected_type) || optional_normalized_equal?(yard_type, expected_type)
253
+ end
254
+
255
+ # Whether normalized types equal.
256
+ #
257
+ # @private
258
+ # @param [String, nil] yard_type
259
+ # @param [String, nil] expected_type
260
+ # @return [Boolean]
261
+ def normalized_equal?(yard_type, expected_type)
262
+ normalize(yard_type) == normalize(expected_type)
263
+ end
264
+
265
+ # Whether optional-normalized types equal.
266
+ #
267
+ # @private
268
+ # @param [String, nil] yard_type
269
+ # @param [String, nil] expected_type
270
+ # @return [Boolean]
271
+ def optional_normalized_equal?(yard_type, expected_type)
272
+ normalize(yard_type).delete_suffix('?') == normalize(expected_type).delete_suffix('?')
273
+ end
274
+
275
+ # Normalize a type string for comparison (strip, squeeze spaces, unify RBS/YARD syntax).
276
+ #
277
+ # @private
278
+ # @param [String, nil] type_str
279
+ # @return [String]
280
+ def normalize(type_str)
281
+ s = type_str.to_s
282
+ s = s.sub(/#.*\z/m, '').strip unless s.lstrip.start_with?('#')
283
+ s.strip.squeeze(' ').gsub('[', '<').gsub(']', '>').gsub(/\buntyped\b/, 'Object').gsub(/\bFALLBACK_TYPE\b/, 'Object')
284
+ end
285
+ end
286
+ end
287
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Docscribe
4
- VERSION = '1.6.1'
4
+ VERSION = '1.6.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docscribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - unurgunite
@@ -188,6 +188,7 @@ files:
188
188
  - lib/docscribe/config/sorting.rb
189
189
  - lib/docscribe/config/template.rb
190
190
  - lib/docscribe/config/utils.rb
191
+ - lib/docscribe/config/validation.rb
191
192
  - lib/docscribe/infer.rb
192
193
  - lib/docscribe/infer/ast_walk.rb
193
194
  - lib/docscribe/infer/behavior.rb
@@ -217,6 +218,7 @@ files:
217
218
  - lib/docscribe/server/daemon.rb
218
219
  - lib/docscribe/server/protocol.rb
219
220
  - lib/docscribe/types/overload_selector.rb
221
+ - lib/docscribe/types/primitive.rb
220
222
  - lib/docscribe/types/provider_chain.rb
221
223
  - lib/docscribe/types/rbs/collection_loader.rb
222
224
  - lib/docscribe/types/rbs/provider.rb
@@ -228,6 +230,9 @@ files:
228
230
  - lib/docscribe/types/yard/formatter.rb
229
231
  - lib/docscribe/types/yard/parser.rb
230
232
  - lib/docscribe/types/yard/types.rb
233
+ - lib/docscribe/types/yard/validator.rb
234
+ - lib/docscribe/validator/generic_compatibility.rb
235
+ - lib/docscribe/validator/type_mismatch_validator.rb
231
236
  - lib/docscribe/version.rb
232
237
  homepage: https://github.com/unurgunite/docscribe
233
238
  licenses:
@@ -238,7 +243,7 @@ metadata:
238
243
  changelog_uri: https://github.com/unurgunite/docscribe/blob/master/CHANGELOG.md
239
244
  rubygems_mfa_required: 'true'
240
245
  post_install_message: |
241
- You installed docscribe 1.6.1. Your future self (and your team) thank you.
246
+ You installed docscribe 1.6.2. Your future self (and your team) thank you.
242
247
 
243
248
  $ docscribe --help
244
249
 
@@ -258,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
263
  - !ruby/object:Gem::Version
259
264
  version: '0'
260
265
  requirements: []
261
- rubygems_version: 4.0.18
266
+ rubygems_version: 4.0.21
262
267
  specification_version: 4
263
268
  summary: Auto-generate inline YARD documentation for Ruby by analyzing code AST. Supports
264
269
  RBS and Sorbet type signatures.