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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YardInlineRbs
|
|
4
|
+
# Converts RBS types into YARD type strings. RBS `bool` becomes YARD's `Boolean`.
|
|
5
|
+
class RbsTypeFormatter
|
|
6
|
+
BASE_TYPE_STRINGS = { #: Hash[Class, String]
|
|
7
|
+
RBS::Types::Bases::Nil => 'nil',
|
|
8
|
+
RBS::Types::Bases::Void => 'void',
|
|
9
|
+
RBS::Types::Bases::Bool => 'Boolean',
|
|
10
|
+
RBS::Types::Bases::Any => 'untyped',
|
|
11
|
+
RBS::Types::Bases::Top => 'top',
|
|
12
|
+
RBS::Types::Bases::Bottom => 'bot',
|
|
13
|
+
RBS::Types::Bases::Self => 'self'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# @rbs (RBS::Types::t rbs_type) -> String
|
|
17
|
+
def self.format(rbs_type)
|
|
18
|
+
BASE_TYPE_STRINGS[rbs_type.class] || format_structured(rbs_type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A comma-separated YARD type list.
|
|
22
|
+
# @rbs (RBS::Types::t rbs_type) -> String
|
|
23
|
+
def self.format_structured(rbs_type)
|
|
24
|
+
case rbs_type
|
|
25
|
+
when RBS::Types::Optional then "#{format(rbs_type.type)}, nil"
|
|
26
|
+
when RBS::Types::Union then format_list(rbs_type.types)
|
|
27
|
+
when RBS::Types::Intersection then format_list(rbs_type.types, ' & ')
|
|
28
|
+
else format_collection(rbs_type)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
private_class_method :format_structured
|
|
32
|
+
|
|
33
|
+
# @rbs (RBS::Types::t rbs_type) -> String
|
|
34
|
+
def self.format_collection(rbs_type)
|
|
35
|
+
case rbs_type
|
|
36
|
+
when RBS::Types::Proc then 'Proc'
|
|
37
|
+
when RBS::Types::Tuple then format_tuple(rbs_type)
|
|
38
|
+
when RBS::Types::Record then format_record(rbs_type)
|
|
39
|
+
else format_named(rbs_type)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
private_class_method :format_collection
|
|
43
|
+
|
|
44
|
+
# @rbs (Array[RBS::Types::t] types, ?String separator) -> String
|
|
45
|
+
def self.format_list(types, separator = ', ')
|
|
46
|
+
types.map {|type| format(type)}.join(separator)
|
|
47
|
+
end
|
|
48
|
+
private_class_method :format_list
|
|
49
|
+
|
|
50
|
+
# `Array(Integer, String)`, YARD's notation for an array of fixed length, or `Array` for `[]`.
|
|
51
|
+
# @rbs (RBS::Types::Tuple rbs_type) -> String
|
|
52
|
+
def self.format_tuple(rbs_type)
|
|
53
|
+
return 'Array' if rbs_type.types.empty?
|
|
54
|
+
|
|
55
|
+
"Array(#{rbs_type.types.map {|type| format_element(type)}.join(', ')})"
|
|
56
|
+
end
|
|
57
|
+
private_class_method :format_tuple
|
|
58
|
+
|
|
59
|
+
# An optional or a union is a list of types itself, so it is grouped in a tuple:
|
|
60
|
+
# `Array((Integer, nil), String)`.
|
|
61
|
+
# @rbs (RBS::Types::t type) -> String
|
|
62
|
+
def self.format_element(type)
|
|
63
|
+
case type
|
|
64
|
+
when RBS::Types::Optional, RBS::Types::Union then "(#{format(type)})"
|
|
65
|
+
else format(type)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
private_class_method :format_element
|
|
69
|
+
|
|
70
|
+
# `Hash{Symbol => String, Integer}` for `{ name: String, age: Integer }`: the names of the keys
|
|
71
|
+
# are lost, YARD has no notation for them. `Hash` for `{}`.
|
|
72
|
+
# @rbs (RBS::Types::Record rbs_type) -> String
|
|
73
|
+
def self.format_record(rbs_type)
|
|
74
|
+
fields = rbs_type.all_fields
|
|
75
|
+
return 'Hash' if fields.empty?
|
|
76
|
+
|
|
77
|
+
keys = fields.keys.map(&:class).uniq.join(', ')
|
|
78
|
+
values = fields.values.map {|type, _required| format(type)}.uniq.join(', ')
|
|
79
|
+
"Hash{#{keys} => #{values}}"
|
|
80
|
+
end
|
|
81
|
+
private_class_method :format_record
|
|
82
|
+
|
|
83
|
+
# Literals and type variables keep their RBS spelling, such as `:ok` and `T`.
|
|
84
|
+
# @rbs (RBS::Types::t rbs_type) -> String
|
|
85
|
+
def self.format_named(rbs_type)
|
|
86
|
+
case rbs_type
|
|
87
|
+
when RBS::Types::ClassInstance, RBS::Types::Alias, RBS::Types::Interface
|
|
88
|
+
format_class_instance(rbs_type)
|
|
89
|
+
else rbs_type.to_s
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
private_class_method :format_named
|
|
93
|
+
|
|
94
|
+
# A class, alias or interface, with its namespace and type arguments: `Shop::path`,
|
|
95
|
+
# `_Each<Integer>`.
|
|
96
|
+
# @rbs (RBS::Types::ClassInstance | RBS::Types::Alias | RBS::Types::Interface rbs_type)
|
|
97
|
+
# -> String
|
|
98
|
+
def self.format_class_instance(rbs_type)
|
|
99
|
+
name = rbs_type.name.to_s.delete_prefix('::')
|
|
100
|
+
return name if rbs_type.args.empty?
|
|
101
|
+
return format_hash_instance(rbs_type) if name == 'Hash' && rbs_type.args.length == 2
|
|
102
|
+
|
|
103
|
+
"#{name}<#{format_list(rbs_type.args)}>"
|
|
104
|
+
end
|
|
105
|
+
private_class_method :format_class_instance
|
|
106
|
+
|
|
107
|
+
# @rbs (RBS::Types::ClassInstance | RBS::Types::Alias | RBS::Types::Interface rbs_type)
|
|
108
|
+
# -> String
|
|
109
|
+
def self.format_hash_instance(rbs_type)
|
|
110
|
+
"Hash{#{format(rbs_type.args[0])} => #{format(rbs_type.args[1])}}"
|
|
111
|
+
end
|
|
112
|
+
private_class_method :format_hash_instance
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YardInlineRbs
|
|
4
|
+
# Injects the typed tags of a ParseResult into a tag target: a YARD DocstringParser or an
|
|
5
|
+
# ObjectTags. RBS types replace existing ones, unless `keep_existing` is set. A parameter that
|
|
6
|
+
# a named and a positional annotation both type keeps the first, the named one, and is recorded
|
|
7
|
+
# in the result as a duplicate. With `keep_existing`, a kept tag that an inline annotation
|
|
8
|
+
# typed is a duplicate too.
|
|
9
|
+
class ResultApplier
|
|
10
|
+
# @rbs @target: YARD::DocstringParser | ObjectTags
|
|
11
|
+
# @rbs @object: YARD::CodeObjects::Base
|
|
12
|
+
# @rbs @keep_existing: bool
|
|
13
|
+
# @rbs @parameter_names: ParameterNames
|
|
14
|
+
# @rbs @tags: Array[YARD::Tags::Tag]
|
|
15
|
+
# @rbs @result: ParseResult
|
|
16
|
+
|
|
17
|
+
# What the last #apply typed, such as `parameter 'count'` and `the return type`.
|
|
18
|
+
attr_reader :typed #: Array[String]
|
|
19
|
+
|
|
20
|
+
# @rbs (YARD::DocstringParser | ObjectTags target, ?keep_existing: bool) -> void
|
|
21
|
+
def initialize(target, keep_existing: false)
|
|
22
|
+
@target = target
|
|
23
|
+
# YARD types the object of a parser as optional, but TagInjector skips a parser without one.
|
|
24
|
+
@object = target.object #: YARD::CodeObjects::Base
|
|
25
|
+
@keep_existing = keep_existing
|
|
26
|
+
@parameter_names = ParameterNames.new(@object)
|
|
27
|
+
@typed = []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @rbs (ParseResult result) -> void
|
|
31
|
+
def apply(result)
|
|
32
|
+
@result = result
|
|
33
|
+
@typed = []
|
|
34
|
+
@tags = @target.tags
|
|
35
|
+
inject_overload_tags(result.overloads)
|
|
36
|
+
inject_param_tags
|
|
37
|
+
inject_return_or_writer_param_tag(result.return_type, result.return_description)
|
|
38
|
+
inject_yield_tags(result.block_annotation)
|
|
39
|
+
@target.tags = @tags
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# The @overload tags of the docstring win: they are written by hand. A declaration that an
|
|
45
|
+
# inline annotation already typed adds none.
|
|
46
|
+
# @rbs (Array[signature] signatures) -> void
|
|
47
|
+
def inject_overload_tags(signatures)
|
|
48
|
+
return if signatures.empty? || @tags.any? {|tag| tag.tag_name == 'overload'}
|
|
49
|
+
if @keep_existing && InlineTypes.any?(@object)
|
|
50
|
+
return @result.add_duplicate(Annotated::SIGNATURE)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@tags += signatures.map do |signature|
|
|
54
|
+
YARD::Tags::OverloadTag.new(:overload, OverloadText.new(@object.name, signature).to_s)
|
|
55
|
+
end
|
|
56
|
+
@typed |= [Annotated::SIGNATURE]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @rbs () -> void
|
|
60
|
+
def inject_param_tags
|
|
61
|
+
@result.named_param_types.each do |name, annotation|
|
|
62
|
+
inject_param_tag(@parameter_names.resolve_splat(name), annotation.type,
|
|
63
|
+
annotation.description)
|
|
64
|
+
end
|
|
65
|
+
inject_positional_param_tags(@result.positional_param_types)
|
|
66
|
+
sort_param_tags
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# YARD renders @param tags in tag order. Unknown names keep their order, at the end.
|
|
70
|
+
# @rbs () -> void
|
|
71
|
+
def sort_param_tags
|
|
72
|
+
tag_order = @parameter_names.tag_order
|
|
73
|
+
param_tags, other_tags = @tags.partition {|tag| tag.tag_name == 'param'}
|
|
74
|
+
@tags = other_tags + param_tags.sort_by.with_index do |tag, index|
|
|
75
|
+
[tag_order.index(tag.name) || tag_order.size, index]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The Ruby names win, because Steep does not check RBS parameter names. Without Ruby
|
|
80
|
+
# parameters, e.g. a method that only exists in a `.rbs` file, the RBS names are used, and
|
|
81
|
+
# the unnamed parameters are called arg0, arg1, ...
|
|
82
|
+
# @rbs (Array[PositionalParamType] positional_param_types) -> void
|
|
83
|
+
def inject_positional_param_tags(positional_param_types)
|
|
84
|
+
names = @parameter_names.positional
|
|
85
|
+
positional_param_types.each_with_index do |param, index|
|
|
86
|
+
name = if @parameter_names.empty?
|
|
87
|
+
param.name || ParameterNames.placeholder(index)
|
|
88
|
+
else
|
|
89
|
+
names[index]
|
|
90
|
+
end
|
|
91
|
+
inject_param_tag(name, param.type, param.description) if name
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @rbs (String name, String? yard_type, String? description) -> void
|
|
96
|
+
def inject_param_tag(name, yard_type, description)
|
|
97
|
+
return if yard_type.nil?
|
|
98
|
+
|
|
99
|
+
annotated = Annotated.parameter(name)
|
|
100
|
+
return @result.add_duplicate(annotated) if @typed.include?(annotated)
|
|
101
|
+
|
|
102
|
+
replace_tag('param', name, [yard_type], description, annotated)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs (BlockAnnotation? block_annotation) -> void
|
|
106
|
+
def inject_yield_tags(block_annotation)
|
|
107
|
+
return if block_annotation.nil?
|
|
108
|
+
|
|
109
|
+
replace_tag('yield', nil, block_annotation.yield_types, nil, Annotated::BLOCK)
|
|
110
|
+
return_type = block_annotation.return_type
|
|
111
|
+
replace_tag('yieldreturn', nil, [return_type], nil, Annotated::BLOCK) if return_type
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Replaces the tags of `tag_name` and `name` with one of `types`, and records `annotated` as
|
|
115
|
+
# typed. The description of the annotation wins, else that of the tag it replaces. A kept tag
|
|
116
|
+
# stays as it is, and is a duplicate when an inline annotation typed it.
|
|
117
|
+
# @rbs (String tag_name, String? name, Array[String?] types, String? description,
|
|
118
|
+
# String annotated) -> void
|
|
119
|
+
def replace_tag(tag_name, name, types, description, annotated)
|
|
120
|
+
replaced, other_tags = @tags.partition {|tag| tag.tag_name == tag_name && tag.name == name}
|
|
121
|
+
return record_kept(annotated) if kept?(replaced)
|
|
122
|
+
|
|
123
|
+
text = description || replaced.first&.text || ''
|
|
124
|
+
@tags = other_tags + [YARD::Tags::Tag.new(tag_name, text, types, name)]
|
|
125
|
+
@typed |= [annotated]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# A writer's type describes its parameter. `writer?` is not set yet in after_parse. A
|
|
129
|
+
# signature such as `(Symbol, Integer) -> Integer` types the value twice, which is no duplicate.
|
|
130
|
+
# @rbs (String? yard_type, String? description) -> void
|
|
131
|
+
def inject_return_or_writer_param_tag(yard_type, description)
|
|
132
|
+
return unless yard_type
|
|
133
|
+
|
|
134
|
+
if @object.name.match?(/\A(\w+|\[\])=\z/)
|
|
135
|
+
value = value_parameter_name
|
|
136
|
+
inject_param_tag(value, yard_type, description) if @typed.none?(Annotated.parameter(value))
|
|
137
|
+
else
|
|
138
|
+
replace_tag('return', nil, [yard_type], description, Annotated::RETURN_TYPE)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The parameter that a writer sets: the last one, as in `[]=(key, value)`. Without Ruby
|
|
143
|
+
# parameters, the one that the last annotated parameter was tagged as.
|
|
144
|
+
# @rbs () -> String
|
|
145
|
+
def value_parameter_name
|
|
146
|
+
@parameter_names.positional.last || annotated_value_name || 'value'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @rbs () -> String?
|
|
150
|
+
def annotated_value_name
|
|
151
|
+
params = @result.positional_param_types
|
|
152
|
+
last = params.last
|
|
153
|
+
return if last.nil?
|
|
154
|
+
|
|
155
|
+
last.name || ParameterNames.placeholder(params.size - 1)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# With keep_existing, a tag that already has types is kept; one with only a description gets
|
|
159
|
+
# the RBS type.
|
|
160
|
+
# @rbs (Array[YARD::Tags::Tag] tags) -> bool
|
|
161
|
+
def kept?(tags)
|
|
162
|
+
@keep_existing && tags.any? {|tag| tag.types.to_a.any?}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @rbs (String annotated) -> void
|
|
166
|
+
def record_kept(annotated)
|
|
167
|
+
@result.add_duplicate(annotated) if InlineTypes.typed?(@object, annotated)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YardInlineRbs
|
|
4
|
+
# Injects typed tags from the RBS annotations of a YARD DocstringParser. RBS types override
|
|
5
|
+
# YARD types. The annotations are always stripped from the docstring, also when invalid: the
|
|
6
|
+
# warning reports the problem, and the raw text would only clutter the documentation.
|
|
7
|
+
class TagInjector
|
|
8
|
+
# @rbs @parser: YARD::DocstringParser
|
|
9
|
+
|
|
10
|
+
# @rbs (YARD::DocstringParser parser) -> void
|
|
11
|
+
def initialize(parser)
|
|
12
|
+
@parser = parser
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @rbs () -> void
|
|
16
|
+
def inject
|
|
17
|
+
object = @parser.object
|
|
18
|
+
return if object.nil?
|
|
19
|
+
|
|
20
|
+
source = DeclarationSource.new(object, @parser.handler)
|
|
21
|
+
inline_annotations = collect_inline_annotations
|
|
22
|
+
rbs_tag_texts = collect_rbs_tag_texts
|
|
23
|
+
result = parse_rbs_annotations(source, rbs_tag_texts, inline_annotations)
|
|
24
|
+
|
|
25
|
+
annotated = inline_annotations.any? || rbs_tag_texts.any?
|
|
26
|
+
record_and_report(object, source, result, apply_result(source, result, inline_annotations))
|
|
27
|
+
keep_previous_description(object) if annotated && @parser.text.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Records what the annotations typed, for RbsFileTags, and warns about their problems. The
|
|
33
|
+
# warnings come after the tags are applied, which finds more duplicates.
|
|
34
|
+
# @rbs (YARD::CodeObjects::Base object, DeclarationSource source, ParseResult result,
|
|
35
|
+
# Array[String] typed) -> void
|
|
36
|
+
def record_and_report(object, source, result, typed)
|
|
37
|
+
location = source.location_text
|
|
38
|
+
InlineTypes.record(object, typed, location)
|
|
39
|
+
AnnotationWarnings.new(location).report(result)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @rbs (DeclarationSource source, Array[String] rbs_tag_texts, Array[String] inline_annotations)
|
|
43
|
+
# -> ParseResult
|
|
44
|
+
def parse_rbs_annotations(source, rbs_tag_texts, inline_annotations)
|
|
45
|
+
RbsCommentParser.new(rbs_tag_texts, own_annotations(source, inline_annotations),
|
|
46
|
+
source.trailing_comment_line).parse
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The `#:` lines of a comment that YARD took from an earlier statement belong to that
|
|
50
|
+
# statement, so they are skipped; they are still removed from the description.
|
|
51
|
+
# @rbs (DeclarationSource source, Array[String] inline_annotations) -> Array[String]
|
|
52
|
+
def own_annotations(source, inline_annotations)
|
|
53
|
+
return [] if source.comment_of_earlier_statement?
|
|
54
|
+
|
|
55
|
+
inline_annotations
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The tags are only rewritten when there is a type to apply.
|
|
59
|
+
# @rbs (DeclarationSource source, ParseResult result, Array[String] inline_annotations)
|
|
60
|
+
# -> Array[String]
|
|
61
|
+
# @return [Array<String>] What the annotations typed, such as `the return type`.
|
|
62
|
+
def apply_result(source, result, inline_annotations)
|
|
63
|
+
clean_description(source, inline_annotations)
|
|
64
|
+
remove_rbs_tags
|
|
65
|
+
return [] if result.empty?
|
|
66
|
+
|
|
67
|
+
applier = ResultApplier.new(@parser)
|
|
68
|
+
applier.apply(result)
|
|
69
|
+
applier.typed
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# YARD replaces the docstring of an object with each comment it parses, also with a comment
|
|
73
|
+
# that only holds annotations. The previous description, such as that of a `.rbs`
|
|
74
|
+
# declaration, is still on the object while the new one is parsed.
|
|
75
|
+
# @rbs (YARD::CodeObjects::Base object) -> void
|
|
76
|
+
def keep_previous_description(object)
|
|
77
|
+
@parser.text = object.docstring.to_s
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @rbs () -> Array[String]
|
|
81
|
+
def collect_rbs_tag_texts
|
|
82
|
+
@parser.tags.select {|tag| tag.tag_name == 'rbs'}.map(&:text)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The description lines of each `#:` annotation: the line starting with `:` and the indented
|
|
86
|
+
# lines below it, on which a signature can continue.
|
|
87
|
+
# @rbs () -> Array[String]
|
|
88
|
+
def collect_inline_annotations
|
|
89
|
+
annotations = [] #: Array[String]
|
|
90
|
+
continued = false
|
|
91
|
+
@parser.text.each_line do |line|
|
|
92
|
+
continuation = continued && line.match?(/\A\s+\S/)
|
|
93
|
+
annotations << line if inline_annotation?(line)
|
|
94
|
+
annotations << "#{annotations.pop}#{line}" if continuation
|
|
95
|
+
continued = continuation || inline_annotation?(line)
|
|
96
|
+
end
|
|
97
|
+
annotations
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# `: Type`, or only `:`, which is reported as an invalid type.
|
|
101
|
+
# @rbs (String line) -> bool
|
|
102
|
+
def inline_annotation?(line)
|
|
103
|
+
stripped = line.strip
|
|
104
|
+
stripped == ':' || stripped.start_with?(': ')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @rbs (DeclarationSource source, Array[String] inline_annotations) -> void
|
|
108
|
+
def clean_description(source, inline_annotations)
|
|
109
|
+
annotation_lines = inline_annotations.flat_map(&:lines).map(&:strip)
|
|
110
|
+
application = source.superclass_type_application
|
|
111
|
+
annotation_lines << application if application
|
|
112
|
+
return if annotation_lines.empty?
|
|
113
|
+
|
|
114
|
+
@parser.text = @parser.text.lines
|
|
115
|
+
.reject {|line| annotation_lines.include?(line.strip)}
|
|
116
|
+
.join
|
|
117
|
+
.strip
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @rbs () -> void
|
|
121
|
+
def remove_rbs_tags
|
|
122
|
+
@parser.tags = @parser.tags.reject {|tag| tag.tag_name == 'rbs'}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YardInlineRbs
|
|
4
|
+
# Parses RBS type strings into YARD types, recording invalid ones as parse errors.
|
|
5
|
+
class TypeParser
|
|
6
|
+
# @rbs @parse_errors: Array[String]
|
|
7
|
+
|
|
8
|
+
# @rbs (Array[String] parse_errors) -> void
|
|
9
|
+
def initialize(parse_errors)
|
|
10
|
+
@parse_errors = parse_errors
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# RBS returns nil for empty text instead of raising.
|
|
14
|
+
# @rbs (String text) -> String?
|
|
15
|
+
def type(text)
|
|
16
|
+
rbs_type = RBS::Parser.parse_type(text, require_eof: true)
|
|
17
|
+
rbs_type ? RbsTypeFormatter.format(rbs_type) : record_error(text)
|
|
18
|
+
rescue RBS::ParsingError
|
|
19
|
+
record_error(text)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A method type starts with its parameters, type parameters, block or arrow:
|
|
23
|
+
# `(Integer) -> String`, `[T] (T) -> T`, `{ () -> void } -> void`, `-> Integer`. A proc type
|
|
24
|
+
# (`^(Integer) -> String`) and a tuple, record or generic type that contains one also have an
|
|
25
|
+
# arrow, but they are plain types. Invalid text such as `(Integer` is recorded as the same parse
|
|
26
|
+
# error, whether it is parsed as a method type or as a plain type.
|
|
27
|
+
# @rbs (String text) -> bool
|
|
28
|
+
def method_type?(text)
|
|
29
|
+
text.start_with?('(', '[', '{', '->') && !plain_type?(text)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @rbs (String text) -> signature?
|
|
33
|
+
def method_signature(text)
|
|
34
|
+
method_type = RBS::Parser.parse_method_type(text, require_eof: true)
|
|
35
|
+
method_type ? signature_of(method_type) : record_error(text)
|
|
36
|
+
rescue RBS::ParsingError
|
|
37
|
+
record_error(text)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @rbs (RBS::MethodType method_type) -> signature
|
|
41
|
+
def signature_of(method_type)
|
|
42
|
+
function = method_type.type
|
|
43
|
+
return untyped_signature(function) if function.is_a?(RBS::Types::UntypedFunction)
|
|
44
|
+
|
|
45
|
+
function_types = RbsFunctionTypes.new(function)
|
|
46
|
+
block = method_type.block
|
|
47
|
+
{ positional_types: function_types.positional_types,
|
|
48
|
+
named_types: function_types.named_types,
|
|
49
|
+
return_type: function_types.return_type,
|
|
50
|
+
block_annotation: block && block_annotation_of(block.type) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A plain type such as `Proc` yields no types.
|
|
54
|
+
#
|
|
55
|
+
# @rbs (String text) -> BlockAnnotation?
|
|
56
|
+
def block_annotation(text)
|
|
57
|
+
method_type = RBS::Parser.parse_method_type(text, require_eof: true)
|
|
58
|
+
method_type ? block_annotation_of(method_type.type) : plain_block_annotation(text)
|
|
59
|
+
rescue RBS::ParsingError
|
|
60
|
+
plain_block_annotation(text)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# `(?) -> untyped` has no parameters to document.
|
|
66
|
+
# @rbs (RBS::Types::UntypedFunction function) -> signature
|
|
67
|
+
def untyped_signature(function)
|
|
68
|
+
{ positional_types: [], named_types: {}, block_annotation: nil,
|
|
69
|
+
return_type: RbsTypeFormatter.format(function.return_type) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @rbs (RBS::Types::function function) -> BlockAnnotation
|
|
73
|
+
def block_annotation_of(function)
|
|
74
|
+
if function.is_a?(RBS::Types::UntypedFunction)
|
|
75
|
+
return BlockAnnotation.new(yield_types: [],
|
|
76
|
+
return_type: RbsTypeFormatter.format(function.return_type))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
function_types = RbsFunctionTypes.new(function)
|
|
80
|
+
BlockAnnotation.new(yield_types: function_types.yield_types,
|
|
81
|
+
return_type: function_types.return_type)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @rbs (String text) -> BlockAnnotation?
|
|
85
|
+
def plain_block_annotation(text)
|
|
86
|
+
return record_error(text) unless RBS::Parser.parse_type(text, require_eof: true)
|
|
87
|
+
|
|
88
|
+
BlockAnnotation.new(yield_types: [], return_type: nil)
|
|
89
|
+
rescue RBS::ParsingError
|
|
90
|
+
record_error(text)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs (String text) -> bool
|
|
94
|
+
def plain_type?(text)
|
|
95
|
+
!RBS::Parser.parse_type(text, require_eof: true).nil?
|
|
96
|
+
rescue RBS::ParsingError
|
|
97
|
+
false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @rbs (String text) -> nil
|
|
101
|
+
def record_error(text)
|
|
102
|
+
@parse_errors << text
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: yard-inline-rbs
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- yard-inline-rbs contributors
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-11-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rbs
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: yard
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 0.9.45
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 0.9.45
|
|
41
|
+
description:
|
|
42
|
+
email:
|
|
43
|
+
executables: []
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- CHANGELOG.md
|
|
48
|
+
- LICENSE
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/yard-inline-rbs.rb
|
|
51
|
+
- lib/yard_inline_rbs/annotation_warnings.rb
|
|
52
|
+
- lib/yard_inline_rbs/declaration_source.rb
|
|
53
|
+
- lib/yard_inline_rbs/inline_annotations.rb
|
|
54
|
+
- lib/yard_inline_rbs/inline_types.rb
|
|
55
|
+
- lib/yard_inline_rbs/member_tags.rb
|
|
56
|
+
- lib/yard_inline_rbs/object_tags.rb
|
|
57
|
+
- lib/yard_inline_rbs/overload_text.rb
|
|
58
|
+
- lib/yard_inline_rbs/parameter_names.rb
|
|
59
|
+
- lib/yard_inline_rbs/parse_result.rb
|
|
60
|
+
- lib/yard_inline_rbs/plugin.rb
|
|
61
|
+
- lib/yard_inline_rbs/rbs_comment_parser.rb
|
|
62
|
+
- lib/yard_inline_rbs/rbs_description.rb
|
|
63
|
+
- lib/yard_inline_rbs/rbs_file_tags.rb
|
|
64
|
+
- lib/yard_inline_rbs/rbs_function_types.rb
|
|
65
|
+
- lib/yard_inline_rbs/rbs_handler_patches.rb
|
|
66
|
+
- lib/yard_inline_rbs/rbs_type_formatter.rb
|
|
67
|
+
- lib/yard_inline_rbs/result_applier.rb
|
|
68
|
+
- lib/yard_inline_rbs/tag_injector.rb
|
|
69
|
+
- lib/yard_inline_rbs/type_parser.rb
|
|
70
|
+
homepage: https://github.com/vdbijl/yard_inline_rbs_plugin
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
rubygems_mfa_required: 'true'
|
|
75
|
+
source_code_uri: https://github.com/vdbijl/yard_inline_rbs_plugin
|
|
76
|
+
changelog_uri: https://github.com/vdbijl/yard_inline_rbs_plugin/blob/main/CHANGELOG.md
|
|
77
|
+
bug_tracker_uri: https://github.com/vdbijl/yard_inline_rbs_plugin/issues
|
|
78
|
+
post_install_message:
|
|
79
|
+
rdoc_options: []
|
|
80
|
+
require_paths:
|
|
81
|
+
- lib
|
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '3.3'
|
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubygems_version: 3.4.22
|
|
94
|
+
signing_key:
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: 'YARD plugin: use inline RBS type annotations in documentation'
|
|
97
|
+
test_files: []
|