yard-sorbet 0.5.1 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca3c823cd78ea11784724185ff336604d2e7b131082945c3fc19200d1b3294cf
4
- data.tar.gz: 43b2a6a38cb3161d2452eaa823ac336cbb5dcb0782fb40d0b5ddbf12afe33467
3
+ metadata.gz: abed4a7ebc07752257feab235b53a7a0162c348b3dd614fed606aa9299840115
4
+ data.tar.gz: 8799257d6fa6a2f24689c3e737ed484fce741c0098c60648eed4e370273bb161
5
5
  SHA512:
6
- metadata.gz: aff435511b0e40b212c3ffcfd8194cec04a70d9e05ce82865dbbf074376cc6da7d9503a71f395aafde9615c99f524051977d9c6b9aaac971ee1c8f378ab4aa5f
7
- data.tar.gz: 746934d64c6bfd728551b43b380cf6efd3da0ccbb9224cbb29bbabe065d17821530e640c3a8377e5935a05d1f9527d51f36da995c21dd0c5821903b86482017f
6
+ metadata.gz: ff78cce014d6d8443ac4b00fd980c6f316e9f11b091e2a031fbe6ed2b9ffc5e68657500a4d952fa9eb7f649f914f102acc509960aa5d6c6cddbc449a86d18e66
7
+ data.tar.gz: 201e32a5fb1de713569b8476c8acaa4634853d11a8538cad9770d06c9bc0fe396b900b50626f883cbd6dfe8ee8846703e91c50a4bee7a3a191d70fac66737b9d
data/lib/yard-sorbet.rb CHANGED
@@ -10,3 +10,5 @@ require_relative 'yard-sorbet/directives'
10
10
  require_relative 'yard-sorbet/handlers'
11
11
  require_relative 'yard-sorbet/node_utils'
12
12
  require_relative 'yard-sorbet/sig_to_yard'
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
- # Extract & re-add directives to a docstring
5
- module YARDSorbet::Directives
6
- extend T::Sig
4
+ module YARDSorbet
5
+ # Extract & re-add directives to a docstring
6
+ module Directives
7
+ extend T::Sig
7
8
 
8
- sig { params(docstring: T.nilable(String)).returns([YARD::Docstring, T::Array[String]]) }
9
- def self.extract_directives(docstring)
10
- parser = YARD::DocstringParser.new.parse(docstring)
11
- # Directives are already parsed at this point, and there doesn't
12
- # seem to be an API to tweeze them from one node to another without
13
- # managing YARD internal state. Instead, we just extract them from
14
- # the raw text and re-attach them.
15
- directives = parser.raw_text&.split("\n")&.select do |line|
16
- line.start_with?('@!')
17
- end || []
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
- [parser.to_docstring, directives]
20
- end
20
+ [parser.to_docstring, directives]
21
+ end
21
22
 
22
- sig { params(docstring: String, directives: T::Array[String]).void }
23
- def self.add_directives(docstring, directives)
24
- directives.each do |directive|
25
- docstring.concat("\n#{directive}")
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
@@ -1,11 +1,14 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- # Custom YARD Handlers
5
- # @see https://rubydoc.info/gems/yard/YARD/Handlers/Base YARD Base Handler documentation
6
- module YARDSorbet::Handlers; end
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
7
9
 
8
10
  require_relative 'handlers/abstract_dsl_handler'
9
11
  require_relative 'handlers/enums_handler'
10
12
  require_relative 'handlers/sig_handler'
11
- require_relative 'handlers/struct_handler'
13
+ require_relative 'handlers/struct_class_handler'
14
+ require_relative 'handlers/struct_prop_handler'
@@ -1,26 +1,30 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
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
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
7
9
 
8
- handles method_call(:abstract!), method_call(:interface!)
9
- namespace_only
10
+ handles method_call(:abstract!), method_call(:interface!)
11
+ namespace_only
10
12
 
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)
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)
17
19
 
18
- sig { void }
19
- def process
20
- return if namespace.has_tag?(:abstract)
20
+ sig { void }
21
+ def process
22
+ return if namespace.has_tag?(:abstract)
21
23
 
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)
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
25
29
  end
26
30
  end
@@ -1,28 +1,34 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Handle `enums` calls, registering enum values as constants
5
- class YARDSorbet::Handlers::EnumsHandler < YARD::Handlers::Ruby::Base
6
- extend T::Sig
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
7
9
 
