yard 0.9.36 → 0.9.40
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 +59 -1
- data/README.md +29 -25
- data/docs/GettingStarted.md +41 -15
- data/docs/Tags.md +5 -5
- data/docs/Templates.md +5 -4
- data/docs/WhatsNew.md +59 -7
- data/docs/templates/default/yard_tags/html/setup.rb +1 -1
- data/lib/yard/autoload.rb +18 -0
- data/lib/yard/cli/diff.rb +7 -2
- data/lib/yard/code_objects/base.rb +1 -1
- data/lib/yard/code_objects/extra_file_object.rb +1 -0
- data/lib/yard/code_objects/macro_object.rb +0 -1
- data/lib/yard/code_objects/proxy.rb +1 -1
- data/lib/yard/docstring_parser.rb +0 -1
- data/lib/yard/handlers/base.rb +23 -1
- data/lib/yard/handlers/processor.rb +1 -1
- data/lib/yard/handlers/rbs/attribute_handler.rb +43 -0
- data/lib/yard/handlers/rbs/base.rb +38 -0
- data/lib/yard/handlers/rbs/constant_handler.rb +18 -0
- data/lib/yard/handlers/rbs/method_handler.rb +327 -0
- data/lib/yard/handlers/rbs/mixin_handler.rb +20 -0
- data/lib/yard/handlers/rbs/namespace_handler.rb +26 -0
- data/lib/yard/handlers/ruby/attribute_handler.rb +7 -4
- data/lib/yard/handlers/ruby/constant_handler.rb +24 -6
- data/lib/yard/handlers/ruby/legacy/visibility_handler.rb +2 -1
- data/lib/yard/handlers/ruby/visibility_handler.rb +14 -1
- data/lib/yard/i18n/locale.rb +1 -1
- data/lib/yard/i18n/pot_generator.rb +1 -1
- data/lib/yard/logging.rb +116 -61
- data/lib/yard/open_struct.rb +67 -0
- data/lib/yard/parser/rbs/rbs_parser.rb +325 -0
- data/lib/yard/parser/rbs/statement.rb +75 -0
- data/lib/yard/parser/ruby/ast_node.rb +5 -4
- data/lib/yard/parser/ruby/legacy/irb/slex.rb +19 -1
- data/lib/yard/parser/ruby/legacy/ruby_lex.rb +20 -5
- data/lib/yard/parser/ruby/ruby_parser.rb +109 -24
- data/lib/yard/parser/source_parser.rb +5 -4
- data/lib/yard/registry_resolver.rb +7 -0
- data/lib/yard/rubygems/specification.rb +1 -1
- data/lib/yard/server/library_version.rb +1 -1
- data/lib/yard/server/templates/default/fulldoc/html/js/autocomplete.js +208 -12
- data/lib/yard/server/templates/default/layout/html/breadcrumb.erb +1 -17
- data/lib/yard/server/templates/default/method_details/html/permalink.erb +4 -2
- data/lib/yard/server/templates/doc_server/library_list/html/headers.erb +3 -3
- data/lib/yard/server/templates/doc_server/library_list/html/library_list.erb +2 -3
- data/lib/yard/server/templates/doc_server/processing/html/processing.erb +22 -16
- data/lib/yard/tags/default_factory.rb +1 -0
- data/lib/yard/tags/directives.rb +7 -1
- data/lib/yard/tags/library.rb +3 -3
- data/lib/yard/tags/overload_tag.rb +2 -1
- data/lib/yard/tags/tag.rb +2 -1
- data/lib/yard/tags/types_explainer.rb +5 -4
- data/lib/yard/templates/engine.rb +0 -1
- data/lib/yard/templates/helpers/base_helper.rb +1 -1
- data/lib/yard/templates/helpers/html_helper.rb +21 -6
- data/lib/yard/templates/helpers/html_syntax_highlight_helper.rb +6 -1
- data/lib/yard/templates/helpers/markup/hybrid_markdown.rb +2125 -0
- data/lib/yard/templates/helpers/markup/rdoc_markup.rb +2 -0
- data/lib/yard/templates/helpers/markup_helper.rb +4 -2
- data/lib/yard/templates/template_options.rb +0 -1
- data/lib/yard/version.rb +1 -1
- data/po/ja.po +82 -82
- data/templates/default/fulldoc/html/css/full_list.css +3 -3
- data/templates/default/fulldoc/html/css/style.css +8 -15
- data/templates/default/fulldoc/html/full_list.erb +8 -5
- data/templates/default/fulldoc/html/js/app.js +546 -281
- data/templates/default/fulldoc/html/js/full_list.js +315 -189
- data/templates/default/fulldoc/html/setup.rb +10 -2
- data/templates/default/layout/html/headers.erb +1 -1
- data/templates/default/method/html/header.erb +3 -3
- data/templates/default/module/html/defines.erb +3 -3
- data/templates/default/module/html/inherited_methods.erb +1 -0
- data/templates/default/module/html/method_summary.erb +8 -0
- data/templates/default/module/setup.rb +20 -0
- data/templates/default/onefile/html/headers.erb +2 -0
- data/templates/default/onefile/html/layout.erb +3 -4
- data/templates/default/tags/html/example.erb +2 -2
- data/templates/guide/fulldoc/html/js/app.js +57 -26
- data/templates/guide/layout/html/layout.erb +9 -11
- metadata +19 -8
data/lib/yard/logging.rb
CHANGED
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
|
-
require 'logger'
|
|
4
3
|
require 'thread'
|
|
5
4
|
|
|
6
5
|
module YARD
|
|
7
6
|
# Handles console logging for info, warnings and errors.
|
|
8
7
|
# Uses the stdlib Logger class in Ruby for all the backend logic.
|
|
9
|
-
class Logger
|
|
8
|
+
class Logger
|
|
9
|
+
# Log severity levels
|
|
10
|
+
module Severity
|
|
11
|
+
# Debugging log level
|
|
12
|
+
DEBUG = 0
|
|
13
|
+
|
|
14
|
+
# Information log level
|
|
15
|
+
INFO = 1
|
|
16
|
+
|
|
17
|
+
# Warning log level
|
|
18
|
+
WARN = 2
|
|
19
|
+
|
|
20
|
+
# Error log level
|
|
21
|
+
ERROR = 3
|
|
22
|
+
|
|
23
|
+
# Fatal log level
|
|
24
|
+
FATAL = 4
|
|
25
|
+
|
|
26
|
+
# Unknown log level
|
|
27
|
+
UNKNOWN = 5
|
|
28
|
+
|
|
29
|
+
# @private
|
|
30
|
+
SEVERITIES = {
|
|
31
|
+
DEBUG => :debug,
|
|
32
|
+
INFO => :info,
|
|
33
|
+
WARN => :warn,
|
|
34
|
+
ERROR => :error,
|
|
35
|
+
FATAL => :fatal,
|
|
36
|
+
UNKNOWN => :unknown
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
include Severity
|
|
41
|
+
|
|
10
42
|
# The list of characters displayed beside the progress bar to indicate
|
|
11
43
|
# "movement".
|
|
12
44
|
# @since 0.8.2
|
|
@@ -14,25 +46,31 @@ module YARD
|
|
|
14
46
|
|
|
15
47
|
# @return [IO] the IO object being logged to
|
|
16
48
|
# @since 0.8.2
|
|
17
|
-
|
|
18
|
-
def io=(pipe) @logdev = pipe end
|
|
49
|
+
attr_accessor :io
|
|
19
50
|
|
|
20
51
|
# @return [Boolean] whether backtraces should be shown (by default
|
|
21
52
|
# this is on).
|
|
22
53
|
def show_backtraces; @show_backtraces || level == DEBUG end
|
|
23
54
|
attr_writer :show_backtraces
|
|
24
55
|
|
|
56
|
+
# @return [DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN] the logging level
|
|
57
|
+
attr_accessor :level
|
|
58
|
+
|
|
59
|
+
# @return [Boolean] whether a warn message has been emitted. Used for status tracking.
|
|
60
|
+
attr_accessor :warned
|
|
61
|
+
|
|
25
62
|
# @return [Boolean] whether progress indicators should be shown when
|
|
26
63
|
# logging CLIs (by default this is off).
|
|
27
64
|
def show_progress
|
|
28
65
|
return false if YARD.ruby18? # threading is too ineffective for progress support
|
|
29
|
-
return false if YARD.windows? # windows has poor ANSI support
|
|
30
66
|
return false unless io.tty? # no TTY support on IO
|
|
31
67
|
return false unless level > INFO # no progress in verbose/debug modes
|
|
32
68
|
@show_progress
|
|
33
69
|
end
|
|
34
70
|
attr_writer :show_progress
|
|
35
71
|
|
|
72
|
+
# @!group Constructor Methods
|
|
73
|
+
|
|
36
74
|
# The logger instance
|
|
37
75
|
# @return [Logger] the logger instance
|
|
38
76
|
def self.instance(pipe = STDOUT)
|
|
@@ -40,13 +78,12 @@ module YARD
|
|
|
40
78
|
end
|
|
41
79
|
|
|
42
80
|
# Creates a new logger
|
|
81
|
+
# @private
|
|
43
82
|
def initialize(pipe, *args)
|
|
44
|
-
super(pipe, *args)
|
|
45
83
|
self.io = pipe
|
|
46
84
|
self.show_backtraces = true
|
|
47
85
|
self.show_progress = false
|
|
48
86
|
self.level = WARN
|
|
49
|
-
self.formatter = method(:format_log)
|
|
50
87
|
self.warned = false
|
|
51
88
|
@progress_indicator = 0
|
|
52
89
|
@mutex = Mutex.new
|
|
@@ -54,36 +91,64 @@ module YARD
|
|
|
54
91
|
@progress_last_update = Time.now
|
|
55
92
|
end
|
|
56
93
|
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
94
|
+
# @!macro [attach] logger.create_log_method
|
|
95
|
+
# @method $1(message)
|
|
96
|
+
# Logs a message with the $1 severity level.
|
|
97
|
+
# @param message [String] the message to log
|
|
98
|
+
# @see #log
|
|
99
|
+
# @return [void]
|
|
100
|
+
# @private
|
|
101
|
+
def self.create_log_method(name)
|
|
102
|
+
severity = Severity.const_get(name.to_s.upcase)
|
|
103
|
+
define_method(name) { |message| log(severity, message) }
|
|
62
104
|
end
|
|
63
105
|
|
|
106
|
+
# @!group Logging Methods
|
|
107
|
+
|
|
108
|
+
create_log_method :info
|
|
109
|
+
create_log_method :error
|
|
110
|
+
create_log_method :fatal
|
|
111
|
+
create_log_method :unknown
|
|
112
|
+
|
|
113
|
+
# Changes the debug level to DEBUG if $DEBUG is set and writes a debugging message.
|
|
114
|
+
create_log_method :debug
|
|
115
|
+
|
|
64
116
|
# Remembers when a warning occurs and writes a warning message.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
117
|
+
create_log_method :warn
|
|
118
|
+
|
|
119
|
+
# Logs a message with a given severity
|
|
120
|
+
# @param severity [DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN] the severity level
|
|
121
|
+
# @param message [String] the message to log
|
|
122
|
+
def log(severity, message)
|
|
123
|
+
self.level = DEBUG if $DEBUG
|
|
124
|
+
return unless severity >= level
|
|
125
|
+
|
|
126
|
+
self.warned = true if severity == WARN
|
|
127
|
+
clear_line
|
|
128
|
+
puts "[#{SEVERITIES[severity].to_s.downcase}]: #{message}"
|
|
68
129
|
end
|
|
69
|
-
attr_accessor :warned
|
|
70
130
|
|
|
71
|
-
#
|
|
72
|
-
|
|
131
|
+
# @!group Level Control Methods
|
|
132
|
+
|
|
133
|
+
# Sets the logger level for the duration of the block
|
|
73
134
|
#
|
|
74
|
-
# @
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
# @
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
135
|
+
# @example
|
|
136
|
+
# log.enter_level(Logger::ERROR) do
|
|
137
|
+
# YARD.parse_string "def x; end"
|
|
138
|
+
# end
|
|
139
|
+
# @param [Fixnum] new_level the logger level for the duration of the block.
|
|
140
|
+
# values can be found in Ruby's Logger class.
|
|
141
|
+
# @yield the block with the logger temporarily set to +new_level+
|
|
142
|
+
def enter_level(new_level = level)
|
|
143
|
+
old_level = level
|
|
144
|
+
self.level = new_level
|
|
82
145
|
yield
|
|
83
146
|
ensure
|
|
84
|
-
|
|
147
|
+
self.level = old_level
|
|
85
148
|
end
|
|
86
149
|
|
|
150
|
+
# @!group Utility Printing Methods
|
|
151
|
+
|
|
87
152
|
# Displays a progress indicator for a given message. This progress report
|
|
88
153
|
# is only displayed on TTY displays, otherwise the message is passed to
|
|
89
154
|
# the +nontty_log+ level.
|
|
@@ -120,7 +185,7 @@ module YARD
|
|
|
120
185
|
# @since 0.8.2
|
|
121
186
|
def clear_progress
|
|
122
187
|
return unless show_progress
|
|
123
|
-
|
|
188
|
+
io.write("\e[?25h\e[2K")
|
|
124
189
|
@progress_msg = nil
|
|
125
190
|
end
|
|
126
191
|
|
|
@@ -133,16 +198,13 @@ module YARD
|
|
|
133
198
|
print("#{msg}\n")
|
|
134
199
|
end
|
|
135
200
|
|
|
136
|
-
alias print_no_newline <<
|
|
137
|
-
private :print_no_newline
|
|
138
|
-
|
|
139
201
|
# Displays an unformatted line to the logger output stream.
|
|
140
202
|
# @param [String] msg the message to display
|
|
141
203
|
# @return [void]
|
|
142
204
|
# @since 0.8.2
|
|
143
205
|
def print(msg = '')
|
|
144
206
|
clear_line
|
|
145
|
-
|
|
207
|
+
io.write(msg)
|
|
146
208
|
end
|
|
147
209
|
alias << print
|
|
148
210
|
|
|
@@ -158,48 +220,41 @@ module YARD
|
|
|
158
220
|
exc.backtrace[0..5].map {|x| "\n\t#{x}" }.join + "\n")
|
|
159
221
|
end
|
|
160
222
|
|
|
223
|
+
# @!group Benchmarking Methods
|
|
224
|
+
|
|
225
|
+
# Captures the duration of a block of code for benchmark analysis. Also
|
|
226
|
+
# calls {#progress} on the message to display it to the user.
|
|
227
|
+
#
|
|
228
|
+
# @todo Implement capture storage for reporting of benchmarks
|
|
229
|
+
# @param [String] msg the message to display
|
|
230
|
+
# @param [Symbol, nil] nontty_log the level to log as if the output
|
|
231
|
+
# stream is not a TTY. Use +nil+ for no alternate logging.
|
|
232
|
+
# @yield a block of arbitrary code to benchmark
|
|
233
|
+
# @return [void]
|
|
234
|
+
def capture(msg, nontty_log = :debug)
|
|
235
|
+
progress(msg, nontty_log)
|
|
236
|
+
yield
|
|
237
|
+
ensure
|
|
238
|
+
clear_progress
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# @!endgroup
|
|
242
|
+
|
|
161
243
|
# Warns that the Ruby environment does not support continuations. Applies
|
|
162
244
|
# to JRuby, Rubinius and MacRuby. This warning will only display once
|
|
163
245
|
# per Ruby process.
|
|
164
246
|
#
|
|
165
247
|
# @deprecated Continuations are no longer needed by YARD 0.8.0+.
|
|
166
248
|
# @return [void]
|
|
249
|
+
# @private
|
|
167
250
|
def warn_no_continuations
|
|
168
251
|
end
|
|
169
252
|
|
|
170
|
-
# Sets the logger level for the duration of the block
|
|
171
|
-
#
|
|
172
|
-
# @example
|
|
173
|
-
# log.enter_level(Logger::ERROR) do
|
|
174
|
-
# YARD.parse_string "def x; end"
|
|
175
|
-
# end
|
|
176
|
-
# @param [Fixnum] new_level the logger level for the duration of the block.
|
|
177
|
-
# values can be found in Ruby's Logger class.
|
|
178
|
-
# @yield the block with the logger temporarily set to +new_level+
|
|
179
|
-
def enter_level(new_level = level)
|
|
180
|
-
old_level = level
|
|
181
|
-
self.level = new_level
|
|
182
|
-
yield
|
|
183
|
-
ensure
|
|
184
|
-
self.level = old_level
|
|
185
|
-
end
|
|
186
|
-
|
|
187
253
|
private
|
|
188
254
|
|
|
189
|
-
# Override this internal Logger method to clear line
|
|
190
|
-
def add(*args)
|
|
191
|
-
clear_line
|
|
192
|
-
super(*args)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
255
|
def clear_line
|
|
196
256
|
return unless @progress_msg
|
|
197
|
-
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Log format (from Logger implementation). Used by Logger internally
|
|
201
|
-
def format_log(sev, _time, _prog, msg)
|
|
202
|
-
"[#{sev.downcase}]: #{msg}\n"
|
|
257
|
+
io.write("\e[2K\r")
|
|
203
258
|
end
|
|
204
259
|
end
|
|
205
260
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module YARD
|
|
2
|
+
# An OpenStruct compatible struct class that allows for basic access of attributes
|
|
3
|
+
# via +struct.attr_name+ and +struct.attr_name = value+.
|
|
4
|
+
class OpenStruct
|
|
5
|
+
def initialize(hash = {})
|
|
6
|
+
@table = hash.each_pair { |k, v| [k.to_sym, v] }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# @private
|
|
10
|
+
def method_missing(name, *args)
|
|
11
|
+
if name.to_s.end_with?('=')
|
|
12
|
+
varname = name.to_s[0..-2].to_sym
|
|
13
|
+
__cache_lookup__(varname)
|
|
14
|
+
send(name, args.first)
|
|
15
|
+
else
|
|
16
|
+
__cache_lookup__(name)
|
|
17
|
+
send(name)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_h
|
|
22
|
+
@table.dup
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ==(other)
|
|
26
|
+
other.is_a?(self.class) && to_h == other.to_h
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def hash
|
|
30
|
+
@table.hash
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dig(*keys)
|
|
34
|
+
@table.dig(*keys)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def []=(key, value)
|
|
38
|
+
@table[key.to_sym] = value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def [](key)
|
|
42
|
+
@table[key.to_sym]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def each_pair(&block)
|
|
46
|
+
@table.each_pair(&block)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def marshal_dump
|
|
50
|
+
@table
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def marshal_load(data)
|
|
54
|
+
@table = data
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def __cache_lookup__(name)
|
|
60
|
+
key = name.to_sym.inspect
|
|
61
|
+
instance_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
62
|
+
def #{name}; @table[#{key}]; end
|
|
63
|
+
def #{name.to_s.sub('?','_')}=(v); @table[#{key}] = v; end unless #{key}.to_s.include?('?')
|
|
64
|
+
RUBY
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module YARD
|
|
3
|
+
module Parser
|
|
4
|
+
module RBS
|
|
5
|
+
# Parses RBS (Ruby type signature) files and produces a list of
|
|
6
|
+
# {Statement} objects for post-processing by handlers.
|
|
7
|
+
#
|
|
8
|
+
# RBS is Ruby's official type signature format (introduced in Ruby 3.0).
|
|
9
|
+
# This parser handles: class/module/interface declarations, method
|
|
10
|
+
# signatures, attribute accessors, mixins, and constants.
|
|
11
|
+
#
|
|
12
|
+
# No external gem dependencies are used; the parser is hand-written.
|
|
13
|
+
class RbsParser < YARD::Parser::Base
|
|
14
|
+
# @param source [String] source code to parse
|
|
15
|
+
# @param filename [String] path to the source file
|
|
16
|
+
def initialize(source, filename)
|
|
17
|
+
@source = source
|
|
18
|
+
@filename = filename
|
|
19
|
+
@statements = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Parses the source and returns self.
|
|
23
|
+
# @return [RbsParser] self
|
|
24
|
+
def parse
|
|
25
|
+
lines = @source.lines.map { |l| l.chomp }
|
|
26
|
+
@statements, = parse_body(lines, 0, false)
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Tokenization is not implemented for RBS.
|
|
31
|
+
def tokenize
|
|
32
|
+
raise NotImplementedError, "RBS parser does not support tokenization"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Array<Statement>] top-level statements for the post-processor
|
|
36
|
+
def enumerator
|
|
37
|
+
@statements
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Parse a sequence of lines, returning statements and the index after the last consumed line.
|
|
43
|
+
#
|
|
44
|
+
# @param lines [Array<String>] source lines
|
|
45
|
+
# @param start [Integer] index to start from (0-based)
|
|
46
|
+
# @param stop_at_end [Boolean] when true, stop parsing when we see a bare `end`
|
|
47
|
+
# @return [Array(Array<Statement>, Integer)] [statements, new_index]
|
|
48
|
+
def parse_body(lines, start, stop_at_end)
|
|
49
|
+
statements = []
|
|
50
|
+
i = start
|
|
51
|
+
pending_comments = []
|
|
52
|
+
pending_start_1 = nil # 1-indexed line number of first pending comment
|
|
53
|
+
|
|
54
|
+
while i < lines.length
|
|
55
|
+
raw = lines[i]
|
|
56
|
+
stripped = raw.strip
|
|
57
|
+
|
|
58
|
+
if stripped =~ /\A#(.*)/
|
|
59
|
+
# Comment line – accumulate into pending docstring.
|
|
60
|
+
# Strip at most one leading space (conventional RBS doc style).
|
|
61
|
+
pending_comments << $1.sub(/\A /, '')
|
|
62
|
+
pending_start_1 ||= i + 1
|
|
63
|
+
i += 1
|
|
64
|
+
|
|
65
|
+
elsif stripped.empty?
|
|
66
|
+
# Blank line resets pending comments.
|
|
67
|
+
pending_comments = []
|
|
68
|
+
pending_start_1 = nil
|
|
69
|
+
i += 1
|
|
70
|
+
|
|
71
|
+
elsif stop_at_end && stripped == 'end'
|
|
72
|
+
# End of enclosing block.
|
|
73
|
+
return [statements, i + 1]
|
|
74
|
+
|
|
75
|
+
else
|
|
76
|
+
stmt, i = parse_statement(lines, i, pending_comments, pending_start_1)
|
|
77
|
+
statements << stmt if stmt
|
|
78
|
+
pending_comments = []
|
|
79
|
+
pending_start_1 = nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
[statements, i]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def strip_inline_comment(line)
|
|
87
|
+
in_single = false
|
|
88
|
+
in_double = false
|
|
89
|
+
escaped = false
|
|
90
|
+
|
|
91
|
+
line.each_char.with_index do |char, index|
|
|
92
|
+
if escaped
|
|
93
|
+
escaped = false
|
|
94
|
+
next
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
case char
|
|
98
|
+
when "\\"
|
|
99
|
+
escaped = true if in_single || in_double
|
|
100
|
+
when "'"
|
|
101
|
+
in_single = !in_single unless in_double
|
|
102
|
+
when '"'
|
|
103
|
+
in_double = !in_double unless in_single
|
|
104
|
+
when '#'
|
|
105
|
+
return line[0...index].rstrip unless in_single || in_double
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
line.rstrip
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def sanitized_statement_lines(lines, start_index)
|
|
113
|
+
overrides = { start_index => strip_inline_comment(lines[start_index]) }
|
|
114
|
+
|
|
115
|
+
j = start_index + 1
|
|
116
|
+
while j < lines.length && lines[j].lstrip.start_with?('|')
|
|
117
|
+
overrides[j] = strip_inline_comment(lines[j])
|
|
118
|
+
j += 1
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
overrides
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Dispatch a single declaration line.
|
|
125
|
+
def parse_statement(lines, i, comments, comment_start_1)
|
|
126
|
+
sanitized = sanitized_statement_lines(lines, i)
|
|
127
|
+
stripped = sanitized.fetch(i, lines[i]).strip
|
|
128
|
+
line_num = i + 1 # 1-indexed
|
|
129
|
+
|
|
130
|
+
docs = comments.empty? ? nil : comments.join("\n")
|
|
131
|
+
crange = comment_start_1 ? (comment_start_1)..(line_num - 1) : nil
|
|
132
|
+
|
|
133
|
+
case stripped
|
|
134
|
+
when /\Aclass\s/
|
|
135
|
+
parse_namespace(:class, lines, i, docs, crange)
|
|
136
|
+
when /\Amodule\s/
|
|
137
|
+
parse_namespace(:module, lines, i, docs, crange)
|
|
138
|
+
when /\Ainterface\s/
|
|
139
|
+
parse_namespace(:interface, lines, i, docs, crange)
|
|
140
|
+
when /\Adef\s/
|
|
141
|
+
parse_method_def(sanitized, lines, i, docs, crange)
|
|
142
|
+
when /\Aattr_reader\s/
|
|
143
|
+
parse_attr(:attr_reader, lines, i, docs, crange)
|
|
144
|
+
when /\Aattr_writer\s/
|
|
145
|
+
parse_attr(:attr_writer, lines, i, docs, crange)
|
|
146
|
+
when /\Aattr_accessor\s/
|
|
147
|
+
parse_attr(:attr_accessor, lines, i, docs, crange)
|
|
148
|
+
when /\A(include|extend|prepend)\s+(\S+)/
|
|
149
|
+
kind = $1.to_sym
|
|
150
|
+
name = $2.delete(';')
|
|
151
|
+
stmt = Statement.new(
|
|
152
|
+
:type => kind,
|
|
153
|
+
:name => name,
|
|
154
|
+
:mixin_name => name,
|
|
155
|
+
:line => line_num,
|
|
156
|
+
:source => stripped,
|
|
157
|
+
:comments => docs,
|
|
158
|
+
:comments_range => crange
|
|
159
|
+
)
|
|
160
|
+
[stmt, i + 1]
|
|
161
|
+
when /\Aalias\s+(\S+)\s+(\S+)/
|
|
162
|
+
stmt = Statement.new(
|
|
163
|
+
:type => :alias,
|
|
164
|
+
:name => $1,
|
|
165
|
+
:line => line_num,
|
|
166
|
+
:source => stripped,
|
|
167
|
+
:comments => docs,
|
|
168
|
+
:comments_range => crange
|
|
169
|
+
)
|
|
170
|
+
[stmt, i + 1]
|
|
171
|
+
when /\A(public|private|protected)\s*(\z|#)/
|
|
172
|
+
# Visibility modifier – skip silently.
|
|
173
|
+
[nil, i + 1]
|
|
174
|
+
when /\Aend\s*(\z|#)/
|
|
175
|
+
# Stray `end` – skip.
|
|
176
|
+
[nil, i + 1]
|
|
177
|
+
when /\Atype\s/
|
|
178
|
+
# Type alias declaration – nothing to document.
|
|
179
|
+
[nil, i + 1]
|
|
180
|
+
else
|
|
181
|
+
# Constant declaration: `NAME: Type`
|
|
182
|
+
if stripped =~ /\A([A-Z][a-zA-Z0-9_]*(?:::[A-Z][a-zA-Z0-9_]*)*)\s*:\s*(.+)\z/
|
|
183
|
+
stmt = Statement.new(
|
|
184
|
+
:type => :constant,
|
|
185
|
+
:name => $1,
|
|
186
|
+
:attr_rbs_type => $2.strip,
|
|
187
|
+
:line => line_num,
|
|
188
|
+
:source => stripped,
|
|
189
|
+
:comments => docs,
|
|
190
|
+
:comments_range => crange
|
|
191
|
+
)
|
|
192
|
+
[stmt, i + 1]
|
|
193
|
+
else
|
|
194
|
+
[nil, i + 1]
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def parse_namespace(type, lines, i, docs, crange)
|
|
200
|
+
# Strip trailing inline comment from the declaration line.
|
|
201
|
+
decl = lines[i].strip.sub(/\s*#.*\z/, '')
|
|
202
|
+
line_num = i + 1
|
|
203
|
+
|
|
204
|
+
name = nil
|
|
205
|
+
superclass = nil
|
|
206
|
+
|
|
207
|
+
case type
|
|
208
|
+
when :class
|
|
209
|
+
# class Foo[T] < Bar[String]
|
|
210
|
+
if decl =~ /\Aclass\s+([^\s<\[]+)(\[[^\]]*\])?(?:\s*<\s*(.+))?\z/
|
|
211
|
+
name = $1.strip
|
|
212
|
+
superclass = $3 ? $3.strip : nil
|
|
213
|
+
# Strip generic params from superclass, e.g. "Array[String]" -> "Array"
|
|
214
|
+
superclass.sub!(/\[.*\]\z/, '') if superclass
|
|
215
|
+
else
|
|
216
|
+
return [nil, i + 1]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
when :module
|
|
220
|
+
# module Foo[T] : SelfType
|
|
221
|
+
if decl =~ /\Amodule\s+([^\s\[(:]+)/
|
|
222
|
+
name = $1.strip
|
|
223
|
+
else
|
|
224
|
+
return [nil, i + 1]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
when :interface
|
|
228
|
+
# interface _Foo[T]
|
|
229
|
+
if decl =~ /\Ainterface\s+([^\s\[]+)/
|
|
230
|
+
name = $1.strip
|
|
231
|
+
else
|
|
232
|
+
return [nil, i + 1]
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
children, new_i = parse_body(lines, i + 1, true)
|
|
237
|
+
source = lines[i...new_i].join("\n")
|
|
238
|
+
|
|
239
|
+
stmt = Statement.new(
|
|
240
|
+
:type => type,
|
|
241
|
+
:name => name,
|
|
242
|
+
:superclass => superclass,
|
|
243
|
+
:line => line_num,
|
|
244
|
+
:source => source,
|
|
245
|
+
:comments => docs,
|
|
246
|
+
:comments_range => crange,
|
|
247
|
+
:block => children
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
[stmt, new_i]
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def parse_method_def(sanitized, lines, i, docs, crange)
|
|
254
|
+
stripped = sanitized.fetch(i, lines[i]).strip
|
|
255
|
+
line_num = i + 1
|
|
256
|
+
|
|
257
|
+
# def method_name: overload1
|
|
258
|
+
# | overload2
|
|
259
|
+
# Also handles: def self.method_name: ...
|
|
260
|
+
unless stripped =~ /\Adef\s+(self\.)?(\S+?)\s*:\s*(.*)\z/
|
|
261
|
+
return [nil, i + 1]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
is_class_side = !$1.nil?
|
|
265
|
+
meth_name = $2
|
|
266
|
+
first_sig = $3.strip
|
|
267
|
+
|
|
268
|
+
sigs = [first_sig]
|
|
269
|
+
j = i + 1
|
|
270
|
+
|
|
271
|
+
# Collect `| overload` continuation lines.
|
|
272
|
+
while j < lines.length
|
|
273
|
+
cont = sanitized.fetch(j, lines[j]).strip
|
|
274
|
+
if cont =~ /\A\|\s*(.*)\z/
|
|
275
|
+
sigs << $1.strip
|
|
276
|
+
j += 1
|
|
277
|
+
else
|
|
278
|
+
break
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
stmt = Statement.new(
|
|
283
|
+
:type => :method_def,
|
|
284
|
+
:name => meth_name,
|
|
285
|
+
:line => line_num,
|
|
286
|
+
:source => lines[i...j].join("\n"),
|
|
287
|
+
:comments => docs,
|
|
288
|
+
:comments_range => crange,
|
|
289
|
+
:signatures => sigs,
|
|
290
|
+
:visibility => is_class_side ? :class : :instance
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
[stmt, j]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def parse_attr(type, lines, i, docs, crange)
|
|
297
|
+
stripped = strip_inline_comment(lines[i]).strip
|
|
298
|
+
line_num = i + 1
|
|
299
|
+
keyword = type.to_s
|
|
300
|
+
|
|
301
|
+
# attr_reader [self.] name : Type
|
|
302
|
+
if stripped =~ /\A#{Regexp.escape(keyword)}\s+(self\.)?(\w+)\s*:\s*(.*)\z/
|
|
303
|
+
is_class = !$1.nil?
|
|
304
|
+
attr_name = $2
|
|
305
|
+
attr_type = $3.strip
|
|
306
|
+
|
|
307
|
+
stmt = Statement.new(
|
|
308
|
+
:type => type,
|
|
309
|
+
:name => attr_name,
|
|
310
|
+
:attr_rbs_type => attr_type,
|
|
311
|
+
:line => line_num,
|
|
312
|
+
:source => stripped,
|
|
313
|
+
:comments => docs,
|
|
314
|
+
:comments_range => crange,
|
|
315
|
+
:visibility => is_class ? :class : :instance
|
|
316
|
+
)
|
|
317
|
+
[stmt, i + 1]
|
|
318
|
+
else
|
|
319
|
+
[nil, i + 1]
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|