class Parser::Source::Comment::Associator
A processor which associates AST
nodes with comments based on their location in source code. It may be used, for example, to implement rdoc-style processing.
@example
require 'parser/current' ast, comments = Parser::CurrentRuby.parse_with_comments(<<-CODE) # Class stuff class Foo # Attr stuff # @see bar attr_accessor :foo end CODE p Parser::Source::Comment.associate(ast, comments) # => { # (class (const nil :Foo) ...) => # [#<Parser::Source::Comment (string):1:1 "# Class stuff">], # (send nil :attr_accessor (sym :foo)) => # [#<Parser::Source::Comment (string):3:3 "# Attr stuff">, # #<Parser::Source::Comment (string):4:3 "# @see bar">] # }
@see {associate}
@!attribute skip_directives
Skip file processing directives disguised as comments. Namely: * Shebang line, * Magic encoding comment. @return [Boolean]
@api public
Attributes
Public Class Methods
@param [Parser::AST::Node] ast @param [Array(Parser::Source::Comment
)] comments
# File lib/parser/source/comment/associator.rb, line 49 def initialize(ast, comments) @ast = ast @comments = comments @skip_directives = true end
Public Instance Methods
Compute a mapping between AST
nodes and comments. Comment
is associated with the node, if it is one of the following types:
-
preceding comment, it ends before the node start
-
sparse comment, it is located inside the node, after all child nodes
-
decorating comment, it starts at the same line, where the node ends
This rule is unambiguous and produces the result one could reasonably expect; for example, this code
# foo hoge # bar + fuga
will result in the following association:
{ (send (lvar :hoge) :+ (lvar :fuga)) => [#<Parser::Source::Comment (string):2:1 "# foo">], (lvar :fuga) => [#<Parser::Source::Comment (string):3:8 "# bar">] }
Note that comments after the end of the end of a passed tree range are ignored (except root decorating comment).
Note that {associate} produces unexpected result for nodes which are equal but have distinct locations; comments for these nodes are merged.
@return [Hash(Parser::AST::Node
, Array(Parser::Source::Comment
))] @deprecated Use {associate_locations}.
# File lib/parser/source/comment/associator.rb, line 89 def associate @map_using_locations = false do_associate end
Same as {associate}, but uses `node.loc` instead of `node` as the hash key, thus producing an unambiguous result even in presence of equal nodes.
@return [Hash(Parser::Source::Map
, Array(Parser::Source::Comment
))]
# File lib/parser/source/comment/associator.rb, line 101 def associate_locations @map_using_locations = true do_associate end
Private Instance Methods
# File lib/parser/source/comment/associator.rb, line 157 def advance_comment @comment_num += 1 @current_comment = @comments[@comment_num] end
# File lib/parser/source/comment/associator.rb, line 187 def advance_through_directives # Skip shebang. if @current_comment && @current_comment.text =~ /^#!/ advance_comment end # Skip encoding line. if @current_comment && @current_comment.text =~ Buffer::ENCODING_RE advance_comment end end
# File lib/parser/source/comment/associator.rb, line 181 def associate_and_advance_comment(node) key = @map_using_locations ? node.location : node @mapping[key] << @current_comment advance_comment end
# File lib/parser/source/comment/associator.rb, line 162 def current_comment_before?(node) return false if !@current_comment comment_loc = @current_comment.location.expression node_loc = node.location.expression comment_loc.end_pos <= node_loc.begin_pos end
# File lib/parser/source/comment/associator.rb, line 169 def current_comment_before_end?(node) return false if !@current_comment comment_loc = @current_comment.location.expression node_loc = node.location.expression comment_loc.end_pos <= node_loc.end_pos end
# File lib/parser/source/comment/associator.rb, line 176 def current_comment_decorates?(node) return false if !@current_comment @current_comment.location.line == node.location.last_line end
# File lib/parser/source/comment/associator.rb, line 108 def do_associate @mapping = Hash.new { |h, k| h[k] = [] } @comment_num = -1 advance_comment advance_through_directives if @skip_directives visit(@ast) if @ast @mapping end
# File lib/parser/source/comment/associator.rb, line 141 def process_leading_comments(node) return if node.type == :begin while current_comment_before?(node) # preceding comment associate_and_advance_comment(node) end end
# File lib/parser/source/comment/associator.rb, line 148 def process_trailing_comments(node) while current_comment_before_end?(node) associate_and_advance_comment(node) # sparse comment end while current_comment_decorates?(node) associate_and_advance_comment(node) # decorating comment end end
# File lib/parser/source/comment/associator.rb, line 120 def visit(node) process_leading_comments(node) return unless @current_comment # If the next comment is beyond the last line of this node, we don't # need to iterate over its subnodes # (Unless this node is a heredoc... there could be a comment in its body, # inside an interpolation) node_loc = node.location if @current_comment.location.line <= node_loc.last_line || node_loc.is_a?(Map::Heredoc) node.children.each do |child| next unless child.is_a?(AST::Node) && child.loc && child.loc.expression visit(child) end process_trailing_comments(node) end end