123456789_123456789_123456789_123456789_123456789_

Class: Prism::ParseResult::Comments

Relationships & Source Files
Namespace Children
Classes:
Inherits: Object
Defined in: lib/prism/parse_result/comments.rb

Overview

When we’ve parsed the source, we have both the syntax tree and the list of comments that we found in the source. This class is responsible for walking the tree and finding the nearest location to attach each comment.

It does this by first finding the nearest locations to each comment. Locations can either come from nodes directly or from location fields on nodes. For example, a ClassNode has an overall location encompassing the entire class, but it also has a location for the class keyword.

Once the nearest locations are found, it determines which one to attach to. If it’s a trailing comment (a comment on the same line as other source code), it will favor attaching to the nearest location that occurs before the comment. Otherwise it will favor attaching to the nearest location that is after the comment.

Class Method Summary

Instance Attribute Summary

  • #parse_result readonly

    The parse result that we are attaching comments to.

Instance Method Summary

  • #attach!

    Attach the comments to their respective locations in the tree by mutating the parse result.

  • #nearest_targets(node, comment) private

    Responsible for finding the nearest targets to the given comment within the context of the given encapsulating node.

Constructor Details

.new(parse_result) ⇒ Comments

Create a new Comments object that will attach comments to the given parse result.

[ GitHub ]

  
# File 'lib/prism/parse_result/comments.rb', line 78

def initialize(parse_result)
  @parse_result = parse_result
end

Instance Attribute Details

#parse_result (readonly)

The parse result that we are attaching comments to.

[ GitHub ]

  
# File 'lib/prism/parse_result/comments.rb', line 74

attr_reader :parse_result

Instance Method Details

#attach!

Attach the comments to their respective locations in the tree by mutating the parse result.

[ GitHub ]

  
# File 'lib/prism/parse_result/comments.rb', line 84

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

#nearest_targets(node, comment) (private)

Responsible for finding the nearest targets to the given comment within the context of the given encapsulating node.

[ GitHub ]

  
# File 'lib/prism/parse_result/comments.rb', line 103

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