class Prism::ParseResult::Comments
that is after the comment.
the comment. Otherwise it will favor attaching to the nearest location
code), it will favor attaching to the nearest location that occurs before
to. If it’s a trailing comment (a comment on the same line as other source
Once the nearest locations are found, it determines which one to attach
entire class, but it also has a location for the ‘class` keyword.
nodes. For example, a `ClassNode` has an overall location encompassing the
Locations can either come from nodes directly or from location fields on
It does this by first finding the nearest locations to each comment.
walking the tree and finding the nearest location to attach each comment.
comments that we found in the source. This class is responsible for
When we’ve parsed the source, we have both the syntax tree and the list of
def attach!
Attach the comments to their respective locations in the tree by
def attach! parse_result.comments.each do |comment| preceding, enclosing, following = nearest_targets(parse_result.value, comment) target = if comment.trailing? preceding || following || enclosing || NodeTarget.new(parse_result.value) else # If a comment exists on its own line, prefer a leading comment. following || preceding || enclosing || NodeTarget.new(parse_result.value) end target << comment end end
def initialize(parse_result)
Create a new Comments object that will attach comments to the given
def initialize(parse_result) @parse_result = parse_result end
def nearest_targets(node, comment)
Responsible for finding the nearest targets to the given comment within
def nearest_targets(node, comment) comment_start = comment.location.start_offset comment_end = comment.location.end_offset targets = [] node.comment_targets.map do |value| case value when StatementsNode targets.concat(value.body.map { |node| NodeTarget.new(node) }) when Node targets << NodeTarget.new(value) when Location targets << LocationTarget.new(value) end end targets.sort_by!(&:start_offset) preceding = nil following = nil left = 0 right = targets.length # This is a custom binary search that finds the nearest nodes to the # given comment. When it finds a node that completely encapsulates the # comment, it recurses downward into the tree. while left < right middle = (left + right) / 2 target = targets[middle] target_start = target.start_offset target_end = target.end_offset if target.encloses?(comment) # The comment is completely contained by this target. Abandon the # binary search at this level. return nearest_targets(target.node, comment) end if target_end <= comment_start # This target falls completely before the comment. Because we will # never consider this target or any targets before it again, this # target must be the closest preceding target we have encountered so # far. preceding = target left = middle + 1 next end if comment_end <= target_start # This target falls completely after the comment. Because we will # never consider this target or any targets after it again, this # target must be the closest following target we have encountered so # far. following = target right = middle next end # This should only happen if there is a bug in this parser. raise "Comment location overlaps with a target location" end [preceding, NodeTarget.new(node), following] end