8
- handles method_call(:enums)
9
- namespace_only
10
+ handles method_call(:enums)
11
+ namespace_only
10
12
 
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
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
19
23
  end
20
24
  end
21
- end
22
- end
23
25
 
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
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
27
33
  end
28
34
  end
@@ -1,102 +1,70 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # A YARD Handler for Sorbet type declarations
5
- class YARDSorbet::Handlers::SigHandler < YARD::Handlers::Ruby::Base
6
- extend T::Sig
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
7
9
 
8
- handles method_call(:sig)
9
- namespace_only
10
+ handles method_call(:sig)
11
+ namespace_only
10
12
 
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
- # These node types attached to sigs represent attr_* declarations
19
- ATTR_NODE_TYPES = T.let(%i[command fcall], T::Array[Symbol])
20
- # Skip these node types when parsing `sig` params
21
- PARAM_EXCLUDES = T.let(%i[array call hash].freeze, T::Array[Symbol])
22
- # Skip these node types when parsing `sig`s
23
- SIG_EXCLUDES = T.let(%i[array hash].freeze, T::Array[Symbol])
24
-
25
- private_constant :ParsedSig, :ATTR_NODE_TYPES, :PARAM_EXCLUDES, :SIG_EXCLUDES
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
26
16
 
27
- # Swap the method definition docstring and the sig docstring.
28
- # Parse relevant parts of the `sig` and include them as well.
29
- sig { void }
30
- def process
31
- method_node = YARDSorbet::NodeUtils.get_method_node(YARDSorbet::NodeUtils.sibling_node(statement))
32
- docstring, directives = YARDSorbet::Directives.extract_directives(statement.docstring)
33
- parsed_sig = parse_sig(statement)
34
- enhance_tag(docstring, :abstract, parsed_sig)
35
- enhance_tag(docstring, :return, parsed_sig)
36
- unless ATTR_NODE_TYPES.include?(method_node.type)
37
- parsed_sig.params.each do |name, types|
38
- enhance_param(docstring, name, types)
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
39
27
  end
40
- end
41
- method_node.docstring = docstring.to_raw
42
- YARDSorbet::Directives.add_directives(method_node.docstring, directives)
43
- statement.docstring = nil
44
- end
45
28
 
46
- sig { params(docstring: YARD::Docstring, name: String, types: T::Array[String]).void }
47
- private def enhance_param(docstring, name, types)
48
- tag = docstring.tags.find { |t| t.tag_name == 'param' && t.name == name }
49
- if tag
50
- docstring.delete_tag_if { |t| t == tag }
51
- tag.types = types
52
- else
53
- tag = YARD::Tags::Tag.new(:param, '', types, name)
54
- end
55
- docstring.add_tag(tag)
56
- end
29
+ private
57
30
 
58
- sig { params(docstring: YARD::Docstring, type: Symbol, parsed_sig: ParsedSig).void }
59
- private def enhance_tag(docstring, type, parsed_sig)
60
- type_value = parsed_sig.public_send(type)
61
- return unless type_value
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
62
44
 
63
- tag = docstring.tags.find { |t| t.tag_name == type.to_s }
64
- if tag
65
- docstring.delete_tags(type)
66
- else
67
- tag = YARD::Tags::Tag.new(type, '')
68
- end
69
- if type_value.is_a?(Array)
70
- tag.types = type_value
71
- end
72
- docstring.add_tag(tag)
73
- end
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)
74
54
 
75
- sig { params(sig_node: YARD::Parser::Ruby::MethodCallNode).returns(ParsedSig) }
76
- private def parse_sig(sig_node)
77
- parsed = ParsedSig.new
78
- found_params = T.let(false, T::Boolean)
79
- found_return = T.let(false, T::Boolean)
80
- YARDSorbet::NodeUtils.bfs_traverse(sig_node, exclude: SIG_EXCLUDES) do |n|
81
- if n.source == 'abstract'
82
- parsed.abstract = true
83
- elsif n.source == 'params' && !found_params
84
- found_params = true
85
- sibling = YARDSorbet::NodeUtils.sibling_node(n)
86
- YARDSorbet::NodeUtils.bfs_traverse(sibling, exclude: PARAM_EXCLUDES) do |p|
87
- if p.type == :assoc
88
- param_name = p.children.first.source[0...-1]
89
- types = YARDSorbet::SigToYARD.convert(p.children.last)
90
- parsed.params[param_name] = types
91
- end
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)
92
60
  end
