docscribe 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +662 -187
- data/exe/docscribe +2 -126
- data/lib/docscribe/cli/config_builder.rb +62 -0
- data/lib/docscribe/cli/init.rb +58 -0
- data/lib/docscribe/cli/options.rb +204 -0
- data/lib/docscribe/cli/run.rb +415 -0
- data/lib/docscribe/cli.rb +31 -0
- data/lib/docscribe/config/defaults.rb +71 -0
- data/lib/docscribe/config/emit.rb +126 -0
- data/lib/docscribe/config/filtering.rb +160 -0
- data/lib/docscribe/config/loader.rb +59 -0
- data/lib/docscribe/config/rbs.rb +51 -0
- data/lib/docscribe/config/sorbet.rb +87 -0
- data/lib/docscribe/config/sorting.rb +23 -0
- data/lib/docscribe/config/template.rb +176 -0
- data/lib/docscribe/config/utils.rb +102 -0
- data/lib/docscribe/config.rb +20 -230
- data/lib/docscribe/infer/ast_walk.rb +28 -0
- data/lib/docscribe/infer/constants.rb +11 -0
- data/lib/docscribe/infer/literals.rb +55 -0
- data/lib/docscribe/infer/names.rb +43 -0
- data/lib/docscribe/infer/params.rb +62 -0
- data/lib/docscribe/infer/raises.rb +68 -0
- data/lib/docscribe/infer/returns.rb +171 -0
- data/lib/docscribe/infer.rb +104 -258
- data/lib/docscribe/inline_rewriter/collector.rb +845 -0
- data/lib/docscribe/inline_rewriter/doc_block.rb +383 -0
- data/lib/docscribe/inline_rewriter/doc_builder.rb +605 -0
- data/lib/docscribe/inline_rewriter/source_helpers.rb +228 -0
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +244 -0
- data/lib/docscribe/inline_rewriter.rb +599 -428
- data/lib/docscribe/parsing.rb +55 -44
- data/lib/docscribe/types/provider_chain.rb +37 -0
- data/lib/docscribe/types/rbs/provider.rb +213 -0
- data/lib/docscribe/types/rbs/type_formatter.rb +132 -0
- data/lib/docscribe/types/signature.rb +65 -0
- data/lib/docscribe/types/sorbet/base_provider.rb +217 -0
- data/lib/docscribe/types/sorbet/rbi_provider.rb +35 -0
- data/lib/docscribe/types/sorbet/source_provider.rb +25 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +37 -3
data/lib/docscribe/config.rb
CHANGED
|
@@ -1,245 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'yaml'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require 'psych'
|
|
4
6
|
|
|
5
7
|
module Docscribe
|
|
6
8
|
class Config
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'return_tag' => true,
|
|
12
|
-
'visibility_tags' => true,
|
|
13
|
-
'raise_tags' => true,
|
|
14
|
-
'rescue_conditional_returns' => true
|
|
15
|
-
},
|
|
16
|
-
'doc' => {
|
|
17
|
-
'default_message' => 'Method documentation.'
|
|
18
|
-
},
|
|
19
|
-
'methods' => {
|
|
20
|
-
'instance' => {
|
|
21
|
-
'public' => {},
|
|
22
|
-
'protected' => {},
|
|
23
|
-
'private' => {}
|
|
24
|
-
},
|
|
25
|
-
'class' => {
|
|
26
|
-
'public' => {},
|
|
27
|
-
'protected' => {},
|
|
28
|
-
'private' => {}
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
'inference' => {
|
|
32
|
-
'fallback_type' => 'Object',
|
|
33
|
-
'nil_as_optional' => true,
|
|
34
|
-
'treat_options_keyword_as_hash' => true
|
|
35
|
-
},
|
|
36
|
-
'filter' => {
|
|
37
|
-
'visibilities' => %w[public protected private],
|
|
38
|
-
'scopes' => %w[instance class],
|
|
39
|
-
'include' => [],
|
|
40
|
-
'exclude' => []
|
|
41
|
-
}
|
|
42
|
-
}.freeze
|
|
43
|
-
|
|
9
|
+
# Raw config hash after deep-merging user config with defaults.
|
|
10
|
+
#
|
|
11
|
+
# @!attribute [r] raw
|
|
12
|
+
# @return [Hash]
|
|
44
13
|
attr_reader :raw
|
|
45
14
|
|
|
46
|
-
#
|
|
15
|
+
# Create a configuration object from a raw config hash.
|
|
47
16
|
#
|
|
48
|
-
#
|
|
17
|
+
# Missing keys are filled from {DEFAULT} via deep merge.
|
|
49
18
|
#
|
|
50
|
-
# @param [Hash] raw
|
|
51
|
-
# @return [
|
|
19
|
+
# @param [Hash, nil] raw user-provided config hash
|
|
20
|
+
# @return [void]
|
|
52
21
|
def initialize(raw = {})
|
|
53
22
|
@raw = deep_merge(DEFAULT, raw || {})
|
|
54
23
|
end
|
|
55
|
-
|
|
56
|
-
# +Docscribe::Config.load+ -> Object
|
|
57
|
-
#
|
|
58
|
-
# Method documentation.
|
|
59
|
-
#
|
|
60
|
-
# @param [nil] path Param documentation.
|
|
61
|
-
# @return [Object]
|
|
62
|
-
def self.load(path = nil)
|
|
63
|
-
raw = {}
|
|
64
|
-
if path && File.file?(path)
|
|
65
|
-
raw = YAML.safe_load_file(path, permitted_classes: [], aliases: true) || {}
|
|
66
|
-
elsif File.file?('docscribe.yml')
|
|
67
|
-
raw = YAML.safe_load_file('docscribe.yml', permitted_classes: [], aliases: true) || {}
|
|
68
|
-
end
|
|
69
|
-
new(raw)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# +Docscribe::Config#emit_header?+ -> Object
|
|
73
|
-
#
|
|
74
|
-
# Method documentation.
|
|
75
|
-
#
|
|
76
|
-
# @return [Object]
|
|
77
|
-
def emit_header?
|
|
78
|
-
fetch_bool(%w[emit header], true)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# +Docscribe::Config#emit_param_tags?+ -> Object
|
|
82
|
-
#
|
|
83
|
-
# Method documentation.
|
|
84
|
-
#
|
|
85
|
-
# @return [Object]
|
|
86
|
-
def emit_param_tags?
|
|
87
|
-
fetch_bool(%w[emit param_tags], true)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# +Docscribe::Config#emit_visibility_tags?+ -> Object
|
|
91
|
-
#
|
|
92
|
-
# Method documentation.
|
|
93
|
-
#
|
|
94
|
-
# @return [Object]
|
|
95
|
-
def emit_visibility_tags?
|
|
96
|
-
fetch_bool(%w[emit visibility_tags], true)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# +Docscribe::Config#emit_raise_tags?+ -> Object
|
|
100
|
-
#
|
|
101
|
-
# Method documentation.
|
|
102
|
-
#
|
|
103
|
-
# @return [Object]
|
|
104
|
-
def emit_raise_tags?
|
|
105
|
-
fetch_bool(%w[emit raise_tags], true)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# +Docscribe::Config#emit_rescue_conditional_returns?+ -> Object
|
|
109
|
-
#
|
|
110
|
-
# Method documentation.
|
|
111
|
-
#
|
|
112
|
-
# @return [Object]
|
|
113
|
-
def emit_rescue_conditional_returns?
|
|
114
|
-
fetch_bool(%w[emit rescue_conditional_returns], true)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# +Docscribe::Config#emit_return_tag?+ -> Object
|
|
118
|
-
#
|
|
119
|
-
# Method documentation.
|
|
120
|
-
#
|
|
121
|
-
# @param [Object] scope Param documentation.
|
|
122
|
-
# @param [Object] visibility Param documentation.
|
|
123
|
-
# @return [Object]
|
|
124
|
-
def emit_return_tag?(scope, visibility)
|
|
125
|
-
method_override_bool(scope, visibility, 'return_tag',
|
|
126
|
-
default: fetch_bool(%w[emit return_tag], true))
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# +Docscribe::Config#default_message+ -> Object
|
|
130
|
-
#
|
|
131
|
-
# Method documentation.
|
|
132
|
-
#
|
|
133
|
-
# @param [Object] scope Param documentation.
|
|
134
|
-
# @param [Object] visibility Param documentation.
|
|
135
|
-
# @return [Object]
|
|
136
|
-
def default_message(scope, visibility)
|
|
137
|
-
method_override_str(scope, visibility, 'default_message',
|
|
138
|
-
default: raw.dig('doc', 'default_message') || 'Method documentation.')
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# +Docscribe::Config#fallback_type+ -> Object
|
|
142
|
-
#
|
|
143
|
-
# Method documentation.
|
|
144
|
-
#
|
|
145
|
-
# @return [Object]
|
|
146
|
-
def fallback_type
|
|
147
|
-
raw.dig('inference', 'fallback_type') || 'Object'
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# +Docscribe::Config#nil_as_optional?+ -> Object
|
|
151
|
-
#
|
|
152
|
-
# Method documentation.
|
|
153
|
-
#
|
|
154
|
-
# @return [Object]
|
|
155
|
-
def nil_as_optional?
|
|
156
|
-
fetch_bool(%w[inference nil_as_optional], true)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# +Docscribe::Config#treat_options_keyword_as_hash?+ -> Object
|
|
160
|
-
#
|
|
161
|
-
# Method documentation.
|
|
162
|
-
#
|
|
163
|
-
# @return [Object]
|
|
164
|
-
def treat_options_keyword_as_hash?
|
|
165
|
-
fetch_bool(%w[inference treat_options_keyword_as_hash], true)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
private
|
|
169
|
-
|
|
170
|
-
# +Docscribe::Config#method_override_bool+ -> Object
|
|
171
|
-
#
|
|
172
|
-
# Method documentation.
|
|
173
|
-
#
|
|
174
|
-
# @private
|
|
175
|
-
# @param [Object] scope Param documentation.
|
|
176
|
-
# @param [Object] vis Param documentation.
|
|
177
|
-
# @param [Object] key Param documentation.
|
|
178
|
-
# @param [Object] default Param documentation.
|
|
179
|
-
# @return [Object]
|
|
180
|
-
def method_override_bool(scope, vis, key, default:)
|
|
181
|
-
node = raw.dig('methods', scope_to_key(scope), vis.to_s, key)
|
|
182
|
-
node.nil? ? default : !!node
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# +Docscribe::Config#fetch_bool+ -> Object
|
|
186
|
-
#
|
|
187
|
-
# Method documentation.
|
|
188
|
-
#
|
|
189
|
-
# @private
|
|
190
|
-
# @param [Object] path Param documentation.
|
|
191
|
-
# @param [Object] default Param documentation.
|
|
192
|
-
# @return [Object]
|
|
193
|
-
def fetch_bool(path, default)
|
|
194
|
-
node = raw
|
|
195
|
-
path.each { |k| node = node[k] if node }
|
|
196
|
-
node.nil? ? default : !!node
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# +Docscribe::Config#method_override_str+ -> Object
|
|
200
|
-
#
|
|
201
|
-
# Method documentation.
|
|
202
|
-
#
|
|
203
|
-
# @private
|
|
204
|
-
# @param [Object] scope Param documentation.
|
|
205
|
-
# @param [Object] vis Param documentation.
|
|
206
|
-
# @param [Object] key Param documentation.
|
|
207
|
-
# @param [Object] default Param documentation.
|
|
208
|
-
# @return [Object]
|
|
209
|
-
def method_override_str(scope, vis, key, default:)
|
|
210
|
-
node = raw.dig('methods', scope_to_key(scope), vis.to_s, key)
|
|
211
|
-
node.nil? ? default : node.to_s
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# +Docscribe::Config#scope_to_key+ -> String
|
|
215
|
-
#
|
|
216
|
-
# Method documentation.
|
|
217
|
-
#
|
|
218
|
-
# @private
|
|
219
|
-
# @param [Object] scope Param documentation.
|
|
220
|
-
# @return [String]
|
|
221
|
-
def scope_to_key(scope)
|
|
222
|
-
scope == :class ? 'class' : 'instance'
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# +Docscribe::Config#deep_merge+ -> Object
|
|
226
|
-
#
|
|
227
|
-
# Method documentation.
|
|
228
|
-
#
|
|
229
|
-
# @private
|
|
230
|
-
# @param [Object] hash1 Param documentation.
|
|
231
|
-
# @param [Object] hash2 Param documentation.
|
|
232
|
-
# @return [Object]
|
|
233
|
-
def deep_merge(hash1, hash2)
|
|
234
|
-
return hash1 unless hash2
|
|
235
|
-
|
|
236
|
-
hash1.merge(hash2) do |_, v1, v2|
|
|
237
|
-
if v1.is_a?(Hash) && v2.is_a?(Hash)
|
|
238
|
-
deep_merge(v1, v2)
|
|
239
|
-
else
|
|
240
|
-
v2
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
24
|
end
|
|
245
25
|
end
|
|
26
|
+
|
|
27
|
+
require_relative 'config/defaults'
|
|
28
|
+
require_relative 'config/utils'
|
|
29
|
+
require_relative 'config/loader'
|
|
30
|
+
require_relative 'config/template'
|
|
31
|
+
require_relative 'config/emit'
|
|
32
|
+
require_relative 'config/filtering'
|
|
33
|
+
require_relative 'config/rbs'
|
|
34
|
+
require_relative 'config/sorting'
|
|
35
|
+
require_relative 'config/sorbet'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# AST traversal helpers for parser AST nodes.
|
|
6
|
+
module ASTWalk
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Depth-first walk over a parser AST.
|
|
10
|
+
#
|
|
11
|
+
# Yields each node exactly once, descending recursively through child nodes.
|
|
12
|
+
# Non-AST values are ignored.
|
|
13
|
+
#
|
|
14
|
+
# @note module_function: when included, also defines #walk (instance visibility: private)
|
|
15
|
+
# @param [Parser::AST::Node, nil] node root AST node
|
|
16
|
+
# @param [Proc] block visitor block
|
|
17
|
+
# @return [void]
|
|
18
|
+
def walk(node, &block)
|
|
19
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
20
|
+
|
|
21
|
+
yield node
|
|
22
|
+
node.children.each do |ch|
|
|
23
|
+
walk(ch, &block) if ch.is_a?(Parser::AST::Node)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# Default fallback type used when inference cannot be certain.
|
|
6
|
+
FALLBACK_TYPE = 'Object'
|
|
7
|
+
|
|
8
|
+
# Ruby's implicit rescue target for bare `rescue`.
|
|
9
|
+
DEFAULT_ERROR = 'StandardError'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# Literal inference: map simple AST literals to type names.
|
|
6
|
+
module Literals
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Infer a type name from a literal-like AST node.
|
|
10
|
+
#
|
|
11
|
+
# Supports common literal/value node types such as:
|
|
12
|
+
# - integers, floats, strings, symbols
|
|
13
|
+
# - booleans and nil
|
|
14
|
+
# - arrays, hashes, regexps
|
|
15
|
+
# - constants
|
|
16
|
+
# - `Foo.new` constructor calls
|
|
17
|
+
#
|
|
18
|
+
# If the node does not match a supported pattern, the fallback type is returned.
|
|
19
|
+
#
|
|
20
|
+
# @note module_function: when included, also defines #type_from_literal (instance visibility: private)
|
|
21
|
+
# @param [Parser::AST::Node, nil] node literal/value node
|
|
22
|
+
# @param [String] fallback_type type returned when inference is uncertain
|
|
23
|
+
# @return [String]
|
|
24
|
+
def type_from_literal(node, fallback_type: FALLBACK_TYPE)
|
|
25
|
+
return fallback_type unless node
|
|
26
|
+
|
|
27
|
+
case node.type
|
|
28
|
+
when :int then 'Integer'
|
|
29
|
+
when :float then 'Float'
|
|
30
|
+
when :str, :dstr then 'String'
|
|
31
|
+
when :sym then 'Symbol'
|
|
32
|
+
when :true, :false then 'Boolean' # rubocop:disable Lint/BooleanSymbol
|
|
33
|
+
when :nil then 'nil'
|
|
34
|
+
when :array then 'Array'
|
|
35
|
+
when :hash then 'Hash'
|
|
36
|
+
when :regexp then 'Regexp'
|
|
37
|
+
|
|
38
|
+
when :const
|
|
39
|
+
node.children.last.to_s
|
|
40
|
+
|
|
41
|
+
when :send
|
|
42
|
+
recv, meth, = node.children
|
|
43
|
+
if meth == :new && recv && recv.type == :const
|
|
44
|
+
recv.children.last.to_s
|
|
45
|
+
else
|
|
46
|
+
fallback_type
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
else
|
|
50
|
+
fallback_type
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# Constant-name helpers for turning AST nodes into fully qualified names.
|
|
6
|
+
module Names
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Convert a `:const` / `:cbase` AST node into a fully qualified constant name.
|
|
10
|
+
#
|
|
11
|
+
# Examples:
|
|
12
|
+
# - `Foo` => `"Foo"`
|
|
13
|
+
# - `Foo::Bar` => `"Foo::Bar"`
|
|
14
|
+
# - `::Foo::Bar` => `"::Foo::Bar"`
|
|
15
|
+
#
|
|
16
|
+
# Returns nil for unsupported nodes.
|
|
17
|
+
#
|
|
18
|
+
# @note module_function: when included, also defines #const_full_name (instance visibility: private)
|
|
19
|
+
# @param [Parser::AST::Node, nil] n constant-like AST node
|
|
20
|
+
# @return [String, nil]
|
|
21
|
+
def const_full_name(n)
|
|
22
|
+
return nil unless n.is_a?(Parser::AST::Node)
|
|
23
|
+
|
|
24
|
+
case n.type
|
|
25
|
+
when :const
|
|
26
|
+
scope, name = *n
|
|
27
|
+
scope_name = const_full_name(scope)
|
|
28
|
+
|
|
29
|
+
if scope_name && !scope_name.empty?
|
|
30
|
+
"#{scope_name}::#{name}"
|
|
31
|
+
elsif scope_name == '' # leading ::
|
|
32
|
+
"::#{name}"
|
|
33
|
+
else
|
|
34
|
+
name.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
when :cbase
|
|
38
|
+
'' # represents leading :: scope
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# Parameter type inference.
|
|
6
|
+
module Params
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Infer a parameter type from an internal parameter name representation and
|
|
10
|
+
# an optional default expression.
|
|
11
|
+
#
|
|
12
|
+
# Handles:
|
|
13
|
+
# - positional/rest/block parameter prefixes (`*`, `**`, `&`)
|
|
14
|
+
# - keyword params with and without defaults
|
|
15
|
+
# - special-casing `options:` as `Hash` when enabled
|
|
16
|
+
# - literal defaults via AST parsing
|
|
17
|
+
#
|
|
18
|
+
# @note module_function: when included, also defines #infer_param_type (instance visibility: private)
|
|
19
|
+
# @param [String] name parameter name as used internally (may include `*`, `**`, `&`, or trailing `:`)
|
|
20
|
+
# @param [String, nil] default_str source for the default value expression
|
|
21
|
+
# @param [String] fallback_type type returned when inference is uncertain
|
|
22
|
+
# @param [Boolean] treat_options_keyword_as_hash whether `options:` should
|
|
23
|
+
# be treated specially as Hash
|
|
24
|
+
# @return [String]
|
|
25
|
+
def infer_param_type(name, default_str, fallback_type: FALLBACK_TYPE, treat_options_keyword_as_hash: true)
|
|
26
|
+
return 'Array' if name.start_with?('*') && !name.start_with?('**')
|
|
27
|
+
return 'Hash' if name.start_with?('**')
|
|
28
|
+
return 'Proc' if name.start_with?('&')
|
|
29
|
+
|
|
30
|
+
is_kw = name.end_with?(':')
|
|
31
|
+
node = parse_expr(default_str)
|
|
32
|
+
ty = Literals.type_from_literal(node, fallback_type: fallback_type)
|
|
33
|
+
|
|
34
|
+
if is_kw && default_str.nil?
|
|
35
|
+
return (treat_options_keyword_as_hash && name == 'options:' ? 'Hash' : fallback_type)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
return 'Hash' if treat_options_keyword_as_hash && name == 'options:' && (default_str == '{}' || ty == 'Hash')
|
|
39
|
+
|
|
40
|
+
ty
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Parse a standalone expression for parameter-default inference.
|
|
44
|
+
#
|
|
45
|
+
# Returns nil if the expression is empty or cannot be parsed.
|
|
46
|
+
#
|
|
47
|
+
# @note module_function: when included, also defines #parse_expr (instance visibility: private)
|
|
48
|
+
# @param [String, nil] src expression source
|
|
49
|
+
# @raise [Parser::SyntaxError]
|
|
50
|
+
# @return [Parser::AST::Node, nil]
|
|
51
|
+
def parse_expr(src)
|
|
52
|
+
return nil if src.nil? || src.strip.empty?
|
|
53
|
+
|
|
54
|
+
buffer = Parser::Source::Buffer.new('(param)')
|
|
55
|
+
buffer.source = src
|
|
56
|
+
Docscribe::Parsing.parse_buffer(buffer)
|
|
57
|
+
rescue Parser::SyntaxError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# Exception inference from AST (`raise`/`fail` calls and `rescue` clauses).
|
|
6
|
+
module Raises
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Infer exception class names raised or rescued within a node.
|
|
10
|
+
#
|
|
11
|
+
# Sources considered:
|
|
12
|
+
# - `rescue Foo, Bar`
|
|
13
|
+
# - bare `rescue` (=> StandardError)
|
|
14
|
+
# - `raise Foo`
|
|
15
|
+
# - bare `raise` / `fail` (=> StandardError)
|
|
16
|
+
#
|
|
17
|
+
# Returns unique exception names in discovery order.
|
|
18
|
+
#
|
|
19
|
+
# @note module_function: when included, also defines #infer_raises_from_node (instance visibility: private)
|
|
20
|
+
# @param [Parser::AST::Node] node method or expression node to inspect
|
|
21
|
+
# @return [Array<String>]
|
|
22
|
+
def infer_raises_from_node(node)
|
|
23
|
+
raises = []
|
|
24
|
+
|
|
25
|
+
ASTWalk.walk(node) do |n|
|
|
26
|
+
case n.type
|
|
27
|
+
when :resbody
|
|
28
|
+
exc_list = n.children[0]
|
|
29
|
+
raises.concat(exception_names_from_rescue_list(exc_list))
|
|
30
|
+
|
|
31
|
+
when :send
|
|
32
|
+
recv, meth, *args = *n
|
|
33
|
+
next unless recv.nil? && %i[raise fail].include?(meth)
|
|
34
|
+
|
|
35
|
+
if args.empty?
|
|
36
|
+
raises << DEFAULT_ERROR
|
|
37
|
+
else
|
|
38
|
+
c = Names.const_full_name(args[0])
|
|
39
|
+
raises << (c || DEFAULT_ERROR)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
raises.uniq
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Extract exception class names from a rescue exception list.
|
|
48
|
+
#
|
|
49
|
+
# Examples:
|
|
50
|
+
# - nil => `[StandardError]`
|
|
51
|
+
# - `Foo` => `["Foo"]`
|
|
52
|
+
# - `[Foo, Bar]` => `["Foo", "Bar"]`
|
|
53
|
+
#
|
|
54
|
+
# @note module_function: when included, also defines #exception_names_from_rescue_list (instance visibility: private)
|
|
55
|
+
# @param [Parser::AST::Node, nil] exc_list rescue exception list node
|
|
56
|
+
# @return [Array<String>]
|
|
57
|
+
def exception_names_from_rescue_list(exc_list)
|
|
58
|
+
if exc_list.nil?
|
|
59
|
+
[DEFAULT_ERROR]
|
|
60
|
+
elsif exc_list.type == :array
|
|
61
|
+
exc_list.children.map { |e| Names.const_full_name(e) || DEFAULT_ERROR }
|
|
62
|
+
else
|
|
63
|
+
[Names.const_full_name(exc_list) || DEFAULT_ERROR]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|