Class: YARD::Parser::RBS::RbsParser
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
::YARD::Parser::Base
|
|
|
Instance Chain:
self,
::YARD::Parser::Base
|
|
| Inherits: |
YARD::Parser::Base
|
| Defined in: | lib/yard/parser/rbs/rbs_parser.rb |
Overview
Parses RBS (Ruby type signature) files and produces a list of
Statement objects for post-processing by handlers.
::YARD::Parser::RBS is Ruby's official type signature format (introduced in ::YARD::Parser::Ruby 3.0).
This parser handles: class/module/interface declarations, method
signatures, attribute accessors, mixins, and constants.
No external gem dependencies are used; the parser is hand-written.
Class Method Summary
- .new(source, filename) ⇒ RbsParser constructor
::YARD::Parser::Base - Inherited
Instance Method Summary
- #enumerator ⇒ Array<Statement>
-
#parse ⇒ RbsParser
Parses the source and returns self.
-
#tokenize
Tokenization is not implemented for
::YARD::Parser::RBS. - #parse_attr(type, lines, i, docs, crange) private
-
#parse_body(lines, start, stop_at_end) ⇒ Array(Array<Statement>, Integer)
private
Parse a sequence of lines, returning statements and the index after the last consumed line.
- #parse_method_def(sanitized, lines, i, docs, crange) private
- #parse_namespace(type, lines, i, docs, crange) private
-
#parse_statement(lines, i, comments, comment_start_1)
private
Dispatch a single declaration line.
- #sanitized_statement_lines(lines, start_index) private
- #strip_inline_comment(line) private
::YARD::Parser::Base - Inherited
| #enumerator | This method should be implemented to return a list of semantic tokens representing the source code to be post-processed. |
| #parse | This method should be implemented to parse the source and return itself. |
| #tokenize | This method should be implemented to tokenize given source. |
Constructor Details
.new(source, filename) ⇒ RbsParser
# File 'lib/yard/parser/rbs/rbs_parser.rb', line 16
def initialize(source, filename) @source = source @filename = filename @statements = nil end
Instance Method Details
#enumerator ⇒ Array<Statement>
# File 'lib/yard/parser/rbs/rbs_parser.rb', line 36
def enumerator @statements end
#parse ⇒ RbsParser
Parses the source and returns self.
# File 'lib/yard/parser/rbs/rbs_parser.rb', line 24
def parse lines = @source.lines.map { |l| l.chomp } @statements, = parse_body(lines, 0, false) self end
#parse_attr(type, lines, i, docs, crange) (private)
[ GitHub ]# File 'lib/yard/parser/rbs/rbs_parser.rb', line 296
def parse_attr(type, lines, i, docs, crange) stripped = strip_inline_comment(lines[i]).strip line_num = i + 1 keyword = type.to_s # attr_reader [self.] name : Type if stripped =~ /\A#{Regexp.escape(keyword)}\s+(self\.)?(\w+)\s*:\s*(.*)\z/ is_class = !$1.nil? attr_name = $2 attr_type = $3.strip stmt = Statement.new( :type => type, :name => attr_name, :attr_rbs_type => attr_type, :line => line_num, :source => stripped, :comments => docs, :comments_range => crange, :visibility => is_class ? :class : :instance ) [stmt, i + 1] else [nil, i + 1] end end
#parse_body(lines, start, stop_at_end) ⇒ Array(Array<Statement>, Integer) (private)
Parse a sequence of lines, returning statements and the index after the last consumed line.
# File 'lib/yard/parser/rbs/rbs_parser.rb', line 48
def parse_body(lines, start, stop_at_end) statements = [] i = start pending_comments = [] pending_start_1 = nil # 1-indexed line number of first pending comment while i < lines.length raw = lines[i] stripped = raw.strip if stripped =~ /\A#(.*)/ # Comment line – accumulate into pending docstring. # Strip at most one leading space (conventional RBS doc style). pending_comments << $1.sub(/\A /, '') pending_start_1 ||= i + 1 i += 1 elsif stripped.empty? # Blank line resets pending comments. pending_comments = [] pending_start_1 = nil i += 1 elsif stop_at_end && stripped == 'end' # End of enclosing block. return [statements, i + 1] else stmt, i = parse_statement(lines, i, pending_comments, pending_start_1) statements << stmt if stmt pending_comments = [] pending_start_1 = nil end end [statements, i] end
#parse_method_def(sanitized, lines, i, docs, crange) (private)
[ GitHub ]# File 'lib/yard/parser/rbs/rbs_parser.rb', line 253
def parse_method_def(sanitized, lines, i, docs, crange) stripped = sanitized.fetch(i, lines[i]).strip line_num = i + 1 # def method_name: overload1 # | overload2 # Also handles: def self.method_name: ... unless stripped =~ /\Adef\s+(self\.)?(\S+?)\s*:\s*(.*)\z/ return [nil, i + 1] end is_class_side = !$1.nil? meth_name = $2 first_sig = $3.strip sigs = [first_sig] j = i + 1 # Collect `| overload` continuation lines. while j < lines.length cont = sanitized.fetch(j, lines[j]).strip if cont =~ /\A\|\s*(.*)\z/ sigs << $1.strip j += 1 else break end end stmt = Statement.new( :type => :method_def, :name => meth_name, :line => line_num, :source => lines[i...j].join("\n"), :comments => docs, :comments_range => crange, :signatures => sigs, :visibility => is_class_side ? :class : :instance ) [stmt, j] end
#parse_namespace(type, lines, i, docs, crange) (private)
[ GitHub ]# File 'lib/yard/parser/rbs/rbs_parser.rb', line 199
def parse_namespace(type, lines, i, docs, crange) # Strip trailing inline comment from the declaration line. decl = lines[i].strip.sub(/\s*#.*\z/, '') line_num = i + 1 name = nil superclass = nil case type when :class # class Foo[T] < Bar[String] if decl =~ /\Aclass\s+([^\s<\[]+)(\[[^\]]*\])?(?:\s*<\s*(.+))?\z/ name = $1.strip superclass = $3 ? $3.strip : nil # Strip generic params from superclass, e.g. "Array[String]" -> "Array" superclass.sub!(/\[.*\]\z/, '') if superclass else return [nil, i + 1] end when :module # module Foo[T] : SelfType if decl =~ /\Amodule\s+([^\s\[(:]+)/ name = $1.strip else return [nil, i + 1] end when :interface # interface _Foo[T] if decl =~ /\Ainterface\s+([^\s\[]+)/ name = $1.strip else return [nil, i + 1] end end children, new_i = parse_body(lines, i + 1, true) source = lines[i...new_i].join("\n") stmt = Statement.new( :type => type, :name => name, :superclass => superclass, :line => line_num, :source => source, :comments => docs, :comments_range => crange, :block => children ) [stmt, new_i] end
#parse_statement(lines, i, comments, comment_start_1) (private)
Dispatch a single declaration line.
# File 'lib/yard/parser/rbs/rbs_parser.rb', line 125
def parse_statement(lines, i, comments, comment_start_1) sanitized = sanitized_statement_lines(lines, i) stripped = sanitized.fetch(i, lines[i]).strip line_num = i + 1 # 1-indexed docs = comments.empty? ? nil : comments.join("\n") crange = comment_start_1 ? (comment_start_1)..(line_num - 1) : nil case stripped when /\Aclass\s/ parse_namespace(:class, lines, i, docs, crange) when /\Amodule\s/ parse_namespace(:module, lines, i, docs, crange) when /\Ainterface\s/ parse_namespace(:interface, lines, i, docs, crange) when /\Adef\s/ parse_method_def(sanitized, lines, i, docs, crange) when /\Aattr_reader\s/ parse_attr(:attr_reader, lines, i, docs, crange) when /\Aattr_writer\s/ parse_attr(:attr_writer, lines, i, docs, crange) when /\Aattr_accessor\s/ parse_attr(:attr_accessor, lines, i, docs, crange) when /\A(include|extend|prepend)\s+(\S+)/ kind = $1.to_sym name = $2.delete(';') stmt = Statement.new( :type => kind, :name => name, :mixin_name => name, :line => line_num, :source => stripped, :comments => docs, :comments_range => crange ) [stmt, i + 1] when /\Aalias\s+(\S+)\s+(\S+)/ stmt = Statement.new( :type => :alias, :name => $1, :line => line_num, :source => stripped, :comments => docs, :comments_range => crange ) [stmt, i + 1] when /\A(public|private|protected)\s*(\z|#)/ # Visibility modifier – skip silently. [nil, i + 1] when /\Aend\s*(\z|#)/ # Stray `end` – skip. [nil, i + 1] when /\Atype\s/ # Type alias declaration – nothing to document. [nil, i + 1] else # Constant declaration: `NAME: Type` if stripped =~ /\A([A-Z][a-zA-Z0-9_]*(?:::[A-Z][a-zA-Z0-9_]*)*)\s*:\s*(.+)\z/ stmt = Statement.new( :type => :constant, :name => $1, :attr_rbs_type => $2.strip, :line => line_num, :source => stripped, :comments => docs, :comments_range => crange ) [stmt, i + 1] else [nil, i + 1] end end end
#sanitized_statement_lines(lines, start_index) (private)
[ GitHub ]# File 'lib/yard/parser/rbs/rbs_parser.rb', line 112
def sanitized_statement_lines(lines, start_index) overrides = { start_index => strip_inline_comment(lines[start_index]) } j = start_index + 1 while j < lines.length && lines[j].lstrip.start_with?('|') overrides[j] = strip_inline_comment(lines[j]) j += 1 end overrides end
#strip_inline_comment(line) (private)
[ GitHub ]# File 'lib/yard/parser/rbs/rbs_parser.rb', line 86
def strip_inline_comment(line) in_single = false in_double = false escaped = false line.each_char.with_index do |char, index| if escaped escaped = false next end case char when "\\" escaped = true if in_single || in_double when "'" in_single = !in_single unless in_double when '"' in_double = !in_double unless in_single when '#' return line[0...index].rstrip unless in_single || in_double end end line.rstrip end
#tokenize
Tokenization is not implemented for ::YARD::Parser::RBS.
# File 'lib/yard/parser/rbs/rbs_parser.rb', line 31
def tokenize raise NotImplementedError, "RBS parser does not support tokenization" end