docscribe 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ccc7ee30955f8dcc7c8d0335788e82878acfdf9136dcf7d956717e972804ffa
4
- data.tar.gz: 24a76c793eb52f4fc5b21ad061e1f659744907ccb0c2b12cd886bd2f9b33c67c
3
+ metadata.gz: 911fd61018509316b42bf5a799232d24be36cb653d5997c4e170dab5db028884
4
+ data.tar.gz: 4ce0e7f0cc78c6b94ce366cb464726e083a6050a42f835e35995c378072507e6
5
5
  SHA512:
6
- metadata.gz: d1173ff834e00563b0d35cb9df4a05dc6a47db9f7f02d9ea8b7191d5eef3a984066c48c754efbe72c533dc830d983e65f64f001a167d897519e9e6d9a7993bf6
7
- data.tar.gz: 4ed3ff6dbd216a5d0d2606170660a78b2492579624ad045757ef42123d92761cb97feda21f62f5b578e8dd2211b1f78a6d6ef73edd8ed50511eb807755857457
6
+ metadata.gz: 107a67dd5c484b840ba5ab85918ec25d53b0ce4adda13e4ef612d3bfa99d4201e82b474cd636f79a60e5e40e5cbaeb8e15c899939b5e74df14f397f4188b4168
7
+ data.tar.gz: 9cac2bb4b0c37b29707f0d6beec99987ab8d0f3d9327b3f44e9684816694e74fced394d88fc493ee6f5dc19ce07b82fc295e38f9c0273faad09976d125103a9e
@@ -14,7 +14,9 @@ module Docscribe
14
14
  # - optional Sorbet integration
