docscribe 1.0.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 +692 -180
- data/exe/docscribe +2 -74
- 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 +110 -259
- 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 +604 -425
- data/lib/docscribe/parsing.rb +120 -0
- 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
- data/lib/docscribe.rb +1 -0
- metadata +85 -17
- data/.rspec +0 -3
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -73
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -73
- data/Rakefile +0 -12
- data/rakelib/docs.rake +0 -73
- data/stingray_docs_internal.gemspec +0 -41
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Infer
|
|
5
|
+
# Return type inference and rescue-conditional return extraction.
|
|
6
|
+
module Returns
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Infer a return type from a full method definition source string.
|
|
10
|
+
#
|
|
11
|
+
# The source must parse to a `:def` or `:defs` node. If parsing fails or inference
|
|
12
|
+
# is uncertain, the fallback type is returned.
|
|
13
|
+
#
|
|
14
|
+
# @note module_function: when included, also defines #infer_return_type (instance visibility: private)
|
|
15
|
+
# @param [String, nil] method_source full method definition source
|
|
16
|
+
# @raise [Parser::SyntaxError]
|
|
17
|
+
# @return [String]
|
|
18
|
+
def infer_return_type(method_source)
|
|
19
|
+
return FALLBACK_TYPE if method_source.nil? || method_source.strip.empty?
|
|
20
|
+
|
|
21
|
+
buffer = Parser::Source::Buffer.new('(method)')
|
|
22
|
+
buffer.source = method_source
|
|
23
|
+
root = Docscribe::Parsing.parse_buffer(buffer)
|
|
24
|
+
return FALLBACK_TYPE unless root && %i[def defs].include?(root.type)
|
|
25
|
+
|
|
26
|
+
body = root.children.last
|
|
27
|
+
last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true) || FALLBACK_TYPE
|
|
28
|
+
rescue Parser::SyntaxError
|
|
29
|
+
FALLBACK_TYPE
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Infer a method's normal return type from an already parsed def/defs node.
|
|
33
|
+
#
|
|
34
|
+
# @note module_function: when included, also defines #infer_return_type_from_node (instance visibility: private)
|
|
35
|
+
# @param [Parser::AST::Node] node `:def` or `:defs` node
|
|
36
|
+
# @return [String]
|
|
37
|
+
def infer_return_type_from_node(node)
|
|
38
|
+
body =
|
|
39
|
+
case node.type
|
|
40
|
+
when :def then node.children[2]
|
|
41
|
+
when :defs then node.children[3]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return FALLBACK_TYPE unless body
|
|
45
|
+
|
|
46
|
+
last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true) || FALLBACK_TYPE
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Return a structured return-type spec for a method node.
|
|
50
|
+
#
|
|
51
|
+
# The result includes:
|
|
52
|
+
# - `:normal` => normal/happy-path return type
|
|
53
|
+
# - `:rescues` => array of `[exception_names, return_type]` pairs for rescue branches
|
|
54
|
+
#
|
|
55
|
+
# @note module_function: when included, also defines #returns_spec_from_node (instance visibility: private)
|
|
56
|
+
# @param [Parser::AST::Node] node `:def` or `:defs` node
|
|
57
|
+
# @param [String] fallback_type type used when inference is uncertain
|
|
58
|
+
# @param [Boolean] nil_as_optional whether `nil` unions should be rendered as optional types
|
|
59
|
+
# @return [Hash]
|
|
60
|
+
def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true)
|
|
61
|
+
body =
|
|
62
|
+
case node.type
|
|
63
|
+
when :def then node.children[2]
|
|
64
|
+
when :defs then node.children[3]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
spec = { normal: FALLBACK_TYPE, rescues: [] }
|
|
68
|
+
return spec unless body
|
|
69
|
+
|
|
70
|
+
if body.type == :rescue
|
|
71
|
+
main_body = body.children[0]
|
|
72
|
+
spec[:normal] =
|
|
73
|
+
last_expr_type(main_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional) || FALLBACK_TYPE
|
|
74
|
+
|
|
75
|
+
body.children.each do |ch|
|
|
76
|
+
next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
|
|
77
|
+
|
|
78
|
+
exc_list, _asgn, rescue_body = *ch
|
|
79
|
+
exc_names = Raises.exception_names_from_rescue_list(exc_list)
|
|
80
|
+
rtype =
|
|
81
|
+
last_expr_type(rescue_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional) ||
|
|
82
|
+
fallback_type
|
|
83
|
+
spec[:rescues] << [exc_names, rtype]
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
spec[:normal] =
|
|
87
|
+
last_expr_type(body, fallback_type: fallback_type, nil_as_optional: nil_as_optional) || FALLBACK_TYPE
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
spec
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Infer the type of the last expression in a node.
|
|
94
|
+
#
|
|
95
|
+
# Supports:
|
|
96
|
+
# - `begin` groups
|
|
97
|
+
# - `if` branches
|
|
98
|
+
# - `case` expressions
|
|
99
|
+
# - explicit `return`
|
|
100
|
+
# - literal-like expressions via {Literals.type_from_literal}
|
|
101
|
+
#
|
|
102
|
+
# @note module_function: when included, also defines #last_expr_type (instance visibility: private)
|
|
103
|
+
# @param [Parser::AST::Node, nil] node expression node
|
|
104
|
+
# @param [String] fallback_type type used when inference is uncertain
|
|
105
|
+
# @param [Boolean] nil_as_optional whether `nil` unions should be rendered as optional types
|
|
106
|
+
# @return [String, nil]
|
|
107
|
+
def last_expr_type(node, fallback_type:, nil_as_optional:)
|
|
108
|
+
return nil unless node
|
|
109
|
+
|
|
110
|
+
case node.type
|
|
111
|
+
when :begin
|
|
112
|
+
last_expr_type(node.children.last, fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
113
|
+
|
|
114
|
+
when :if
|
|
115
|
+
t = last_expr_type(node.children[1], fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
116
|
+
e = last_expr_type(node.children[2], fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
117
|
+
unify_types(t, e, fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
118
|
+
|
|
119
|
+
when :case
|
|
120
|
+
branches = node.children[1..].compact.flat_map do |child|
|
|
121
|
+
if child.type == :when
|
|
122
|
+
last_expr_type(child.children.last, fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
123
|
+
else
|
|
124
|
+
last_expr_type(child, fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
125
|
+
end
|
|
126
|
+
end.compact
|
|
127
|
+
|
|
128
|
+
if branches.empty?
|
|
129
|
+
fallback_type
|
|
130
|
+
else
|
|
131
|
+
branches.reduce do |a, b|
|
|
132
|
+
unify_types(a, b, fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
when :return
|
|
137
|
+
Literals.type_from_literal(node.children.first, fallback_type: fallback_type)
|
|
138
|
+
|
|
139
|
+
else
|
|
140
|
+
Literals.type_from_literal(node, fallback_type: fallback_type)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Unify two inferred types into a single type string.
|
|
145
|
+
#
|
|
146
|
+
# Rules:
|
|
147
|
+
# - identical types remain unchanged
|
|
148
|
+
# - `nil` unions may become optional types if enabled
|
|
149
|
+
# - otherwise falls back conservatively to `fallback_type`
|
|
150
|
+
#
|
|
151
|
+
# @note module_function: when included, also defines #unify_types (instance visibility: private)
|
|
152
|
+
# @param [String, nil] a
|
|
153
|
+
# @param [String, nil] b
|
|
154
|
+
# @param [String] fallback_type
|
|
155
|
+
# @param [Boolean] nil_as_optional
|
|
156
|
+
# @return [String, nil]
|
|
157
|
+
def unify_types(a, b, fallback_type:, nil_as_optional:)
|
|
158
|
+
a ||= fallback_type
|
|
159
|
+
b ||= fallback_type
|
|
160
|
+
return a if a == b
|
|
161
|
+
|
|
162
|
+
if a == 'nil' || b == 'nil'
|
|
163
|
+
non_nil = (a == 'nil' ? b : a)
|
|
164
|
+
return nil_as_optional ? "#{non_nil}?" : "#{non_nil}, nil"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
fallback_type
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
data/lib/docscribe/infer.rb
CHANGED
|
@@ -1,301 +1,152 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# NOTE: parser/base references Racc::Parser in some environments, so require runtime first.
|
|
4
|
+
require 'racc/parser'
|
|
5
|
+
require 'ast'
|
|
6
|
+
require 'parser/ast/node'
|
|
7
|
+
require 'parser/source/buffer'
|
|
8
|
+
require 'parser/base'
|
|
9
|
+
|
|
10
|
+
require 'docscribe/parsing'
|
|
11
|
+
|
|
12
|
+
require_relative 'infer/constants'
|
|
13
|
+
require_relative 'infer/ast_walk'
|
|
14
|
+
require_relative 'infer/names'
|
|
15
|
+
require_relative 'infer/literals'
|
|
16
|
+
require_relative 'infer/params'
|
|
17
|
+
require_relative 'infer/returns'
|
|
18
|
+
require_relative 'infer/raises'
|
|
4
19
|
|
|
5
20
|
module Docscribe
|
|
21
|
+
# Best-effort inference utilities used to generate YARD tags.
|
|
22
|
+
#
|
|
23
|
+
# This module is intentionally heuristic:
|
|
24
|
+
# - it aims to be useful for common Ruby patterns
|
|
25
|
+
# - it prefers safe fallback behavior when uncertain
|
|
26
|
+
# - when inference cannot be specific, it falls back to `Object`
|
|
27
|
+
#
|
|
28
|
+
# External signature sources such as RBS and Sorbet are applied later in the
|
|
29
|
+
# doc builder and can override these inferred types.
|
|
6
30
|
module Infer
|
|
7
31
|
class << self
|
|
8
|
-
#
|
|
32
|
+
# Infer exception classes raised or rescued within an AST node.
|
|
9
33
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# @param [Object] node Param documentation.
|
|
13
|
-
# @return [Object]
|
|
34
|
+
# @param [Parser::AST::Node] node
|
|
35
|
+
# @return [Array<String>]
|
|
14
36
|
def infer_raises_from_node(node)
|
|
15
|
-
|
|
16
|
-
walk = lambda do |n|
|
|
17
|
-
return unless n.is_a?(Parser::AST::Node)
|
|
18
|
-
|
|
19
|
-
case n.type
|
|
20
|
-
when :rescue
|
|
21
|
-
n.children.each { |ch| walk.call(ch) }
|
|
22
|
-
when :resbody
|
|
23
|
-
exc_list = n.children[0]
|
|
24
|
-
if exc_list.nil?
|
|
25
|
-
raises << 'StandardError'
|
|
26
|
-
elsif exc_list.type == :array
|
|
27
|
-
exc_list.children.each { |e| (c = const_full_name(e)) && (raises << c) }
|
|
28
|
-
else
|
|
29
|
-
(c = const_full_name(exc_list)) && (raises << c)
|
|
30
|
-
end
|
|
31
|
-
n.children.each { |ch| walk.call(ch) if ch.is_a?(Parser::AST::Node) }
|
|
32
|
-
when :send
|
|
33
|
-
recv, meth, *args = *n
|
|
34
|
-
if recv.nil? && %i[raise fail].include?(meth)
|
|
35
|
-
if args.empty?
|
|
36
|
-
raises << 'StandardError'
|
|
37
|
-
else
|
|
38
|
-
c = const_full_name(args[0])
|
|
39
|
-
raises << (c || 'StandardError')
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
n.children.each { |ch| walk.call(ch) if ch.is_a?(Parser::AST::Node) }
|
|
43
|
-
else
|
|
44
|
-
n.children.each { |ch| walk.call(ch) if ch.is_a?(Parser::AST::Node) }
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
walk.call(node)
|
|
48
|
-
raises.uniq
|
|
37
|
+
Raises.infer_raises_from_node(node)
|
|
49
38
|
end
|
|
50
39
|
|
|
51
|
-
#
|
|
40
|
+
# Infer a parameter type from its internal name form and optional default
|
|
41
|
+
# expression.
|
|
52
42
|
#
|
|
53
|
-
#
|
|
43
|
+
# The internal parameter name may include:
|
|
44
|
+
# - `*` for rest args
|
|
45
|
+
# - `**` for keyword rest args
|
|
46
|
+
# - `&` for block args
|
|
47
|
+
# - trailing `:` for keyword args
|
|
54
48
|
#
|
|
55
|
-
# @param [
|
|
56
|
-
# @param [
|
|
57
|
-
# @
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
node = parse_expr(default_str)
|
|
68
|
-
ty = type_from_literal(node)
|
|
69
|
-
|
|
70
|
-
# If kw with no default, still show Object (or Hash for options:)
|
|
71
|
-
if is_kw && default_str.nil?
|
|
72
|
-
return (name == 'options:' ? 'Hash' : 'Object')
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# If param named options and default is {}, call it Hash
|
|
76
|
-
return 'Hash' if name == 'options:' && (default_str == '{}' || ty == 'Hash')
|
|
77
|
-
|
|
78
|
-
ty
|
|
49
|
+
# @param [String] name internal parameter name representation
|
|
50
|
+
# @param [String, nil] default_str source for the default expression
|
|
51
|
+
# @param [String] fallback_type
|
|
52
|
+
# @param [Boolean] treat_options_keyword_as_hash
|
|
53
|
+
# @return [String]
|
|
54
|
+
def infer_param_type(name, default_str, fallback_type: FALLBACK_TYPE, treat_options_keyword_as_hash: true)
|
|
55
|
+
Params.infer_param_type(
|
|
56
|
+
name,
|
|
57
|
+
default_str,
|
|
58
|
+
fallback_type: fallback_type,
|
|
59
|
+
treat_options_keyword_as_hash: treat_options_keyword_as_hash
|
|
60
|
+
)
|
|
79
61
|
end
|
|
80
62
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# Method documentation.
|
|
63
|
+
# Parse a standalone expression source string for inference helpers.
|
|
84
64
|
#
|
|
85
|
-
# @param [
|
|
86
|
-
# @
|
|
87
|
-
# @return [Object]
|
|
88
|
-
# @return [nil] if Parser::SyntaxError
|
|
65
|
+
# @param [String, nil] src
|
|
66
|
+
# @return [Parser::AST::Node, nil]
|
|
89
67
|
def parse_expr(src)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
buffer = Parser::Source::Buffer.new('(param)')
|
|
93
|
-
buffer.source = src
|
|
94
|
-
Parser::CurrentRuby.new.parse(buffer)
|
|
95
|
-
rescue Parser::SyntaxError
|
|
96
|
-
nil
|
|
68
|
+
Params.parse_expr(src)
|
|
97
69
|
end
|
|
98
70
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
# Method documentation.
|
|
71
|
+
# Infer a return type from full method source.
|
|
102
72
|
#
|
|
103
|
-
# @param [
|
|
104
|
-
# @
|
|
105
|
-
# @return [Object]
|
|
106
|
-
# @return [String] if Parser::SyntaxError
|
|
73
|
+
# @param [String, nil] method_source
|
|
74
|
+
# @return [String]
|
|
107
75
|
def infer_return_type(method_source)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
buffer = Parser::Source::Buffer.new('(method)')
|
|
111
|
-
buffer.source = method_source
|
|
112
|
-
root = Parser::CurrentRuby.new.parse(buffer)
|
|
113
|
-
return 'Object' unless root && %i[def defs].include?(root.type)
|
|
114
|
-
|
|
115
|
-
body = root.children.last # method body node
|
|
116
|
-
ty = last_expr_type(body)
|
|
117
|
-
ty || 'Object'
|
|
118
|
-
rescue Parser::SyntaxError
|
|
119
|
-
'Object'
|
|
76
|
+
Returns.infer_return_type(method_source)
|
|
120
77
|
end
|
|
121
78
|
|
|
122
|
-
#
|
|
79
|
+
# Infer a return type from an already parsed `:def` / `:defs` node.
|
|
123
80
|
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
# @param [Object] node Param documentation.
|
|
127
|
-
# @return [Object]
|
|
81
|
+
# @param [Parser::AST::Node] node
|
|
82
|
+
# @return [String]
|
|
128
83
|
def infer_return_type_from_node(node)
|
|
129
|
-
|
|
130
|
-
case node.type
|
|
131
|
-
when :def then node.children[2] # [name, args, body]
|
|
132
|
-
when :defs then node.children[3] # [recv, name, args, body]
|
|
133
|
-
end
|
|
134
|
-
return 'Object' unless body
|
|
135
|
-
|
|
136
|
-
ty = last_expr_type(body)
|
|
137
|
-
ty || 'Object'
|
|
84
|
+
Returns.infer_return_type_from_node(node)
|
|
138
85
|
end
|
|
139
86
|
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if body.type == :rescue
|
|
158
|
-
# child[0] is the main body (before rescue)
|
|
159
|
-
main_body = body.children[0]
|
|
160
|
-
spec[:normal] = last_expr_type(main_body) || 'Object'
|
|
161
|
-
|
|
162
|
-
# :resbody nodes hold exception list, optional var, and rescue body
|
|
163
|
-
body.children.each do |ch|
|
|
164
|
-
next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
|
|
165
|
-
|
|
166
|
-
exc_list, _asgn, rescue_body = *ch
|
|
167
|
-
|
|
168
|
-
exc_names = []
|
|
169
|
-
if exc_list.nil?
|
|
170
|
-
exc_names << 'StandardError'
|
|
171
|
-
elsif exc_list.type == :array
|
|
172
|
-
exc_list.children.each do |e|
|
|
173
|
-
name = const_full_name(e)
|
|
174
|
-
exc_names << (name || 'StandardError')
|
|
175
|
-
end
|
|
176
|
-
else
|
|
177
|
-
name = const_full_name(exc_list)
|
|
178
|
-
exc_names << (name || 'StandardError')
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
rtype = last_expr_type(rescue_body) || 'Object'
|
|
182
|
-
spec[:rescues] << [exc_names, rtype]
|
|
183
|
-
end
|
|
184
|
-
else
|
|
185
|
-
spec[:normal] = last_expr_type(body) || 'Object'
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
spec
|
|
87
|
+
# Return structured normal/rescue return information for a method node.
|
|
88
|
+
#
|
|
89
|
+
# Result shape:
|
|
90
|
+
# - `:normal` => the normal return type
|
|
91
|
+
# - `:rescues` => rescue-branch conditional return info
|
|
92
|
+
#
|
|
93
|
+
# @param [Parser::AST::Node] node
|
|
94
|
+
# @param [String] fallback_type
|
|
95
|
+
# @param [Boolean] nil_as_optional
|
|
96
|
+
# @return [Hash]
|
|
97
|
+
def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true)
|
|
98
|
+
Returns.returns_spec_from_node(
|
|
99
|
+
node,
|
|
100
|
+
fallback_type: fallback_type,
|
|
101
|
+
nil_as_optional: nil_as_optional
|
|
102
|
+
)
|
|
189
103
|
end
|
|
190
104
|
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
# @param [
|
|
196
|
-
# @return [
|
|
197
|
-
def last_expr_type(node)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
last_expr_type(last)
|
|
204
|
-
when :if
|
|
205
|
-
t = last_expr_type(node.children[1])
|
|
206
|
-
e = last_expr_type(node.children[2])
|
|
207
|
-
unify_types(t, e)
|
|
208
|
-
when :case
|
|
209
|
-
# check whens and else
|
|
210
|
-
branches = node.children[1..].compact.flat_map do |child|
|
|
211
|
-
if child && child.type == :when
|
|
212
|
-
last_expr_type(child.children.last)
|
|
213
|
-
else
|
|
214
|
-
last_expr_type(child)
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
branches.compact!
|
|
218
|
-
branches.empty? ? 'Object' : branches.reduce { |a, b| unify_types(a, b) }
|
|
219
|
-
when :return
|
|
220
|
-
type_from_literal(node.children.first)
|
|
221
|
-
else
|
|
222
|
-
type_from_literal(node)
|
|
223
|
-
end
|
|
105
|
+
# Infer the type of the last expression in an AST node.
|
|
106
|
+
#
|
|
107
|
+
# @param [Parser::AST::Node, nil] node
|
|
108
|
+
# @param [String] fallback_type
|
|
109
|
+
# @param [Boolean] nil_as_optional
|
|
110
|
+
# @return [String, nil]
|
|
111
|
+
def last_expr_type(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true)
|
|
112
|
+
Returns.last_expr_type(
|
|
113
|
+
node,
|
|
114
|
+
fallback_type: fallback_type,
|
|
115
|
+
nil_as_optional: nil_as_optional
|
|
116
|
+
)
|
|
224
117
|
end
|
|
225
118
|
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
# Method documentation.
|
|
119
|
+
# Convert a constant AST node into its fully qualified name.
|
|
229
120
|
#
|
|
230
|
-
# @param [
|
|
231
|
-
# @return [
|
|
121
|
+
# @param [Parser::AST::Node, nil] n
|
|
122
|
+
# @return [String, nil]
|
|
232
123
|
def const_full_name(n)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
case n.type
|
|
236
|
-
when :const
|
|
237
|
-
scope, name = *n
|
|
238
|
-
scope_name = const_full_name(scope)
|
|
239
|
-
if scope_name && !scope_name.empty?
|
|
240
|
-
"#{scope_name}::#{name}"
|
|
241
|
-
elsif scope_name == '' # leading ::
|
|
242
|
-
"::#{name}"
|
|
243
|
-
else
|
|
244
|
-
name.to_s
|
|
245
|
-
end
|
|
246
|
-
when :cbase
|
|
247
|
-
'' # represents leading :: scope
|
|
248
|
-
end
|
|
124
|
+
Names.const_full_name(n)
|
|
249
125
|
end
|
|
250
126
|
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
# Method documentation.
|
|
127
|
+
# Infer a YARD-ish type string from a literal AST node.
|
|
254
128
|
#
|
|
255
|
-
# @param [
|
|
256
|
-
# @
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
case node.type
|
|
261
|
-
when :int then 'Integer'
|
|
262
|
-
when :float then 'Float'
|
|
263
|
-
when :str, :dstr then 'String'
|
|
264
|
-
when :sym then 'Symbol'
|
|
265
|
-
when :true, :false then 'Boolean' # rubocop:disable Lint/BooleanSymbol
|
|
266
|
-
when :nil then 'nil'
|
|
267
|
-
when :array then 'Array'
|
|
268
|
-
when :hash then 'Hash'
|
|
269
|
-
when :regexp then 'Regexp'
|
|
270
|
-
when :const
|
|
271
|
-
node.children.last.to_s
|
|
272
|
-
when :send
|
|
273
|
-
recv, meth, = node.children
|
|
274
|
-
if meth == :new && recv && recv.type == :const
|
|
275
|
-
recv.children.last.to_s
|
|
276
|
-
else
|
|
277
|
-
'Object'
|
|
278
|
-
end
|
|
279
|
-
else
|
|
280
|
-
'Object'
|
|
281
|
-
end
|
|
129
|
+
# @param [Parser::AST::Node, nil] node
|
|
130
|
+
# @param [String] fallback_type
|
|
131
|
+
# @return [String]
|
|
132
|
+
def type_from_literal(node, fallback_type: FALLBACK_TYPE)
|
|
133
|
+
Literals.type_from_literal(node, fallback_type: fallback_type)
|
|
282
134
|
end
|
|
283
135
|
|
|
284
|
-
#
|
|
136
|
+
# Unify two inferred type strings conservatively.
|
|
285
137
|
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
# @param [
|
|
289
|
-
# @param [
|
|
138
|
+
# @param [String, nil] a
|
|
139
|
+
# @param [String, nil] b
|
|
140
|
+
# @param [String] fallback_type
|
|
141
|
+
# @param [Boolean] nil_as_optional
|
|
290
142
|
# @return [String]
|
|
291
|
-
def unify_types(a, b)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
'Object'
|
|
143
|
+
def unify_types(a, b, fallback_type: FALLBACK_TYPE, nil_as_optional: true)
|
|
144
|
+
Returns.unify_types(
|
|
145
|
+
a,
|
|
146
|
+
b,
|
|
147
|
+
fallback_type: fallback_type,
|
|
148
|
+
nil_as_optional: nil_as_optional
|
|
149
|
+
)
|
|
299
150
|
end
|
|
300
151
|
end
|
|
301
152
|
end
|