yard-sorbet 0.4.1 → 0.5.0

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