15
15
  DEFAULT = {
16
16
  'emit' => {
17
- 'header' => true,
17
+ 'header' => false,
18
+ 'include_default_message' => true,
19
+ 'include_param_documentation' => true,
18
20
  'param_tags' => true,
19
21
  'return_tag' => true,
20
22
  'visibility_tags' => true,
@@ -53,11 +55,12 @@ module Docscribe
53
55
  'exclude' => [],
54
56
  'files' => {
55
57
  'include' => [],
56
- 'exclude' => []
58
+ 'exclude' => ['spec']
57
59
  }
58
60
  },
59
61
  'rbs' => {
60
62
  'enabled' => false,
63
+ 'collection' => false,
61
64
  'sig_dirs' => ['sig'],
62
65
  'collapse_generics' => false
63
66
  },
@@ -11,6 +11,7 @@ module Docscribe
11
11
  # @return [Docscribe::Types::RBS::Provider, nil]
12
12
  def rbs_provider
13
13
  return nil unless rbs_enabled?
14
+ return nil unless ruby_supports_rbs?
14
15
 
15
16
  @rbs_provider ||= begin
16
17
  require 'docscribe/types/rbs/provider'
@@ -30,8 +31,43 @@ module Docscribe
30
31
  fetch_bool(%w[rbs enabled], false)
31
32
  end
32
33
 
34
+ # Method documentation.
35
+ #
36
+ # @raise [LoadError]
37
+ # @return [Object]
38
+ def core_rbs_provider
39
+ return nil unless ruby_supports_rbs?
40
+
41
+ @core_rbs_provider ||= begin
42
+ require 'docscribe/types/rbs/provider'
43
+ Docscribe::Types::RBS::Provider.new(
44
+ sig_dirs: [],
45
+ collapse_generics: false
46
+ )
47
+ rescue LoadError
48
+ nil
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # Method documentation.
55
+ #
56
+ # @private
57
+ # @return [Boolean]
58
+ def ruby_supports_rbs?
59
+ return true if RUBY_VERSION >= '3.0'
60
+
61
+ @rbs_warning_emitted ||= begin
62
+ warn 'Docscribe: RBS requires Ruby 3.0+. Falling back to inference.'
63
+ true
64
+ end
65
+ false
66
+ end
67
+
33
68
  # Signature directories used by the RBS provider.
34
69
  #
70
+ # @private
35
71
  # @return [Array<String>]
36
72
  def rbs_sig_dirs
37
73
  Array(raw.dig('rbs', 'sig_dirs') || DEFAULT.dig('rbs', 'sig_dirs')).map(&:to_s)
@@ -43,7 +79,8 @@ module Docscribe
43
79
  # - `Hash<Symbol, String>` => `Hash`
44
80
  # - `Array<Integer>` => `Array`
45
81
  #
46
- # @return [Boolean]
82
+ # @private
83
+ # @return [Object]
47
84
  def rbs_collapse_generics?
48
85
  fetch_bool(%w[rbs collapse_generics], false)
49
86
  end
@@ -14,187 +14,94 @@ module Docscribe
14
14
  ---
15
15
  # Docscribe configuration file
16
16
  #
17
- # Inspect what safe doc updates would be applied:
18
- # bundle exec docscribe lib
19
- #
20
- # Apply safe doc updates:
21
- # bundle exec docscribe -a lib
22
- #
23
- # Apply aggressive doc updates (rebuild existing doc blocks):
24
- # bundle exec docscribe -A lib
17
+ # Docscribe works without this file create it only for customization.
25
18
  #
19
+ # Quick start:
20
+ # bundle exec docscribe lib # check what would change
21
+ # bundle exec docscribe -a lib # apply safe updates
22
+ # bundle exec docscribe -A lib # rebuild all doc blocks
26
23
 
27
24
  emit:
28
- # Emit the header line:
29
- #
30
- # +MyClass#my_method+ -> ReturnType
31
- header: false
32
-
33
- # Whether to include the default placeholder line:
34
- # # Method documentation.
35
- include_default_message: true
36
-
37
- # Whether to append placeholder text to generated @param tags:
38
- # # @param [String] name Param documentation.
39
- include_param_documentation: true
40
-
41
- # Emit @param tags.
42
- param_tags: true
43
-
44
- # Emit @return tag (can be overridden per scope/visibility under methods:).
45
- return_tag: true
46
-
47
- # Emit @private / @protected tags based on Ruby visibility context.
48
- visibility_tags: true
49
-
50
- # Emit @raise tags inferred from rescue clauses / raise/fail calls.
51
- raise_tags: true
52
-
53
- # Emit conditional rescue return tags:
54
- #
55
- # @return [String] if FooError, BarError
56
- rescue_conditional_returns: true
57
-
58
- # Generate @!attribute docs for attr_reader/attr_writer/attr_accessor.
59
- attributes: false
25
+ # What to include in generated documentation
26
+ header: false # +MyClass#foo+ -> ReturnType
27
+ param_tags: true # @param tags
28
+ return_tag: true # @return tag
29
+ visibility_tags: true # @private / @protected
30
+ raise_tags: true # @raise tags
31
+ rescue_conditional_returns: true # @return [Type] if Error
32
+ attributes: false # @!attribute for attr_*
33
+
34
+ # Placeholder text for generated docs
35
+ include_default_message: true # "Method documentation."
36
+ include_param_documentation: true # "Param documentation."
60
37
 
61
38
  doc:
62
- # Default text inserted into each generated doc block.
39
+ # Default text and formatting
63
40
  default_message: "Method documentation."
64
-
65
- # Default text appended to generated @param tags.
66
41
  param_documentation: "Param documentation."
42
+ param_tag_style: "type_name" # "type_name" or "name_type"
43
+ sort_tags: true
44
+ tag_order: ["todo", "note", "api", "private", "protected", "param", "option", "yieldparam", "raise", "return"]
67
45
 
68
- # Style for generated @param tags:
69
- # - type_name => @param [Type] name
70
- # - name_type => @param name [Type]
71
- param_tag_style: "type_name"
46
+ inference:
47
+ # Type inference behavior
48
+ fallback_type: "Object" # when uncertain
49
+ nil_as_optional: true # String | nil => String?
50
+ treat_options_keyword_as_hash: true # options: keyword => Hash
72
51
 
73
- # Sort generated / merged tags in safe mode when possible.
74
- sort_tags: true
52
+ filter:
53
+ # Which methods and files to process
54
+ # Method format: "Container#method" (instance) or "Container.method" (class)
55
+ # Supports globs ("*#initialize") and regex ("/^MyApp::.*$/")
56
+ include: []
57
+ exclude: []
58
+ visibilities: ["public", "protected", "private"]
59
+ scopes: ["instance", "class"]
75
60
 
76
- # Tag order used when sorting contiguous tag runs.
77
- tag_order: ["todo", "note", "api", "private", "protected", "param", "option", "yieldparam", "raise", "return"]
61
+ files:
62
+ # File paths relative to project root (globs or /regex/)
63
+ include: []
64
+ exclude: ["spec"]
78
65
 
79
66
  methods:
80
- # Per-scope / per-visibility overrides.
67
+ # Override defaults per scope and visibility.
68
+ # Empty {} means "use values from `doc` section".
81
69
  #
82
70
  # Example:
83
- # methods:
84
71
  # instance:
85
72
  # public:
86
73
  # default_message: "Public API."
87
- # return_tag: true
74
+ # private:
75
+ # return_tag: false
88
76
  instance:
89
77
  public: {}
90
78
  protected: {}
91
79
  private: {}
92
-
93
80
  class:
94
81
  public: {}
95
82
  protected: {}
96
83
  private: {}
97
84
 
98
- inference:
99
- # Type used when inference is uncertain.
100
- fallback_type: "Object"
101
-
102
- # Whether nil unions become optional types (for example String | nil => String?).
103
- nil_as_optional: true
104
-
105
- # Special-case: treat keyword arg named options/options: as a Hash.
106
- treat_options_keyword_as_hash: true
107
-
108
- filter:
109
- # Filter which methods Docscribe touches.
110
- #
111
- # Method id format:
112
- # instance: "MyModule::MyClass#instance_method"
113
- # class: "MyModule::MyClass.class_method"
114
- #
115
- # Patterns:
116
- # - glob: "*#initialize", "MyApp::*#*"
117
- # - regex: "/^MyApp::.*#(foo|bar)$/"
118
- #
119
- # Semantics:
120
- # - scopes / visibilities act as allow-lists
121
- # - exclude wins
122
- # - if include is empty => include everything (subject to allow-lists)
123
- visibilities: ["public", "protected", "private"]
124
- scopes: ["instance", "class"]
125
- include: []
126
- exclude: []
127
-
128
- files:
129
- # Filter which files Docscribe processes (paths are matched relative
130
- # to the project root).
131
- #
132
- # Tips:
133
- # - Use directory shorthand to exclude a whole directory:
134
- # exclude: ["spec"]
135
- # - Or use globs:
136
- # exclude: ["spec/**/*.rb", "vendor/**/*.rb"]
137
- include: []
138
- exclude: ["spec"]
139
-
140
- plugins:
141
- # Load custom plugins by path or gem name.
142
- #
143
- # Each entry is passed to `require`. Registration happens inside
144
- # the required file via Docscribe::Plugin::Registry.register.
145
- #
146
- # Example:
147
- # require:
148
- # - ./docscribe_plugins
149
- # - docscribe-rails-associations
150
- require: []
151
-
152
85
  rbs:
153
- # Optional: use RBS signatures to improve @param / @return types.
154
- #
155
- # CLI equivalent:
156
- # bundle exec docscribe -a --rbs --sig-dir sig lib
157
- #
158
- # Under Bundler, you may need `gem "rbs"` in your Gemfile (or a
159
- # Gemfile that includes it), otherwise `require "rbs"` may fail and
160
- # Docscribe will fall back to inference.
86
+ # Use RBS signatures for better types (requires `gem "rbs"`)
161
87
  enabled: false
162
-
163
- # Signature directories (repeatable via --sig-dir).
164
88
  sig_dirs: ["sig"]
165
-
166
- # If true, simplify generic types:
167
- # - Hash<Symbol, String> => Hash
168
- # - Array<Integer> => Array
169
- collapse_generics: false
170
- # Auto-discover RBS collection from rbs_collection.lock.yaml.
171
- # Equivalent to --rbs-collection CLI flag.
172
- # Requires `bundle exec rbs collection install` to have been run.
173
- #
174
- collection: false
89
+ collapse_generics: false # Hash<Symbol, String> => Hash
90
+ collection: false # auto-discover from rbs_collection.lock.yaml
175
91
 
176
92
  sorbet:
177
- # Optional: use Sorbet signatures from inline `sig` declarations and
178
- # RBI files to improve @param / @return types.
179
- #
180
- # CLI equivalent:
181
- # bundle exec docscribe -a --sorbet --rbi-dir sorbet/rbi lib
182
- #
183
- # Sorbet resolution order is:
184
- # 1. inline `sig` in the current source file
185
- # 2. RBI files
186
- # 3. RBS
187
- # 4. AST inference
93
+ # Use Sorbet inline sigs and RBI files for better types
188
94
  enabled: false
189
-
190
- # RBI directories scanned recursively for `.rbi` files
191
- # (repeatable via --rbi-dir).
192
95
  rbi_dirs: ["sorbet/rbi", "rbi"]
193
-
194
- # If true, simplify generic types:
195
- # - Hash<Symbol, String> => Hash
196
- # - Array<Integer> => Array
197
96
  collapse_generics: false
97
+
98
+ plugins:
99
+ # Load custom plugins
100
+ # Example:
101
+ # require:
102
+ # - ./docscribe_plugins
103
+ # - docscribe-rails-associations
104
+ require: []
198
105
  YAML
199
106
  end
200
107
  end
@@ -24,7 +24,9 @@ module Docscribe
24
24
  return FALLBACK_TYPE unless root && %i[def defs].include?(root.type)
25
25
 
26
26
  body = root.children.last
27
- last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true) || FALLBACK_TYPE
27
+ local_var_types = build_local_variable_types(body)
28
+ last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true,
29
+ local_var_types: local_var_types) || FALLBACK_TYPE
28
30
  rescue Parser::SyntaxError
