yard 0.9.43 → 0.9.45
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 +4 -4
- data/CHANGELOG.md +20 -0
- data/LICENSE +1 -1
- data/README.md +1 -1
- data/docs/Tags.md +22 -1
- data/lib/yard/autoload.rb +1 -0
- data/lib/yard/handlers/ruby/struct_handler_methods.rb +41 -5
- data/lib/yard/parser/ruby/ruby_parser.rb +3 -0
- data/lib/yard/server/commands/base.rb +4 -10
- data/lib/yard/server/router.rb +1 -1
- data/lib/yard/server/static_caching.rb +36 -5
- data/lib/yard/server.rb +9 -0
- data/lib/yard/tags/types_explainer.rb +173 -39
- data/lib/yard/templates/helpers/markup/html_entities.rb +2140 -0
- data/lib/yard/templates/helpers/markup/hybrid_markdown.rb +2 -18
- data/lib/yard/templates/helpers/markup/rdoc_markup.rb +5 -1
- data/lib/yard/templates/helpers/method_helper.rb +2 -0
- data/lib/yard/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e6dbc08e061218c5f0736d4b07923de3407ef37427f48307c01917e84c904e2
|
|
4
|
+
data.tar.gz: 7fc6e859d2f4a385eb7a956796782b3d6f45b0b480aadb566ed31aa862c5191b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ef54ea2c613b5b67200f0770ca5accb0c592511087efb5b66cb8c8847e89277247030befc947838eae3ef1f3f911acc66dfbaa0691e9119168e0d76d546ca76
|
|
7
|
+
data.tar.gz: 1d459a1eed141b9183ed4323e16cf0802b42c3043bd7fecc8aa6cc234e4c73a4b4fe90a39ad9bf063f6bc8b42ed911ab16ae56437ac7e4deaa595ef9829485aa
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# main
|
|
2
2
|
|
|
3
|
+
# [0.9.45] - July 14th, 2026
|
|
4
|
+
|
|
5
|
+
[0.9.45]: https://github.com/lsegal/yard/compare/v0.9.44...v0.9.45
|
|
6
|
+
|
|
7
|
+
- Use `@param` types to document generated `Struct.new` and `Data.define` members (#1684)
|
|
8
|
+
- Add compatibility with RDoc 8
|
|
9
|
+
- Fix TypesExplainer parsing of types following Hash collections (#1688)
|
|
10
|
+
- Fix HTML generation for RBS constants without source values (#1686)
|
|
11
|
+
- Fix method redefinition warnings when loading YARD with Ruby warnings enabled (#1687)
|
|
12
|
+
- Improve sanitization of `yard server` request paths
|
|
13
|
+
|
|
14
|
+
# [0.9.44] - May 25th, 2026
|
|
15
|
+
|
|
16
|
+
[0.9.44]: https://github.com/lsegal/yard/compare/v0.9.43...v0.9.44
|
|
17
|
+
|
|
18
|
+
- Fix possible path traversal with document_root (`--docroot`) and disk caching (`--cache`) set in `yard server` ([GHSA-pxcc-8665-phx8](https://github.com/lsegal/yard/security/advisories/GHSA-pxcc-8665-phx8))
|
|
19
|
+
- Fix support for HTML entities in HybridMarkup (#1680, #1681)
|
|
20
|
+
- Add support for string literals in TypesExplainer (#1628)
|
|
21
|
+
- Add support for multiple & nested Hash keys definition (#1630)
|
|
22
|
+
|
|
3
23
|
# [0.9.43] - April 17th, 2026
|
|
4
24
|
|
|
5
25
|
[0.9.43]: https://github.com/lsegal/yard/compare/v0.9.42...v0.9.43
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -323,7 +323,7 @@ See {file:CHANGELOG.md} for a list of changes.
|
|
|
323
323
|
|
|
324
324
|
## License
|
|
325
325
|
|
|
326
|
-
YARD ©
|
|
326
|
+
YARD © by [Loren Segal](mailto:lsegal@soen.ca). YARD is licensed
|
|
327
327
|
under the MIT license except for some files which come from the RDoc/Ruby
|
|
328
328
|
distributions. Please see the {file:LICENSE} and {file:LEGAL} documents for more
|
|
329
329
|
information.
|
data/docs/Tags.md
CHANGED
|
@@ -201,6 +201,27 @@ in the form `Hash<KeyType, ValueType>`, or using the hash specific syntax:
|
|
|
201
201
|
`Hash{KeyTypes=>ValueTypes}`. In the latter case, KeyTypes or ValueTypes can
|
|
202
202
|
also be a list of types separated by commas.
|
|
203
203
|
|
|
204
|
+
For more precise type signatures, the hash-specific syntax also supports
|
|
205
|
+
multiple keys mapping to the same value type(s), multiple key/value groups,
|
|
206
|
+
and nested hashes:
|
|
207
|
+
|
|
208
|
+
* **Multiple keys for the same value type(s)** — A comma-separated list of
|
|
209
|
+
keys on the left of `=>` all share the value types on the right. For example,
|
|
210
|
+
`Hash{:name, :title => String}` describes a Hash where both `:name` and
|
|
211
|
+
`:title` keys map to String values.
|
|
212
|
+
* **Multiple key/value groups** — Separate distinct key/value groups with
|
|
213
|
+
a semicolon (`;`). For example,
|
|
214
|
+
`Hash{:name => String; :age => Integer}` describes a Hash with a `:name` key
|
|
215
|
+
that maps to a String and an `:age` key that maps to an Integer.
|
|
216
|
+
* **Nested hashes** — A value type can itself be a hash, allowing nested
|
|
217
|
+
structures. For example,
|
|
218
|
+
`Hash{:user => Hash{:name => String, :age => Integer}}` describes a Hash with
|
|
219
|
+
a `:user` key whose value is another Hash with `:name` and `:age` keys.
|
|
220
|
+
|
|
221
|
+
Keys in the hash-specific syntax are commonly [literal values](#Literals) such
|
|
222
|
+
as symbols (`:key`) or strings (`'key'`, `"key"`), but any type listed in the
|
|
223
|
+
[type conventions](#Type_List_Conventions) is allowed.
|
|
224
|
+
|
|
204
225
|
#### Order-Dependent Lists
|
|
205
226
|
|
|
206
227
|
An order dependent list is a set of types surrounded by "()" and separated by
|
|
@@ -213,7 +234,7 @@ having exactly those 3 elements) would be listed as: `Array(String, Fixnum, Hash
|
|
|
213
234
|
Some literals are accepted by virtue of being Ruby literals, but also by YARD
|
|
214
235
|
conventions. Here is a non-exhaustive list of certain accepted literal values:
|
|
215
236
|
|
|
216
|
-
* `true`, `false`, `nil`, `:foo` — used when a method returns
|
|
237
|
+
* `true`, `false`, `nil`, `:foo`, `"bar"` — used when a method returns
|
|
217
238
|
these explicit literal values. Note that if your method returns both
|
|
218
239
|
`true` or `false`, you should use the `Boolean` conventional type
|
|
219
240
|
instead.
|
data/lib/yard/autoload.rb
CHANGED
|
@@ -287,6 +287,7 @@ module YARD
|
|
|
287
287
|
module Templates
|
|
288
288
|
module Helpers # Namespace for template helpers
|
|
289
289
|
module Markup # Namespace for markup providers
|
|
290
|
+
autoload :HtmlEntities, __p('templates/helpers/markup/html_entities')
|
|
290
291
|
autoload :HybridMarkdown, __p('templates/helpers/markup/hybrid_markdown')
|
|
291
292
|
autoload :RDocMarkup, __p('templates/helpers/markup/rdoc_markup')
|
|
292
293
|
autoload :RDocMarkdown, __p('templates/helpers/markup/rdoc_markdown')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
# Helper methods to
|
|
2
|
+
# Helper methods to document generated Struct and Data members.
|
|
3
3
|
#
|
|
4
4
|
# @deprecated The use of +@attr+ tags are deprecated since 0.8.0 in favour of
|
|
5
5
|
# the +@!attribute+ directive. This module should not be relied on.
|
|
@@ -19,6 +19,26 @@ module YARD::Handlers::Ruby::StructHandlerMethods
|
|
|
19
19
|
(klass.tags(specific_tag) + klass.tags(:attr)).find {|tag| tag.name == member }
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Extracts the user's defined @param tag for a given generated member.
|
|
23
|
+
#
|
|
24
|
+
# @param [ClassObject] klass the class whose tags we're searching
|
|
25
|
+
# @param [String] member the name of the struct or data member we need
|
|
26
|
+
# @return [Tags::Tag, nil] the matching tag, or nil if not found
|
|
27
|
+
def parameter_tag_for_member(klass, member)
|
|
28
|
+
klass.tags(:param).find {|tag| tag.name == member }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns the tag that supplies a generated member's type. Existing @attr*
|
|
32
|
+
# tags take precedence over the more concise @param form.
|
|
33
|
+
#
|
|
34
|
+
# @param [ClassObject] klass the class whose tags we're searching
|
|
35
|
+
# @param [String] member the name of the struct or data member we need
|
|
36
|
+
# @param [Symbol] type reader method, or writer method?
|
|
37
|
+
# @return [Tags::Tag, nil] the tag supplying the type, or nil if not found
|
|
38
|
+
def type_tag_for_member(klass, member, type = :read)
|
|
39
|
+
member_tag_for_member(klass, member, type) || parameter_tag_for_member(klass, member)
|
|
40
|
+
end
|
|
41
|
+
|
|
22
42
|
# Retrieves all members defined in @attr* tags
|
|
23
43
|
#
|
|
24
44
|
# @param [ClassObject] klass the class with the attributes
|
|
@@ -61,7 +81,7 @@ module YARD::Handlers::Ruby::StructHandlerMethods
|
|
|
61
81
|
# @return [String] a docstring to be attached to the getter method for this member
|
|
62
82
|
def add_reader_tags(klass, new_method, member)
|
|
63
83
|
member_tag = member_tag_for_member(klass, member, :read)
|
|
64
|
-
return_type = return_type_from_tag(
|
|
84
|
+
return_type = return_type_from_tag(type_tag_for_member(klass, member, :read))
|
|
65
85
|
getter_doc_text = member_tag ? member_tag.text : "Returns the value of attribute #{member}"
|
|
66
86
|
new_method.docstring.replace(getter_doc_text)
|
|
67
87
|
new_method.add_tag YARD::Tags::Tag.new(:return, "the current value of #{member}", return_type)
|
|
@@ -76,7 +96,7 @@ module YARD::Handlers::Ruby::StructHandlerMethods
|
|
|
76
96
|
# @return [String] a docstring to be attached to the setter method for this member
|
|
77
97
|
def add_writer_tags(klass, new_method, member)
|
|
78
98
|
member_tag = member_tag_for_member(klass, member, :write)
|
|
79
|
-
return_type = return_type_from_tag(
|
|
99
|
+
return_type = return_type_from_tag(type_tag_for_member(klass, member, :write))
|
|
80
100
|
setter_doc_text = member_tag ? member_tag.text : "Sets the attribute #{member}"
|
|
81
101
|
new_method.docstring.replace(setter_doc_text)
|
|
82
102
|
new_method.add_tag YARD::Tags::Tag.new(:param, "the value to set the attribute #{member} to.", return_type, "value")
|
|
@@ -104,7 +124,7 @@ module YARD::Handlers::Ruby::StructHandlerMethods
|
|
|
104
124
|
def create_writer(klass, member)
|
|
105
125
|
# We want to convert these members into attributes just like
|
|
106
126
|
# as if they were declared using attr_accessor.
|
|
107
|
-
new_meth =
|
|
127
|
+
new_meth = register_struct_member_method MethodObject.new(klass, "#{member}=", :instance) do |o|
|
|
108
128
|
o.parameters = [['value', nil]]
|
|
109
129
|
o.signature ||= "def #{member}=(value)"
|
|
110
130
|
o.source ||= "#{o.signature}\n @#{member} = value\nend"
|
|
@@ -119,7 +139,7 @@ module YARD::Handlers::Ruby::StructHandlerMethods
|
|
|
119
139
|
# @param [ClassObject] klass the class to attach the method to
|
|
120
140
|
# @param [String] member the name of the member we're generating a method for
|
|
121
141
|
def create_reader(klass, member)
|
|
122
|
-
new_meth =
|
|
142
|
+
new_meth = register_struct_member_method MethodObject.new(klass, member, :instance) do |o|
|
|
123
143
|
o.signature ||= "def #{member}"
|
|
124
144
|
o.source ||= "#{o.signature}\n @#{member}\nend"
|
|
125
145
|
end
|
|
@@ -140,4 +160,20 @@ module YARD::Handlers::Ruby::StructHandlerMethods
|
|
|
140
160
|
create_reader klass, member if create_member_method?(klass, member, :read)
|
|
141
161
|
end
|
|
142
162
|
end
|
|
163
|
+
|
|
164
|
+
# Registers an auto-generated member method without reapplying the class's
|
|
165
|
+
# docstring to it. The generated reader or writer receives its own docstring
|
|
166
|
+
# and tags immediately after registration.
|
|
167
|
+
def register_struct_member_method(method, &block)
|
|
168
|
+
previous = @registering_struct_member_method
|
|
169
|
+
@registering_struct_member_method = true
|
|
170
|
+
register(method, &block)
|
|
171
|
+
ensure
|
|
172
|
+
@registering_struct_member_method = previous
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def register_docstring(object, docstring = statement.comments, stmt = statement)
|
|
176
|
+
docstring = nil if @registering_struct_member_method
|
|
177
|
+
super(object, docstring, stmt)
|
|
178
|
+
end
|
|
143
179
|
end
|
|
@@ -379,6 +379,7 @@ module YARD
|
|
|
379
379
|
# entries from corrupting source ranges of later hash literals and brace blocks.
|
|
380
380
|
# Bare hash patterns (key: val without braces) fire no brace scanner events, so
|
|
381
381
|
# we only clean up when @map[:rbrace] confirms a closing brace was scanned.
|
|
382
|
+
begin; undef on_hshptn; rescue NameError; end
|
|
382
383
|
def on_hshptn(*args)
|
|
383
384
|
if (@map[:rbrace] ||= []).any?
|
|
384
385
|
(@map[:lbrace] ||= []).pop
|
|
@@ -443,6 +444,7 @@ module YARD
|
|
|
443
444
|
# on_rbracket scanner events. The corresponding parser events are on_aryptn/on_fndptn
|
|
444
445
|
# (not on_aref), so we must clean up the bracket maps to prevent stale entries from
|
|
445
446
|
# corrupting source ranges of later array indexing expressions.
|
|
447
|
+
begin; undef on_aryptn; rescue NameError; end
|
|
446
448
|
def on_aryptn(*args)
|
|
447
449
|
(@map[:lbracket] ||= []).pop
|
|
448
450
|
(@map[:aref] ||= []).shift
|
|
@@ -451,6 +453,7 @@ module YARD
|
|
|
451
453
|
AstNode.new(:aryptn, args)
|
|
452
454
|
end
|
|
453
455
|
|
|
456
|
+
begin; undef on_fndptn; rescue NameError; end
|
|
454
457
|
def on_fndptn(*args)
|
|
455
458
|
(@map[:lbracket] ||= []).pop
|
|
456
459
|
(@map[:aref] ||= []).shift
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'fileutils'
|
|
3
|
-
|
|
4
2
|
module YARD
|
|
5
3
|
module Server
|
|
6
4
|
module Commands
|
|
@@ -32,6 +30,8 @@ module YARD
|
|
|
32
30
|
# @abstract
|
|
33
31
|
# @see #run
|
|
34
32
|
class Base
|
|
33
|
+
include StaticCaching
|
|
34
|
+
|
|
35
35
|
# @group Basic Command and Adapter Options
|
|
36
36
|
|
|
37
37
|
# @return [Hash] the options passed to the command's constructor
|
|
@@ -88,7 +88,7 @@ module YARD
|
|
|
88
88
|
# of status, headers, and body wrapped in an array.
|
|
89
89
|
def call(request)
|
|
90
90
|
self.request = request
|
|
91
|
-
self.path ||=
|
|
91
|
+
self.path ||= Server.clean_path(request.path_info[1..-1])
|
|
92
92
|
self.headers = {'Content-Type' => 'text/html'}
|
|
93
93
|
self.body = ''
|
|
94
94
|
self.status = 200
|
|
@@ -163,13 +163,7 @@ module YARD
|
|
|
163
163
|
# @return [String] the same cached data (for chaining)
|
|
164
164
|
# @see StaticCaching
|
|
165
165
|
def cache(data)
|
|
166
|
-
if caching
|
|
167
|
-
path = File.join(adapter.document_root, request.path_info.sub(/\.html$/, '') + '.html')
|
|
168
|
-
path = path.sub(%r{/\.html$}, '.html')
|
|
169
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
170
|
-
log.debug "Caching data to #{path}"
|
|
171
|
-
File.open(path, 'wb') {|f| f.write(data) }
|
|
172
|
-
end
|
|
166
|
+
super if caching
|
|
173
167
|
self.body = data
|
|
174
168
|
end
|
|
175
169
|
|
data/lib/yard/server/router.rb
CHANGED
|
@@ -179,7 +179,7 @@ module YARD
|
|
|
179
179
|
# @param (see #route_docs)
|
|
180
180
|
# @return [Hash] finalized options
|
|
181
181
|
def final_options(library, paths)
|
|
182
|
-
path =
|
|
182
|
+
path = Server.clean_path(paths.join('/'))
|
|
183
183
|
adapter.options.merge(:library => library, :path => path)
|
|
184
184
|
end
|
|
185
185
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
2
4
|
module YARD
|
|
3
5
|
module Server
|
|
4
6
|
# Implements static caching for requests.
|
|
@@ -10,9 +12,8 @@ module YARD
|
|
|
10
12
|
# implement your own +#check_static_cache+ method and mix the module into
|
|
11
13
|
# the Router class.
|
|
12
14
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# {Commands::Base#cache}.
|
|
15
|
+
# This method checks for the existence of cached data. To actually cache
|
|
16
|
+
# a response, see {#cache}.
|
|
16
17
|
#
|
|
17
18
|
# @example Implementing In-Memory Cache Checking
|
|
18
19
|
# module MemoryCaching
|
|
@@ -33,14 +34,44 @@ module YARD
|
|
|
33
34
|
# @see Commands::Base#cache
|
|
34
35
|
def check_static_cache
|
|
35
36
|
return nil unless adapter.document_root
|
|
36
|
-
cache_path =
|
|
37
|
-
|
|
37
|
+
cache_path = cache_path(request.path)
|
|
38
|
+
return nil unless cache_path
|
|
39
|
+
|
|
38
40
|
if File.file?(cache_path)
|
|
39
41
|
log.debug "Loading cache from disk: #{cache_path}"
|
|
40
42
|
return [200, {'Content-Type' => 'text/html'}, [File.read_binary(cache_path)]]
|
|
41
43
|
end
|
|
42
44
|
nil
|
|
43
45
|
end
|
|
46
|
+
|
|
47
|
+
# Caches rendered HTML response data to disk.
|
|
48
|
+
#
|
|
49
|
+
# @param [String] data the data to cache
|
|
50
|
+
# @return [void]
|
|
51
|
+
# @since 0.9.44
|
|
52
|
+
def cache(data)
|
|
53
|
+
return unless adapter.document_root
|
|
54
|
+
|
|
55
|
+
path = cache_path(request.path_info)
|
|
56
|
+
return unless path
|
|
57
|
+
|
|
58
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
59
|
+
log.debug "Caching data to #{path}"
|
|
60
|
+
File.open(path, 'wb') {|f| f.write(data) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def cache_path(request_path)
|
|
66
|
+
return nil if request_path.split(/[\/\\]/).include?('..')
|
|
67
|
+
|
|
68
|
+
path = request_path.sub(/\.html$/, '') + '.html'
|
|
69
|
+
path = path.sub(%r{\A/+}, '')
|
|
70
|
+
return nil if path =~ /\A[A-Za-z]:/
|
|
71
|
+
|
|
72
|
+
path = File.cleanpath(path)
|
|
73
|
+
File.join(adapter.document_root, path)
|
|
74
|
+
end
|
|
44
75
|
end
|
|
45
76
|
end
|
|
46
77
|
end
|
data/lib/yard/server.rb
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module YARD
|
|
3
3
|
module Server
|
|
4
|
+
# Normalizes an HTTP request path into a relative command path.
|
|
5
|
+
# @api private
|
|
6
|
+
# @param [String] path the request path to normalize
|
|
7
|
+
# @return [String] a relative path using forward slashes
|
|
8
|
+
def self.clean_path(path)
|
|
9
|
+
path = File.cleanpath(path.tr('\\', '/'))
|
|
10
|
+
path.sub(/\A[A-Za-z]:(?!:)/, '').sub(%r{\A/+}, '')
|
|
11
|
+
end
|
|
12
|
+
|
|
4
13
|
# Registers a static path to be used in static asset lookup.
|
|
5
14
|
# @param [String] path the pathname to register
|
|
6
15
|
# @return [void]
|
|
@@ -4,6 +4,9 @@ require 'strscan'
|
|
|
4
4
|
module YARD
|
|
5
5
|
module Tags
|
|
6
6
|
class TypesExplainer
|
|
7
|
+
# Regular expression to match symbol and string literals
|
|
8
|
+
LITERALMATCH = /:\w+|'[^']*'|"[^"]*"/
|
|
9
|
+
|
|
7
10
|
# (see Tag#explain_types)
|
|
8
11
|
# @param types [Array<String>] a list of types to parse and summarize
|
|
9
12
|
def self.explain(*types)
|
|
@@ -31,16 +34,14 @@ module YARD
|
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
def to_s(singular = true)
|
|
34
|
-
if name[0, 1]
|
|
35
|
-
(singular ? "an object that responds to " : "objects that respond to ") + list_join(name.split(/ *& */), with: "and")
|
|
36
|
-
elsif name[0, 1] =~ /[A-Z]/
|
|
37
|
+
if name[0, 1] =~ /[A-Z]/
|
|
37
38
|
singular ? "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} " + name : "#{name}#{name[-1, 1] =~ /[A-Z]/ ? "'" : ''}s"
|
|
38
39
|
else
|
|
39
40
|
name
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
protected
|
|
44
45
|
|
|
45
46
|
def list_join(list, with: "or")
|
|
46
47
|
index = 0
|
|
@@ -54,6 +55,20 @@ module YARD
|
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
|
|
58
|
+
# @private
|
|
59
|
+
class LiteralType < Type
|
|
60
|
+
def to_s(_singular = true)
|
|
61
|
+
"a literal value #{name}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @private
|
|
66
|
+
class DuckType < Type
|
|
67
|
+
def to_s(singular = true)
|
|
68
|
+
(singular ? "an object that responds to " : "objects that respond to ") + list_join(name.split(/ *& */), with: "and")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
57
72
|
# @private
|
|
58
73
|
class CollectionType < Type
|
|
59
74
|
attr_accessor :types
|
|
@@ -77,18 +92,56 @@ module YARD
|
|
|
77
92
|
|
|
78
93
|
# @private
|
|
79
94
|
class HashCollectionType < Type
|
|
80
|
-
attr_accessor :
|
|
95
|
+
attr_accessor :key_value_pairs
|
|
81
96
|
|
|
82
|
-
def initialize(name,
|
|
97
|
+
def initialize(name, key_types_or_pairs, value_types = nil)
|
|
83
98
|
@name = name
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
|
|
100
|
+
if value_types.nil?
|
|
101
|
+
# New signature: (name, key_value_pairs)
|
|
102
|
+
@key_value_pairs = key_types_or_pairs || []
|
|
103
|
+
else
|
|
104
|
+
# Old signature: (name, key_types, value_types)
|
|
105
|
+
@key_value_pairs = [[key_types_or_pairs, value_types]]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Backward compatibility accessors
|
|
110
|
+
def key_types
|
|
111
|
+
return [] if @key_value_pairs.empty?
|
|
112
|
+
@key_value_pairs.first[0] || []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def key_types=(types)
|
|
116
|
+
if @key_value_pairs.empty?
|
|
117
|
+
@key_value_pairs = [[types, []]]
|
|
118
|
+
else
|
|
119
|
+
@key_value_pairs[0][0] = types
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def value_types
|
|
124
|
+
return [] if @key_value_pairs.empty?
|
|
125
|
+
@key_value_pairs.first[1] || []
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def value_types=(types)
|
|
129
|
+
if @key_value_pairs.empty?
|
|
130
|
+
@key_value_pairs = [[[], types]]
|
|
131
|
+
else
|
|
132
|
+
@key_value_pairs[0][1] = types
|
|
133
|
+
end
|
|
86
134
|
end
|
|
87
135
|
|
|
88
136
|
def to_s(_singular = true)
|
|
89
|
-
"a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} #{name}
|
|
90
|
-
|
|
91
|
-
|
|
137
|
+
return "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} #{name}" if @key_value_pairs.empty?
|
|
138
|
+
|
|
139
|
+
result = "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} #{name} with "
|
|
140
|
+
parts = @key_value_pairs.map do |keys, values|
|
|
141
|
+
"keys made of (" + list_join(keys.map {|t| t.to_s(false) }) +
|
|
142
|
+
") and values of (" + list_join(values.map {|t| t.to_s(false) }) + ")"
|
|
143
|
+
end
|
|
144
|
+
result + parts.join(" and ")
|
|
92
145
|
end
|
|
93
146
|
end
|
|
94
147
|
|
|
@@ -101,13 +154,15 @@ module YARD
|
|
|
101
154
|
:collection_end => />/,
|
|
102
155
|
:fixed_collection_start => /\(/,
|
|
103
156
|
:fixed_collection_end => /\)/,
|
|
104
|
-
:type_name => /#{ISEP}#{METHODNAMEMATCH}|#{NAMESPACEMATCH}|\w+/,
|
|
157
|
+
:type_name => /#{ISEP}#{METHODNAMEMATCH}|#{NAMESPACEMATCH}|#{LITERALMATCH}|\w+/,
|
|
105
158
|
:symbol => /:#{METHODNAMEMATCH}/,
|
|
106
|
-
:type_next => /[
|
|
159
|
+
:type_next => /[,]/,
|
|
107
160
|
:whitespace => /\s+/,
|
|
108
161
|
:hash_collection_start => /\{/,
|
|
109
|
-
:
|
|
162
|
+
:hash_collection_value => /=>/,
|
|
163
|
+
:hash_collection_value_end => /;/,
|
|
110
164
|
:hash_collection_end => /\}/,
|
|
165
|
+
# :symbol_start => /:/,
|
|
111
166
|
:parse_end => nil
|
|
112
167
|
}
|
|
113
168
|
|
|
@@ -119,10 +174,53 @@ module YARD
|
|
|
119
174
|
@scanner = StringScanner.new(string)
|
|
120
175
|
end
|
|
121
176
|
|
|
122
|
-
|
|
123
|
-
|
|
177
|
+
# @return [Array(Boolean, Array<Type>)] - finished, types
|
|
178
|
+
def parse(until_tokens: [:parse_end])
|
|
179
|
+
parse_until(until_tokens).first
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
def parse_until(until_tokens)
|
|
185
|
+
current_parsed_types = []
|
|
124
186
|
type = nil
|
|
125
187
|
name = nil
|
|
188
|
+
finished = false
|
|
189
|
+
end_token = nil
|
|
190
|
+
types = parse_with_handlers do |token_type, token|
|
|
191
|
+
case token_type
|
|
192
|
+
when *until_tokens
|
|
193
|
+
raise SyntaxError, "expecting name, got '#{token}'" if name.nil?
|
|
194
|
+
type = create_type(name) unless type
|
|
195
|
+
current_parsed_types << type
|
|
196
|
+
finished = true
|
|
197
|
+
end_token = token_type
|
|
198
|
+
when :type_name
|
|
199
|
+
raise SyntaxError, "expecting END, got name '#{token}'" if name
|
|
200
|
+
name = token
|
|
201
|
+
when :type_next
|
|
202
|
+
raise SyntaxError, "expecting name, got '#{token}' at #{@scanner.pos}" if name.nil?
|
|
203
|
+
type = create_type(name) unless type
|
|
204
|
+
current_parsed_types << type
|
|
205
|
+
name = nil
|
|
206
|
+
type = nil
|
|
207
|
+
when :fixed_collection_start, :collection_start
|
|
208
|
+
name ||= "Array"
|
|
209
|
+
klass = token_type == :collection_start ? CollectionType : FixedCollectionType
|
|
210
|
+
nested_types, = parse_until([:fixed_collection_end, :collection_end, :parse_end])
|
|
211
|
+
type = klass.new(name, nested_types)
|
|
212
|
+
when :hash_collection_start
|
|
213
|
+
name ||= "Hash"
|
|
214
|
+
type = parse_hash_collection(name)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
[finished, current_parsed_types]
|
|
218
|
+
end
|
|
219
|
+
[types, end_token]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# @return [Array<Type>]
|
|
223
|
+
def parse_with_handlers
|
|
126
224
|
loop do
|
|
127
225
|
found = false
|
|
128
226
|
TOKENS.each do |token_type, match|
|
|
@@ -130,32 +228,68 @@ module YARD
|
|
|
130
228
|
# rubocop:disable Lint/AssignmentInCondition
|
|
131
229
|
next unless (match.nil? && @scanner.eos?) || (match && token = @scanner.scan(match))
|
|
132
230
|
found = true
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
when :type_next
|
|
138
|
-
raise SyntaxError, "expecting name, got '#{token}' at #{@scanner.pos}" if name.nil?
|
|
139
|
-
type = Type.new(name) unless type
|
|
140
|
-
types << type
|
|
141
|
-
type = nil
|
|
142
|
-
name = nil
|
|
143
|
-
when :fixed_collection_start, :collection_start
|
|
144
|
-
name ||= "Array"
|
|
145
|
-
klass = token_type == :collection_start ? CollectionType : FixedCollectionType
|
|
146
|
-
type = klass.new(name, parse)
|
|
147
|
-
when :hash_collection_start
|
|
148
|
-
name ||= "Hash"
|
|
149
|
-
type = HashCollectionType.new(name, parse, parse)
|
|
150
|
-
when :hash_collection_next, :hash_collection_end, :fixed_collection_end, :collection_end, :parse_end
|
|
151
|
-
raise SyntaxError, "expecting name, got '#{token}'" if name.nil?
|
|
152
|
-
type = Type.new(name) unless type
|
|
153
|
-
types << type
|
|
154
|
-
return types
|
|
155
|
-
end
|
|
231
|
+
# @type [Array<Type>]
|
|
232
|
+
finished, types = yield(token_type, token)
|
|
233
|
+
return types if finished
|
|
234
|
+
break
|
|
156
235
|
end
|
|
157
236
|
raise SyntaxError, "invalid character at #{@scanner.peek(1)}" unless found
|
|
158
237
|
end
|
|
238
|
+
nil
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def parse_hash_collection(name)
|
|
242
|
+
key_value_pairs = []
|
|
243
|
+
current_keys = []
|
|
244
|
+
finished = false
|
|
245
|
+
|
|
246
|
+
parse_with_handlers do |token_type, token|
|
|
247
|
+
case token_type
|
|
248
|
+
when :type_name
|
|
249
|
+
current_keys << create_type(token)
|
|
250
|
+
when :type_next
|
|
251
|
+
# Comma - continue collecting keys unless we just processed a value
|
|
252
|
+
# In that case, start a new key group
|
|
253
|
+
when :hash_collection_value
|
|
254
|
+
# => - current keys map to the next value(s)
|
|
255
|
+
raise SyntaxError, "no keys before =>" if current_keys.empty?
|
|
256
|
+
values, end_token = parse_until([:hash_collection_value_end, :hash_collection_end, :parse_end])
|
|
257
|
+
key_value_pairs << [current_keys, values]
|
|
258
|
+
current_keys = []
|
|
259
|
+
finished = end_token != :hash_collection_value_end
|
|
260
|
+
when :hash_collection_end, :parse_end
|
|
261
|
+
# End of hash
|
|
262
|
+
finished = true
|
|
263
|
+
when :whitespace
|
|
264
|
+
# Ignore whitespace
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
[finished, HashCollectionType.new(name, key_value_pairs)]
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
private
|
|
272
|
+
|
|
273
|
+
def create_type(name)
|
|
274
|
+
if name[0, 1] == ":" || (name[0, 1] =~ /['"]/ && name[-1, 1] =~ /['"]/)
|
|
275
|
+
LiteralType.new(name)
|
|
276
|
+
elsif name[0, 1] == "#"
|
|
277
|
+
DuckType.new(name)
|
|
278
|
+
else
|
|
279
|
+
Type.new(name)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
|
|
285
|
+
def create_type(name)
|
|
286
|
+
if name[0, 1] == ":" || (name[0, 1] =~ /['"]/ && name[-1, 1] =~ /['"]/)
|
|
287
|
+
LiteralType.new(name)
|
|
288
|
+
elsif name[0, 1] == "#"
|
|
289
|
+
DuckType.new(name)
|
|
290
|
+
else
|
|
291
|
+
Type.new(name)
|
|
292
|
+
end
|
|
159
293
|
end
|
|
160
294
|
end
|
|
161
295
|
end
|