Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 91 additions & 77 deletions lib/rbi/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ def parse(source, file:)
end

class Visitor < Prism::Visitor
#: (String source, file: String) -> void
def initialize(source, file:)
#: (String source, file: String, ?comments_by_line: Hash[Integer, Prism::Comment]?) -> void
def initialize(source, file:, comments_by_line: nil)
super()

@source = source
@file = file
@comments_by_line = comments_by_line || {} #: Hash[Integer, Prism::Comment]
end

private
Expand Down Expand Up @@ -159,6 +160,76 @@ def self?(node)
def t_sig_without_runtime?(node)
!!(node.is_a?(Prism::ConstantPathNode) && node_string(node) =~ /(::)?T::Sig::WithoutRuntime/)
end

#: (Prism::Node node, ?min_line: Integer?) -> Array[Comment]
def node_comments(node, min_line: nil)
comments = []

start_line = node.location.start_line
start_line -= 1 unless @comments_by_line.key?(start_line)
start_line = [start_line, min_line].max if min_line

rbs_continuation = [] #: Array[Prism::Comment]

start_line.downto(1) do |line|
comment = @comments_by_line[line]
break unless comment

text = comment.location.slice

# If we find a RBS comment continuation `#|`, we store it until we find the start with `#:`
if text.start_with?("#|")
rbs_continuation << comment
@comments_by_line.delete(line)
next
end

loc = Loc.from_prism(@file, comment.location)

# If we find the start of a RBS comment, we create a new RBSComment
# Note that we ignore RDoc directives such as `:nodoc:`
# See https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Directives
if text.start_with?("#:") && !(text =~ /^#:[a-z_]+:/)
text = text.sub(/^#: ?/, "").rstrip

# If we found continuation comments, we merge them in reverse order (since we go from bottom to top)
rbs_continuation.reverse_each do |rbs_comment|
continuation_text = rbs_comment.location.slice.sub(/^#\| ?/, "").strip
continuation_loc = Loc.from_prism(@file, rbs_comment.location)
loc = loc.join(continuation_loc)
text = "#{text}#{continuation_text}"
end

rbs_continuation.clear
comments.unshift(RBSComment.new(text, loc: loc))
else
# If we have unused continuation comments, we should inject them back to not lose them
rbs_continuation.each do |rbs_comment|
comments.unshift(parse_comment(rbs_comment))
end

rbs_continuation.clear
comments.unshift(parse_comment(comment))
end

@comments_by_line.delete(line)
end

# If we have unused continuation comments, we should inject them back to not lose them
rbs_continuation.each do |rbs_comment|
comments.unshift(parse_comment(rbs_comment))
end
rbs_continuation.clear

comments
end

#: (Prism::Comment node) -> Comment
def parse_comment(node)
text = node.location.slice.sub(/^# ?/, "").rstrip
loc = Loc.from_prism(@file, node.location)
Comment.new(text, loc: loc)
end
end

class TreeBuilder < Visitor
Expand All @@ -170,9 +241,12 @@ class TreeBuilder < Visitor

#: (String source, comments: Array[Prism::Comment], file: String) -> void
def initialize(source, comments:, file:)
super(source, file: file)
super(
source,
comments_by_line: comments.to_h { |c| [c.location.start_line, c] },
file: file,
)

@comments_by_line = comments.to_h { |c| [c.location.start_line, c] } #: Hash[Integer, Prism::Comment]
@tree = Tree.new #: Tree

@scopes_stack = [@tree] #: Array[Tree]
Expand Down Expand Up @@ -600,75 +674,6 @@ def detach_comments_from_sigs(sigs)
comments
end

#: (Prism::Node node) -> Array[Comment]
def node_comments(node)
comments = []

start_line = node.location.start_line
start_line -= 1 unless @comments_by_line.key?(start_line)

rbs_continuation = [] #: Array[Prism::Comment]

start_line.downto(1) do |line|
comment = @comments_by_line[line]
break unless comment

text = comment.location.slice

# If we find a RBS comment continuation `#|`, we store it until we find the start with `#:`
if text.start_with?("#|")
rbs_continuation << comment
@comments_by_line.delete(line)
next
end

loc = Loc.from_prism(@file, comment.location)

# If we find the start of a RBS comment, we create a new RBSComment
# Note that we ignore RDoc directives such as `:nodoc:`
# See https://ruby.github.io/rdoc/RDoc/MarkupReference.html#class-RDoc::MarkupReference-label-Directives
if text.start_with?("#:") && !(text =~ /^#:[a-z_]+:/)
text = text.sub(/^#: ?/, "").rstrip

# If we found continuation comments, we merge them in reverse order (since we go from bottom to top)
rbs_continuation.reverse_each do |rbs_comment|
continuation_text = rbs_comment.location.slice.sub(/^#\| ?/, "").strip
continuation_loc = Loc.from_prism(@file, rbs_comment.location)
loc = loc.join(continuation_loc)
text = "#{text}#{continuation_text}"
end

rbs_continuation.clear
comments.unshift(RBSComment.new(text, loc: loc))
else
# If we have unused continuation comments, we should inject them back to not lose them
rbs_continuation.each do |rbs_comment|
comments.unshift(parse_comment(rbs_comment))
end

rbs_continuation.clear
comments.unshift(parse_comment(comment))
end

@comments_by_line.delete(line)
end

# If we have unused continuation comments, we should inject them back to not lose them
rbs_continuation.each do |rbs_comment|
comments.unshift(parse_comment(rbs_comment))
end
rbs_continuation.clear

comments
end

#: (Prism::Comment node) -> Comment
def parse_comment(node)
text = node.location.slice.sub(/^# ?/, "").rstrip
loc = Loc.from_prism(@file, node.location)
Comment.new(text, loc: loc)
end

#: (Prism::Node? node) -> Array[Arg]
def parse_send_args(node)
args = [] #: Array[Arg]
Expand Down Expand Up @@ -763,7 +768,7 @@ def parse_params(node)

#: (Prism::CallNode node) -> Sig
def parse_sig(node)
builder = SigBuilder.new(@source, file: @file)
builder = SigBuilder.new(@source, comments_by_line: @comments_by_line, file: @file)
builder.current.loc = node_loc(node)
builder.visit_call_node(node)
builder.current.comments = node_comments(node)
Expand Down Expand Up @@ -916,11 +921,16 @@ class SigBuilder < Visitor
#: Sig
attr_reader :current

#: (String content, file: String) -> void
def initialize(content, file:)
super
# Bounds sig param comment lookup to comments inside the current `params(...)` call.
#: Integer?
attr_reader :params_start_line

#: (String content, comments_by_line: Hash[Integer, Prism::Comment], file: String) -> void
def initialize(content, comments_by_line:, file:)
super(content, comments_by_line: comments_by_line, file: file)

@current = Sig.new #: Sig
@params_start_line = nil #: Integer?
end

# @override
Expand Down Expand Up @@ -951,7 +961,9 @@ def visit_call_node(node)
when "overridable"
@current.is_overridable = true
when "params"
@params_start_line = node.location.start_line
visit(node.arguments)
@params_start_line = nil
when "returns"
args = node.arguments
if args.is_a?(Prism::ArgumentsNode)
Expand Down Expand Up @@ -979,6 +991,8 @@ def visit_assoc_node(node)
@current.params << SigParam.new(
node_string!(node.key).delete_suffix(":"),
node_string!(node.value),
loc: node_loc(node),
comments: node_comments(node, min_line: params_start_line),
)
end

Expand Down
Loading
Loading