29
31
  FALLBACK_TYPE
30
32
  end
@@ -43,7 +45,9 @@ module Docscribe
43
45
 
44
46
  return FALLBACK_TYPE unless body
45
47
 
46
- last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true) || FALLBACK_TYPE
48
+ local_var_types = build_local_variable_types(body)
49
+ last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true,
50
+ local_var_types: local_var_types) || FALLBACK_TYPE
47
51
  end
48
52
 
49
53
  # Return a structured return-type spec for a method node.
@@ -70,11 +74,16 @@ module Docscribe
70
74
  spec = { normal: FALLBACK_TYPE, rescues: [] }
71
75
  return spec unless body
72
76
 
77
+ local_var_types = build_local_variable_types(body)
78
+
73
79
  if body.type == :rescue
74
80
  main_body = body.children[0]
81
+ rescue_local_var_types = build_local_variable_types(body)
82
+ all_local_var_types = rescue_local_var_types || local_var_types
75
83
  spec[:normal] =
76
84
  last_expr_type(main_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
77
- core_rbs_provider: core_rbs_provider, param_types: param_types) || FALLBACK_TYPE
85
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
86
+ local_var_types: all_local_var_types) || FALLBACK_TYPE
78
87
 
79
88
  body.children.each do |ch|
80
89
  next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
