yard-sorbet 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50408c1c7ce4b8fe04219273c326e3ac432fe8eaee9c02fe7ba243c204202ff3
4
- data.tar.gz: e01aabf41806def071f4807d5b4bd45e49c82cdb24360b9e3b9920361505b690
3
+ metadata.gz: 6269732c6f6c20a5757281dea8e97537116082e9f6fcc01af8e37b375585c573
4
+ data.tar.gz: 3ba3e2a3e9caf49054d30a3e6030540804d7515eecda12eda07140402406879b
5
5
  SHA512:
6
- metadata.gz: fa3a9a39d00923dd39824b553900dc12d30cebb3f3770580bbf6400272f52c1bd55905211de9372d7e1bd80ff26d3d2ddaa0c56c437c9a65bb41c24d53e60b08
7
- data.tar.gz: e322305cd757bb65135a90e9f9bf166161a98756449110714a4ce6dc7f47657cc539d72175c303114e67de7ac5c750c8216c31d8a1d8c818dacdeb5b404cbe1f
6
+ metadata.gz: aa63a4e71daaffb9e18d5613bed37d366b47f80ca040570e2195716b1ae2f275d2e1cad568df123064b0678ee9862d757e05b026efcaa164d80eaae26f2eb65a
7
+ data.tar.gz: 3d7de3147fe46b1ca6339ef32d66ae98c0f48851205a3baee320a88bbafec44921f64672c335c291a2b117895fa858e49fb254cd7ec3f2b20eb339f06e324f39
@@ -16,10 +16,8 @@ module YARDSorbet
16
16
  def process
17
17
  statement.parameters(false).each do |mixin|
18
18
  obj = YARD::CodeObjects::Proxy.new(namespace, mixin.source)
19
- class_methods_namespace = MixesInClassMethodsHandler.mixed_in_class_methods(obj.to_s)
20
- next unless class_methods_namespace
21
-
22
- included_in.mixins(:class) << YARD::CodeObjects::Proxy.new(obj, class_methods_namespace)
19
+ class_methods_namespaces = MixesInClassMethodsHandler.mixed_in_class_methods(obj.to_s)
20
+ class_methods_namespaces&.each { included_in.mixins(:class) << YARD::CodeObjects::Proxy.new(obj, _1) }
23
21
  end
24
22
  end
25
23
 
@@ -12,16 +12,17 @@ module YARDSorbet
12
12
  handles method_call(:mixes_in_class_methods)
13
13
  namespace_only
14
14
 
15
- @@mix_in_class_methods = T.let({}, T::Hash[String, String]) # rubocop:disable Style/ClassVars
15
+ @@mix_in_class_methods = T.let({}, T::Hash[String, T::Array[String]]) # rubocop:disable Style/ClassVars
16
16
 
17
- sig { params(code_obj: String).returns(T.nilable(String)) }
17
+ sig { params(code_obj: String).returns(T.nilable(T::Array[String])) }
18
18
  def self.mixed_in_class_methods(code_obj)
19
19
  @@mix_in_class_methods[code_obj]
20
20
  end
21
21
 
22
22
  sig { void }
23
23
  def process
24
- @@mix_in_class_methods[namespace.to_s] = statement.parameters(false)[0].source
24
+ @@mix_in_class_methods[namespace.to_s] ||= []
25
+ @@mix_in_class_methods.fetch(namespace.to_s) << statement.parameters(false)[0].source
25
26
  end
26
27
  end
27
28
  end
@@ -10,59 +10,104 @@ module YARDSorbet
10
10
  handles method_call(:sig)
11
11
  namespace_only
12
12
 
13
- # These node types attached to sigs represent attr_* declarations
14
- ATTR_NODE_TYPES = T.let(%i[command fcall].freeze, T::Array[Symbol])
15
- private_constant :ATTR_NODE_TYPES
13
+ # YARD types that can have docstrings attached to them
14
+ Documentable = T.type_alias do
15
+ T.any(
16
+ YARD::CodeObjects::MethodObject, YARD::Parser::Ruby::MethodCallNode, YARD::Parser::Ruby::MethodDefinitionNode
17
+ )
18
+ end
19
+ private_constant :Documentable
16
20
 
