yard-sorbet 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 360c73bcd93390b21ebf0206cce339822368fca005d03fa3d08a4444a3cab367
4
- data.tar.gz: 7b03b59846235fd395bca99868561eaa299b7dbe21eea7c91307c602236baec9
3
+ metadata.gz: 809250a1720fa07f83c0a4545ef5addf33f5a2d4be0dc21e2ca69bd63730bd66
4
+ data.tar.gz: 7f8381d22e69b630627cc3a833ddde59938522800ea7d2966bd98ae39fe33a35
5
5
  SHA512:
6
- metadata.gz: 364ee6ff02687a3a4e2ffd3384c2eb68a848afe60ad0e94c8d675df81ae9a820e805ed07ce1537cee59eb3e979ce2488a1a2210aa19cc1b40365b9dc97368864
7
- data.tar.gz: f26eb01fcad908815f27afc9a42ad72050625e5279e3b3b7576991aa1b14ba685148c12d74bbc2cdba19e69b1f416f48fa89253d34a5635259794d088ad7415f
6
+ metadata.gz: bf72fdb12fb92b88414edd595484734175d2d8f2a6be4030414e8871d858de62c077c862f64aa738911b9ea2d90a2ae616e986c128e35ceb5a0de6ed052a0576
7
+ data.tar.gz: 5f04cb364f05793cd5112ebfcd2681291aedad3e851c1f9ba8e5302750fd16446d61e3e9862a1634f8d472a7ad3fb2325fcffde8789e6df9a34d4484c055fba2
@@ -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,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
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module YARDSorbet
5
+ module Handlers
6
+ # Extends any modules included via `mixes_in_class_methods`
7
+ # @see https://sorbet.org/docs/abstract#interfaces-and-the-included-hook
8
+ # Sorbet `mixes_in_class_methods` documentation
9
+ class IncludeHandler < YARD::Handlers::Ruby::Base
10
+ extend T::Sig
11
+
12
+ handles method_call(:include)
13
+ namespace_only
14
+
15
+ sig { void }
16
+ def process
17
+ return unless extra_state.mix_in_class_methods
18
+
19
+ statement.parameters(false).each do |mixin|
20
+ obj = YARD::CodeObjects::Proxy.new(namespace, mixin.source)
21
+ class_methods_namespace = extra_state.mix_in_class_methods[obj.to_s]
22
+ next unless class_methods_namespace
23
+
24
+ included_in.mixins(:class) << YARD::CodeObjects::Proxy.new(obj, class_methods_namespace)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # @return the namespace object that is including the module
31
+ sig { returns(YARD::CodeObjects::NamespaceObject) }
32
+ def included_in
33
+ statement.namespace ? YARD::CodeObjects::Proxy.new(namespace, statement.namespace.source) : namespace
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module YARDSorbet
5
+ module Handlers
6
+ # Tracks modules that invoke `mixes_in_class_methods` for use in {IncludeHandler}
7
+ # @see https://sorbet.org/docs/abstract#interfaces-and-the-included-hook
8
+ # Sorbet `mixes_in_class_methods` documentation
9
+ class MixesInClassMethodsHandler < YARD::Handlers::Ruby::Base
10
+ extend T::Sig
11
+
12
+ handles method_call(:mixes_in_class_methods)
13
+ namespace_only
14
+
15
+ sig { void }
16
+ def process
17
+ extra_state.mix_in_class_methods ||= {}
18
+ extra_state.mix_in_class_methods[namespace.to_s] = statement.parameters(false)[0].source
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,100 +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
- # 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
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
24
16
 
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)
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
37
27
  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
28
 
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
29
+ private
55
30
 
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
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
60
44
 
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
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)
72
54
 
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
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)
90
60
  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']
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)
96
67
  end
97
68
  end
98
- parsed
99
69
  end
100
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,5 @@
1
+ # typed: strict
2
+ # This is in an rbi so the runtime doesn't depend on experimental sorbet features
3
+ module YARDSorbet::Handlers::StructClassHandler
4
+ requires_ancestor { YARD::Handlers::Ruby::ClassHandler }
5
+ end
@@ -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,11 +1,16 @@
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'
12
+ require_relative 'handlers/include_handler'
13
+ require_relative 'handlers/mixes_in_class_methods_handler'
10
14
  require_relative 'handlers/sig_handler'
11
- require_relative 'handlers/struct_handler'
15
+ require_relative 'handlers/struct_class_handler'
16
+ require_relative 'handlers/struct_prop_handler'
@@ -1,68 +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
- # @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
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))
34
36
  end
35
37
  end
36
- end
37
38
 
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
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
47
48
 
48
- node.jump(:def, :defs)
49
- end
49
+ node.jump(:def, :defs)
50
+ end
50
51
 
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)
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
59
  end
60
60
  end
61
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
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,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
@@ -1,7 +1,8 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ # Types are documentation
4
5
  module YARDSorbet
5
6
  # {https://rubygems.org/gems/yard-sorbet Version history}
6
- VERSION = '0.5.0'
7
+ VERSION = '0.6.0'
7
8
  end
data/lib/yard-sorbet.rb CHANGED
@@ -4,11 +4,11 @@
4
4
  require 'sorbet-runtime'
