class JsDuck::Js::Associator

Associates comments with syntax nodes.

Constants

NODE_TYPES

All possible node types in Esprima-created abstract syntax tree

Each node type maps to list of properties of that node into which we can recurse for further parsing.

Public Class Methods

new(input) click to toggle source
# File lib/jsduck/js/associator.rb, line 9
def initialize(input)
  @input = input

  # Initialize line number counting
  @start_index = 0
  @start_linenr = 1
end

Public Instance Methods

associate(ast) click to toggle source

Analyzes the comments and AST nodes and returns array of hashes like this:

{
    :comment => "The contents of the comment",
    :code => {...AST data structure for code following the comment...},
    :linenr => 12,  // Beginning with 1
    :type => :doc_comment, // or :plain_comment
}
# File lib/jsduck/js/associator.rb, line 27
def associate(ast)
  @ast = ast

  @ast["comments"] = merge_comments(@ast["comments"])
  locate_comments
end

Private Instance Methods

child_nodes(node) click to toggle source

Returns array of child nodes of given node

# File lib/jsduck/js/associator.rb, line 156
def child_nodes(node)
  properties = NODE_TYPES[node["type"]]

  unless properties
    Logger.fatal("Unknown node type: "+node["type"])
    exit(1)
  end

  # First flatten, then get rid of all nils, because the inner
  # arrays may also contain nils.
  properties.map {|p| node[p] }.flatten.compact
end
code_after(range, parent) click to toggle source

Looks for code following the given range.

The second argument is the parent node within which we perform our search.

# File lib/jsduck/js/associator.rb, line 121
def code_after(range, parent)
  # Look through all child nodes of parent...
  child_nodes(parent).each do |node|
    if less(range, node["range"])
      # If node is after our range, then that's it.  There could
      # be comments in our way, but that's taken care of in
      # #stuff_after method.
      return node
    elsif within(range, node["range"])
      # Our range is within the node --> recurse
      return code_after(range, node)
    end
  end

  return nil
end
greater(a, b) click to toggle source

True if range A is greater than range B

# File lib/jsduck/js/associator.rb, line 145
def greater(a, b)
  return a[0] >= b[1]
end
less(a, b) click to toggle source

True if range A is less than range B

# File lib/jsduck/js/associator.rb, line 140
def less(a, b)
  return a[1] <= b[0]
end
line_number(index) click to toggle source

Given index inside input string, returns the corresponding line number

# File lib/jsduck/js/associator.rb, line 96
def line_number(index)
  # To speed things up, remember the index until which we counted,
  # then next time just begin counting from there.  This way we
  # only count each line once.
  @start_linenr = @input[@start_index...index].count("\n") + @start_linenr
  @start_index = index
  return @start_linenr
end
locate_comments() click to toggle source
# File lib/jsduck/js/associator.rb, line 75
def locate_comments
  @ast["comments"].map do |comment|
    # Detect comment type and strip * at the beginning of doc-comment
    value = comment["value"]
    if comment["type"] == "Block" && value =~ /\A\*/
      type = :doc_comment
      value = value.slice(1, value.length-1)
    else
      type = :plain_comment
    end

    {
      :comment => value,
      :code => stuff_after(comment),
      :linenr => line_number(comment["range"][0]),
      :type => type,
    }
  end
end
merge_comments(original_comments) click to toggle source

Merges consecutive line-comments and Establishes links between comments, so we can easily use comment to get to the next comment.

# File lib/jsduck/js/associator.rb, line 39
def merge_comments(original_comments)
  result = []

  comment = original_comments[0]
  i = 0

  while comment
    i += 1
    next_comment = original_comments[i]

    if next_comment && mergeable?(comment, next_comment)
      # Merge next comment to current one
      comment["value"] += "\n" + next_comment["value"]
      comment["range"][1] = next_comment["range"][1]
    else
      # Create a link and continue with next comment
      comment["next"] = next_comment
      result << comment
      comment = next_comment
    end
  end

  result
end
mergeable?(c1, c2) click to toggle source

Two comments can be merged if they are both line-comments and they are separated only by whitespace (only one newline at the end of the first comment is allowed)

# File lib/jsduck/js/associator.rb, line 67
def mergeable?(c1, c2)
  if c1["type"] == "Line" && c2["type"] == "Line"
    /\A(\r\n|\n|\r)?[ \t]*\z/ =~ @input.slice((c1["range"][1])..(c2["range"][0]-1))
  else
    false
  end
end
stuff_after(comment) click to toggle source

Sees if there is some code following the comment. Returns the code found. But if the comment is instead followed by another comment, returns nil.

# File lib/jsduck/js/associator.rb, line 108
def stuff_after(comment)
  code = code_after(comment["range"], @ast)
  if code && comment["next"]
    return code["range"][0] < comment["next"]["range"][0] ? code : nil
  else
    code
  end
end
within(a, b) click to toggle source

True if range A is within range B

# File lib/jsduck/js/associator.rb, line 150
def within(a, b)
  return b[0] <= a[0] && a[1] <= b[1]
end