17
21
  # Swap the method definition docstring and the sig docstring.
18
22
  # Parse relevant parts of the `sig` and include them as well.
19
23
  sig { void }
20
24
  def process
21
25
  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
+ case method_node
27
+ when YARD::Parser::Ruby::MethodDefinitionNode then process_def(method_node)
28
+ when YARD::Parser::Ruby::MethodCallNode then process_attr(method_node)
29
+ end
26
30
  statement.docstring = nil
27
31
  end
28
32
 
29
33
  private
30
34
 
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
35
+ sig { params(def_node: YARD::Parser::Ruby::MethodDefinitionNode).void }
36
+ def process_def(def_node)
37
+ separator = scope == :instance && def_node.type == :def ? '#' : '.'
38
+ registered = YARD::Registry.at("#{namespace}#{separator}#{def_node.method_name(true)}")
39
+ if registered
40
+ parse_node(registered, registered.docstring)
41
+ # Since we're probably in an RBI file, delete the def node, which could otherwise erroneously override the
42
+ # visibility setting
43
+ NodeUtils.delete_node(def_node)
44
+ else
45
+ parse_node(def_node, statement.docstring)
42
46
  end
43
47
  end
44
48
 
45
- sig do
46
- params(
47
- method_node: YARD::Parser::Ruby::AstNode,
48
- node: YARD::Parser::Ruby::AstNode,
49
- docstring: YARD::Docstring
50
- ).void
49
+ sig { params(attr_node: YARD::Parser::Ruby::MethodCallNode).void }
50
+ def process_attr(attr_node)
51
+ return if merged_into_attr?(attr_node)
52
+
53
+ parse_node(attr_node, statement.docstring, include_params: false)
51
54
  end
52
- def parse_params(method_node, node, docstring)
53
- return if ATTR_NODE_TYPES.include?(method_node.type)
54
55
 
56
+ # An attr* sig can be merged into a previous attr* docstring if it is the only parameter passed to the attr*
57
+ # declaration. This is to avoid needing to rewrite the source code to separate merged and unmerged attr*
58
+ # declarations.
59
+ sig { params(attr_node: YARD::Parser::Ruby::MethodCallNode).returns(T::Boolean) }
60
+ def merged_into_attr?(attr_node)
61
+ names = NodeUtils.validated_attribute_names(attr_node)
62
+ return false if names.size != 1
63
+
64
+ attrs = namespace.attributes[scope][names[0]]
65
+ return false if attrs.nil? || attrs.empty?
66
+
67
+ document_attr_methods(attrs.values.compact)
68
+ attr_node.docstring = nil
69
+ true
70
+ end
71
+
72
+ sig { params(method_objects: T::Array[YARD::CodeObjects::MethodObject]).void }
73
+ def document_attr_methods(method_objects)
74
+ method_objects.each { parse_node(_1, _1.docstring, include_params: false) }
75
+ end
76
+
77
+ sig { params(attach_to: Documentable, docstring: T.nilable(String), include_params: T::Boolean).void }
78
+ def parse_node(attach_to, docstring, include_params: true)
79
+ existing_docstring = docstring.is_a?(YARD::Docstring)
80
+ docstring, directives = Directives.extract_directives(docstring) unless existing_docstring
81
+ parse_sig(docstring, include_params: include_params)
82
+ attach_to.docstring = docstring.to_raw
83
+ Directives.add_directives(attach_to.docstring, directives) unless existing_docstring
84
+ end
85
+
86
+ sig { params(docstring: YARD::Docstring, include_params: T::Boolean).void }
87
+ def parse_sig(docstring, include_params: true)
88
+ NodeUtils.bfs_traverse(statement) do |node|
89
+ case node.source
90
+ when 'returns' then parse_return(node, docstring)
91
+ when 'params' then parse_params(node, docstring) if include_params
92
+ when 'void' then TagUtils.upsert_tag(docstring, 'return', TagUtils::VOID_RETURN_TYPE)
93
+ when 'abstract' then TagUtils.upsert_tag(docstring, 'abstract')
94
+ end
95
+ end
96
+ end
97
+
98
+ sig { params(node: YARD::Parser::Ruby::AstNode, docstring: YARD::Docstring).void }
99
+ def parse_params(node, docstring)
55
100
  sibling = NodeUtils.sibling_node(node)
