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 +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE +21 -0
- data/README.md +174 -0
- data/lib/yard-inline-rbs.rb +32 -0
- data/lib/yard_inline_rbs/annotation_warnings.rb +58 -0
- data/lib/yard_inline_rbs/declaration_source.rb +115 -0
- data/lib/yard_inline_rbs/inline_annotations.rb +63 -0
- data/lib/yard_inline_rbs/inline_types.rb +39 -0
- data/lib/yard_inline_rbs/member_tags.rb +43 -0
- data/lib/yard_inline_rbs/object_tags.rb +26 -0
- data/lib/yard_inline_rbs/overload_text.rb +65 -0
- data/lib/yard_inline_rbs/parameter_names.rb +65 -0
- data/lib/yard_inline_rbs/parse_result.rb +177 -0
- data/lib/yard_inline_rbs/plugin.rb +24 -0
- data/lib/yard_inline_rbs/rbs_comment_parser.rb +143 -0
- data/lib/yard_inline_rbs/rbs_description.rb +47 -0
- data/lib/yard_inline_rbs/rbs_file_tags.rb +124 -0
- data/lib/yard_inline_rbs/rbs_function_types.rb +60 -0
- data/lib/yard_inline_rbs/rbs_handler_patches.rb +41 -0
- data/lib/yard_inline_rbs/rbs_type_formatter.rb +114 -0
- data/lib/yard_inline_rbs/result_applier.rb +170 -0
- data/lib/yard_inline_rbs/tag_injector.rb +125 -0
- data/lib/yard_inline_rbs/type_parser.rb +106 -0
- metadata +97 -0
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
|