class Prism::ParseResult::Comments

当我们解析完源代码时,我们既有语法树,也有源代码中找到的注释列表。这个类的职责是遍历语法树,找到每个注释要附加的最近位置。

它通过首先找到每个注释的最近位置来实现这一点。位置可以直接来自节点,也可以来自节点上的位置字段。例如,一个 ‘ClassNode` 拥有一个包含整个类的总体位置,但它也拥有 `class` 关键字的位置。

一旦找到最近的位置,它会确定要附加到哪个位置。如果它是一个尾随注释(与其它源代码在同一行的注释),它将倾向于附加到注释之前出现的最近位置。否则,它将倾向于附加到注释之后的最近位置。

属性

parse_result[R]

我们要将注释附加到的解析结果。

公共类方法

new(parse_result) 点击切换源代码

创建一个新的 Comments 对象,该对象会将注释附加到给定的解析结果。

# File prism/parse_result/comments.rb, line 86
def initialize(parse_result)
  @parse_result = parse_result
end

公共实例方法

attach!() 点击切换源代码

通过改变解析结果,将注释附加到树中各自的位置。

# File prism/parse_result/comments.rb, line 92
def attach!
  parse_result.comments.each do |comment|
    preceding, enclosing, following = nearest_targets(parse_result.value, comment)

    if comment.trailing?
      if preceding
        preceding.trailing_comment(comment)
      else
        (following || enclosing || NodeTarget.new(parse_result.value)).leading_comment(comment)
      end
    else
      # If a comment exists on its own line, prefer a leading comment.
      if following
        following.leading_comment(comment)
      elsif preceding
        preceding.trailing_comment(comment)
      else
        (enclosing || NodeTarget.new(parse_result.value)).leading_comment(comment)
      end
    end
  end
end

私有实例方法

nearest_targets(node, comment) 点击切换源代码

负责在给定封装节点的上下文中查找给定注释的最近目标。

# File prism/parse_result/comments.rb, line 119
def nearest_targets(node, comment)
  comment_start = comment.location.start_offset
  comment_end = comment.location.end_offset

  targets = [] #: Array[_Target]
  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 #: _Target?
  following = nil #: _Target?

  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)
      # @type var target: NodeTarget
      # 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