@@ -83,19 +92,50 @@ module Docscribe
83
92
  exc_names = Raises.exception_names_from_rescue_list(exc_list)
84
93
  rtype =
85
94
  last_expr_type(rescue_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
86
- core_rbs_provider: core_rbs_provider, param_types: param_types) ||
95
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
96
+ local_var_types: all_local_var_types) ||
87
97
  fallback_type
88
98
  spec[:rescues] << [exc_names, rtype]
89
99
  end
90
100
  else
91
101
  spec[:normal] =
92
102
  last_expr_type(body, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
93
- core_rbs_provider: core_rbs_provider, param_types: param_types) || FALLBACK_TYPE
103
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
104
+ local_var_types: local_var_types) || FALLBACK_TYPE
94
105
  end
95
106
 
96
107
  spec
97
108
  end
98
109
 
110
+ # Resolve a return type from core RBS for a method call.
111
+ #
112
+ # @note module_function: when included, also defines #resolve_rbs_return_type (instance visibility: private)
113
+ # @private
114
+ # @param [Object] node Param documentation.
115
+ # @return [String] FALLBACK_TYPE if lookup fails
116
+ def build_local_variable_types(node)
117
+ types = {}
118
+ ASTWalk.walk(node) do |n|
119
+ case n.type
120
+ when :lvasgn, :gvasgn, :ivasgn
121
+ name = n.children[0].to_s
122
+ value = n.children[1]
123
+ if value
124
+ inferred = Literals.type_from_literal(value, fallback_type: FALLBACK_TYPE)
125
+ types[name] = inferred if inferred && inferred != FALLBACK_TYPE
126
+ end
127
+ when :casgn
128
+ name = n.children[0].to_s
129
+ value = n.children[2]
130
+ if value
131
+ inferred = Literals.type_from_literal(value, fallback_type: FALLBACK_TYPE)
132
+ types[name] = inferred if inferred && inferred != FALLBACK_TYPE
133
+ end
134
+ end
135
+ end
136
+ types.empty? ? nil : types
137
+ end
138
+
99
139
  # Infer the type of the last expression in a node.
100
140
  #
101
141
  # Supports:
@@ -112,30 +152,37 @@ module Docscribe
112
152
  # @param [Boolean] nil_as_optional whether `nil` unions should be rendered as optional types
113
153
  # @param [Object, nil] core_rbs_provider optional RBS provider for core type lookup
114
154
  # @param [Hash, nil] param_types parameter name -> type map for lvar resolution
