yard-sorbet 0.4.1 → 0.5.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/lib/yard-sorbet.rb +2 -2
- data/lib/yard-sorbet/handlers.rb +11 -0
- data/lib/yard-sorbet/handlers/abstract_dsl_handler.rb +26 -0
- data/lib/yard-sorbet/handlers/enums_handler.rb +28 -0
- data/lib/yard-sorbet/handlers/sig_handler.rb +100 -0
- data/lib/yard-sorbet/{struct_handler.rb → handlers/struct_handler.rb} +12 -13
- data/lib/yard-sorbet/node_utils.rb +68 -0
- data/lib/yard-sorbet/sig_to_yard.rb +105 -81
- data/lib/yard-sorbet/version.rb +2 -1
- metadata +34 -13
- data/lib/yard-sorbet/sig_handler.rb +0 -180
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 360c73bcd93390b21ebf0206cce339822368fca005d03fa3d08a4444a3cab367
|
4
|
+
data.tar.gz: 7b03b59846235fd395bca99868561eaa299b7dbe21eea7c91307c602236baec9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 364ee6ff02687a3a4e2ffd3384c2eb68a848afe60ad0e94c8d675df81ae9a820e805ed07ce1537cee59eb3e979ce2488a1a2210aa19cc1b40365b9dc97368864
|
7
|
+
data.tar.gz: f26eb01fcad908815f27afc9a42ad72050625e5279e3b3b7576991aa1b14ba685148c12d74bbc2cdba19e69b1f416f48fa89253d34a5635259794d088ad7415f
|
data/lib/yard-sorbet.rb
CHANGED
@@ -8,7 +8,7 @@ require 'yard'
|
|
8
8
|
module YARDSorbet; end
|
9
9
|
|
10
10
|
require_relative 'yard-sorbet/directives'
|
11
|
-
require_relative 'yard-sorbet/
|
11
|
+
require_relative 'yard-sorbet/handlers'
|
12
|
+
require_relative 'yard-sorbet/node_utils'
|
12
13
|
require_relative 'yard-sorbet/sig_to_yard'
|
13
|
-
require_relative 'yard-sorbet/struct_handler'
|
14
14
|
require_relative 'yard-sorbet/version'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Custom YARD Handlers
|
5
|
+
# @see https://rubydoc.info/gems/yard/YARD/Handlers/Base YARD Base Handler documentation
|
6
|
+
module YARDSorbet::Handlers; end
|
7
|
+
|
8
|
+
require_relative 'handlers/abstract_dsl_handler'
|
9
|
+
require_relative 'handlers/enums_handler'
|
10
|
+
require_relative 'handlers/sig_handler'
|
11
|
+
require_relative 'handlers/struct_handler'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Apllies an +@abstract+ tag to +abstract!+/+interface!+ modules (if not alerady present).
|
5
|
+
class YARDSorbet::Handlers::AbstractDSLHandler < YARD::Handlers::Ruby::Base
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
handles method_call(:abstract!), method_call(:interface!)
|
9
|
+
namespace_only
|
10
|
+
|
11
|
+
# The text accompanying the `@abstract` tag.
|
12
|
+
# @see https://github.com/lsegal/yard/blob/main/templates/default/docstring/html/abstract.erb
|
13
|
+
# The `@abstract` tag template
|
14
|
+
TAG_TEXT = 'Subclasses must implement the `abstract` methods below.'
|
15
|
+
# Extra text for class namespaces
|
16
|
+
CLASS_TAG_TEXT = T.let("It cannont be directly instantiated. #{TAG_TEXT}", String)
|
17
|
+
|
18
|
+
sig { void }
|
19
|
+
def process
|
20
|
+
return if namespace.has_tag?(:abstract)
|
21
|
+
|
22
|
+
text = namespace.is_a?(YARD::CodeObjects::ClassObject) ? CLASS_TAG_TEXT : TAG_TEXT
|
23
|
+
tag = YARD::Tags::Tag.new(:abstract, text)
|
24
|
+
namespace.add_tag(tag)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Handle +enums+ calls, registering enum values as constants
|
5
|
+
class YARDSorbet::Handlers::EnumsHandler < YARD::Handlers::Ruby::Base
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
handles method_call(:enums)
|
9
|
+
namespace_only
|
10
|
+
|
11
|
+
sig { void }
|
12
|
+
def process
|
13
|
+
statement.traverse do |node|
|
14
|
+
if const_assign_node?(node)
|
15
|
+
register YARD::CodeObjects::ConstantObject.new(namespace, node.first.source) do |obj|
|
16
|
+
obj.docstring = node.docstring
|
17
|
+
obj.source = node
|
18
|
+
obj.value = node.last.source
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Boolean) }
|
25
|
+
private def const_assign_node?(node)
|
26
|
+
node.type == :assign && node.children.first.children.first.type == :const
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# A YARD Handler for Sorbet type declarations
|
5
|
+
class YARDSorbet::Handlers::SigHandler < YARD::Handlers::Ruby::Base
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
handles method_call(:sig)
|
9
|
+
namespace_only
|
10
|
+
|
11
|
+
# A struct that holds the parsed contents of a Sorbet type signature
|
12
|
+
class ParsedSig < T::Struct
|
13
|
+
prop :abstract, T::Boolean, default: false
|
14
|
+
prop :params, T::Hash[String, T::Array[String]], default: {}
|
15
|
+
prop :return, T.nilable(T::Array[String])
|
16
|
+
end
|
17
|
+
|
18
|
+
# Skip these node types when parsing `sig` params
|
19
|
+
PARAM_EXCLUDES = T.let(%i[array call hash].freeze, T::Array[Symbol])
|
20
|
+
# Skip these node types when parsing `sig`s
|
21
|
+
SIG_EXCLUDES = T.let(%i[array hash].freeze, T::Array[Symbol])
|
22
|
+
|
23
|
+
private_constant :ParsedSig, :PARAM_EXCLUDES, :SIG_EXCLUDES
|
24
|
+
|
25
|
+
# Swap the method definition docstring and the sig docstring.
|
26
|
+
# Parse relevant parts of the +sig+ and include them as well.
|
27
|
+
sig { void }
|
28
|
+
def process
|
29
|
+
method_node = YARDSorbet::NodeUtils.get_method_node(YARDSorbet::NodeUtils.sibling_node(statement))
|
30
|
+
docstring, directives = YARDSorbet::Directives.extract_directives(statement.docstring)
|
31
|
+
parsed_sig = parse_sig(statement)
|
32
|
+
enhance_tag(docstring, :abstract, parsed_sig)
|
33
|
+
enhance_tag(docstring, :return, parsed_sig)
|
34
|
+
if method_node.type != :command
|
35
|
+
parsed_sig.params.each do |name, types|
|
36
|
+
enhance_param(docstring, name, types)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
method_node.docstring = docstring.to_raw
|
40
|
+
YARDSorbet::Directives.add_directives(method_node.docstring, directives)
|
41
|
+
statement.docstring = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(docstring: YARD::Docstring, name: String, types: T::Array[String]).void }
|
45
|
+
private def enhance_param(docstring, name, types)
|
46
|
+
tag = docstring.tags.find { |t| t.tag_name == 'param' && t.name == name }
|
47
|
+
if tag
|
48
|
+
docstring.delete_tag_if { |t| t == tag }
|
49
|
+
tag.types = types
|
50
|
+
else
|
51
|
+
tag = YARD::Tags::Tag.new(:param, '', types, name)
|
52
|
+
end
|
53
|
+
docstring.add_tag(tag)
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { params(docstring: YARD::Docstring, type: Symbol, parsed_sig: ParsedSig).void }
|
57
|
+
private def enhance_tag(docstring, type, parsed_sig)
|
58
|
+
type_value = parsed_sig.public_send(type)
|
59
|
+
return unless type_value
|
60
|
+
|
61
|
+
tag = docstring.tags.find { |t| t.tag_name == type.to_s }
|
62
|
+
if tag
|
63
|
+
docstring.delete_tags(type)
|
64
|
+
else
|
65
|
+
tag = YARD::Tags::Tag.new(type, '')
|
66
|
+
end
|
67
|
+
if type_value.is_a?(Array)
|
68
|
+
tag.types = type_value
|
69
|
+
end
|
70
|
+
docstring.add_tag(tag)
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { params(sig_node: YARD::Parser::Ruby::MethodCallNode).returns(ParsedSig) }
|
74
|
+
private def parse_sig(sig_node)
|
75
|
+
parsed = ParsedSig.new
|
76
|
+
found_params = T.let(false, T::Boolean)
|
77
|
+
found_return = T.let(false, T::Boolean)
|
78
|
+
YARDSorbet::NodeUtils.bfs_traverse(sig_node, exclude: SIG_EXCLUDES) do |n|
|
79
|
+
if n.source == 'abstract'
|
80
|
+
parsed.abstract = true
|
81
|
+
elsif n.source == 'params' && !found_params
|
82
|
+
found_params = true
|
83
|
+
sibling = YARDSorbet::NodeUtils.sibling_node(n)
|
84
|
+
YARDSorbet::NodeUtils.bfs_traverse(sibling, exclude: PARAM_EXCLUDES) do |p|
|
85
|
+
if p.type == :assoc
|
86
|
+
param_name = p.children.first.source[0...-1]
|
87
|
+
types = YARDSorbet::SigToYARD.convert(p.children.last)
|
88
|
+
parsed.params[param_name] = types
|
89
|
+
end
|
90
|
+
end
|
91
|
+
elsif n.source == 'returns' && !found_return
|
92
|
+
found_return = true
|
93
|
+
parsed.return = YARDSorbet::SigToYARD.convert(YARDSorbet::NodeUtils.sibling_node(n))
|
94
|
+
elsif n.source == 'void'
|
95
|
+
parsed.return ||= ['void']
|
96
|
+
end
|
97
|
+
end
|
98
|
+
parsed
|
99
|
+
end
|
100
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
# Handles all
|
4
|
+
# Handles all +const+/+prop+ calls, creating accessor methods, and compiles them for later usage at the class level
|
5
5
|
# in creating a constructor
|
6
|
-
class YARDSorbet::StructHandler < YARD::Handlers::Ruby::Base
|
6
|
+
class YARDSorbet::Handlers::StructHandler < YARD::Handlers::Ruby::Base
|
7
7
|
extend T::Sig
|
8
8
|
|
9
9
|
handles method_call(:const), method_call(:prop)
|
@@ -53,24 +53,25 @@ class YARDSorbet::StructHandler < YARD::Handlers::Ruby::Base
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
# Class-level handler that folds all
|
56
|
+
# Class-level handler that folds all +const+ and +prop+ declarations into the constructor documentation
|
57
57
|
# this needs to be injected as a module otherwise the default Class handler will overwrite documentation
|
58
58
|
module YARDSorbet::StructClassHandler
|
59
|
+
extend T::Helpers
|
59
60
|
extend T::Sig
|
60
61
|
|
62
|
+
requires_ancestor YARD::Handlers::Ruby::ClassHandler
|
63
|
+
|
61
64
|
sig { void }
|
62
65
|
def process
|
63
|
-
|
66
|
+
super
|
64
67
|
|
65
|
-
return
|
68
|
+
return if extra_state.prop_docs.nil?
|
66
69
|
|
67
70
|
# lookup the full YARD path for the current class
|
68
|
-
class_ns = YARD::CodeObjects::ClassObject.new(
|
69
|
-
|
70
|
-
)
|
71
|
-
props = T.unsafe(self).extra_state.prop_docs[class_ns]
|
71
|
+
class_ns = YARD::CodeObjects::ClassObject.new(namespace, statement[0].source.gsub(/\s/, ''))
|
72
|
+
props = extra_state.prop_docs[class_ns]
|
72
73
|
|
73
|
-
return
|
74
|
+
return if props.empty?
|
74
75
|
|
75
76
|
# Create a virtual `initialize` method with all the `prop`/`const` arguments
|
76
77
|
# having the name :initialize & the scope :instance marks this as the constructor.
|
@@ -97,13 +98,11 @@ module YARDSorbet::StructClassHandler
|
|
97
98
|
object.source ||= props.map { |p| p[:source] }.join("\n")
|
98
99
|
object.explicit ||= false # not strictly necessary
|
99
100
|
|
100
|
-
|
101
|
+
register(object)
|
101
102
|
|
102
103
|
object.docstring = docstring.to_raw
|
103
104
|
|
104
105
|
YARDSorbet::Directives.add_directives(object.docstring, directives)
|
105
|
-
|
106
|
-
ret
|
107
106
|
end
|
108
107
|
end
|
109
108
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Helper methods for working with YARD AST Nodes
|
5
|
+
module YARDSorbet::NodeUtils
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
# Command node types that can have type signatures
|
9
|
+
ATTRIBUTE_METHODS = T.let(%i[attr attr_accessor attr_reader attr_writer].freeze, T::Array[Symbol])
|
10
|
+
# Node types that can have type signatures
|
11
|
+
SIGABLE_NODE = T.type_alias do
|
12
|
+
T.any(YARD::Parser::Ruby::MethodDefinitionNode, YARD::Parser::Ruby::MethodCallNode)
|
13
|
+
end
|
14
|
+
|
15
|
+
private_constant :ATTRIBUTE_METHODS, :SIGABLE_NODE
|
16
|
+
|
17
|
+
# @yield [YARD::Parser::Ruby::AstNode]
|
18
|
+
sig do
|
19
|
+
params(
|
20
|
+
node: YARD::Parser::Ruby::AstNode,
|
21
|
+
exclude: T::Array[Symbol],
|
22
|
+
_blk: T.proc.params(n: YARD::Parser::Ruby::AstNode).void
|
23
|
+
).void
|
24
|
+
end
|
25
|
+
def self.bfs_traverse(node, exclude: [], &_blk)
|
26
|
+
queue = [node]
|
27
|
+
until queue.empty?
|
28
|
+
n = T.must(queue.shift)
|
29
|
+
yield n
|
30
|
+
n.children.each do |c|
|
31
|
+
unless exclude.include?(c.type)
|
32
|
+
queue.push(c)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Gets the node that a sorbet `sig` can be attached do, bypassing visisbility modifiers and the like
|
39
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(SIGABLE_NODE) }
|
40
|
+
def self.get_method_node(node)
|
41
|
+
case node
|
42
|
+
when YARD::Parser::Ruby::MethodDefinitionNode
|
43
|
+
return node
|
44
|
+
when YARD::Parser::Ruby::MethodCallNode
|
45
|
+
return node if ATTRIBUTE_METHODS.include?(node.method_name(true))
|
46
|
+
end
|
47
|
+
|
48
|
+
node.jump(:def, :defs)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Find and return the adjacent node (ascending)
|
52
|
+
# @raise [IndexError] if the node does not have an adjacent sibling (ascending)
|
53
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(YARD::Parser::Ruby::AstNode) }
|
54
|
+
def self.sibling_node(node)
|
55
|
+
siblings = node.parent.children
|
56
|
+
siblings.each_with_index.find do |sibling, i|
|
57
|
+
if sibling.equal?(node)
|
58
|
+
return siblings.fetch(i + 1)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns true if the given node represents a type signature.
|
64
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Boolean) }
|
65
|
+
def self.type_signature?(node)
|
66
|
+
node.is_a?(YARD::Parser::Ruby::MethodCallNode) && node.method_name(true) == :sig
|
67
|
+
end
|
68
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
# Translate sig type syntax to YARD type syntax.
|
4
|
+
# Translate +sig+ type syntax to +YARD+ type syntax.
|
5
5
|
module YARDSorbet::SigToYARD
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
+
# Ruby 2.5 parsed call nodes slightly differently
|
8
9
|
IS_LEGACY_RUBY_VERSION = T.let(RUBY_VERSION.start_with?('2.5.'), T::Boolean)
|
10
|
+
private_constant :IS_LEGACY_RUBY_VERSION
|
9
11
|
|
10
12
|
# @see https://yardoc.org/types.html
|
11
13
|
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
@@ -16,81 +18,23 @@ module YARDSorbet::SigToYARD
|
|
16
18
|
end
|
17
19
|
|
18
20
|
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
19
|
-
def self.convert_type(node)
|
20
|
-
children = node.children
|
21
|
+
private_class_method def self.convert_type(node)
|
21
22
|
case node.type
|
22
|
-
when :aref
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
when :arg_paren
|
38
|
-
convert(children.first.children.first)
|
39
|
-
when :array
|
40
|
-
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Order-Dependent_Lists
|
41
|
-
member_types = children.first.children.map { |n| convert(n) }.join(', ')
|
42
|
-
["Array(#{member_types})"]
|
43
|
-
when :call
|
44
|
-
if children[0].source == 'T'
|
45
|
-
t_method = IS_LEGACY_RUBY_VERSION ? children[1].source : children[2].source
|
46
|
-
case t_method
|
47
|
-
when 'all', 'attached_class', 'class_of', 'enum', 'noreturn', 'self_type', 'type_parameter', 'untyped'
|
48
|
-
# YARD doesn't have equivalent notions, so we just use the raw source
|
49
|
-
[node.source]
|
50
|
-
when 'any'
|
51
|
-
children.last.children.first.children.map { |n| convert(n) }.flatten
|
52
|
-
when 'nilable'
|
53
|
-
# Order matters here, putting `nil` last results in a more concise
|
54
|
-
# return syntax in the UI (superscripted `?`)
|
55
|
-
convert(children.last) + ['nil']
|
56
|
-
else
|
57
|
-
log.warn("Unsupported T method #{node.source}")
|
58
|
-
[node.source]
|
59
|
-
end
|
60
|
-
else
|
61
|
-
[node.source]
|
62
|
-
end
|
63
|
-
when :const_path_ref, :const
|
64
|
-
case node.source
|
65
|
-
when 'T::Boolean'
|
66
|
-
['Boolean'] # YARD convention for booleans
|
67
|
-
else
|
68
|
-
[node.source]
|
69
|
-
end
|
70
|
-
when :hash, :list
|
71
|
-
# Fixed hashes as return values are unsupported:
|
72
|
-
# https://github.com/lsegal/yard/issues/425
|
73
|
-
#
|
74
|
-
# Hash key params can be individually documented with `@option`, but
|
75
|
-
# sig translation is unsupported.
|
76
|
-
['Hash']
|
77
|
-
when :var_ref
|
78
|
-
# YARD convention is use singleton objects when applicable:
|
79
|
-
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Literals
|
80
|
-
case node.source
|
81
|
-
when 'FalseClass'
|
82
|
-
['false']
|
83
|
-
when 'NilClass'
|
84
|
-
['nil']
|
85
|
-
when 'TrueClass'
|
86
|
-
['true']
|
87
|
-
else
|
88
|
-
[node.source]
|
89
|
-
end
|
90
|
-
when :top_const_ref
|
91
|
-
# A top-level constant reference, such as ::Klass
|
92
|
-
# It contains a child node of type :const
|
93
|
-
convert(children.first)
|
23
|
+
when :aref then handle_aref(node)
|
24
|
+
when :arg_paren then handle_arg_paren(node)
|
25
|
+
when :array then handle_array(node)
|
26
|
+
when :call then handle_call(node)
|
27
|
+
when :const_path_ref, :const then handle_const(node)
|
28
|
+
# Fixed hashes as return values are unsupported:
|
29
|
+
# https://github.com/lsegal/yard/issues/425
|
30
|
+
#
|
31
|
+
# Hash key params can be individually documented with `@option`, but
|
32
|
+
# sig translation is unsupported.
|
33
|
+
when :hash, :list then ['Hash']
|
34
|
+
when :var_ref then handle_var_ref(node)
|
35
|
+
# A top-level constant reference, such as ::Klass
|
36
|
+
# It contains a child node of type :const
|
37
|
+
when :top_const_ref then convert(node.children.first)
|
94
38
|
else
|
95
39
|
log.warn("Unsupported sig #{node.type} node #{node.source}")
|
96
40
|
[node.source]
|
@@ -98,14 +42,94 @@ module YARDSorbet::SigToYARD
|
|
98
42
|
end
|
99
43
|
|
100
44
|
sig { params(node: YARD::Parser::Ruby::AstNode).returns(String) }
|
101
|
-
def self.build_generic_type(node)
|
102
|
-
|
45
|
+
private_class_method def self.build_generic_type(node)
|
46
|
+
children = node.children
|
47
|
+
return node.source if children.empty? || node.type != :aref
|
103
48
|
|
104
|
-
collection_type =
|
105
|
-
member_type =
|
106
|
-
.map { |child| build_generic_type(child) }
|
107
|
-
.join(', ')
|
49
|
+
collection_type = children.first.source
|
50
|
+
member_type = children.last.children.map { |child| build_generic_type(child) }.join(', ')
|
108
51
|
|
109
52
|
"#{collection_type}[#{member_type}]"
|
110
53
|
end
|
54
|
+
|
55
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
56
|
+
private_class_method def self.handle_aref(node)
|
57
|
+
children = node.children
|
58
|
+
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Parametrized_Types
|
59
|
+
case children.first.source
|
60
|
+
when 'T::Array', 'T::Enumerable', 'T::Range', 'T::Set'
|
61
|
+
collection_type = children.first.source.split('::').last
|
62
|
+
member_type = convert(children.last.children.first).join(', ')
|
63
|
+
["#{collection_type}<#{member_type}>"]
|
64
|
+
when 'T::Hash'
|
65
|
+
kv = children.last.children
|
66
|
+
key_type = convert(kv.first).join(', ')
|
67
|
+
value_type = convert(kv.last).join(', ')
|
68
|
+
["Hash{#{key_type} => #{value_type}}"]
|
69
|
+
else
|
70
|
+
log.info("Unsupported sig aref node #{node.source}")
|
71
|
+
[build_generic_type(node)]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
76
|
+
private_class_method def self.handle_arg_paren(node)
|
77
|
+
convert(node.children.first.children.first)
|
78
|
+
end
|
79
|
+
|
80
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
81
|
+
private_class_method def self.handle_array(node)
|
82
|
+
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Order-Dependent_Lists
|
83
|
+
member_types = node.children.first.children.map { |n| convert(n) }.join(', ')
|
84
|
+
["Array(#{member_types})"]
|
85
|
+
end
|
86
|
+
|
87
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
88
|
+
private_class_method def self.handle_const(node)
|
89
|
+
source = node.source
|
90
|
+
case source
|
91
|
+
# YARD convention for booleans
|
92
|
+
when 'T::Boolean' then ['Boolean']
|
93
|
+
else [source]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
98
|
+
private_class_method def self.handle_call(node)
|
99
|
+
children = node.children
|
100
|
+
if children[0].source == 'T'
|
101
|
+
t_method = IS_LEGACY_RUBY_VERSION ? children[1].source : children[2].source
|
102
|
+
handle_t_method(t_method, node)
|
103
|
+
else
|
104
|
+
[node.source]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(method_name: String, node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
109
|
+
private_class_method def self.handle_t_method(method_name, node)
|
110
|
+
case method_name
|
111
|
+
# YARD doesn't have equivalent notions, so we just use the raw source
|
112
|
+
when 'all', 'attached_class', 'class_of', 'enum', 'noreturn', 'self_type', 'type_parameter', 'untyped'
|
113
|
+
[node.source]
|
114
|
+
when 'any' then node.children.last.children.first.children.map { |n| convert(n) }.flatten
|
115
|
+
# Order matters here, putting `nil` last results in a more concise
|
116
|
+
# return syntax in the UI (superscripted `?`)
|
117
|
+
when 'nilable' then convert(node.children.last) + ['nil']
|
118
|
+
else
|
119
|
+
log.warn("Unsupported T method #{node.source}")
|
120
|
+
[node.source]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
125
|
+
private_class_method def self.handle_var_ref(node)
|
126
|
+
# YARD convention is use singleton objects when applicable:
|
127
|
+
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Literals
|
128
|
+
case node.source
|
129
|
+
when 'FalseClass' then ['false']
|
130
|
+
when 'NilClass' then ['nil']
|
131
|
+
when 'TrueClass' then ['true']
|
132
|
+
else [node.source]
|
133
|
+
end
|
134
|
+
end
|
111
135
|
end
|
data/lib/yard-sorbet/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yard-sorbet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Douglas Eichelberger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler-audit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: codecov
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +72,14 @@ dependencies:
|
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
75
|
+
version: 1.15.0
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
82
|
+
version: 1.15.0
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rubocop-performance
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,42 +156,42 @@ dependencies:
|
|
142
156
|
requirements:
|
143
157
|
- - "~>"
|
144
158
|
- !ruby/object:Gem::Version
|
145
|
-
version: 0.5.
|
159
|
+
version: 0.5.6289
|
146
160
|
type: :development
|
147
161
|
prerelease: false
|
148
162
|
version_requirements: !ruby/object:Gem::Requirement
|
149
163
|
requirements:
|
150
164
|
- - "~>"
|
151
165
|
- !ruby/object:Gem::Version
|
152
|
-
version: 0.5.
|
166
|
+
version: 0.5.6289
|
153
167
|
- !ruby/object:Gem::Dependency
|
154
168
|
name: sorbet-runtime
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|
156
170
|
requirements:
|
157
171
|
- - ">="
|
158
172
|
- !ruby/object:Gem::Version
|
159
|
-
version: 0.5.
|
173
|
+
version: 0.5.6289
|
160
174
|
type: :runtime
|
161
175
|
prerelease: false
|
162
176
|
version_requirements: !ruby/object:Gem::Requirement
|
163
177
|
requirements:
|
164
178
|
- - ">="
|
165
179
|
- !ruby/object:Gem::Version
|
166
|
-
version: 0.5.
|
180
|
+
version: 0.5.6289
|
167
181
|
- !ruby/object:Gem::Dependency
|
168
182
|
name: yard
|
169
183
|
requirement: !ruby/object:Gem::Requirement
|
170
184
|
requirements:
|
171
185
|
- - ">="
|
172
186
|
- !ruby/object:Gem::Version
|
173
|
-
version: 0.9.
|
187
|
+
version: 0.9.21
|
174
188
|
type: :runtime
|
175
189
|
prerelease: false
|
176
190
|
version_requirements: !ruby/object:Gem::Requirement
|
177
191
|
requirements:
|
178
192
|
- - ">="
|
179
193
|
- !ruby/object:Gem::Version
|
180
|
-
version: 0.9.
|
194
|
+
version: 0.9.21
|
181
195
|
description: 'A YARD plugin that incorporates Sorbet type information
|
182
196
|
|
183
197
|
'
|
@@ -189,17 +203,24 @@ files:
|
|
189
203
|
- LICENSE
|
190
204
|
- lib/yard-sorbet.rb
|
191
205
|
- lib/yard-sorbet/directives.rb
|
192
|
-
- lib/yard-sorbet/
|
206
|
+
- lib/yard-sorbet/handlers.rb
|
207
|
+
- lib/yard-sorbet/handlers/abstract_dsl_handler.rb
|
208
|
+
- lib/yard-sorbet/handlers/enums_handler.rb
|
209
|
+
- lib/yard-sorbet/handlers/sig_handler.rb
|
210
|
+
- lib/yard-sorbet/handlers/struct_handler.rb
|
211
|
+
- lib/yard-sorbet/node_utils.rb
|
193
212
|
- lib/yard-sorbet/sig_to_yard.rb
|
194
|
-
- lib/yard-sorbet/struct_handler.rb
|
195
213
|
- lib/yard-sorbet/version.rb
|
196
214
|
homepage: https://github.com/dduugg/yard-sorbet
|
197
215
|
licenses:
|
198
216
|
- Apache-2.0
|
199
217
|
metadata:
|
218
|
+
bug_tracker_uri: https://github.com/dduugg/yard-sorbet/issues
|
219
|
+
changelog_uri: https://github.com/dduugg/yard-sorbet/blob/master/CHANGELOG.md
|
220
|
+
documentation_uri: https://dduugg.github.io/yard-sorbet/
|
200
221
|
homepage_uri: https://github.com/dduugg/yard-sorbet
|
201
222
|
source_code_uri: https://github.com/dduugg/yard-sorbet
|
202
|
-
|
223
|
+
wiki_uri: https://github.com/dduugg/yard-sorbet/wiki
|
203
224
|
post_install_message:
|
204
225
|
rdoc_options: []
|
205
226
|
require_paths:
|
@@ -1,180 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
# A YARD Handler for Sorbet type declarations
|
5
|
-
class YARDSorbet::SigHandler < YARD::Handlers::Ruby::Base
|
6
|
-
extend T::Sig
|
7
|
-
handles :class, :module, :singleton_class?
|
8
|
-
|
9
|
-
# A struct that holds the parsed contents of a Sorbet type signature
|
10
|
-
class ParsedSig < T::Struct
|
11
|
-
prop :abstract, T::Boolean, default: false
|
12
|
-
prop :params, T::Hash[String, T::Array[String]], default: {}
|
13
|
-
prop :return, T.nilable(T::Array[String])
|
14
|
-
end
|
15
|
-
|
16
|
-
PARAM_EXCLUDES = T.let(%i[array call hash].freeze, T::Array[Symbol])
|
17
|
-
PROCESSABLE_NODES = T.let(%i[def defs command].freeze, T::Array[Symbol])
|
18
|
-
SIG_EXCLUDES = T.let(%i[array hash].freeze, T::Array[Symbol])
|
19
|
-
SIG_NODE_TYPES = T.let(%i[call fcall vcall].freeze, T::Array[Symbol])
|
20
|
-
|
21
|
-
private_constant :ParsedSig, :PARAM_EXCLUDES, :PROCESSABLE_NODES, :SIG_EXCLUDES, :SIG_NODE_TYPES
|
22
|
-
|
23
|
-
sig { void }
|
24
|
-
def process
|
25
|
-
# Find the list of declarations inside the class
|
26
|
-
class_def = statement.children.find { |c| c.type == :list }
|
27
|
-
class_contents = class_def.children
|
28
|
-
|
29
|
-
process_class_contents(class_contents)
|
30
|
-
end
|
31
|
-
|
32
|
-
sig { params(class_contents: T::Array[YARD::Parser::Ruby::MethodCallNode]).void }
|
33
|
-
private def process_class_contents(class_contents)
|
34
|
-
class_contents.each_with_index do |child, i|
|
35
|
-
if child.type == :sclass && child.children.size == 2 && child.children[1].type == :list
|
36
|
-
singleton_class_contents = child.children[1]
|
37
|
-
process_class_contents(singleton_class_contents)
|
38
|
-
end
|
39
|
-
next unless type_signature?(child)
|
40
|
-
|
41
|
-
next_statement = class_contents[i + 1]
|
42
|
-
next unless processable_method?(next_statement)
|
43
|
-
|
44
|
-
process_method_definition(T.must(next_statement), child)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
sig { params(next_statement: T.nilable(YARD::Parser::Ruby::AstNode)).returns(T::Boolean) }
|
49
|
-
private def processable_method?(next_statement)
|
50
|
-
PROCESSABLE_NODES.include?(next_statement&.type)
|
51
|
-
end
|
52
|
-
|
53
|
-
sig do
|
54
|
-
params(
|
55
|
-
method_node: YARD::Parser::Ruby::AstNode,
|
56
|
-
sig_node: YARD::Parser::Ruby::MethodCallNode
|
57
|
-
).void
|
58
|
-
end
|
59
|
-
private def process_method_definition(method_node, sig_node)
|
60
|
-
# Swap the method definition docstring and the sig docstring.
|
61
|
-
# Parse relevant parts of the `sig` and include them as well.
|
62
|
-
docstring, directives = YARDSorbet::Directives.extract_directives(sig_node.docstring)
|
63
|
-
parsed_sig = parse_sig(sig_node)
|
64
|
-
enhance_tag(docstring, :abstract, parsed_sig)
|
65
|
-
enhance_tag(docstring, :return, parsed_sig)
|
66
|
-
if method_node.type != :command
|
67
|
-
parsed_sig.params.each do |name, types|
|
68
|
-
enhance_param(docstring, name, types)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
method_node.docstring = docstring.to_raw
|
72
|
-
YARDSorbet::Directives.add_directives(method_node.docstring, directives)
|
73
|
-
sig_node.docstring = nil
|
74
|
-
end
|
75
|
-
|
76
|
-
sig { params(docstring: YARD::Docstring, name: String, types: T::Array[String]).void }
|
77
|
-
private def enhance_param(docstring, name, types)
|
78
|
-
tag = docstring.tags.find { |t| t.tag_name == 'param' && t.name == name }
|
79
|
-
if tag
|
80
|
-
docstring.delete_tag_if { |t| t == tag }
|
81
|
-
tag.types = types
|
82
|
-
else
|
83
|
-
tag = YARD::Tags::Tag.new(:param, '', types, name)
|
84
|
-
end
|
85
|
-
docstring.add_tag(tag)
|
86
|
-
end
|
87
|
-
|
88
|
-
sig { params(docstring: YARD::Docstring, type: Symbol, parsed_sig: ParsedSig).void }
|
89
|
-
private def enhance_tag(docstring, type, parsed_sig)
|
90
|
-
type_value = parsed_sig.public_send(type)
|
91
|
-
return if !type_value
|
92
|
-
|
93
|
-
tag = docstring.tags.find { |t| t.tag_name == type.to_s }
|
94
|
-
if tag
|
95
|
-
docstring.delete_tags(type)
|
96
|
-
else
|
97
|
-
tag = YARD::Tags::Tag.new(type, '')
|
98
|
-
end
|
99
|
-
if type_value.is_a?(Array)
|
100
|
-
tag.types = type_value
|
101
|
-
end
|
102
|
-
docstring.add_tag(tag)
|
103
|
-
end
|
104
|
-
|
105
|
-
sig { params(sig_node: YARD::Parser::Ruby::MethodCallNode).returns(ParsedSig) }
|
106
|
-
private def parse_sig(sig_node)
|
107
|
-
parsed = ParsedSig.new
|
108
|
-
found_params = T.let(false, T::Boolean)
|
109
|
-
found_return = T.let(false, T::Boolean)
|
110
|
-
bfs_traverse(sig_node, exclude: SIG_EXCLUDES) do |n|
|
111
|
-
if n.source == 'abstract'
|
112
|
-
parsed.abstract = true
|
113
|
-
elsif n.source == 'params' && !found_params
|
114
|
-
found_params = true
|
115
|
-
sibling = T.must(sibling_node(n))
|
116
|
-
bfs_traverse(sibling, exclude: PARAM_EXCLUDES) do |p|
|
117
|
-
if p.type == :assoc
|
118
|
-
param_name = p.children.first.source[0...-1]
|
119
|
-
types = YARDSorbet::SigToYARD.convert(p.children.last)
|
120
|
-
parsed.params[param_name] = types
|
121
|
-
end
|
122
|
-
end
|
123
|
-
elsif n.source == 'returns' && !found_return
|
124
|
-
found_return = true
|
125
|
-
parsed.return = YARDSorbet::SigToYARD.convert(T.must(sibling_node(n)))
|
126
|
-
elsif n.source == 'void'
|
127
|
-
parsed.return ||= ['void']
|
128
|
-
end
|
129
|
-
end
|
130
|
-
parsed
|
131
|
-
end
|
132
|
-
|
133
|
-
# Returns true if the given node is part of a type signature.
|
134
|
-
sig { params(node: T.nilable(YARD::Parser::Ruby::AstNode)).returns(T::Boolean) }
|
135
|
-
private def type_signature?(node)
|
136
|
-
loop do
|
137
|
-
return false if node.nil?
|
138
|
-
return false unless SIG_NODE_TYPES.include?(node.type)
|
139
|
-
return true if T.unsafe(node).method_name(true) == :sig
|
140
|
-
|
141
|
-
node = T.let(node.children.first, T.nilable(YARD::Parser::Ruby::AstNode))
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T.nilable(YARD::Parser::Ruby::AstNode)) }
|
146
|
-
private def sibling_node(node)
|
147
|
-
found_sibling = T.let(false, T::Boolean)
|
148
|
-
node.parent.children.each do |n|
|
149
|
-
if found_sibling
|
150
|
-
return n
|
151
|
-
end
|
152
|
-
|
153
|
-
if n == node
|
154
|
-
found_sibling = true
|
155
|
-
end
|
156
|
-
end
|
157
|
-
nil
|
158
|
-
end
|
159
|
-
|
160
|
-
# @yield [YARD::Parser::Ruby::AstNode]
|
161
|
-
sig do
|
162
|
-
params(
|
163
|
-
node: YARD::Parser::Ruby::AstNode,
|
164
|
-
exclude: T::Array[Symbol],
|
165
|
-
_blk: T.proc.params(n: YARD::Parser::Ruby::AstNode).void
|
166
|
-
).void
|
167
|
-
end
|
168
|
-
private def bfs_traverse(node, exclude: [], &_blk)
|
169
|
-
queue = [node]
|
170
|
-
while !queue.empty?
|
171
|
-
n = T.must(queue.shift)
|
172
|
-
yield n
|
173
|
-
n.children.each do |c|
|
174
|
-
if !exclude.include?(c.type)
|
175
|
-
queue.push(c)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|