docscribe 1.1.0 → 1.2.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 +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 +142 -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 +184 -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 +607 -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
|
@@ -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,306 +1,152 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# NOTE: parser/base references Racc::Parser in some environments, so require runtime first.
|
|
3
4
|
require 'racc/parser'
|
|
4
5
|
require 'ast'
|
|
5
6
|
require 'parser/ast/node'
|
|
6
7
|
require 'parser/source/buffer'
|
|
7
8
|
require 'parser/base'
|
|
9
|
+
|
|
8
10
|
require 'docscribe/parsing'
|
|
9
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'
|
|
19
|
+
|
|
10
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.
|
|
11
30
|
module Infer
|
|
12
31
|
class << self
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# Method documentation.
|
|
32
|
+
# Infer exception classes raised or rescued within an AST node.
|
|
16
33
|
#
|
|
17
|
-
# @param [
|
|
18
|
-
# @return [
|
|
34
|
+
# @param [Parser::AST::Node] node
|
|
35
|
+
# @return [Array<String>]
|
|
19
36
|
def infer_raises_from_node(node)
|
|
20
|
-
|
|
21
|
-
walk = lambda do |n|
|
|
22
|
-
return unless n.is_a?(Parser::AST::Node)
|
|
23
|
-
|
|
24
|
-
case n.type
|
|
25
|
-
when :rescue
|
|
26
|
-
n.children.each { |ch| walk.call(ch) }
|
|
27
|
-
when :resbody
|
|
28
|
-
exc_list = n.children[0]
|
|
29
|
-
if exc_list.nil?
|
|
30
|
-
raises << 'StandardError'
|
|
31
|
-
elsif exc_list.type == :array
|
|
32
|
-
exc_list.children.each { |e| (c = const_full_name(e)) && (raises << c) }
|
|
33
|
-
else
|
|
34
|
-
(c = const_full_name(exc_list)) && (raises << c)
|
|
35
|
-
end
|
|
36
|
-
n.children.each { |ch| walk.call(ch) if ch.is_a?(Parser::AST::Node) }
|
|
37
|
-
when :send
|
|
38
|
-
recv, meth, *args = *n
|
|
39
|
-
if recv.nil? && %i[raise fail].include?(meth)
|
|
40
|
-
if args.empty?
|
|
41
|
-
raises << 'StandardError'
|
|
42
|
-
else
|
|
43
|
-
c = const_full_name(args[0])
|
|
44
|
-
raises << (c || 'StandardError')
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
n.children.each { |ch| walk.call(ch) if ch.is_a?(Parser::AST::Node) }
|
|
48
|
-
else
|
|
49
|
-
n.children.each { |ch| walk.call(ch) if ch.is_a?(Parser::AST::Node) }
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
walk.call(node)
|
|
53
|
-
raises.uniq
|
|
37
|
+
Raises.infer_raises_from_node(node)
|
|
54
38
|
end
|
|
55
39
|
|
|
56
|
-
#
|
|
40
|
+
# Infer a parameter type from its internal name form and optional default
|
|
41
|
+
# expression.
|
|
57
42
|
#
|
|
58
|
-
#
|
|
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
|
|
59
48
|
#
|
|
60
|
-
# @param [
|
|
61
|
-
# @param [
|
|
62
|
-
# @
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
node = parse_expr(default_str)
|
|
73
|
-
ty = type_from_literal(node)
|
|
74
|
-
|
|
75
|
-
# If kw with no default, still show Object (or Hash for options:)
|
|
76
|
-
if is_kw && default_str.nil?
|
|
77
|
-
return (name == 'options:' ? 'Hash' : 'Object')
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# If param named options and default is {}, call it Hash
|
|
81
|
-
return 'Hash' if name == 'options:' && (default_str == '{}' || ty == 'Hash')
|
|
82
|
-
|
|
83
|
-
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
|
+
)
|
|
84
61
|
end
|
|
85
62
|
|
|
86
|
-
#
|
|
63
|
+
# Parse a standalone expression source string for inference helpers.
|
|
87
64
|
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# @param [Object] src Param documentation.
|
|
91
|
-
# @raise [Parser::SyntaxError]
|
|
92
|
-
# @return [Object]
|
|
93
|
-
# @return [nil] if Parser::SyntaxError
|
|
65
|
+
# @param [String, nil] src
|
|
66
|
+
# @return [Parser::AST::Node, nil]
|
|
94
67
|
def parse_expr(src)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
buffer = Parser::Source::Buffer.new('(param)')
|
|
98
|
-
buffer.source = src
|
|
99
|
-
Docscribe::Parsing.parse_buffer(buffer)
|
|
100
|
-
rescue Parser::SyntaxError
|
|
101
|
-
nil
|
|
68
|
+
Params.parse_expr(src)
|
|
102
69
|
end
|
|
103
70
|
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# Method documentation.
|
|
71
|
+
# Infer a return type from full method source.
|
|
107
72
|
#
|
|
108
|
-
# @param [
|
|
109
|
-
# @
|
|
110
|
-
# @return [Object]
|
|
111
|
-
# @return [String] if Parser::SyntaxError
|
|
73
|
+
# @param [String, nil] method_source
|
|
74
|
+
# @return [String]
|
|
112
75
|
def infer_return_type(method_source)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
buffer = Parser::Source::Buffer.new('(method)')
|
|
116
|
-
buffer.source = method_source
|
|
117
|
-
root = Docscribe::Parsing.parse_buffer(buffer)
|
|
118
|
-
return 'Object' unless root && %i[def defs].include?(root.type)
|
|
119
|
-
|
|
120
|
-
body = root.children.last # method body node
|
|
121
|
-
ty = last_expr_type(body)
|
|
122
|
-
ty || 'Object'
|
|
123
|
-
rescue Parser::SyntaxError
|
|
124
|
-
'Object'
|
|
76
|
+
Returns.infer_return_type(method_source)
|
|
125
77
|
end
|
|
126
78
|
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
# Method documentation.
|
|
79
|
+
# Infer a return type from an already parsed `:def` / `:defs` node.
|
|
130
80
|
#
|
|
131
|
-
# @param [
|
|
132
|
-
# @return [
|
|
81
|
+
# @param [Parser::AST::Node] node
|
|
82
|
+
# @return [String]
|
|
133
83
|
def infer_return_type_from_node(node)
|
|
134
|
-
|
|
135
|
-
case node.type
|
|
136
|
-
when :def then node.children[2] # [name, args, body]
|
|
137
|
-
when :defs then node.children[3] # [recv, name, args, body]
|
|
138
|
-
end
|
|
139
|
-
return 'Object' unless body
|
|
140
|
-
|
|
141
|
-
ty = last_expr_type(body)
|
|
142
|
-
ty || 'Object'
|
|
84
|
+
Returns.infer_return_type_from_node(node)
|
|
143
85
|
end
|
|
144
86
|
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if body.type == :rescue
|
|
163
|
-
# child[0] is the main body (before rescue)
|
|
164
|
-
main_body = body.children[0]
|
|
165
|
-
spec[:normal] = last_expr_type(main_body) || 'Object'
|
|
166
|
-
|
|
167
|
-
# :resbody nodes hold exception list, optional var, and rescue body
|
|
168
|
-
body.children.each do |ch|
|
|
169
|
-
next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
|
|
170
|
-
|
|
171
|
-
exc_list, _asgn, rescue_body = *ch
|
|
172
|
-
|
|
173
|
-
exc_names = []
|
|
174
|
-
if exc_list.nil?
|
|
175
|
-
exc_names << 'StandardError'
|
|
176
|
-
elsif exc_list.type == :array
|
|
177
|
-
exc_list.children.each do |e|
|
|
178
|
-
name = const_full_name(e)
|
|
179
|
-
exc_names << (name || 'StandardError')
|
|
180
|
-
end
|
|
181
|
-
else
|
|
182
|
-
name = const_full_name(exc_list)
|
|
183
|
-
exc_names << (name || 'StandardError')
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
rtype = last_expr_type(rescue_body) || 'Object'
|
|
187
|
-
spec[:rescues] << [exc_names, rtype]
|
|
188
|
-
end
|
|
189
|
-
else
|
|
190
|
-
spec[:normal] = last_expr_type(body) || 'Object'
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
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
|
+
)
|
|
194
103
|
end
|
|
195
104
|
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
# @param [
|
|
201
|
-
# @return [
|
|
202
|
-
def last_expr_type(node)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
last_expr_type(last)
|
|
209
|
-
when :if
|
|
210
|
-
t = last_expr_type(node.children[1])
|
|
211
|
-
e = last_expr_type(node.children[2])
|
|
212
|
-
unify_types(t, e)
|
|
213
|
-
when :case
|
|
214
|
-
# check whens and else
|
|
215
|
-
branches = node.children[1..].compact.flat_map do |child|
|
|
216
|
-
if child && child.type == :when
|
|
217
|
-
last_expr_type(child.children.last)
|
|
218
|
-
else
|
|
219
|
-
last_expr_type(child)
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
branches.compact!
|
|
223
|
-
branches.empty? ? 'Object' : branches.reduce { |a, b| unify_types(a, b) }
|
|
224
|
-
when :return
|
|
225
|
-
type_from_literal(node.children.first)
|
|
226
|
-
else
|
|
227
|
-
type_from_literal(node)
|
|
228
|
-
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
|
+
)
|
|
229
117
|
end
|
|
230
118
|
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
# Method documentation.
|
|
119
|
+
# Convert a constant AST node into its fully qualified name.
|
|
234
120
|
#
|
|
235
|
-
# @param [
|
|
236
|
-
# @return [
|
|
121
|
+
# @param [Parser::AST::Node, nil] n
|
|
122
|
+
# @return [String, nil]
|
|
237
123
|
def const_full_name(n)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
case n.type
|
|
241
|
-
when :const
|
|
242
|
-
scope, name = *n
|
|
243
|
-
scope_name = const_full_name(scope)
|
|
244
|
-
if scope_name && !scope_name.empty?
|
|
245
|
-
"#{scope_name}::#{name}"
|
|
246
|
-
elsif scope_name == '' # leading ::
|
|
247
|
-
"::#{name}"
|
|
248
|
-
else
|
|
249
|
-
name.to_s
|
|
250
|
-
end
|
|
251
|
-
when :cbase
|
|
252
|
-
'' # represents leading :: scope
|
|
253
|
-
end
|
|
124
|
+
Names.const_full_name(n)
|
|
254
125
|
end
|
|
255
126
|
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
# Method documentation.
|
|
127
|
+
# Infer a YARD-ish type string from a literal AST node.
|
|
259
128
|
#
|
|
260
|
-
# @param [
|
|
261
|
-
# @
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
case node.type
|
|
266
|
-
when :int then 'Integer'
|
|
267
|
-
when :float then 'Float'
|
|
268
|
-
when :str, :dstr then 'String'
|
|
269
|
-
when :sym then 'Symbol'
|
|
270
|
-
when :true, :false then 'Boolean' # rubocop:disable Lint/BooleanSymbol
|
|
271
|
-
when :nil then 'nil'
|
|
272
|
-
when :array then 'Array'
|
|
273
|
-
when :hash then 'Hash'
|
|
274
|
-
when :regexp then 'Regexp'
|
|
275
|
-
when :const
|
|
276
|
-
node.children.last.to_s
|
|
277
|
-
when :send
|
|
278
|
-
recv, meth, = node.children
|
|
279
|
-
if meth == :new && recv && recv.type == :const
|
|
280
|
-
recv.children.last.to_s
|
|
281
|
-
else
|
|
282
|
-
'Object'
|
|
283
|
-
end
|
|
284
|
-
else
|
|
285
|
-
'Object'
|
|
286
|
-
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)
|
|
287
134
|
end
|
|
288
135
|
|
|
289
|
-
#
|
|
136
|
+
# Unify two inferred type strings conservatively.
|
|
290
137
|
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
# @param [
|
|
294
|
-
# @param [
|
|
138
|
+
# @param [String, nil] a
|
|
139
|
+
# @param [String, nil] b
|
|
140
|
+
# @param [String] fallback_type
|
|
141
|
+
# @param [Boolean] nil_as_optional
|
|
295
142
|
# @return [String]
|
|
296
|
-
def unify_types(a, b)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
'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
|
+
)
|
|
304
150
|
end
|
|
305
151
|
end
|
|
306
152
|
end
|