56
- sibling[0][0].each do |p|
57
- param_name = p[0][0]
58
- types = SigToYARD.convert(p.last)
101
+ sibling[0][0].each do |param|
102
+ param_name = param[0][0]
103
+ types = SigToYARD.convert(param.last)
59
104
  TagUtils.upsert_tag(docstring, 'param', types, param_name)
60
105
  end
61
106
  end
62
107
 
63
108
  sig { params(node: YARD::Parser::Ruby::AstNode, docstring: YARD::Docstring).void }
64
109
  def parse_return(node, docstring)
65
- type = node.source == 'void' ? ['void'] : SigToYARD.convert(NodeUtils.sibling_node(node))
110
+ type = SigToYARD.convert(NodeUtils.sibling_node(node))
66
111
  TagUtils.upsert_tag(docstring, 'return', type)
67
112
  end
68
113
  end
@@ -35,7 +35,7 @@ module YARDSorbet
35
35
  docstring, directives = Directives.extract_directives(object.docstring)
36
36
  object.tags.each { docstring.add_tag(_1) }
37
37
  props.each { TagUtils.upsert_tag(docstring, 'param', _1.types, _1.prop_name, _1.doc) }
38
- TagUtils.upsert_tag(docstring, 'return', ['void'])
38
+ TagUtils.upsert_tag(docstring, 'return', TagUtils::VOID_RETURN_TYPE)
39
39
  decorate_t_struct_init(object, props, docstring, directives)
40
40
  end
41
41
 
@@ -13,7 +13,7 @@ module YARDSorbet
13
13
 
14
14
  sig { void }
15
15
  def process
16
- name = statement.parameters.first.last.last.source
16
+ name = params[0][-1][-1].source
17
17
  prop = make_prop(name)
18
18
  update_state(prop)
19
19
  object = YARD::CodeObjects::MethodObject.new(namespace, name, scope)
@@ -27,36 +27,45 @@ module YARDSorbet
27
27
  sig { params(object: YARD::CodeObjects::MethodObject, prop: TStructProp).void }
28
28
  def decorate_object(object, prop)
29
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
30
+ # TODO: this should use `+` to delimit the prop name when markdown is disabled
31
+ reader_docstring = prop.doc.empty? ? "Returns the value of prop `#{prop.prop_name}`." : prop.doc
32
32
  docstring = YARD::DocstringParser.new.parse(reader_docstring).to_docstring
33
33
  docstring.add_tag(YARD::Tags::Tag.new(:return, '', prop.types))
34
34
  object.docstring = docstring.to_raw
35
35
  end
36
36
 
37
- # Get the default prop value
38
- sig { returns(T.nilable(String)) }
39
- def default_value
40
- statement.traverse { break _1 if _1.type == :label && _1.source == 'default:' }&.parent&.[](1)&.source
37
+ sig { returns(T::Boolean) }
38
+ def immutable?
39
+ statement.method_name(true) == :const || kw_arg('immutable:') == 'true'
40
+ end
41
+
42
+ # @return the value passed to the keyword argument, or nil
43
+ sig { params(kwd: String).returns(T.nilable(String)) }
44
+ def kw_arg(kwd)
45
+ params[2]&.find { _1[0].source == kwd }&.[](1)&.source
41
46
  end
42
47
 
43
48
  sig { params(name: String).returns(TStructProp) }