155
+ # @param [nil] local_var_types Param documentation.
115
156
  # @return [String, nil]
116
- def last_expr_type(node, fallback_type:, nil_as_optional:, core_rbs_provider: nil, param_types: nil)
157
+ def last_expr_type(node, fallback_type:, nil_as_optional:, core_rbs_provider: nil, param_types: nil,
158
+ local_var_types: nil)
117
159
  return nil unless node
118
160
 
119
161
  case node.type
120
162
  when :begin
121
163
  last_expr_type(node.children.last, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
122
- core_rbs_provider: core_rbs_provider, param_types: param_types)
164
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
165
+ local_var_types: local_var_types)
123
166
 
124
167
  when :if
125
168
  t = last_expr_type(node.children[1], fallback_type: fallback_type, nil_as_optional: nil_as_optional,
126
- core_rbs_provider: core_rbs_provider, param_types: param_types)
169
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
170
+ local_var_types: local_var_types)
127
171
  e = last_expr_type(node.children[2], fallback_type: fallback_type, nil_as_optional: nil_as_optional,
128
- core_rbs_provider: core_rbs_provider, param_types: param_types)
172
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
173
+ local_var_types: local_var_types)
129
174
  unify_types(t, e, fallback_type: fallback_type, nil_as_optional: nil_as_optional)
130
175
 
131
176
  when :case
132
177
  branches = node.children[1..].compact.flat_map do |child|
133
178
  if child.type == :when
134
179
  last_expr_type(child.children.last, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
135
- core_rbs_provider: core_rbs_provider, param_types: param_types)
180
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
181
+ local_var_types: local_var_types)
136
182
  else
137
183
  last_expr_type(child, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
138
- core_rbs_provider: core_rbs_provider, param_types: param_types)
184
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
185
+ local_var_types: local_var_types)
139
186
  end
140
187
  end.compact
141
188
 
@@ -150,6 +197,36 @@ module Docscribe
150
197
  when :return
151
198
  Literals.type_from_literal(node.children.first, fallback_type: fallback_type)
152
199
 
200
+ when :block
201
+ send_node = node.children[0]
202
+ if send_node&.type == :send
203
+ recv = send_node.children[0]
204
+ meth = send_node.children[1]
205
+
206
+ if core_rbs_provider && recv&.type == :lvar
207
+ lvar_name = recv.children.first
208
+ recv_type = nil
209
+ recv_type = local_var_types[lvar_name.to_s] if local_var_types && lvar_name
210
+ recv_type = param_types[lvar_name.to_s] if !recv_type && param_types && lvar_name
211
+ if recv_type
212
+ rbs_type = resolve_rbs_return_type(recv_type, meth, core_rbs_provider)
213
+ return rbs_type unless rbs_type == FALLBACK_TYPE
214
+ end
215
+ elsif core_rbs_provider && recv&.type == :send
216
+ inner_type = last_expr_type(recv, fallback_type: nil, nil_as_optional: false,
217
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
218
+ local_var_types: local_var_types)
219
+ if inner_type
220
+ rbs_type = resolve_rbs_return_type(inner_type, meth, core_rbs_provider)
221
+ return rbs_type unless rbs_type == FALLBACK_TYPE
222
+ end
223
+ end
224
+ end
225
+
226
+ last_expr_type(node.children[2], fallback_type: fallback_type, nil_as_optional: nil_as_optional,
227
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
228
+ local_var_types: local_var_types)
229
+
153
230
  when :send
154
231
  recv = node.children[0]
155
232
  meth = node.children[1]
@@ -158,20 +235,22 @@ module Docscribe
158
235
  if core_rbs_provider && recv&.type == :send
159
236
  # Chained call: arg.to_i.positive?
160
237
  inner_type = last_expr_type(recv, fallback_type: nil, nil_as_optional: false,
161
- core_rbs_provider: core_rbs_provider, param_types: param_types)
238
+ core_rbs_provider: core_rbs_provider, param_types: param_types,
239
+ local_var_types: local_var_types)
162
240
  if inner_type
163
241
  rbs_type = resolve_rbs_return_type(inner_type, meth, core_rbs_provider)
164
242
  return rbs_type unless rbs_type == FALLBACK_TYPE
165
243
  end
244
+
166
245
  elsif core_rbs_provider && recv&.type == :lvar
167
- # Direct call: arg.positive?
246
+ # Direct call on local variable: p1.positive? or admins.any?
168
247
  lvar_name = recv.children.first