5
5
  require 'yard'
6
6
 
7
- # top-level namespace
8
- module YARDSorbet; end
7
+ require_relative 'yard-sorbet/version'
9
8
 
10
9
  require_relative 'yard-sorbet/directives'
11
10
  require_relative 'yard-sorbet/handlers'
12
11
  require_relative 'yard-sorbet/node_utils'
13
12
  require_relative 'yard-sorbet/sig_to_yard'
14
- require_relative 'yard-sorbet/version'
13
+ require_relative 'yard-sorbet/t_struct_prop'
14
+ require_relative 'yard-sorbet/tag_utils'
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.0
4
+ version: 0.6.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-05-28 00:00:00.000000000 Z
11
+ date: 2021-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
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'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +86,14 @@ dependencies:
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 1.15.0
89
+ version: 1.22.0
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 1.15.0
96
+ version: 1.22.0
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubocop-performance
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -100,28 +114,28 @@ dependencies:
100
114
  requirements:
101
115
  - - "~>"
102
116
  - !ruby/object:Gem::Version
103
- version: 0.5.1
117
+ version: 0.6.0
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
- version: 0.5.1
124
+ version: 0.6.0
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rubocop-rspec
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - "~>"
116
130
  - !ruby/object:Gem::Version
117
- version: 2.3.0
131
+ version: 2.5.0
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
136
  - - "~>"
123
137
  - !ruby/object:Gem::Version
124
- version: 2.3.0
138
+ version: 2.5.0
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: rubocop-sorbet
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -156,42 +170,42 @@ dependencies:
156
170
  requirements:
157
171
  - - "~>"
158
172
  - !ruby/object:Gem::Version
159
- version: 0.5.6289
173
+ version: 0.5.9204
160
174
  type: :development
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.6289
180
+ version: 0.5.9204
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: sorbet-runtime
169
183
  requirement: !ruby/object:Gem::Requirement
170
184
  requirements:
171
185
  - - ">="
172
186
  - !ruby/object:Gem::Version
173
- version: 0.5.6289
187
+ version: '0.5'
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.5.6289
194
+ version: '0.5'
181
195
  - !ruby/object:Gem::Dependency
182
196
  name: yard
183
197
  requirement: !ruby/object:Gem::Requirement
184
198
  requirements:
185
199
  - - ">="
186
200
  - !ruby/object:Gem::Version
187
- version: 0.9.21
201
+ version: '0.9'
188
202
  type: :runtime
189
203
  prerelease: false
190
204
  version_requirements: !ruby/object:Gem::Requirement
191
205
  requirements:
192
206
  - - ">="
193
207
  - !ruby/object:Gem::Version
194
- version: 0.9.21
208
+ version: '0.9'
195
209
  description: 'A YARD plugin that incorporates Sorbet type information
196
210
 
197
211
  '
@@ -206,17 +220,23 @@ files:
206
220
  - lib/yard-sorbet/handlers.rb
207
221
  - lib/yard-sorbet/handlers/abstract_dsl_handler.rb
208
222
  - lib/yard-sorbet/handlers/enums_handler.rb
223
+ - lib/yard-sorbet/handlers/include_handler.rb
224
+ - lib/yard-sorbet/handlers/mixes_in_class_methods_handler.rb
209
225
  - lib/yard-sorbet/handlers/sig_handler.rb
210
- - lib/yard-sorbet/handlers/struct_handler.rb
226
+ - lib/yard-sorbet/handlers/struct_class_handler.rb
227
+ - lib/yard-sorbet/handlers/struct_class_handler.rbi
228
+ - lib/yard-sorbet/handlers/struct_prop_handler.rb
211
229
  - lib/yard-sorbet/node_utils.rb
212
230
  - lib/yard-sorbet/sig_to_yard.rb
231
+ - lib/yard-sorbet/t_struct_prop.rb
232
+ - lib/yard-sorbet/tag_utils.rb
213
233
  - lib/yard-sorbet/version.rb
214
234
  homepage: https://github.com/dduugg/yard-sorbet
215
235
  licenses:
216
236
  - Apache-2.0
217
237
  metadata:
218
238
  bug_tracker_uri: https://github.com/dduugg/yard-sorbet/issues
219
- changelog_uri: https://github.com/dduugg/yard-sorbet/blob/master/CHANGELOG.md
239
+ changelog_uri: https://github.com/dduugg/yard-sorbet/blob/main/CHANGELOG.md
220
240
  documentation_uri: https://dduugg.github.io/yard-sorbet/
221
241
  homepage_uri: https://github.com/dduugg/yard-sorbet
222
242
  source_code_uri: https://github.com/dduugg/yard-sorbet
@@ -236,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
236
256
  - !ruby/object:Gem::Version
237
257
  version: '0'
238
258
  requirements: []
239
- rubygems_version: 3.1.4
259
+ rubygems_version: 3.1.6
240
260
  signing_key:
241
261
  specification_version: 4
242
262
  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
- 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::Helpers
60
- extend T::Sig
61
-
62
- requires_ancestor YARD::Handlers::Ruby::ClassHandler
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::StructClassHandler