44
49
  def make_prop(name)
45
50
  TStructProp.new(
46
- default: default_value,
51
+ default: kw_arg('default:'),
47
52
  doc: statement.docstring.to_s,
48
53
  prop_name: name,
49
54
  source: statement.source,
50
- types: SigToYARD.convert(statement.parameters[1])
55
+ types: SigToYARD.convert(params[1])
51
56
  )
52
57
  end
53
58
 
59
+ sig { returns(T::Array[T.untyped]) }
60
+ def params
61
+ @params ||= T.let(statement.parameters(false), T.nilable(T::Array[T.untyped]))
62
+ end
63
+
54
64
  # Register the field explicitly as an attribute.
55
- # While `const` attributes are immutable, `prop` attributes may be reassigned.
56
65
  sig { params(object: YARD::CodeObjects::MethodObject, name: String).void }
57
66
  def register_attrs(object, name)
58
- # Create the virtual method in our current scope
59
- write = statement.method_name(true) == :prop ? object : nil
67
+ write = immutable? ? nil : object
68
+ # Create the virtual attribute in our current scope
60
69
  namespace.attributes[scope][name] ||= SymbolHash[read: object, write: write]
61
70
  end
62
71
 
@@ -12,7 +12,6 @@ module YARDSorbet
12
12
  SKIP_METHOD_CONTENTS = T.let(%i[params returns].freeze, T::Array[Symbol])
13
13
  # Node types that can have type signatures
14
14
  SigableNode = T.type_alias { T.any(YARD::Parser::Ruby::MethodDefinitionNode, YARD::Parser::Ruby::MethodCallNode) }
15
-
16
15
  private_constant :ATTRIBUTE_METHODS, :SKIP_METHOD_CONTENTS, :SigableNode
17
16
 
18
17
  # Traverse AST nodes in breadth-first order
@@ -29,17 +28,15 @@ module YARDSorbet
29
28
  end
30
29
  end
31
30
 
31
+ sig { params(node: YARD::Parser::Ruby::AstNode).void }
32
+ def self.delete_node(node)
33
+ node.parent.children.delete(node)
34
+ end
35
+
32
36
  # Gets the node that a sorbet `sig` can be attached do, bypassing visisbility modifiers and the like
33
37
  sig { params(node: YARD::Parser::Ruby::AstNode).returns(SigableNode) }
34
38
  def self.get_method_node(node)
35
- case node
36
- when YARD::Parser::Ruby::MethodDefinitionNode
37
- return node
38
- when YARD::Parser::Ruby::MethodCallNode
39
- return node if ATTRIBUTE_METHODS.include?(node.method_name(true))
40
- end
41
-
42
- node.jump(:def, :defs)
39
+ sigable_node?(node) ? node : node.jump(:def, :defs)
43
40
  end
44
41
 
45
42
  # Find and return the adjacent node (ascending)
@@ -50,5 +47,26 @@ module YARDSorbet
50
47
  node_index = siblings.find_index { _1.equal?(node) }
51
48
  siblings.fetch(node_index + 1)
52
49
  end
50
+
51
+ sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Boolean) }
52
+ def self.sigable_node?(node)
53
+ case node
54
+ when YARD::Parser::Ruby::MethodDefinitionNode then true
55
+ when YARD::Parser::Ruby::MethodCallNode then ATTRIBUTE_METHODS.include?(node.method_name(true))
56
+ else false
57
+ end
58
+ end
59
+
60
+ # @see https://github.com/lsegal/yard/blob/main/lib/yard/handlers/ruby/attribute_handler.rb
61
+ # YARD::Handlers::Ruby::AttributeHandler.validated_attribute_names
62
+ sig { params(attr_node: YARD::Parser::Ruby::MethodCallNode).returns(T::Array[String]) }
63
+ def self.validated_attribute_names(attr_node)
64
+ attr_node.parameters(false).map do |obj|
65
+ case obj
66
+ when YARD::Parser::Ruby::LiteralNode then obj[0][0].source
67
+ else raise YARD::Parser::UndocumentableError, obj.source
68
+ end
69
+ end
70
+ end
53
71
  end