169
- if lvar_name && param_types
170
- recv_type = param_types[lvar_name.to_s]
171
- if recv_type
172
- rbs_type = resolve_rbs_return_type(recv_type, meth, core_rbs_provider)
173
- return rbs_type unless rbs_type == FALLBACK_TYPE
174
- end
248
+ recv_type = nil
249
+ recv_type = local_var_types[lvar_name.to_s] if local_var_types && lvar_name
250
+ recv_type = param_types[lvar_name.to_s] if !recv_type && param_types && lvar_name
251
+ if recv_type
252
+ rbs_type = resolve_rbs_return_type(recv_type, meth, core_rbs_provider)
253
+ return rbs_type unless rbs_type == FALLBACK_TYPE
175
254
  end
176
255
  end
177
256
 
@@ -182,14 +261,13 @@ module Docscribe
182
261
  end
183
262
  end
184
263
 
185
- # Resolve a return type from core RBS for a method call.
264
+ # Method documentation.
186
265
  #
187
266
  # @note module_function: when included, also defines #resolve_rbs_return_type (instance visibility: private)
188
- # @private
189
- # @param [String] container_type e.g. "Numeric", "String"
190
- # @param [Symbol] method_name e.g. :positive?
191
- # @param [Object, nil] core_rbs_provider RBS provider
192
- # @return [String] FALLBACK_TYPE if lookup fails
267
+ # @param [Object] container_type Param documentation.
268
+ # @param [Object] method_name Param documentation.
269
+ # @param [Object] core_rbs_provider Param documentation.
270
+ # @return [Object]
193
271
  def resolve_rbs_return_type(container_type, method_name, core_rbs_provider)
194
272
  return FALLBACK_TYPE unless core_rbs_provider
195
273
 
@@ -65,11 +65,20 @@ module Docscribe
65
65
  # @param [Array<String>] missing_lines generated tag lines to add
66
66
  # @param [Boolean] sort_tags whether sortable tags should be reordered
67
67
  # @param [Array<String>] tag_order configured sortable tag order
68
+ # @param [Hash] filter_existing Param documentation.
68
69
  # @return [Array<String>]
69
- def merge(existing_lines, missing_lines:, sort_tags:, tag_order:)
70
+ def merge(existing_lines, missing_lines:, sort_tags:, tag_order:, filter_existing: {})
70
71
  existing_entries = parse(existing_lines, tag_order: tag_order)
71
72
  missing_entries = parse_generated(missing_lines, tag_order: tag_order)
72
73
 
74
+ filter_param_names = filter_existing[:param_names] || []
75
+ filter_return = !!filter_existing[:return]
76
+
77
+ existing_entries = existing_entries.reject do |e|
78
+ (e.kind == :tag && e.tag == 'param' && filter_param_names.include?(e.subject)) ||
79
+ (e.kind == :tag && e.tag == 'return' && filter_return)
80
+ end
81
+
73
82
  entries = existing_entries + missing_entries
74
83
  entries = sort(entries, tag_order: tag_order) if sort_tags
75
84
 
@@ -228,10 +228,11 @@ module Docscribe
228
228
  # @param [Object, nil] signature_provider
229
229
  # @param [nil] core_rbs_provider Param documentation.
230
230
  # @param [nil] param_types Param documentation.
231
+ # @param [nil] strategy Param documentation.
231
232
  # @raise [StandardError]
232
233
  # @return [Hash]
233
234
  def build_missing_merge_result(insertion, existing_lines:, config:, signature_provider: nil,
234
- core_rbs_provider: nil, param_types: nil)
235
+ core_rbs_provider: nil, param_types: nil, strategy: nil)
235
236
  node = insertion.node
236
237
  name = SourceHelpers.node_name(node)
237
238
  return { lines: [], reasons: [] } unless name
@@ -283,10 +284,22 @@ module Docscribe
283
284
 
284
285
  all_params&.each do |pl|
285
286
  pname = extract_param_name_from_param_line(pl)
286
- next if pname.nil? || info[:param_names].include?(pname)
287
-
288
- lines << "#{pl}\n"
289
- reasons << { type: :missing_param, message: "missing @param #{pname}", extra: { param: pname } }
287
+ next unless pname
288
+
289
+ if !info[:param_names].include?(pname)
290
+ lines << "#{pl}\n"
291
+ reasons << { type: :missing_param, message: "missing @param #{pname}", extra: { param: pname } }
292
+ elsif info[:param_types][pname] && strategy != :safe
293
+ new_type = extract_param_type_from_param_line(pl)
294
+ if new_type && info[:param_types][pname] != new_type
295
+ lines << "#{pl}\n"
296
+ reasons << {
297
+ type: :updated_param,
298
+ message: "updated @param #{pname} from #{info[:param_types][pname]} to #{new_type}",
299
+ extra: { param: pname }
300
+ }
301
+ end
302
+ end
290
303
  end