93
- elsif n.source == 'returns' && !found_return
94
- found_return = true
95
- parsed.return = YARDSorbet::SigToYARD.convert(YARDSorbet::NodeUtils.sibling_node(n))
96
- elsif n.source == 'void'
97
- parsed.return ||= ['void']
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)
98
67
  end
99
68
  end
100
- parsed
101
69
  end
102
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
+ props.each do |prop|
37
+ docstring.add_tag(YARD::Tags::Tag.new(:param, prop.doc, prop.types, prop.prop_name))
38
+ end
39
+ docstring.add_tag(YARD::Tags::Tag.new(:return, '', class_ns))
40
+ decorate_t_struct_init(object, props, docstring, directives)
41
+ register(object)
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.to_raw
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
@@ -1,69 +1,62 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
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
- # Traverese AST nodes in breadth-first order
18
- # @yield [YARD::Parser::Ruby::AstNode]
19
- sig do
20
- params(
21
- node: YARD::Parser::Ruby::AstNode,
22
- exclude: T::Array[Symbol],
23
- _blk: T.proc.params(n: YARD::Parser::Ruby::AstNode).void
24
- ).void
25
- end
26
- def self.bfs_traverse(node, exclude: [], &_blk)
27
- queue = [node]
28
- until queue.empty?
29
- n = T.must(queue.shift)
30
- yield n
31
- n.children.each do |c|
32
- unless exclude.include?(c.type)
33
- queue.push(c)
34
- end
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))
35
36
  end
36
37
  end
37
- end
38
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
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
48
 
49
- node.jump(:def, :defs)
50
- end
49
+ node.jump(:def, :defs)
50
+ end
51
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
- if sibling.equal?(node)
59
- return siblings.fetch(i + 1)
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)
60
59
  end
61
60
  end
62
61
  end
63
-
64
- # Returns true if the given node represents a type signature.
65
- sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Boolean) }
66
- def self.type_signature?(node)
67
- node.is_a?(YARD::Parser::Ruby::MethodCallNode) && node.method_name(true) == :sig
68
- end
69
62
  end
@@ -1,135 +1,130 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Translate `sig` type syntax to `YARD` type syntax.
5
- module YARDSorbet::SigToYARD
6
- extend T::Sig
7
-
8
- # Ruby 2.5 parsed call nodes slightly differently
9
- IS_LEGACY_RUBY_VERSION = T.let(RUBY_VERSION.start_with?('2.5.'), T::Boolean)
10
- private_constant :IS_LEGACY_RUBY_VERSION
11
-
12
- # @see https://yardoc.org/types.html
13
- sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
14
- def self.convert(node)
15
- types = convert_type(node)
16
- # scrub newlines, as they break the YARD parser
17
- types.map { |type| type.gsub(/\n\s*/, ' ') }
18
- end
4
+ module YARDSorbet
5
+ # Translate `sig` type syntax to `YARD` type syntax.
6
+ module SigToYARD
7
+ extend T::Sig
19
8
 
20
- sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
21
- private_class_method def self.convert_type(node)
22
- case node.type
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)
38
- else
39
- log.warn("Unsupported sig #{node.type} node #{node.source}")
40
- [node.source]
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*/, ' ') }
41
14
  end
42
- end
43
15
 
44
- sig { params(node: YARD::Parser::Ruby::AstNode).returns(String) }
45
- private_class_method def self.build_generic_type(node)
46
- children = node.children
47
- return node.source if children.empty? || node.type != :aref
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
48
24
 
49
- collection_type = children.first.source
50
- member_type = children.last.children.map { |child| build_generic_type(child) }.join(', ')
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
51
42
 
52
- "#{collection_type}[#{member_type}]"
53
- end
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
54
46
 
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)]
47
+ collection_type = node.first.source
48
+ member_type = node.last.children.map { |child| build_generic_type(child) }.join(', ')
49
+
50
+ "#{collection_type}[#{member_type}]"
72
51
  end
