yard-sorbet 0.4.1 → 0.5.3
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 +5 -5
- data/lib/yard-sorbet/directives.rb +21 -19
- data/lib/yard-sorbet/handlers.rb +14 -0
- data/lib/yard-sorbet/handlers/abstract_dsl_handler.rb +30 -0
- data/lib/yard-sorbet/handlers/enums_handler.rb +34 -0
- data/lib/yard-sorbet/handlers/sig_handler.rb +70 -0
- data/lib/yard-sorbet/handlers/struct_class_handler.rb +72 -0
- data/lib/yard-sorbet/handlers/struct_class_handler.rbi +5 -0
- data/lib/yard-sorbet/handlers/struct_prop_handler.rb +72 -0
- data/lib/yard-sorbet/node_utils.rb +62 -0
- data/lib/yard-sorbet/sig_to_yard.rb +112 -93
- data/lib/yard-sorbet/t_struct_prop.rb +13 -0
- data/lib/yard-sorbet/tag_utils.rb +44 -0
- data/lib/yard-sorbet/version.rb +3 -1
- metadata +57 -18
- data/lib/yard-sorbet/sig_handler.rb +0 -180
- data/lib/yard-sorbet/struct_handler.rb +0 -110
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee5168e45f37fe7d16db534727b57fad2a08e05acda8298ef421589aa72f0354
|
4
|
+
data.tar.gz: ce10ba6df63c35ba4b52eb4f7d8b02878cfb01fea7424e966aad739691d1d767
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: faa0b506388d501f6567ddfd08144185b1e761a1d4d4cfec12e3256f3985f626ddbc2da1423b37896fbe132a1e729a3c44b995b957eb0391aa48ded9519978d4
|
7
|
+
data.tar.gz: 2afbc9875afecab271abccfd12b5023c3e7db429f23f689fdbed18ed2e0fd7f3b51137ee86b649bb501ea921c469774e2096887d1d063599ae72939951b2f719
|
data/lib/yard-sorbet.rb
CHANGED
@@ -4,11 +4,11 @@
|
|
4
4
|
require 'sorbet-runtime'
|
5
5
|
require 'yard'
|
6
6
|
|
7
|
-
|
8
|
-
module YARDSorbet; end
|
7
|
+
require_relative 'yard-sorbet/version'
|
9
8
|
|
10
9
|
require_relative 'yard-sorbet/directives'
|
11
|
-
require_relative 'yard-sorbet/
|
10
|
+
require_relative 'yard-sorbet/handlers'
|
11
|
+
require_relative 'yard-sorbet/node_utils'
|
12
12
|
require_relative 'yard-sorbet/sig_to_yard'
|
13
|
-
require_relative 'yard-sorbet/
|
14
|
-
require_relative 'yard-sorbet/
|
13
|
+
require_relative 'yard-sorbet/t_struct_prop'
|
14
|
+
require_relative 'yard-sorbet/tag_utils'
|
@@ -1,28 +1,30 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
module YARDSorbet
|
5
|
+
# Extract & re-add directives to a docstring
|
6
|
+
module Directives
|
7
|
+
extend T::Sig
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
sig { params(docstring: T.nilable(String)).returns([YARD::Docstring, T::Array[String]]) }
|
10
|
+
def self.extract_directives(docstring)
|
11
|
+
parser = YARD::DocstringParser.new.parse(docstring)
|
12
|
+
# Directives are already parsed at this point, and there doesn't
|
13
|
+
# seem to be an API to tweeze them from one node to another without
|
14
|
+
# managing YARD internal state. Instead, we just extract them from
|
15
|
+
# the raw text and re-attach them.
|
16
|
+
directives = parser.raw_text&.split("\n")&.select do |line|
|
17
|
+
line.start_with?('@!')
|
18
|
+
end || []
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
[parser.to_docstring, directives]
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
sig { params(docstring: String, directives: T::Array[String]).void }
|
24
|
+
def self.add_directives(docstring, directives)
|
25
|
+
directives.each do |directive|
|
26
|
+
docstring.concat("\n#{directive}")
|
27
|
+
end
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
# Custom YARD Handlers
|
6
|
+
# @see https://rubydoc.info/gems/yard/YARD/Handlers/Base YARD Base Handler documentation
|
7
|
+
module Handlers; end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'handlers/abstract_dsl_handler'
|
11
|
+
require_relative 'handlers/enums_handler'
|
12
|
+
require_relative 'handlers/sig_handler'
|
13
|
+
require_relative 'handlers/struct_class_handler'
|
14
|
+
require_relative 'handlers/struct_prop_handler'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
module Handlers
|
6
|
+
# Apllies an `@abstract` tag to `abstract!`/`interface!` modules (if not alerady present).
|
7
|
+
class AbstractDSLHandler < YARD::Handlers::Ruby::Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
handles method_call(:abstract!), method_call(:interface!)
|
11
|
+
namespace_only
|
12
|
+
|
13
|
+
# The text accompanying the `@abstract` tag.
|
14
|
+
# @see https://github.com/lsegal/yard/blob/main/templates/default/docstring/html/abstract.erb
|
15
|
+
# The `@abstract` tag template
|
16
|
+
TAG_TEXT = 'Subclasses must implement the `abstract` methods below.'
|
17
|
+
# Extra text for class namespaces
|
18
|
+
CLASS_TAG_TEXT = T.let("It cannont be directly instantiated. #{TAG_TEXT}", String)
|
19
|
+
|
20
|
+
sig { void }
|
21
|
+
def process
|
22
|
+
return if namespace.has_tag?(:abstract)
|
23
|
+
|
24
|
+
text = namespace.is_a?(YARD::CodeObjects::ClassObject) ? CLASS_TAG_TEXT : TAG_TEXT
|
25
|
+
tag = YARD::Tags::Tag.new(:abstract, text)
|
26
|
+
namespace.add_tag(tag)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
module Handlers
|
6
|
+
# Handle `enums` calls, registering enum values as constants
|
7
|
+
class EnumsHandler < YARD::Handlers::Ruby::Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
handles method_call(:enums)
|
11
|
+
namespace_only
|
12
|
+
|
13
|
+
sig { void }
|
14
|
+
def process
|
15
|
+
statement.traverse do |node|
|
16
|
+
if const_assign_node?(node)
|
17
|
+
register YARD::CodeObjects::ConstantObject.new(namespace, node.first.source) do |obj|
|
18
|
+
obj.docstring = node.docstring
|
19
|
+
obj.source = node
|
20
|
+
obj.value = node.last.source
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Boolean) }
|
29
|
+
def const_assign_node?(node)
|
30
|
+
node.type == :assign && node[0][0].type == :const
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
module Handlers
|
6
|
+
# A YARD Handler for Sorbet type declarations
|
7
|
+
class SigHandler < YARD::Handlers::Ruby::Base
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
handles method_call(:sig)
|
11
|
+
namespace_only
|
12
|
+
|
13
|
+
# These node types attached to sigs represent attr_* declarations
|
14
|
+
ATTR_NODE_TYPES = T.let(%i[command fcall], T::Array[Symbol])
|
15
|
+
private_constant :ATTR_NODE_TYPES
|
16
|
+
|
17
|
+
# Swap the method definition docstring and the sig docstring.
|
18
|
+
# Parse relevant parts of the `sig` and include them as well.
|
19
|
+
sig { void }
|
20
|
+
def process
|
21
|
+
method_node = NodeUtils.get_method_node(NodeUtils.sibling_node(statement))
|
22
|
+
docstring, directives = Directives.extract_directives(statement.docstring)
|
23
|
+
parse_sig(method_node, docstring)
|
24
|
+
method_node.docstring = docstring.to_raw
|
25
|
+
Directives.add_directives(method_node.docstring, directives)
|
26
|
+
statement.docstring = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
sig { params(method_node: YARD::Parser::Ruby::AstNode, docstring: YARD::Docstring).void }
|
32
|
+
def parse_sig(method_node, docstring)
|
33
|
+
NodeUtils.bfs_traverse(statement) do |n|
|
34
|
+
case n.source
|
35
|
+
when 'abstract'
|
36
|
+
YARDSorbet::TagUtils.upsert_tag(docstring, 'abstract')
|
37
|
+
when 'params'
|
38
|
+
parse_params(method_node, n, docstring)
|
39
|
+
when 'returns', 'void'
|
40
|
+
parse_return(n, docstring)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
sig do
|
46
|
+
params(
|
47
|
+
method_node: YARD::Parser::Ruby::AstNode,
|
48
|
+
node: YARD::Parser::Ruby::AstNode,
|
49
|
+
docstring: YARD::Docstring
|
50
|
+
).void
|
51
|
+
end
|
52
|
+
def parse_params(method_node, node, docstring)
|
53
|
+
return if ATTR_NODE_TYPES.include?(method_node.type)
|
54
|
+
|
55
|
+
sibling = NodeUtils.sibling_node(node)
|
56
|
+
sibling[0][0].each do |p|
|
57
|
+
param_name = p[0][0]
|
58
|
+
types = SigToYARD.convert(p.last)
|
59
|
+
TagUtils.upsert_tag(docstring, 'param', types, param_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
sig { params(node: YARD::Parser::Ruby::AstNode, docstring: YARD::Docstring).void }
|
64
|
+
def parse_return(node, docstring)
|
65
|
+
type = node.source == 'void' ? ['void'] : SigToYARD.convert(NodeUtils.sibling_node(node))
|
66
|
+
TagUtils.upsert_tag(docstring, 'return', type)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
module Handlers
|
6
|
+
# Class-level handler that folds all `const` and `prop` declarations into the constructor documentation
|
7
|
+
# this needs to be injected as a module otherwise the default Class handler will overwrite documentation
|
8
|
+
#
|
9
|
+
# @note this module is included in `YARD::Handlers::Ruby::ClassHandler`
|
10
|
+
module StructClassHandler
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { void }
|
14
|
+
def process
|
15
|
+
super
|
16
|
+
return if extra_state.prop_docs.nil?
|
17
|
+
|
18
|
+
# lookup the full YARD path for the current class
|
19
|
+
class_ns = YARD::CodeObjects::ClassObject.new(namespace, statement[0].source.gsub(/\s/, ''))
|
20
|
+
props = extra_state.prop_docs[class_ns]
|
21
|
+
return if props.empty?
|
22
|
+
|
23
|
+
process_t_struct_props(props, class_ns)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Create a virtual `initialize` method with all the `prop`/`const` arguments
|
29
|
+
sig { params(props: T::Array[TStructProp], class_ns: YARD::CodeObjects::ClassObject).void }
|
30
|
+
def process_t_struct_props(props, class_ns)
|
31
|
+
# having the name :initialize & the scope :instance marks this as the constructor.
|
32
|
+
object = YARD::CodeObjects::MethodObject.new(class_ns, :initialize, :instance)
|
33
|
+
# There is a chance that there is a custom initializer, so make sure we steal the existing docstring
|
34
|
+
# and source
|
35
|
+
docstring, directives = Directives.extract_directives(object.docstring)
|
36
|
+
object.tags.each { |tag| docstring.add_tag(tag) }
|
37
|
+
props.each do |prop|
|
38
|
+
TagUtils.upsert_tag(docstring, 'param', prop.types, prop.prop_name, prop.doc)
|
39
|
+
end
|
40
|
+
TagUtils.upsert_tag(docstring, 'return', ['void'])
|
41
|
+
decorate_t_struct_init(object, props, docstring, directives)
|
42
|
+
end
|
43
|
+
|
44
|
+
sig do
|
45
|
+
params(
|
46
|
+
object: YARD::CodeObjects::MethodObject,
|
47
|
+
props: T::Array[TStructProp],
|
48
|
+
docstring: YARD::Docstring,
|
49
|
+
directives: T::Array[String]
|
50
|
+
).void
|
51
|
+
end
|
52
|
+
def decorate_t_struct_init(object, props, docstring, directives)
|
53
|
+
# Use kwarg style arguments, with optionals being marked with a default (unless an actual default was specified)
|
54
|
+
object.parameters = to_object_parameters(props)
|
55
|
+
# The "source" of our constructor is the field declarations
|
56
|
+
object.source ||= props.map(&:source).join("\n")
|
57
|
+
object.docstring = docstring
|
58
|
+
Directives.add_directives(object.docstring, directives)
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { params(props: T::Array[TStructProp]).returns(T::Array[[String, T.nilable(String)]]) }
|
62
|
+
def to_object_parameters(props)
|
63
|
+
props.map do |prop|
|
64
|
+
default = prop.default || (prop.types.include?('nil') ? 'nil' : nil)
|
65
|
+
["#{prop.prop_name}:", default]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
YARD::Handlers::Ruby::ClassHandler.include YARDSorbet::Handlers::StructClassHandler
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
module Handlers
|
6
|
+
# Handles all `const`/`prop` calls, creating accessor methods, and compiles them for later usage at the class level
|
7
|
+
# in creating a constructor
|
8
|
+
class StructPropHandler < YARD::Handlers::Ruby::Base
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
handles method_call(:const), method_call(:prop)
|
12
|
+
namespace_only
|
13
|
+
|
14
|
+
sig { void }
|
15
|
+
def process
|
16
|
+
name = statement.parameters.first.last.last.source
|
17
|
+
prop = make_prop(name)
|
18
|
+
update_state(prop)
|
19
|
+
object = YARD::CodeObjects::MethodObject.new(namespace, name, scope)
|
20
|
+
decorate_object(object, prop)
|
21
|
+
register_attrs(object, name)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Add the source and docstring to the method object
|
27
|
+
sig { params(object: YARD::CodeObjects::MethodObject, prop: TStructProp).void }
|
28
|
+
def decorate_object(object, prop)
|
29
|
+
object.source = prop.source
|
30
|
+
# TODO: this should use `+` to delimit the attribute name when markdown is disabled
|
31
|
+
reader_docstring = prop.doc.empty? ? "Returns the value of attribute `#{prop.prop_name}`." : prop.doc
|
32
|
+
docstring = YARD::DocstringParser.new.parse(reader_docstring).to_docstring
|
33
|
+
docstring.add_tag(YARD::Tags::Tag.new(:return, '', prop.types))
|
34
|
+
object.docstring = docstring.to_raw
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get the default prop value
|
38
|
+
sig { returns(T.nilable(String)) }
|
39
|
+
def default_value
|
40
|
+
default_node = statement.traverse { |n| break n if n.type == :label && n.source == 'default:' }
|
41
|
+
default_node.parent[1].source if default_node
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(name: String).returns(TStructProp) }
|
45
|
+
def make_prop(name)
|
46
|
+
TStructProp.new(
|
47
|
+
default: default_value,
|
48
|
+
doc: statement.docstring.to_s,
|
49
|
+
prop_name: name,
|
50
|
+
source: statement.source,
|
51
|
+
types: SigToYARD.convert(statement.parameters[1])
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Register the field explicitly as an attribute.
|
56
|
+
# While `const` attributes are immutable, `prop` attributes may be reassigned.
|
57
|
+
sig { params(object: YARD::CodeObjects::MethodObject, name: String).void }
|
58
|
+
def register_attrs(object, name)
|
59
|
+
# Create the virtual method in our current scope
|
60
|
+
write = statement.method_name(true) == :prop ? object : nil
|
61
|
+
namespace.attributes[scope][name] ||= SymbolHash[read: object, write: write]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Store the prop for use in the constructor definition
|
65
|
+
sig { params(prop: TStructProp).void }
|
66
|
+
def update_state(prop)
|
67
|
+
extra_state.prop_docs ||= Hash.new { |h, k| h[k] = [] }
|
68
|
+
extra_state.prop_docs[namespace] << prop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
# Helper methods for working with `YARD` AST Nodes
|
6
|
+
module NodeUtils
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# Command node types that can have type signatures
|
10
|
+
ATTRIBUTE_METHODS = T.let(%i[attr attr_accessor attr_reader attr_writer].freeze, T::Array[Symbol])
|
11
|
+
# Node types that can have type signatures
|
12
|
+
SIGABLE_NODE = T.type_alias do
|
13
|
+
T.any(YARD::Parser::Ruby::MethodDefinitionNode, YARD::Parser::Ruby::MethodCallNode)
|
14
|
+
end
|
15
|
+
# Skip these method contents during BFS node traversal, they can have their own nested types via `T.Proc`
|
16
|
+
SKIP_METHOD_CONTENTS = T.let(%i[params returns], T::Array[Symbol])
|
17
|
+
|
18
|
+
private_constant :ATTRIBUTE_METHODS, :SIGABLE_NODE
|
19
|
+
|
20
|
+
# Traverese AST nodes in breadth-first order
|
21
|
+
# @note This will skip over some node types.
|
22
|
+
# @yield [YARD::Parser::Ruby::AstNode]
|
23
|
+
sig do
|
24
|
+
params(
|
25
|
+
node: YARD::Parser::Ruby::AstNode,
|
26
|
+
_blk: T.proc.params(n: YARD::Parser::Ruby::AstNode).void
|
27
|
+
).void
|
28
|
+
end
|
29
|
+
def self.bfs_traverse(node, &_blk)
|
30
|
+
queue = [node]
|
31
|
+
until queue.empty?
|
32
|
+
n = T.must(queue.shift)
|
33
|
+
yield n
|
34
|
+
n.children.each { |c| queue.push(c) }
|
35
|
+
queue.pop if n.is_a?(YARD::Parser::Ruby::MethodCallNode) && SKIP_METHOD_CONTENTS.include?(n.method_name(true))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Gets the node that a sorbet `sig` can be attached do, bypassing visisbility modifiers and the like
|
40
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(SIGABLE_NODE) }
|
41
|
+
def self.get_method_node(node)
|
42
|
+
case node
|
43
|
+
when YARD::Parser::Ruby::MethodDefinitionNode
|
44
|
+
return node
|
45
|
+
when YARD::Parser::Ruby::MethodCallNode
|
46
|
+
return node if ATTRIBUTE_METHODS.include?(node.method_name(true))
|
47
|
+
end
|
48
|
+
|
49
|
+
node.jump(:def, :defs)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Find and return the adjacent node (ascending)
|
53
|
+
# @raise [IndexError] if the node does not have an adjacent sibling (ascending)
|
54
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(YARD::Parser::Ruby::AstNode) }
|
55
|
+
def self.sibling_node(node)
|
56
|
+
siblings = node.parent.children
|
57
|
+
siblings.each_with_index.find do |sibling, i|
|
58
|
+
return siblings.fetch(i + 1) if sibling.equal?(node)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,111 +1,130 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
module YARDSorbet
|
5
|
+
# Translate `sig` type syntax to `YARD` type syntax.
|
6
|
+
module SigToYARD
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# @see https://yardoc.org/types.html
|
10
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
11
|
+
def self.convert(node)
|
12
|
+
# scrub newlines, as they break the YARD parser
|
13
|
+
convert_node(node).map { |type| type.gsub(/\n\s*/, ' ') }
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
17
|
+
private_class_method def self.convert_node(node)
|
18
|
+
case node
|
19
|
+
when YARD::Parser::Ruby::MethodCallNode then convert_call(node)
|
20
|
+
when YARD::Parser::Ruby::ReferenceNode then convert_ref(node)
|
21
|
+
else convert_node_type(node)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
26
|
+
private_class_method def self.convert_node_type(node)
|
27
|
+
case node.type
|
28
|
+
when :aref then convert_aref(node)
|
29
|
+
when :arg_paren then convert_node(node.first)
|
30
|
+
when :array then convert_array(node)
|
31
|
+
# Fixed hashes as return values are unsupported:
|
32
|
+
# https://github.com/lsegal/yard/issues/425
|
33
|
+
#
|
34
|
+
# Hash key params can be individually documented with `@option`, but
|
35
|
+
# sig translation is currently unsupported.
|
36
|
+
when :hash then ['Hash']
|
37
|
+
# seen when sig methods omit parentheses
|
38
|
+
when :list then convert_list(node)
|
39
|
+
else convert_unknown(node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(String) }
|
44
|
+
private_class_method def self.build_generic_type(node)
|
45
|
+
return node.source if node.empty? || node.type != :aref
|
46
|
+
|
47
|
+
collection_type = node.first.source
|
48
|
+
member_type = node.last.children.map { |child| build_generic_type(child) }.join(', ')
|
17
49
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
50
|
+
"#{collection_type}[#{member_type}]"
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
54
|
+
private_class_method def self.convert_aref(node)
|
23
55
|
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Parametrized_Types
|
24
|
-
case
|
25
|
-
when 'T::Array', 'T::Enumerable', 'T::Range', 'T::Set'
|
26
|
-
|
27
|
-
member_type = convert(children.last.children.first).join(', ')
|
28
|
-
["#{collection_type}<#{member_type}>"]
|
29
|
-
when 'T::Hash'
|
30
|
-
key_type = convert(children.last.children.first).join(', ')
|
31
|
-
value_type = convert(children.last.children.last).join(', ')
|
32
|
-
["Hash{#{key_type} => #{value_type}}"]
|
56
|
+
case node.first.source
|
57
|
+
when 'T::Array', 'T::Enumerable', 'T::Range', 'T::Set' then convert_collection(node)
|
58
|
+
when 'T::Hash' then convert_hash(node)
|
33
59
|
else
|
34
60
|
log.info("Unsupported sig aref node #{node.source}")
|
35
61
|
[build_generic_type(node)]
|
36
62
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
66
|
+
private_class_method def self.convert_array(node)
|
40
67
|
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Order-Dependent_Lists
|
41
|
-
member_types =
|
42
|
-
["
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
['Hash']
|
77
|
-
when :var_ref
|
68
|
+
member_types = node.first.children.map { |n| convert_node(n) }
|
69
|
+
sequence = member_types.map { |mt| mt.size == 1 ? mt[0] : mt.to_s.tr('"', '') }.join(', ')
|
70
|
+
["Array(#{sequence})"]
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { params(node: YARD::Parser::Ruby::MethodCallNode).returns(T::Array[String]) }
|
74
|
+
private_class_method def self.convert_call(node)
|
75
|
+
node.namespace.source == 'T' ? convert_t_method(node) : [node.source]
|
76
|
+
end
|
77
|
+
|
78
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
79
|
+
private_class_method def self.convert_collection(node)
|
80
|
+
collection_type = node.first.source.split('::').last
|
81
|
+
member_type = convert_node(node.last.first).join(', ')
|
82
|
+
["#{collection_type}<#{member_type}>"]
|
83
|
+
end
|
84
|
+
|
85
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
86
|
+
private_class_method def self.convert_hash(node)
|
87
|
+
kv = node.last.children
|
88
|
+
key_type = convert_node(kv.first).join(', ')
|
89
|
+
value_type = convert_node(kv.last).join(', ')
|
90
|
+
["Hash{#{key_type} => #{value_type}}"]
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
94
|
+
private_class_method def self.convert_list(node)
|
95
|
+
node.children.size == 1 ? convert_node(node.children.first) : [node.source]
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
99
|
+
private_class_method def self.convert_ref(node)
|
100
|
+
source = node.source
|
101
|
+
case source
|
102
|
+
when 'T::Boolean' then ['Boolean'] # YARD convention for booleans
|
78
103
|
# YARD convention is use singleton objects when applicable:
|
79
104
|
# https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Literals
|
80
|
-
|
81
|
-
when '
|
82
|
-
|
83
|
-
|
84
|
-
['nil']
|
85
|
-
when 'TrueClass'
|
86
|
-
['true']
|
87
|
-
else
|
88
|
-
[node.source]
|
105
|
+
when 'FalseClass' then ['false']
|
106
|
+
when 'NilClass' then ['nil']
|
107
|
+
when 'TrueClass' then ['true']
|
108
|
+
else [source]
|
89
109
|
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)
|
94
|
-
else
|
95
|
-
log.warn("Unsupported sig #{node.type} node #{node.source}")
|
96
|
-
[node.source]
|
97
110
|
end
|
98
|
-
end
|
99
|
-
|
100
|
-
sig { params(node: YARD::Parser::Ruby::AstNode).returns(String) }
|
101
|
-
def self.build_generic_type(node)
|
102
|
-
return node.source if node.children.empty? || node.type != :aref
|
103
111
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
112
|
+
sig { params(node: YARD::Parser::Ruby::MethodCallNode).returns(T::Array[String]) }
|
113
|
+
private_class_method def self.convert_t_method(node)
|
114
|
+
case node.method_name(true)
|
115
|
+
when :any then node.last.first.children.map { |n| convert_node(n) }.flatten
|
116
|
+
# Order matters here, putting `nil` last results in a more concise
|
117
|
+
# return syntax in the UI (superscripted `?`)
|
118
|
+
# https://github.com/lsegal/yard/blob/cfa62ae/lib/yard/templates/helpers/html_helper.rb#L499-L500
|
119
|
+
when :nilable then convert_node(node.last) + ['nil']
|
120
|
+
else [node.source]
|
121
|
+
end
|
122
|
+
end
|
108
123
|
|
109
|
-
|
124
|
+
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
|
125
|
+
private_class_method def self.convert_unknown(node)
|
126
|
+
log.warn("Unsupported sig #{node.type} node #{node.source}")
|
127
|
+
[node.source]
|
128
|
+
end
|
110
129
|
end
|
111
130
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
# Used to store the details of a `T::Struct` `prop` definition
|
6
|
+
class TStructProp < T::Struct
|
7
|
+
const :default, T.nilable(String)
|
8
|
+
const :doc, String
|
9
|
+
const :prop_name, String
|
10
|
+
const :source, String
|
11
|
+
const :types, T::Array[String]
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module YARDSorbet
|
5
|
+
# Helper methods for working with `YARD` tags
|
6
|
+
module TagUtils
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# @return the tag with the matching `tag_name` and `name`, or `nil`
|
10
|
+
sig do
|
11
|
+
params(docstring: YARD::Docstring, tag_name: String, name: T.nilable(String))
|
12
|
+
.returns(T.nilable(YARD::Tags::Tag))
|
13
|
+
end
|
14
|
+
def self.find_tag(docstring, tag_name, name)
|
15
|
+
docstring.tags.find { |t| t.tag_name == tag_name && t.name == name }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create or update a `YARD` tag with type information
|
19
|
+
sig do
|
20
|
+
params(
|
21
|
+
docstring: YARD::Docstring,
|
22
|
+
tag_name: String,
|
23
|
+
types: T.nilable(T::Array[String]),
|
24
|
+
name: T.nilable(String),
|
25
|
+
text: String
|
26
|
+
).void
|
27
|
+
end
|
28
|
+
def self.upsert_tag(docstring, tag_name, types = nil, name = nil, text = '')
|
29
|
+
tag = find_tag(docstring, tag_name, name)
|
30
|
+
if tag
|
31
|
+
return unless types
|
32
|
+
|
33
|
+
# Updating a tag in place doesn't seem to work, so we'll delete it, add the types, and re-add it
|
34
|
+
docstring.delete_tag_if { |t| t == tag }
|
35
|
+
# overwrite any existing type annotation (sigs should win)
|
36
|
+
tag.types = types
|
37
|
+
tag.text = text unless text.empty?
|
38
|
+
else
|
39
|
+
tag = YARD::Tags::Tag.new(tag_name, text, types, name)
|
40
|
+
end
|
41
|
+
docstring.add_tag(tag)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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.3
|
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-08-01 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
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '13.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redcarpet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.5'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: rspec
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +86,14 @@ dependencies:
|
|
58
86
|
requirements:
|
59
87
|
- - "~>"
|
60
88
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
89
|
+
version: 1.18.0
|
62
90
|
type: :development
|
63
91
|
prerelease: false
|
64
92
|
version_requirements: !ruby/object:Gem::Requirement
|
65
93
|
requirements:
|
66
94
|
- - "~>"
|
67
95
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
96
|
+
version: 1.18.0
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: rubocop-performance
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,28 +114,28 @@ dependencies:
|
|
86
114
|
requirements:
|
87
115
|
- - "~>"
|
88
116
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
117
|
+
version: 0.6.0
|
90
118
|
type: :development
|
91
119
|
prerelease: false
|
92
120
|
version_requirements: !ruby/object:Gem::Requirement
|
93
121
|
requirements:
|
94
122
|
- - "~>"
|
95
123
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
124
|
+
version: 0.6.0
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: rubocop-rspec
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
100
128
|
requirements:
|
101
129
|
- - "~>"
|
102
130
|
- !ruby/object:Gem::Version
|
103
|
-
version: 2.
|
131
|
+
version: 2.4.0
|
104
132
|
type: :development
|
105
133
|
prerelease: false
|
106
134
|
version_requirements: !ruby/object:Gem::Requirement
|
107
135
|
requirements:
|
108
136
|
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
|
-
version: 2.
|
138
|
+
version: 2.4.0
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: rubocop-sorbet
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,42 +170,42 @@ dependencies:
|
|
142
170
|
requirements:
|
143
171
|
- - "~>"
|
144
172
|
- !ruby/object:Gem::Version
|
145
|
-
version: 0.5.
|
173
|
+
version: 0.5.6289
|
146
174
|
type: :development
|
147
175
|
prerelease: false
|
148
176
|
version_requirements: !ruby/object:Gem::Requirement
|
149
177
|
requirements:
|
150
178
|
- - "~>"
|
151
179
|
- !ruby/object:Gem::Version
|
152
|
-
version: 0.5.
|
180
|
+
version: 0.5.6289
|
153
181
|
- !ruby/object:Gem::Dependency
|
154
182
|
name: sorbet-runtime
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
156
184
|
requirements:
|
157
185
|
- - ">="
|
158
186
|
- !ruby/object:Gem::Version
|
159
|
-
version: 0.5
|
187
|
+
version: '0.5'
|
160
188
|
type: :runtime
|
161
189
|
prerelease: false
|
162
190
|
version_requirements: !ruby/object:Gem::Requirement
|
163
191
|
requirements:
|
164
192
|
- - ">="
|
165
193
|
- !ruby/object:Gem::Version
|
166
|
-
version: 0.5
|
194
|
+
version: '0.5'
|
167
195
|
- !ruby/object:Gem::Dependency
|
168
196
|
name: yard
|
169
197
|
requirement: !ruby/object:Gem::Requirement
|
170
198
|
requirements:
|
171
199
|
- - ">="
|
172
200
|
- !ruby/object:Gem::Version
|
173
|
-
version: 0.9
|
201
|
+
version: '0.9'
|
174
202
|
type: :runtime
|
175
203
|
prerelease: false
|
176
204
|
version_requirements: !ruby/object:Gem::Requirement
|
177
205
|
requirements:
|
178
206
|
- - ">="
|
179
207
|
- !ruby/object:Gem::Version
|
180
|
-
version: 0.9
|
208
|
+
version: '0.9'
|
181
209
|
description: 'A YARD plugin that incorporates Sorbet type information
|
182
210
|
|
183
211
|
'
|
@@ -189,17 +217,28 @@ files:
|
|
189
217
|
- LICENSE
|
190
218
|
- lib/yard-sorbet.rb
|
191
219
|
- lib/yard-sorbet/directives.rb
|
192
|
-
- lib/yard-sorbet/
|
220
|
+
- lib/yard-sorbet/handlers.rb
|
221
|
+
- lib/yard-sorbet/handlers/abstract_dsl_handler.rb
|
222
|
+
- lib/yard-sorbet/handlers/enums_handler.rb
|
223
|
+
- lib/yard-sorbet/handlers/sig_handler.rb
|
224
|
+
- lib/yard-sorbet/handlers/struct_class_handler.rb
|
225
|
+
- lib/yard-sorbet/handlers/struct_class_handler.rbi
|
226
|
+
- lib/yard-sorbet/handlers/struct_prop_handler.rb
|
227
|
+
- lib/yard-sorbet/node_utils.rb
|
193
228
|
- lib/yard-sorbet/sig_to_yard.rb
|
194
|
-
- lib/yard-sorbet/
|
229
|
+
- lib/yard-sorbet/t_struct_prop.rb
|
230
|
+
- lib/yard-sorbet/tag_utils.rb
|
195
231
|
- lib/yard-sorbet/version.rb
|
196
232
|
homepage: https://github.com/dduugg/yard-sorbet
|
197
233
|
licenses:
|
198
234
|
- Apache-2.0
|
199
235
|
metadata:
|
236
|
+
bug_tracker_uri: https://github.com/dduugg/yard-sorbet/issues
|
237
|
+
changelog_uri: https://github.com/dduugg/yard-sorbet/blob/main/CHANGELOG.md
|
238
|
+
documentation_uri: https://dduugg.github.io/yard-sorbet/
|
200
239
|
homepage_uri: https://github.com/dduugg/yard-sorbet
|
201
240
|
source_code_uri: https://github.com/dduugg/yard-sorbet
|
202
|
-
|
241
|
+
wiki_uri: https://github.com/dduugg/yard-sorbet/wiki
|
203
242
|
post_install_message:
|
204
243
|
rdoc_options: []
|
205
244
|
require_paths:
|
@@ -215,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
215
254
|
- !ruby/object:Gem::Version
|
216
255
|
version: '0'
|
217
256
|
requirements: []
|
218
|
-
rubygems_version: 3.1.
|
257
|
+
rubygems_version: 3.1.6
|
219
258
|
signing_key:
|
220
259
|
specification_version: 4
|
221
260
|
summary: Create YARD docs from Sorbet type signatures
|
@@ -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
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
# Handles all `const`/`prop` calls, creating accessor methods, and compiles them for later usage at the class level
|
5
|
-
# in creating a constructor
|
6
|
-
class YARDSorbet::StructHandler < YARD::Handlers::Ruby::Base
|
7
|
-
extend T::Sig
|
8
|
-
|
9
|
-
handles method_call(:const), method_call(:prop)
|
10
|
-
namespace_only
|
11
|
-
|
12
|
-
sig { void }
|
13
|
-
def process
|
14
|
-
# Store the property for use in the constructor definition
|
15
|
-
name = statement.parameters[0].jump(
|
16
|
-
:ident, # handles most "normal" identifiers
|
17
|
-
:kw, # handles prop names using reserved words like `end` or `def`
|
18
|
-
:const # handles capitalized prop names like Foo
|
19
|
-
).source
|
20
|
-
|
21
|
-
doc = statement.docstring.to_s
|
22
|
-
source = statement.source
|
23
|
-
types = YARDSorbet::SigToYARD.convert(statement.parameters[1])
|
24
|
-
default_node = statement.traverse { |n| break n if n.source == 'default:' && n.type == :label }
|
25
|
-
default = default_node.parent[1].source if default_node
|
26
|
-
|
27
|
-
extra_state.prop_docs ||= Hash.new { |h, k| h[k] = [] }
|
28
|
-
extra_state.prop_docs[namespace] << {
|
29
|
-
doc: doc,
|
30
|
-
prop_name: name,
|
31
|
-
types: types,
|
32
|
-
source: source,
|
33
|
-
default: default
|
34
|
-
}
|
35
|
-
|
36
|
-
# Create the virtual method in our current scope
|
37
|
-
namespace.attributes[scope][name] ||= SymbolHash[read: nil, write: nil]
|
38
|
-
|
39
|
-
object = MethodObject.new(namespace, name, scope)
|
40
|
-
object.source = source
|
41
|
-
|
42
|
-
reader_docstring = doc.empty? ? "Returns the value of attribute +#{name}+." : doc
|
43
|
-
docstring = YARD::DocstringParser.new.parse(reader_docstring).to_docstring
|
44
|
-
docstring.add_tag(YARD::Tags::Tag.new(:return, '', types))
|
45
|
-
object.docstring = docstring.to_raw
|
46
|
-
|
47
|
-
# Register the object explicitly as an attribute.
|
48
|
-
# While `const` attributes are immutable, `prop` attributes may be reassigned.
|
49
|
-
if statement.method_name.source == 'prop'
|
50
|
-
namespace.attributes[scope][name][:write] = object
|
51
|
-
end
|
52
|
-
namespace.attributes[scope][name][:read] = object
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Class-level handler that folds all `const` and `prop` declarations into the constructor documentation
|
57
|
-
# this needs to be injected as a module otherwise the default Class handler will overwrite documentation
|
58
|
-
module YARDSorbet::StructClassHandler
|
59
|
-
extend T::Sig
|
60
|
-
|
61
|
-
sig { void }
|
62
|
-
def process
|
63
|
-
ret = super
|
64
|
-
|
65
|
-
return ret if T.unsafe(self).extra_state.prop_docs.nil?
|
66
|
-
|
67
|
-
# lookup the full YARD path for the current class
|
68
|
-
class_ns = YARD::CodeObjects::ClassObject.new(
|
69
|
-
T.unsafe(self).namespace, T.unsafe(self).statement[0].source.gsub(/\s/, '')
|
70
|
-
)
|
71
|
-
props = T.unsafe(self).extra_state.prop_docs[class_ns]
|
72
|
-
|
73
|
-
return ret if props.empty?
|
74
|
-
|
75
|
-
# Create a virtual `initialize` method with all the `prop`/`const` arguments
|
76
|
-
# having the name :initialize & the scope :instance marks this as the constructor.
|
77
|
-
# There is a chance that there is a custom initializer, so make sure we steal the existing docstring
|
78
|
-
# and source
|
79
|
-
object = YARD::CodeObjects::MethodObject.new(class_ns, :initialize, :instance)
|
80
|
-
|
81
|
-
docstring, directives = YARDSorbet::Directives.extract_directives(object.docstring || '')
|
82
|
-
|
83
|
-
# Annotate the parameters of the constructor with the prop docs
|
84
|
-
props.each do |prop|
|
85
|
-
docstring.add_tag(YARD::Tags::Tag.new(:param, prop[:doc], prop[:types], prop[:prop_name]))
|
86
|
-
end
|
87
|
-
|
88
|
-
docstring.add_tag(YARD::Tags::Tag.new(:return, '', class_ns))
|
89
|
-
|
90
|
-
# Use kwarg style arguments, with optionals being marked with a default (unless an actual default was specified)
|
91
|
-
object.parameters = props.map do |prop|
|
92
|
-
default = prop[:default] || (prop[:types].include?('nil') ? 'nil' : nil)
|
93
|
-
["#{prop[:prop_name]}:", default]
|
94
|
-
end
|
95
|
-
|
96
|
-
# The "source" of our constructor is compromised with the props/consts
|
97
|
-
object.source ||= props.map { |p| p[:source] }.join("\n")
|
98
|
-
object.explicit ||= false # not strictly necessary
|
99
|
-
|
100
|
-
T.unsafe(self).register(object)
|
101
|
-
|
102
|
-
object.docstring = docstring.to_raw
|
103
|
-
|
104
|
-
YARDSorbet::Directives.add_directives(object.docstring, directives)
|
105
|
-
|
106
|
-
ret
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
YARD::Handlers::Ruby::ClassHandler.include YARDSorbet::StructClassHandler
|