yard-inline-rbs 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: eadf817f51b889044ba2a36a401178e947a87d5ccc723652c6ff383a667babce
4
+ data.tar.gz: 9d98968fa49153690fdf1391981abd99c09f4742891faa863337ef4a7fc66617
5
+ SHA512:
6
+ metadata.gz: 72352816a0e4d88c490d9bedc9a8f12be780a0073e5370fc6c5cff24e1b541fb4d50bb2a1fd7638a7adf3a984eb81c5fd78b3b744b04197d7872d30b7a47c80b
7
+ data.tar.gz: e0dbb396bf8a57e62d04d5ff7bbf34236dd38977bb5cd8ad48e1744f559a2c49106ea8dfd24ae919d75b34e83dacb2cc9e075b41f5350f46fb8b38067620223f
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release.
6
+
7
+ - Types from `@rbs` tags, `#:` comments and trailing `#:` comments become `@param`, `@return`,
8
+ `@yield` and `@yieldreturn` tags.
9
+ - The declarations of `.rbs` files become the same tags.
10
+ - A method with overloads gets an `@overload` tag for each signature.
11
+ - A tuple is `Array(Integer, String)`, a record is `Hash{Symbol => Integer}` and an intersection is
12
+ `A & B`.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yard-inline-rbs contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # yard-inline-rbs
2
+
3
+ A YARD plugin that uses [inline RBS] type annotations in the generated documentation, so types are
4
+ written once: Steep checks them, YARD documents them.
5
+
6
+ Without the plugin, a typed and documented method needs two type annotations that easily drift
7
+ apart:
8
+
9
+ ```ruby
10
+ # @param name [String] who to greet
11
+ # @return [Array<String>]
12
+ # @rbs name: String
13
+ # @rbs return: Array[String]
14
+ def greet(name); end
15
+ ```
16
+
17
+ With the plugin, the RBS annotations are enough:
18
+
19
+ ```ruby
20
+ # Greets someone.
21
+ # @rbs name: String -- who to greet
22
+ # @rbs return: Array[String] -- the greetings
23
+ def greet(name); end
24
+ ```
25
+
26
+ YARD then documents `@param name [String] who to greet` and
27
+ `@return [Array<String>] the greetings`. The RBS types replace any YARD types for the same
28
+ parameter or return value, and the `@rbs` lines are removed from the description. A YARD tag
29
+ keeps its description when the annotation has none; this includes `@yield` and `@yieldreturn`.
30
+ A parameter, the return value or the block that is typed twice, such as by an `@rbs` tag and a
31
+ `#:` line, gets a warning; the first annotation wins, and `@rbs` tags come before `#:` lines.
32
+
33
+ ## Installation
34
+
35
+ Add the gem to the Gemfile of the project to document:
36
+
37
+ ```ruby
38
+ gem 'yard-inline-rbs', path: '../yard_inline_rbs_plugin'
39
+ ```
40
+
41
+ Enable the plugin in its `.yardopts`:
42
+
43
+ ```
44
+ --plugin inline-rbs
45
+ ```
46
+
47
+ Then run `yard doc` as usual.
48
+
49
+ ## Regular RBS files
50
+
51
+ The plugin also documents the types in regular `.rbs` files. YARD parses `sig/**/*.rbs` by
52
+ default; when `.yardopts` lists files, add them to the list:
53
+
54
+ ```
55
+ --plugin inline-rbs
56
+ lib/**/*.rb
57
+ sig/**/*.rbs
58
+ ```
59
+
60
+ The methods, attributes and constants of a `.rbs` file get the same tags as inline annotations:
61
+ `@param`, `@return`, `@yield` and `@yieldreturn`, with types in YARD notation (`Array<String>`,
62
+ not `Array[String]`). A method with several signatures gets an `@overload` tag for each.
63
+ Parameters take the name of the Ruby parameter, also when the signature names them differently:
64
+ Steep does not check RBS parameter names. Without a Ruby method, they keep the name in the
65
+ signature, or are called `arg0`, `arg1`, ...
66
+
67
+ YARD 0.9.45 or later is required, because it parses the `.rbs` files into objects. The
68
+ plugin parses the `.rbs` files before the Ruby files and writes the tags once all files are
69
+ parsed, so the order of the files in `.yardopts` does not matter.
70
+
71
+ - **Existing tags win.** A `.rbs` declaration only adds types that are missing, so a type in the
72
+ Ruby docstring or an inline annotation takes precedence over `sig/`. A tag with only a
73
+ description, such as `@param name The name.`, gets the type of the `.rbs` file. This differs from inline
74
+ annotations, which replace YARD types. A type that an inline annotation and a `.rbs` file both
75
+ declare gets a warning, such as `lib/shop.rb:7: two RBS annotations for the return type, one
76
+ in sig/shop.rbs`; a YARD type such as `@param [String] name` does not.
77
+ - **Comments in `.rbs` files** show up only where the Ruby declaration has no description. A
78
+ Ruby comment with only annotations, such as a trailing `#: String`, has no description.
79
+ - `def self?.name` is not supported: YARD does not create objects for it.
80
+
81
+ ## Supported annotations
82
+
83
+ The plugin follows the inline RBS syntax built into the rbs gem, as specified in [inline RBS].
84
+
85
+ | Annotation | Result |
86
+ |---|---|
87
+ | `# @rbs (Integer, ?String, *Symbol, name: String, **bool) -> void` | `@param` for every parameter, `@return` |
88
+ | `#: (Integer) -> String` above a method | `@param`, `@return` |
89
+ | `# @rbs (String) { (Integer) -> void } -> void` | `@param`, `@yield`, `@yieldreturn`, `@return` |
90
+ | `# @rbs { () -> void } -> void`, without parameter list | `@yield`, `@yieldreturn`, `@return` |
91
+ | `# @rbs (Integer) -> String`, then `# \| (Float) -> Float` on the next line | An `@overload` tag for each signature, and no `@param` or `@return` on the method itself |
92
+ | A signature that continues on indented comment lines (`# -> String`) | As on one line |
93
+ | `# @rbs name: String -- description` | `@param` with description |
94
+ | `# @rbs *names: String`, `# @rbs **options: bool`, also unnamed | `@param` for the splat or keyword rest |
95
+ | `# @rbs &block: (String) -> void`, also optional (`? (…) -> …`) | `@yield`, `@yieldreturn` |
96
+ | `# @rbs return: String -- description` | `@return` with description |
97
+ | `def name #: String` and `def name = value #: String` | `@return` |
98
+ | `attr_reader :name #: String`, also `attr_writer` and `attr_accessor` | `@return` on the reader, `@param` on the writer |
99
+ | `ITEMS = [] #: Array[String]` | `@return` on the constant |
100
+
101
+ A description after `--` may continue on the next lines, or start on the line below a trailing
102
+ `--`. Annotations that only instruct the type checker are removed without a trace: `@rbs skip`,
103
+ `@rbs ...`, `@rbs module-self:`, `#: class-alias`, `#: module-alias` and generic superclass
104
+ comments such as `class StringArray < Array #[String]`.
105
+
106
+ RBS types are converted to the YARD notation:
107
+
108
+ | RBS | YARD |
109
+ |---|---|
110
+ | `Integer?` | `Integer, nil` |
111
+ | `String \| Symbol` | `String, Symbol` |
112
+ | `bool` | `Boolean` |
113
+ | `Array[String]` | `Array<String>` |
114
+ | `Hash[Symbol, Integer]` | `Hash{Symbol => Integer}` |
115
+ | `Comparable & Enumerable[Integer]` | `Comparable & Enumerable<Integer>` |
116
+ | `Shop::path` (alias), `_Each[Integer]` (interface) | `Shop::path`, `_Each<Integer>` |
117
+ | `[Integer, String]` (tuple) | `Array(Integer, String)` |
118
+ | `{ id: Integer, name: String }` (record) | `Hash{Symbol => Integer, String}` |
119
+ | `^(Integer) -> String` (proc) | `Proc` |
120
+
121
+ A record loses the names of its keys: YARD has no notation for them. A tuple or record without
122
+ elements (`[]`, `{ }`) is `Array` or `Hash`.
123
+
124
+ When an annotation cannot be parsed or is not supported, the plugin logs a warning with the file
125
+ and line, and still generates the documentation:
126
+
127
+ ```
128
+ [warn]: [yard-inline-rbs] lib/foo.rb:12: could not parse RBS type 'BadType!!!'
129
+ ```
130
+
131
+ ## Limitations
132
+
133
+ Some Ruby constructs, such as `Struct.new` and `Data.define`, cannot be typed with inline RBS
134
+ yet. See [docs/limitations.md](docs/limitations.md) for what is not supported and why.
135
+
136
+ Not yet implemented in the plugin itself:
137
+
138
+ - A `#:` signature cannot have overloads; Steep rejects them too. Use `@rbs` for overloads.
139
+ - A hand-written `@overload` tag wins: the overloads of an annotation or of a `.rbs` file are not
140
+ added next to it.
141
+ - RBS types without a YARD equivalent are shown as written, e.g. `singleton(Foo)`, `boolish` and
142
+ `instance`.
143
+ - YARD has no notation for nested lists. A tuple groups an optional or a union element:
144
+ `[Integer?, String]` is `Array((Integer, nil), String)`. A record joins the types of its values
145
+ into one list, so `{ a: Integer?, b: String }` is `Hash{Symbol => Integer, nil, String}`.
146
+ - Instance variable annotations (`@rbs @name: String`) are reported as unsupported, because YARD
147
+ does not document instance variables.
148
+
149
+ ## Development
150
+
151
+ ```shell
152
+ bundle install
153
+ bundle exec rake
154
+ ```
155
+
156
+ `rake` runs all checks: RuboCop, Steep, the dependency audit, the specs with coverage minimums,
157
+ and YARD documentation and RSpec coverage. [AGENTS.md](AGENTS.md#running-checks) explains each
158
+ check and how to run one on its own.
159
+
160
+ The plugin hooks into `YARD::DocstringParser.after_parse` (see `lib/yard_inline_rbs/plugin.rb`):
161
+
162
+ - `TagInjector` collects the annotations of a docstring and replaces them with YARD tags.
163
+ - `RbsCommentParser` parses the annotations with the rbs gem.
164
+ - `RbsFunctionTypes` converts the parameters, return type and block of a method type.
165
+ - `RbsDescription` splits an annotation into its type and `--` description.
166
+ - `RbsTypeFormatter` converts RBS types into YARD type strings.
167
+ - `RbsFileTags` writes the types of `.rbs` files onto the YARD objects after all files are parsed,
168
+ using `MemberTags` and `ResultApplier`.
169
+
170
+ [inline RBS]: https://github.com/ruby/rbs/blob/master/docs/inline.md
171
+
172
+ ## License
173
+
174
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yard'
4
+ require 'rbs'
5
+ require_relative 'yard_inline_rbs/rbs_type_formatter'
6
+ require_relative 'yard_inline_rbs/rbs_description'
7
+ require_relative 'yard_inline_rbs/rbs_function_types'
8
+ require_relative 'yard_inline_rbs/parse_result'
9
+ require_relative 'yard_inline_rbs/type_parser'
10
+ require_relative 'yard_inline_rbs/inline_annotations'
11
+ require_relative 'yard_inline_rbs/rbs_comment_parser'
12
+ require_relative 'yard_inline_rbs/parameter_names'
13
+ require_relative 'yard_inline_rbs/annotation_warnings'
14
+ require_relative 'yard_inline_rbs/declaration_source'
15
+ require_relative 'yard_inline_rbs/inline_types'
16
+ require_relative 'yard_inline_rbs/result_applier'
17
+ require_relative 'yard_inline_rbs/object_tags'
18
+ require_relative 'yard_inline_rbs/tag_injector'
19
+ require_relative 'yard_inline_rbs/overload_text'
20
+ require_relative 'yard_inline_rbs/member_tags'
21
+ require_relative 'yard_inline_rbs/rbs_file_tags'
22
+ require_relative 'yard_inline_rbs/rbs_handler_patches'
23
+ require_relative 'yard_inline_rbs/plugin'
24
+
25
+ # YARD plugin that turns inline RBS annotations into `@param`, `@return`, `@yield` and
26
+ # `@yieldreturn` tags, so the documented types are the ones Steep checks.
27
+ module YardInlineRbs
28
+ # @rbs (String message) -> void
29
+ def self.warn(message)
30
+ YARD::Logger.instance.warn("[yard-inline-rbs] #{message}")
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YardInlineRbs
4
+ # Warns about annotations the plugin cannot use, once per location. YARD parses some docstrings
5
+ # more than once: an attribute with its default docstring, both methods of an attr_accessor, and
6
+ # a trailing comment that is also the docstring.
7
+ class AnnotationWarnings
8
+ # @rbs @location: String
9
+
10
+ # The warnings that were logged, so each is logged once.
11
+ LOGGED = Set.new #: Set[String]
12
+
13
+ # Forgets the warnings that were logged. Called before each file is parsed: the location of a
14
+ # warning includes the file, so a file parsed again warns again.
15
+ # @rbs () -> nil
16
+ def self.clear_logged
17
+ LOGGED.clear
18
+ nil
19
+ end
20
+
21
+ # The location is that of the declaration, such as `lib/shop.rb:7`.
22
+ # @rbs (String location) -> void
23
+ def initialize(location)
24
+ @location = location
25
+ end
26
+
27
+ # @rbs (ParseResult result) -> void
28
+ def report(result)
29
+ warn_each(result.parse_errors, 'could not parse RBS type')
30
+ warn_each(result.unsupported_annotations, 'unsupported RBS annotation')
31
+ warn_duplicates(result.duplicate_annotations)
32
+ end
33
+
34
+ # Types that both an inline annotation and the `.rbs` file at `rbs_path` declare.
35
+ # @rbs (Array[String] duplicate_annotations, String rbs_path) -> void
36
+ def report_rbs_file_duplicates(duplicate_annotations, rbs_path)
37
+ warn_duplicates(duplicate_annotations, ", one in #{rbs_path}")
38
+ end
39
+
40
+ private
41
+
42
+ # @rbs (Array[String] duplicate_annotations, ?String suffix) -> void
43
+ def warn_duplicates(duplicate_annotations, suffix = '')
44
+ duplicate_annotations.each {|annotated| log("two RBS annotations for #{annotated}#{suffix}")}
45
+ end
46
+
47
+ # @rbs (Array[String] annotations, String problem) -> void
48
+ def warn_each(annotations, problem)
49
+ annotations.each {|annotation| log("#{problem} '#{annotation}'")}
50
+ end
51
+
52
+ # @rbs (String problem) -> void
53
+ def log(problem)
54
+ message = "#{@location}: #{problem}"
55
+ YardInlineRbs.warn(message) if LOGGED.add?(message)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YardInlineRbs
4
+ # Where the declaration of a code object is, and its source line.
5
+ class DeclarationSource
6
+ # @rbs @object: YARD::CodeObjects::Base
7
+ # @rbs @handler: YARD::Handlers::Base?
8
+ # @rbs @first_line: String?
9
+
10
+ # Forgets the files that were read. Called when the class loads, before each parse list,
11
+ # because a file can change between two parses in a long-running process, and after it, to
12
+ # free the memory.
13
+ # @rbs () -> void
14
+ def self.clear_cache
15
+ @lines = {} #: Hash[String, Array[String]]
16
+ end
17
+ clear_cache
18
+
19
+ # @rbs (String file) -> Array[String]
20
+ def self.lines_of(file)
21
+ @lines[file] ||= File.readlines(file)
22
+ end
23
+
24
+ # The handler is the one that parses the docstring, if any.
25
+ # @rbs (YARD::CodeObjects::Base object, ?YARD::Handlers::Base? handler) -> void
26
+ def initialize(object, handler = nil)
27
+ @object = object
28
+ @handler = handler
29
+ end
30
+
31
+ # The statement of the handler, else the first declaration outside a `.rbs` file. YARD's own
32
+ # `object.file` is the first declaration with a comment: the `.rbs` file when it has a
33
+ # comment, because the plugin parses the `.rbs` files first.
34
+ # @rbs () -> [String, Integer?]?
35
+ def location
36
+ handler = @handler
37
+ return [handler.parser.file, handler.statement.line] if handler
38
+
39
+ files = @object.files
40
+ files.find {|file, _line| !file.end_with?('.rbs')} || files.first
41
+ end
42
+
43
+ # The first line, for its trailing `#:` comment, or nil when that comment is the docstring:
44
+ # without a comment above it, its annotation is already among the description lines.
45
+ # @rbs () -> String?
46
+ def trailing_comment_line
47
+ first_line if !comment_on_declaration_line?
48
+ end
49
+
50
+ # YARD takes the trailing comment of a statement as the docstring of the next statement when
51
+ # that has no comment above it, e.g. after a statement with a comment above it. The
52
+ # annotations in such a comment belong to the earlier statement.
53
+ # @rbs () -> bool
54
+ def comment_of_earlier_statement?
55
+ handler = @handler
56
+ return false if handler.nil?
57
+
58
+ comments_range = handler.statement.comments_range
59
+ line = handler.statement.line
60
+ return false if comments_range.nil? || line.nil? || comments_range.first >= line
61
+
62
+ comment_line = file_line(handler.parser.file, comments_range.first)
63
+ !comment_line.nil? && !comment_line.match?(/\A\s*#/)
64
+ end
65
+
66
+ # `lib/shop.rb:7`, or an empty string without a location.
67
+ # @rbs () -> String
68
+ def location_text
69
+ location.to_a.compact.join(':')
70
+ end
71
+
72
+ # The first line. YARD's source of attr_* methods is synthetic, so the original file line
73
+ # is read.
74
+ # @rbs () -> String
75
+ def first_line
76
+ @first_line ||= original_line || @object.source.to_s.lines.first.to_s
77
+ end
78
+
79
+ # The `[String]` of `class StringArray < Array #[String]`.
80
+ # @rbs () -> String?
81
+ def superclass_type_application
82
+ first_line[/\A\s*class\b[^#]*<[^#]*#(\[.+\])\s*\z/, 1]
83
+ end
84
+
85
+ private
86
+
87
+ # @rbs () -> bool
88
+ def comment_on_declaration_line?
89
+ handler = @handler
90
+ return false if handler.nil?
91
+
92
+ statement = handler.statement
93
+ comments_range = statement.comments_range
94
+ !comments_range.nil? && comments_range.first == statement.line
95
+ end
96
+
97
+ # nil without a file, e.g. with YARD.parse_string, when the file cannot be read, or when it
98
+ # has no such line.
99
+ # @rbs () -> String?
100
+ def original_line
101
+ file, line = location
102
+ file_line(file, line) if file && line
103
+ end
104
+
105
+ # nil when the file cannot be read, e.g. `(stdin)` with YARD.parse_string, or has no such line.
106
+ # @rbs (String file, Integer line) -> String?
107
+ def file_line(file, line)
108
+ return if file == '(stdin)'
109
+
110
+ self.class.lines_of(file)[line - 1]
111
+ rescue SystemCallError
112
+ nil
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YardInlineRbs
4
+ # Reads the inline `#:` annotations. Values already in the result win, because `@rbs`
5
+ # tags take precedence; the result records the duplicates.
6
+ class InlineAnnotations
7
+ # @rbs @types: TypeParser
8
+ # @rbs @result: ParseResult
9
+
10
+ # @rbs (TypeParser types, ParseResult result) -> void
11
+ def initialize(types, result)
12
+ @types = types
13
+ @result = result
14
+ end
15
+
16
+ # A description line starting with `:`, with the indented lines below it. Unlike `@rbs`, a
17
+ # `#:` signature cannot have overloads: Steep rejects it.
18
+ # @rbs (String line) -> void
19
+ def process_line(line)
20
+ annotation = line.strip.delete_prefix(':')
21
+ if overloads?(annotation)
22
+ @result.parse_errors << annotation.lines.map(&:strip).join(' ')
23
+ else
24
+ annotate(annotation) {|type_str| @result.add_signature(@types.method_signature(type_str))}
25
+ end
26
+ end
27
+
28
+ # The declaration line may end in `#: Type`.
29
+ # @rbs (String source_line) -> void
30
+ def process_trailing(source_line)
31
+ match = source_line.match(/(?:\A|\s)#:\s*(.+)$/)
32
+ return unless match
33
+
34
+ annotate(match[1]) do |type_str|
35
+ signature = @types.method_signature(type_str)
36
+ @result.add_return_type(signature[:return_type]) if signature
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # @rbs (String annotation) -> bool
43
+ def overloads?(annotation)
44
+ RbsDescription.overloads(annotation).size > 1
45
+ end
46
+
47
+ # `class-alias` and `module-alias` declare a type name, not a type.
48
+ # @rbs (String type_str) -> bool
49
+ def alias_annotation?(type_str)
50
+ type_str.match?(/\A(class|module)-alias\b/)
51
+ end
52
+
53
+ # Yields method types; any other type is the return type.
54
+ # @rbs (String annotation) { (String) -> void } -> void
55
+ def annotate(annotation)
56
+ type_str, = RbsDescription.split(annotation)
57
+ return if alias_annotation?(type_str)
58
+ return yield(type_str) if @types.method_type?(type_str)
59
+
60
+ @result.add_return_type(@types.type(type_str))
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YardInlineRbs
4
+ # What the inline annotations of an object typed, such as `the return type`, and where they
5
+ # are. RbsFileTags warns when a `.rbs` file types the same. It is stored on the object and
6
+ # replaced with each docstring, so it follows the docstring that YARD keeps, also in its cache.
7
+ class InlineTypes
8
+ # Stores nothing on an object that had no inline types and still has none.
9
+ # @rbs (YARD::CodeObjects::Base object, Array[String] typed, String location) -> void
10
+ def self.record(object, typed, location)
11
+ return if typed.empty? && !any?(object)
12
+
13
+ object[:rbs_inline_typed] = typed
14
+ object[:rbs_inline_location] = location
15
+ end
16
+
17
+ # @rbs (YARD::CodeObjects::Base object, String annotated) -> bool
18
+ def self.typed?(object, annotated)
19
+ typed(object).include?(annotated)
20
+ end
21
+
22
+ # @rbs (YARD::CodeObjects::Base object) -> bool
23
+ def self.any?(object)
24
+ typed(object).any?
25
+ end
26
+
27
+ # @rbs (YARD::CodeObjects::Base object) -> String?
28
+ # @return [String, nil] The location of the inline annotations, such as `lib/shop.rb:7`.
29
+ def self.location(object)
30
+ object[:rbs_inline_location]
31
+ end
32
+
33
+ # @rbs (YARD::CodeObjects::Base object) -> Array[String]
34
+ def self.typed(object)
35
+ Array(object[:rbs_inline_typed])
36
+ end
37
+ private_class_method :typed
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YardInlineRbs
4
+ # Tags a registered YARD object with the types of an RBS declaration. Tags that are already
5
+ # there win, so a Ruby docstring or an inline annotation beats a `.rbs` declaration.
6
+ class MemberTags
7
+ # @rbs @object: YARD::CodeObjects::Base
8
+ # @rbs @tags: ObjectTags
9
+ # @rbs @types: TypeParser
10
+
11
+ # What an inline annotation typed as well, such as `the return type`.
12
+ attr_reader :duplicate_annotations #: Array[String]
13
+
14
+ # @rbs (YARD::CodeObjects::Base object) -> void
15
+ def initialize(object)
16
+ @object = object
17
+ @tags = ObjectTags.new(object)
18
+ @types = TypeParser.new([])
19
+ @duplicate_annotations = []
20
+ end
21
+
22
+ # A method with several signatures gets an @overload tag for each.
23
+ # @rbs (Array[RBS::MethodType] method_types) -> void
24
+ def method_types(method_types)
25
+ signatures = method_types.map {|method_type| @types.signature_of(method_type)}
26
+ apply(ParseResult.new.tap {|result| result.add_signatures(signatures)})
27
+ end
28
+
29
+ # The type of an attribute or constant.
30
+ # @rbs (RBS::Types::t type) -> void
31
+ def type(type)
32
+ apply(ParseResult.new.tap {|result| result.add_return_type(RbsTypeFormatter.format(type))})
33
+ end
34
+
35
+ private
36
+
37
+ # @rbs (ParseResult result) -> void
38
+ def apply(result)
39
+ ResultApplier.new(@tags, keep_existing: true).apply(result)
40
+ @duplicate_annotations.concat(result.duplicate_annotations)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YardInlineRbs
4
+ # Gives a registered code object the `tags` and `tags=` of a DocstringParser. The docstring
5
+ # of an object returns a copy of its tags, so changes must go through its own mutators.
6
+ class ObjectTags
7
+ attr_reader :object #: YARD::CodeObjects::Base
8
+
9
+ # @rbs (YARD::CodeObjects::Base object) -> void
10
+ def initialize(object)
11
+ @object = object
12
+ end
13
+
14
+ # @rbs () -> Array[YARD::Tags::Tag]
15
+ def tags
16
+ @object.tags
17
+ end
18
+
19
+ # @rbs (Array[YARD::Tags::Tag] new_tags) -> void
20
+ def tags=(new_tags)
21
+ docstring = @object.docstring
22
+ docstring.delete_tag_if {true}
23
+ new_tags.each {|tag| docstring.add_tag(tag)}
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YardInlineRbs
4
+ # The text of an @overload tag for one signature of a method, in the format YARD parses:
5
+ # the call on the first line, then the nested tags.
6
+ class OverloadText
7
+ # @rbs @name: Symbol
8
+ # @rbs @signature: signature
9
+
10
+ # The name of a keyword rest that has none.
11
+ KEYWORD_REST_NAME = 'options' #: String
12
+
13
+ # @rbs (Symbol name, signature signature) -> void
14
+ def initialize(name, signature)
15
+ @name = name
16
+ @signature = signature
17
+ end
18
+
19
+ # @rbs () -> String
20
+ def to_s
21
+ lines = ["#{@name}(#{call_arguments.join(', ')})"]
22
+ lines += params.map {|name, type| " @param #{name} [#{type}]"}
23
+ lines += block_lines
24
+ lines << " @return [#{@signature[:return_type]}]"
25
+ lines.join("\n")
26
+ end
27
+
28
+ private
29
+
30
+ # @rbs () -> Array[String]
31
+ def call_arguments
32
+ positional_names + keywords.map do |name, annotation|
33
+ annotation.rest ? "**#{name}" : "#{name}:"
34
+ end
35
+ end
36
+
37
+ # @rbs () -> Array[String]
38
+ def positional_names
39
+ @signature[:positional_types].each_with_index.map do |param, index|
40
+ param.name || ParameterNames.placeholder(index)
41
+ end
42
+ end
43
+
44
+ # @rbs () -> Array[[String, String?]]
45
+ def params
46
+ positional = @signature[:positional_types].map(&:type)
47
+ positional_names.zip(positional) + keywords.transform_values(&:type).to_a
48
+ end
49
+
50
+ # @rbs () -> Hash[String, NamedParamAnnotation]
51
+ def keywords
52
+ @signature[:named_types].transform_keys do |name|
53
+ name == ParameterNames::KEYWORD_REST_KEY ? KEYWORD_REST_NAME : name
54
+ end
55
+ end
56
+
57
+ # @rbs () -> Array[String]
58
+ def block_lines
59
+ block = @signature[:block_annotation]
60
+ return [] if block.nil?
61
+
62
+ [" @yield [#{block.yield_types.join(', ')}]", " @yieldreturn [#{block.return_type}]"]
63
+ end
64
+ end
65
+ end