73
- end
74
52
 
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
53
+ sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
54
+ private_class_method def self.convert_aref(node)
55
+ # https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Parametrized_Types
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)
59
+ else
60
+ log.info("Unsupported sig aref node #{node.source}")
61
+ [build_generic_type(node)]
62
+ end
63
+ end
79
64
 
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
65
+ sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
66
+ private_class_method def self.convert_array(node)
67
+ # https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Order-Dependent_Lists
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
86
72
 
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]
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]
94
76
  end
95
- end
96
77
 
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]
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}>"]
105
83
  end
106
- end
107
84
 
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]
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}}"]
121
91
  end
122
- end
123
92
 
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]
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
103
+ # YARD convention is use singleton objects when applicable:
104
+ # https://www.rubydoc.info/gems/yard/file/docs/Tags.md#Literals
105
+ when 'FalseClass' then ['false']
106
+ when 'NilClass' then ['nil']
107
+ when 'TrueClass' then ['true']
108
+ else [source]
109
+ end
110
+ end
111
+
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
123
+
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]
133
128
  end
134
129
  end
135
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,41 @@
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
+ # Create or update a `YARD` tag with type information
10
+ sig do
11
+ params(
12
+ docstring: YARD::Docstring,
13
+ tag_name: String,
14
+ types: T.nilable(T::Array[String]),
15
+ name: T.nilable(String)
16
+ ).void
17
+ end
18
+ def self.upsert_tag(docstring, tag_name, types = nil, name = nil)
19
+ tag = find_tag(docstring, tag_name, name)
20
+ if tag
21
+ return unless types
22
+
23
+ # Updating a tag in place doesn't seem to work, so we'll delete it, add the types, and re-add it
24
+ docstring.delete_tag_if { |t| t == tag }
25
+ # overwrite any existing type annotation (sigs should win)
26
+ tag.types = types
27
+ else
28
+ tag = YARD::Tags::Tag.new(tag_name, '', types, name)
29
+ end
30
+ docstring.add_tag(tag)
31
+ end
32
+
33
+ sig do
34
+ params(docstring: YARD::Docstring, tag_name: String, name: T.nilable(String))
35
+ .returns(T.nilable(YARD::Tags::Tag))
36
+ end
37
+ private_class_method def self.find_tag(docstring, tag_name, name)
38
+ docstring.tags.find { |t| t.tag_name == tag_name && t.name == name }
39
+ end
40
+ end
41
+ end
@@ -4,5 +4,5 @@
4
4
  # Types are documentation
5
5
  module YARDSorbet
6
6
  # {https://rubygems.org/gems/yard-sorbet Version history}
7
- VERSION = '0.5.1'
7
+ VERSION = '0.5.2'
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yard-sorbet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
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-06-08 00:00:00.000000000 Z
11
+ date: 2021-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 1.16.0
89
+ version: 1.18.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 1.16.0
96
+ version: 1.18.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rubocop-performance
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -114,28 +114,28 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.5.1
117
+ version: 0.6.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.5.1
124
+ version: 0.6.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rubocop-rspec
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 2.3.0
131
+ version: 2.4.0
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 2.3.0
138
+ version: 2.4.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rubocop-sorbet
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -184,28 +184,28 @@ dependencies:
184
184
  requirements:
185
185
  - - ">="
186
186
  - !ruby/object:Gem::Version
187
- version: 0.5.5845
187
+ version: '0.5'
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
- version: 0.5.5845
194
+ version: '0.5'
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: yard
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
199
  - - ">="
200
200
  - !ruby/object:Gem::Version
201
- version: 0.9.21
201
+ version: '0.9'
202
202
  type: :runtime
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
206
  - - ">="
207
207
  - !ruby/object:Gem::Version
208
- version: 0.9.21
208
+ version: '0.9'
209
209
  description: 'A YARD plugin that incorporates Sorbet type information
210
210
 
211
211
  '
@@ -221,17 +221,20 @@ files:
221
221
  - lib/yard-sorbet/handlers/abstract_dsl_handler.rb
222
222
  - lib/yard-sorbet/handlers/enums_handler.rb
223
223
  - lib/yard-sorbet/handlers/sig_handler.rb
224
- - lib/yard-sorbet/handlers/struct_handler.rb
225
- - lib/yard-sorbet/handlers/struct_handler.rbi
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
226
227
  - lib/yard-sorbet/node_utils.rb
227
228
  - lib/yard-sorbet/sig_to_yard.rb