291
304
  end
292
305
 
@@ -301,9 +314,17 @@ module Docscribe
301
314
  end
302
315
  end
303
316
 
304
- if config.emit_return_tag?(scope, visibility) && !info[:has_return]
305
- lines << "#{indent}# @return [#{normal_type}]\n"
306
- reasons << { type: :missing_return, message: 'missing @return' }
317
+ if config.emit_return_tag?(scope, visibility)
318
+ if !info[:has_return]
319
+ lines << "#{indent}# @return [#{normal_type}]\n"
320
+ reasons << { type: :missing_return, message: 'missing @return' }
321
+ elsif info[:return_type] && info[:return_type] != normal_type && strategy != :safe
322
+ lines << "#{indent}# @return [#{normal_type}]\n"
323
+ reasons << {
324
+ type: :updated_return,
325
+ message: "updated @return from #{info[:return_type]} to #{normal_type}"
326
+ }
327
+ end
307
328
  end
308
329
 
309
330
  if config.emit_rescue_conditional_returns? && !info[:has_return]
@@ -341,7 +362,9 @@ module Docscribe
341
362
  # @return [Hash] parsed tag info
342
363
  def parse_existing_doc_tags(lines)
343
364
  param_names = {}
365
+ param_types = {}
344
366
  has_return = false
367
+ return_type = nil
345
368
  has_private = false
346
369
  has_protected = false
347
370
  has_module_function_note = false
@@ -354,9 +377,17 @@ module Docscribe
354
377
  end
355
378
  if (pname = extract_param_name_from_param_line(line))
356
379
  param_names[pname] = true
380
+ if (type_match = line.match(/@param\s+\[([^\]]+)\]\s+\S+/) || line.match(/@param\s+\S+\s+\[([^\]]+)\]/))
381
+ param_types[pname] = type_match[1]
382
+ end
357
383
  end
358
384
 
