yard 0.9.28 → 0.9.43

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.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +139 -1
  3. data/LEGAL +29 -1
  4. data/README.md +29 -25
  5. data/docs/GettingStarted.md +41 -15
  6. data/docs/Parser.md +17 -42
  7. data/docs/Tags.md +6 -6
  8. data/docs/Templates.md +5 -4
  9. data/docs/WhatsNew.md +61 -9
  10. data/docs/templates/default/yard_tags/html/setup.rb +1 -1
  11. data/lib/yard/autoload.rb +20 -1
  12. data/lib/yard/cli/command.rb +1 -1
  13. data/lib/yard/cli/diff.rb +7 -2
  14. data/lib/yard/cli/yardoc.rb +1 -1
  15. data/lib/yard/code_objects/base.rb +6 -2
  16. data/lib/yard/code_objects/extra_file_object.rb +1 -0
  17. data/lib/yard/code_objects/macro_object.rb +0 -1
  18. data/lib/yard/code_objects/proxy.rb +1 -1
  19. data/lib/yard/docstring_parser.rb +1 -2
  20. data/lib/yard/handlers/base.rb +23 -1
  21. data/lib/yard/handlers/processor.rb +1 -1
  22. data/lib/yard/handlers/rbs/attribute_handler.rb +79 -0
  23. data/lib/yard/handlers/rbs/base.rb +38 -0
  24. data/lib/yard/handlers/rbs/constant_handler.rb +18 -0
  25. data/lib/yard/handlers/rbs/method_handler.rb +327 -0
  26. data/lib/yard/handlers/rbs/mixin_handler.rb +20 -0
  27. data/lib/yard/handlers/rbs/namespace_handler.rb +26 -0
  28. data/lib/yard/handlers/ruby/attribute_handler.rb +7 -4
  29. data/lib/yard/handlers/ruby/constant_handler.rb +24 -6
  30. data/lib/yard/handlers/ruby/legacy/attribute_handler.rb +1 -1
  31. data/lib/yard/handlers/ruby/legacy/visibility_handler.rb +2 -1
  32. data/lib/yard/handlers/ruby/mixin_handler.rb +13 -6
  33. data/lib/yard/handlers/ruby/visibility_handler.rb +14 -1
  34. data/lib/yard/i18n/locale.rb +2 -2
  35. data/lib/yard/i18n/message.rb +2 -2
  36. data/lib/yard/i18n/messages.rb +1 -1
  37. data/lib/yard/i18n/pot_generator.rb +2 -2
  38. data/lib/yard/logging.rb +116 -61
  39. data/lib/yard/open_struct.rb +67 -0
  40. data/lib/yard/options.rb +1 -1
  41. data/lib/yard/parser/rbs/rbs_parser.rb +325 -0
  42. data/lib/yard/parser/rbs/statement.rb +75 -0
  43. data/lib/yard/parser/ruby/ast_node.rb +5 -4
  44. data/lib/yard/parser/ruby/legacy/irb/slex.rb +19 -1
  45. data/lib/yard/parser/ruby/legacy/ruby_lex.rb +20 -5
  46. data/lib/yard/parser/ruby/ruby_parser.rb +117 -26
  47. data/lib/yard/parser/source_parser.rb +7 -7
  48. data/lib/yard/registry_resolver.rb +9 -1
  49. data/lib/yard/rubygems/specification.rb +1 -1
  50. data/lib/yard/server/commands/base.rb +2 -2
  51. data/lib/yard/server/commands/library_command.rb +8 -8
  52. data/lib/yard/server/commands/static_file_helpers.rb +1 -2
  53. data/lib/yard/server/http_utils.rb +512 -0
  54. data/lib/yard/server/library_version.rb +1 -1
  55. data/lib/yard/server/rack_adapter.rb +13 -5
  56. data/lib/yard/server/templates/default/fulldoc/html/css/custom.css +168 -88
  57. data/lib/yard/server/templates/default/fulldoc/html/js/autocomplete.js +203 -12
  58. data/lib/yard/server/templates/default/layout/html/breadcrumb.erb +1 -17
  59. data/lib/yard/server/templates/default/method_details/html/permalink.erb +4 -2
  60. data/lib/yard/server/templates/doc_server/library_list/html/headers.erb +3 -3
  61. data/lib/yard/server/templates/doc_server/library_list/html/library_list.erb +2 -3
  62. data/lib/yard/server/templates/doc_server/processing/html/processing.erb +22 -16
  63. data/lib/yard/tags/default_factory.rb +1 -0
  64. data/lib/yard/tags/directives.rb +7 -1
  65. data/lib/yard/tags/library.rb +3 -3
  66. data/lib/yard/tags/overload_tag.rb +2 -1
  67. data/lib/yard/tags/tag.rb +4 -3
  68. data/lib/yard/tags/types_explainer.rb +6 -5
  69. data/lib/yard/templates/engine.rb +0 -1
  70. data/lib/yard/templates/helpers/base_helper.rb +1 -1
  71. data/lib/yard/templates/helpers/html_helper.rb +21 -6
  72. data/lib/yard/templates/helpers/html_syntax_highlight_helper.rb +6 -1
  73. data/lib/yard/templates/helpers/markup/hybrid_markdown.rb +2147 -0
  74. data/lib/yard/templates/helpers/markup/rdoc_markup.rb +2 -0
  75. data/lib/yard/templates/helpers/markup_helper.rb +4 -2
  76. data/lib/yard/templates/template_options.rb +0 -1
  77. data/lib/yard/version.rb +1 -1
  78. data/po/ja.po +101 -101
  79. data/templates/default/fulldoc/html/css/common.css +1 -1
  80. data/templates/default/fulldoc/html/css/full_list.css +201 -53
  81. data/templates/default/fulldoc/html/css/style.css +991 -399
  82. data/templates/default/fulldoc/html/frames.erb +9 -4
  83. data/templates/default/fulldoc/html/full_list.erb +8 -5
  84. data/templates/default/fulldoc/html/js/app.js +799 -312
  85. data/templates/default/fulldoc/html/js/full_list.js +332 -214
  86. data/templates/default/fulldoc/html/setup.rb +10 -2
  87. data/templates/default/layout/html/headers.erb +1 -1
  88. data/templates/default/layout/html/layout.erb +3 -1
  89. data/templates/default/method/html/header.erb +3 -3
  90. data/templates/default/module/html/defines.erb +3 -3
  91. data/templates/default/module/html/inherited_methods.erb +1 -0
  92. data/templates/default/module/html/method_summary.erb +8 -0
  93. data/templates/default/module/setup.rb +20 -0
  94. data/templates/default/onefile/html/headers.erb +2 -0
  95. data/templates/default/onefile/html/layout.erb +3 -4
  96. data/templates/default/tags/html/example.erb +2 -2
  97. data/templates/default/tags/html/option.erb +1 -1
  98. data/templates/guide/fulldoc/html/css/style.css +347 -97
  99. data/templates/guide/fulldoc/html/js/app.js +61 -33
  100. data/templates/guide/layout/html/layout.erb +69 -72
  101. metadata +21 -60
  102. data/.dockerignore +0 -2
  103. data/.gitattributes +0 -4
  104. data/.github/FUNDING.yml +0 -3
  105. data/.github/ISSUE_TEMPLATE.md +0 -33
  106. data/.github/PULL_REQUEST_TEMPLATE.md +0 -12
  107. data/.github/workflows/ci.yml +0 -30
  108. data/.github/workflows/gem.yml +0 -19
  109. data/.gitignore +0 -14
  110. data/.rspec +0 -2
  111. data/.rubocop.yml +0 -112
  112. data/CODE_OF_CONDUCT.md +0 -15
  113. data/CONTRIBUTING.md +0 -140
  114. data/Dockerfile.samus +0 -28
  115. data/Gemfile +0 -34
  116. data/Rakefile +0 -36
  117. data/SECURITY.md +0 -26
  118. data/benchmarks/builtins_vs_eval.rb +0 -24
  119. data/benchmarks/concat_vs_join.rb +0 -13
  120. data/benchmarks/erb_vs_erubis.rb +0 -54
  121. data/benchmarks/format_args.rb +0 -47
  122. data/benchmarks/generation.rb +0 -38
  123. data/benchmarks/marshal_vs_dbm.rb +0 -64
  124. data/benchmarks/parsing.rb +0 -46
  125. data/benchmarks/pathname_vs_string.rb +0 -51
  126. data/benchmarks/rdoc_vs_yardoc.rb +0 -11
  127. data/benchmarks/registry_store_types.rb +0 -49
  128. data/benchmarks/ri_vs_yri.rb +0 -19
  129. data/benchmarks/ripper_parser.rb +0 -13
  130. data/benchmarks/splat_vs_flatten.rb +0 -13
  131. data/benchmarks/template_erb.rb +0 -23
  132. data/benchmarks/template_format.rb +0 -7
  133. data/benchmarks/template_profile.rb +0 -18
  134. data/benchmarks/yri_cache.rb +0 -20
  135. data/samus.json +0 -49
  136. data/tasks/prepare_tag.rake +0 -45
  137. data/tasks/update_error_map.rake +0 -53
  138. data/yard.gemspec +0 -25
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 < ::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
- def io; @logdev end
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
- # Changes the debug level to DEBUG if $DEBUG is set
58
- # and writes a debugging message.
59
- def debug(*args)
60
- self.level = DEBUG if $DEBUG
61
- super
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
- def warn(*args)
66
- self.warned = true
67
- super
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
- # Captures the duration of a block of code for benchmark analysis. Also
72
- # calls {#progress} on the message to display it to the user.
131
+ # @!group Level Control Methods
132
+
133
+ # Sets the logger level for the duration of the block
73
134
  #
74
- # @todo Implement capture storage for reporting of benchmarks
75
- # @param [String] msg the message to display
76
- # @param [Symbol, nil] nontty_log the level to log as if the output
77
- # stream is not a TTY. Use +nil+ for no alternate logging.
78
- # @yield a block of arbitrary code to benchmark
79
- # @return [void]
80
- def capture(msg, nontty_log = :debug)
81
- progress(msg, nontty_log)
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
- clear_progress
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
- print_no_newline("\e[?25h\e[2K")
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
- print_no_newline(msg)
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
- print_no_newline("\e[2K\r")
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
data/lib/yard/options.rb CHANGED
@@ -94,7 +94,7 @@ module YARD
94
94
  #
95
95
  # @example Setting an option with Hash syntax
96
96
  # options[:format] = :html # equivalent to: options.format = :html
97
- # @param [Symbol, String] key the optin to set
97
+ # @param [Symbol, String] key the option to set
98
98
  # @param [Object] value the value to set for the option
99
99
  # @return [Object] the value being set
100
100
  def []=(key, value) send("#{key}=", value) 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