Skip to content

Commit a7d3a18

Browse files
committed
Merge attr sigs too
1 parent a8f2abb commit a7d3a18

File tree

7 files changed

+60
-11
lines changed

7 files changed

+60
-11
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
## main
44

5-
###
5+
### New Features
6+
7+
* [#141](https://github.com/dduugg/yard-sorbet/issues/141) Merge RBI sigs into existing documentation
8+
9+
### Bug Fixes
610

711
* Handle multiple invocations of `mixes_in_class_methods` within a class
812

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A YARD [plugin](https://rubydoc.info/gems/yard/file/docs/GettingStarted.md#Plugi
1212
- Generates constant definitions from `T::Enum` enums
1313
- Modules marked `abstract!` or `interface!` are tagged `@abstract`
1414
- Modules using `mixes_in_class_methods` will attach class methods
15+
- Merges `sig`s in rbi files with source code documentation (rbi files must come after source code in yard configuration)
1516

1617
## Usage
1718

lib/yard-sorbet/handlers/sig_handler.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class SigHandler < YARD::Handlers::Ruby::Base
1313
# YARD types that can have docstrings attached to them
1414
Documentable = T.type_alias do
1515
T.any(
16-
YARD::CodeObjects::MethodObject, YARD::Parser::Ruby::MethodDefinitionNode, YARD::Parser::Ruby::MethodCallNode
16+
YARD::CodeObjects::MethodObject, YARD::Parser::Ruby::MethodCallNode, YARD::Parser::Ruby::MethodDefinitionNode
1717
)
1818
end
1919
private_constant :Documentable
@@ -29,6 +29,8 @@ def process
2929
end
3030
end
3131

32+
private
33+
3234
sig { params(def_node: YARD::Parser::Ruby::MethodDefinitionNode).void }
3335
def process_def(def_node)
3436
separator = scope == :instance && def_node.type == :def ? '#' : '.'
@@ -44,12 +46,27 @@ def process_def(def_node)
4446

4547
sig { params(attr_node: YARD::Parser::Ruby::MethodCallNode).void }
4648
def process_attr(attr_node)
47-
# TODO: Merge with existing attr documentation (#141)
49+
names = NodeUtils.validated_attribute_names(attr_node)
50+
return if merged_into_attr?(attr_node, names)
51+
4852
parse_node(attr_node, statement.docstring, include_params: false)
4953
statement.docstring = nil
5054
end
5155

52-
private
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, names: T::Array[String]).returns(T::Boolean) }
60+
def merged_into_attr?(attr_node, names)
61+
return false if names.size == 1
62+
63+
nodes = namespace.attributes[scope][names.fetch(0)]
64+
return false if nodes.nil? || nodes.empty?
65+
66+
nodes.each_value { parse_node(_1, _1.docstring, include_params: false) }
67+
attr_node.docstring = statement.docstring = nil
68+
true
69+
end
5370

5471
sig { params(attach_to: Documentable, docstring: T.nilable(String), include_params: T::Boolean).void }
5572
def parse_node(attach_to, docstring, include_params: true)

lib/yard-sorbet/node_utils.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,18 @@ def self.sigable_node?(node)
5151
else false
5252
end
5353
end
54+
55+
# @see https://github.com/lsegal/yard/blob/main/lib/yard/handlers/ruby/attribute_handler.rb
56+
# YARD::Handlers::Ruby::AttributeHandler.validated_attribute_names
57+
sig { params(attr_node: YARD::Parser::Ruby::MethodCallNode).returns(T::Array[String]) }
58+
def self.validated_attribute_names(attr_node)
59+
attr_node.parameters(false).map do |obj|
60+
case obj.type
61+
when :symbol_literal then obj.jump(:ident, :op, :kw, :const).source
62+
when :string_literal then obj.jump(:string_content).source
63+
else raise YARD::Parser::UndocumentableError, obj.source
64+
end
65+
end
66+
end
5467
end
5568
end

spec/data/sig_handler.rbi.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
module Merge
22
class A
3+
sig { returns(Numeric) }
4+
attr_accessor :a_foo
5+
6+
sig { returns(T.nilable(String)) }
7+
attr_reader :a_bar
8+
9+
sig { params(writer: Integer).returns(Integer) }
10+
attr_writer :a_baz
11+
312
sig { returns(String) }
413
def foo; end
514

spec/data/sig_handler.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,13 @@ end
340340

341341
module Merge
342342
class A
343+
# annotated attr_accessor
344+
attr_accessor :a_foo
345+
346+
attr_reader :a_bar
347+
348+
attr_writer :a_baz
349+
343350
# The foo instance method for A
344351
def foo; end
345352

spec/yard_sorbet/handlers/sig_handler_spec.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@
22
# frozen_string_literal: true
33

44
RSpec.describe YARDSorbet::Handlers::SigHandler do
5-
path = File.join(File.expand_path('../../data', __dir__), 'sig_handler.txt')
65

7-
before do
6+
# The rubocop disable isn't necessary, but it speeds up tests considerably
7+
before(:all) do # rubocop:disable RSpec/BeforeAfterAll
88
YARD::Registry.clear
9+
path = File.join(File.expand_path('../../data', __dir__), 'sig_handler.txt')
910
YARD::Parser::SourceParser.parse(path)
11+
rbi_path = File.join(File.expand_path('../../data', __dir__), 'sig_handler.rbi.txt')
12+
YARD::Parser::SourceParser.parse(rbi_path)
1013
end
1114

1215
describe 'Merging an RBI file' do
13-
before do
14-
rbi_path = File.join(File.expand_path('../../data', __dir__), 'sig_handler.rbi.txt')
15-
YARD::Parser::SourceParser.parse(rbi_path)
16-
end
17-
1816
it 'includes docstring from original instance def' do
1917
expect(YARD::Registry.at('Merge::A#foo').docstring).to eq('The foo instance method for A')
2018
end

0 commit comments

Comments
 (0)