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
# 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
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
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
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
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
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
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
# 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
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
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
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
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