229
+ - lib/yard-sorbet/t_struct_prop.rb
230
+ - lib/yard-sorbet/tag_utils.rb
228
231
  - lib/yard-sorbet/version.rb
229
232
  homepage: https://github.com/dduugg/yard-sorbet
230
233
  licenses:
231
234
  - Apache-2.0
232
235
  metadata:
233
236
  bug_tracker_uri: https://github.com/dduugg/yard-sorbet/issues
234
- changelog_uri: https://github.com/dduugg/yard-sorbet/blob/master/CHANGELOG.md
237
+ changelog_uri: https://github.com/dduugg/yard-sorbet/blob/main/CHANGELOG.md
235
238
  documentation_uri: https://dduugg.github.io/yard-sorbet/
236
239
  homepage_uri: https://github.com/dduugg/yard-sorbet
237
240
  source_code_uri: https://github.com/dduugg/yard-sorbet
@@ -251,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
251
254
  - !ruby/object:Gem::Version
252
255
  version: '0'
253
256
  requirements: []
254
- rubygems_version: 3.1.4
257
+ rubygems_version: 3.1.6
255
258
  signing_key:
256
259
  specification_version: 4
257
260
  summary: Create YARD docs from Sorbet type signatures
@@ -1,109 +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::Handlers::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
- # TODO: this should use `+` to delimit the attribute name when markdown is disabled
43
- reader_docstring = doc.empty? ? "Returns the value of attribute `#{name}`." : doc
44
- docstring = YARD::DocstringParser.new.parse(reader_docstring).to_docstring
45
- docstring.add_tag(YARD::Tags::Tag.new(:return, '', types))
46
- object.docstring = docstring.to_raw
47
-
48
- # Register the object explicitly as an attribute.
49
- # While `const` attributes are immutable, `prop` attributes may be reassigned.
50
- if statement.method_name.source == 'prop'
51
- namespace.attributes[scope][name][:write] = object
52
- end
53
- namespace.attributes[scope][name][:read] = object
54
- end
55
- end
56
-
57
- # Class-level handler that folds all `const` and `prop` declarations into the constructor documentation
58
- # this needs to be injected as a module otherwise the default Class handler will overwrite documentation
59
- #
60
- # @note this module is included in `YARD::Handlers::Ruby::ClassHandler`
61
- module YARDSorbet::Handlers::StructClassHandler
62
- extend T::Sig
63
-
64
- sig { void }
65
- def process
66
- super
67
-
68
- return if extra_state.prop_docs.nil?
69
-
70
- # lookup the full YARD path for the current class
71
- class_ns = YARD::CodeObjects::ClassObject.new(namespace, statement[0].source.gsub(/\s/, ''))
72
- props = extra_state.prop_docs[class_ns]
73
-
74
- return if props.empty?
75
-
76
- # Create a virtual `initialize` method with all the `prop`/`const` arguments
77
- # having the name :initialize & the scope :instance marks this as the constructor.
78
- # There is a chance that there is a custom initializer, so make sure we steal the existing docstring
79
- # and source
80
- object = YARD::CodeObjects::MethodObject.new(class_ns, :initialize, :instance)
81
-
82
- docstring, directives = YARDSorbet::Directives.extract_directives(object.docstring || '')
83
-
84
- # Annotate the parameters of the constructor with the prop docs
85
- props.each do |prop|
86
- docstring.add_tag(YARD::Tags::Tag.new(:param, prop[:doc], prop[:types], prop[:prop_name]))
87
- end
88
-
89
- docstring.add_tag(YARD::Tags::Tag.new(:return, '', class_ns))
90
-
91
- # Use kwarg style arguments, with optionals being marked with a default (unless an actual default was specified)
92
- object.parameters = props.map do |prop|
93
- default = prop[:default] || (prop[:types].include?('nil') ? 'nil' : nil)
94
- ["#{prop[:prop_name]}:", default]
95
- end
96
-
97
- # The "source" of our constructor is compromised with the props/consts
98
- object.source ||= props.map { |p| p[:source] }.join("\n")
99
- object.explicit ||= false # not strictly necessary
100
-
101
- register(object)
102
-
103
- object.docstring = docstring.to_raw
104
-
105
- YARDSorbet::Directives.add_directives(object.docstring, directives)
106
- end
107
- end
108
-
109
- YARD::Handlers::Ruby::ClassHandler.include YARDSorbet::Handlers::StructClassHandler