54
72
  end
@@ -6,6 +6,7 @@ module YARDSorbet
6
6
  module SigToYARD
7
7
  extend T::Sig
8
8
 
9
+ # Map of common types to YARD conventions (in order to reduce allocations)
9
10
  REF_TYPES = T.let({
10
11
  'T::Boolean' => ['Boolean'].freeze, # YARD convention for booleans
11
12
  # YARD convention is use singleton objects when applicable:
@@ -87,7 +88,7 @@ module YARDSorbet
87
88
  sig { params(node: YARD::Parser::Ruby::AstNode).returns([String]) }
88
89
  private_class_method def self.convert_collection(node)
89
90
  collection_type = node.first.source.split('::').last
90
- member_type = convert_node(node.last.first).join(', ')
91
+ member_type = convert_node(node[-1][0]).join(', ')
91
92
  ["#{collection_type}<#{member_type}>"]
92
93
  end
93
94
 
@@ -106,17 +107,16 @@ module YARDSorbet
106
107
 
107
108
  sig { params(node_source: String).returns([String]) }
108
109
  private_class_method def self.convert_ref(node_source)
109
- REF_TYPES.fetch(node_source, [node_source])
110
+ REF_TYPES[node_source] || [node_source]
110
111
  end
111
112
 
112
113
  sig { params(node: YARD::Parser::Ruby::MethodCallNode).returns(T::Array[String]) }
113
114
  private_class_method def self.convert_t_method(node)
114
115
  case node.method_name(true)
115
- when :any then node.last.first.children.flat_map { convert_node(_1) }
116
- # Order matters here, putting `nil` last results in a more concise
117
- # return syntax in the UI (superscripted `?`)
116
+ # Order matters here, putting `nil` last results in a more concise return syntax in the UI (superscripted `?`):
118
117
  # 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']
118
+ when :nilable then convert_node(node.last) + REF_TYPES.fetch('NilClass')
119
+ when :any then node[-1][0].children.flat_map { convert_node(_1) }
120
120
  else [node.source]
121
121
  end
122
122
  end
@@ -6,6 +6,9 @@ module YARDSorbet
6
6
  module TagUtils
7
7
  extend T::Sig
8
8
 
9
+ # The `void` return type, as a constant to reduce array allocations
10
+ VOID_RETURN_TYPE = T.let(['void'].freeze, [String])
11
+
9
12
  # @return the tag with the matching `tag_name` and `name`, or `nil`
10
13
  sig do
11
14
  params(docstring: YARD::Docstring, tag_name: String, name: T.nilable(String)).returns(T.nilable(YARD::Tags::Tag))
@@ -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.7.0'
7
+ VERSION = '0.8.0'
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.7.0
4
+ version: 0.8.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: 2022-08-24 00:00:00.000000000 Z
11
+ date: 2023-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -86,28 +86,28 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 1.35.0
89
+ version: 1.42.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.35.0
96
+ version: 1.42.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rubocop-performance
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.14.0
103
+ version: 1.15.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.14.0
110
+ version: 1.15.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubocop-rake
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 2.12.1
131
+ version: 2.16.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.12.1
138
+ version: 2.16.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rubocop-sorbet
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -170,14 +170,14 @@ dependencies:
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: 0.9.4
173
+ version: 0.10.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: 0.9.4
180
+ version: 0.10.0
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: sorbet-runtime
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -206,9 +206,7 @@ dependencies:
206
206
  - - ">="
207
207
  - !ruby/object:Gem::Version
208
208
  version: '0.9'
209
- description: 'A YARD plugin that incorporates Sorbet type information
210
-
211
- '
209
+ description: A YARD plugin that incorporates Sorbet type information
212
210
  email: dduugg@gmail.com
213
211
  executables: []
214
212
  extensions: []