359
- has_return ||= line.match?(/^\s*#\s*@return\b/)
385
+ if line.match?(/^\s*#\s*@return\b/)
386
+ has_return = true
387
+ if (m = line.match(/@return\s+\[([^\]]+)\]/))
388
+ return_type = m[1]
389
+ end
390
+ end
360
391
  has_private ||= line.match?(/^\s*#\s*@private\b/)
361
392
  has_protected ||= line.match?(/^\s*#\s*@protected\b/)
362
393
  has_module_function_note ||= line.match?(/^\s*#\s*@note\s+module_function:/)
@@ -366,7 +397,9 @@ module Docscribe
366
397
 
367
398
  {
368
399
  param_names: param_names,
400
+ param_types: param_types,
369
401
  has_return: has_return,
402
+ return_type: return_type,
370
403
  raise_types: raise_types,
371
404
  has_private: has_private,
372
405
  has_protected: has_protected,
@@ -462,6 +495,19 @@ module Docscribe
462
495
  treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?
463
496
  )
464
497
  param_types[pname] = ty
498
+
499
+ when :kwoptarg
500
+ pname, default = *a
501
+ pname = pname.to_s
502
+ default_src = default&.loc&.expression&.source
503
+ ty = external_sig&.param_types&.[](pname) ||
504
+ Infer.infer_param_type(
505
+ "#{pname}:",
506
+ default_src,
507
+ fallback_type: config.fallback_type,
508
+ treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?
509
+ )
510
+ param_types[pname] = ty
465
511
  end
466
512
  end
467
513
 
@@ -672,6 +718,17 @@ module Docscribe
672
718
  nil
673
719
  end
674
720
 
721
+ # Method documentation.
722
+ #
723
+ # @note module_function: when included, also defines #extract_param_type_from_param_line (instance visibility: private)
724
+ # @param [Object] line Param documentation.
725
+ # @return [Object]
726
+ def extract_param_type_from_param_line(line)
727
+ if (m = line.match(/@param\s+\[([^\]]+)\]\s+\S+/) || line.match(/@param\s+\S+\s+\[([^\]]+)\]/))
728
+ m[1]
729
+ end
730
+ end
731
+
675
732
  # Build a Plugin::Context from a collected insertion.
676
733
  #
677
734
  # @note module_function
@@ -83,20 +83,11 @@ module Docscribe
83
83
 
84
84
  config ||= Docscribe::Config.load
85
85
  signature_provider = build_signature_provider(config, code, file.to_s)
86
- unless core_rbs_provider
87
- if config.respond_to?(:core_rbs_provider)
88
- begin
89
- core_rbs_provider = config.core_rbs_provider
90
- rescue StandardError
91
- core_rbs_provider = nil
92
- end
93
- elsif config.respond_to?(:rbs_provider)
94
- begin
95
- core_rbs_provider = config.rbs_provider
96
- rescue StandardError
97
- core_rbs_provider = nil
98
- end
99
- end
86
+ begin
87
+ core_rbs_provider ||= config.core_rbs_provider if config.respond_to?(:core_rbs_provider)
88
+ rescue StandardError => e
89
+ warn "Docscribe: failed to load core RBS provider: #{e.message}" if ENV['DOCSCRIBE_DEBUG']
90
+ core_rbs_provider = nil
100
91
  end
101
92
 
102
93
  collector = Docscribe::InlineRewriter::Collector.new(buffer)
@@ -345,8 +336,14 @@ module Docscribe
345
336
  rewriter.remove(range)
346
337
  end
347
338
 
339
+ effective_param_types = external_sig&.param_types || DocBuilder.build_param_types_from_node(
340
+ insertion.node,
341
+ external_sig: external_sig,
342
+ config: config
343
+ )
344
+
348
345
  doc = build_method_doc(insertion, config: config, signature_provider: signature_provider,
349
- core_rbs_provider: core_rbs_provider, param_types: external_sig&.param_types)
346
+ core_rbs_provider: core_rbs_provider, param_types: effective_param_types)
350
347
  return if doc.nil? || doc.empty?
351
348
 
352
349
  rewriter.insert_before(anchor_bol_range, doc)
@@ -369,7 +366,8 @@ module Docscribe
369
366
  config: config,
370
367
  signature_provider: signature_provider,
371
368
  core_rbs_provider: core_rbs_provider,
372
- param_types: external_sig&.param_types
369
+ param_types: external_sig&.param_types,
370
+ strategy: strategy
373
371
  )
374
372
 
375
373
  missing_lines = merge_result[:lines]
@@ -423,7 +421,8 @@ module Docscribe
423
421
  end
424
422
 
425
423
  doc = build_method_doc(insertion, config: config, signature_provider: signature_provider,
426
- core_rbs_provider: core_rbs_provider, param_types: external_sig&.param_types)
424
+ core_rbs_provider: core_rbs_provider,
425
+ param_types: external_sig&.param_types)
427
426
  return if doc.nil? || doc.empty?
428
427
 
429
428
  rewriter.insert_before(anchor_bol_range, doc)
@@ -790,16 +789,18 @@ module Docscribe
790
789
  # @param [Object, nil] signature_provider external signature provider
791
790
  # @param [Object, nil] core_rbs_provider RBS core type provider
792
791
  # @param [Hash, nil] param_types parameter name -> type map
792
+ # @param [Object] strategy Param documentation.
793
793
  # @return [Hash] result with `:lines` and `:reasons` keys
794
794
  def build_missing_method_merge_result(insertion, existing_lines:, config:, signature_provider:,
795
- core_rbs_provider:, param_types:)
795
+ core_rbs_provider:, param_types:, strategy:)
796
796
  DocBuilder.build_missing_merge_result(
797
797
  insertion,
798
798
  existing_lines: existing_lines,
799
799
  config: config,
800
800
  signature_provider: signature_provider,
801
801
  core_rbs_provider: core_rbs_provider,
802
- param_types: param_types
802
+ param_types: param_types,
803
+ strategy: strategy
803
804
  )
804
805
  end
805
806
 
@@ -41,8 +41,6 @@ module Docscribe
41
41
  # - :anchor_node => Parser::AST::Node — node above which to insert doc
42
42
  # - :doc => String — complete doc block including newlines
43
43
  #
44
- # @param [Parser::AST::Node] ast root AST node of the file
45
- # @param [Parser::Source::Buffer] buffer source buffer
46
44
  # @param [Object] _ast Param documentation.
47
45
  # @param [Object] _buffer Param documentation.
48
46
  # @return [Array<Hash>]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Docscribe
4
- VERSION = '1.3.0'
4
+ VERSION = '1.3.1'
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.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - unurgunite