class Solargraph::Source
A Ruby file that has been parsed into an AST.
Constants
- FOLDING_NODE_TYPES
Attributes
@return [String]
@return [Hash{Integer => Array<String>}]
@return [Array<Range>]
@return [String]
@return [Source::Updater]
@return [Parser::AST::Node]
@return [Boolean]
@return [String]
@return [Boolean]
@todo Deprecate? @return [Integer]
Public Class Methods
@param code [String] @param filename [String] @param version [Integer]
# File lib/solargraph/source.rb, line 37 def initialize code, filename = nil, version = 0 @code = normalize(code) @repaired = code @filename = filename @version = version @domains = [] begin @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename) @parsed = true rescue Parser::SyntaxError, EncodingError => e @node = nil @comments = {} @parsed = false ensure @code.freeze end end
Private Class Methods
@param filename [String] @return [Solargraph::Source]
# File lib/solargraph/source.rb, line 497 def load filename file = File.open(filename) code = file.read file.close Source.load_string(code, filename) end
@param code [String] @param filename [String] @param version [Integer] @return [Solargraph::Source]
# File lib/solargraph/source.rb, line 508 def load_string code, filename = nil, version = 0 Source.new code, filename, version end
@param comments [String] @return [YARD::DocstringParser]
# File lib/solargraph/source.rb, line 514 def parse_docstring comments # HACK: Pass a dummy code object to the parser for plugins that # expect it not to be nil YARD::Docstring.parser.parse(comments, YARD::CodeObjects::Base.new(:root, 'stub')) end
Public Instance Methods
Get a hash of comments grouped by the line numbers of the associated code.
@return [Hash{Integer => Array<Parser::Source::Comment>}]
# File lib/solargraph/source.rb, line 304 def associated_comments @associated_comments ||= begin result = {} buffer = String.new('') last = nil @comments.each_pair do |num, snip| if !last || num == last + 1 buffer.concat "#{snip.text}\n" else result[first_not_empty_from(last + 1)] = buffer.clone buffer.replace "#{snip.text}\n" end last = num end result[first_not_empty_from(last + 1)] = buffer unless buffer.empty? || last.nil? result end end
@param range [Solargraph::Range] @return [String]
# File lib/solargraph/source.rb, line 57 def at range from_to range.start.line, range.start.character, range.ending.line, range.ending.character end
@param node [Parser::AST::Node] @return [String]
# File lib/solargraph/source.rb, line 243 def code_for(node) rng = Range.from_node(node) b = Position.line_char_to_offset(@code, rng.start.line, rng.start.column) e = Position.line_char_to_offset(@code, rng.ending.line, rng.ending.column) frag = code[b..e-1].to_s frag.strip.gsub(/,$/, '') end
@param position [Position] @return [Boolean]
# File lib/solargraph/source.rb, line 221 def comment_at? position comment_ranges.each do |range| return true if range.include?(position) || (range.ending.line == position.line && range.ending.column < position.column) break if range.ending.line > position.line end false end
@param node [Parser::AST::Node] @return [String]
# File lib/solargraph/source.rb, line 253 def comments_for node rng = Range.from_node(node) stringified_comments[rng.start.line] ||= begin buff = associated_comments[rng.start.line] buff ? stringify_comment_array(buff) : nil end end
@param position [Position, Array(Integer, Integer)] @return [Source::Cursor]
# File lib/solargraph/source.rb, line 166 def cursor_at position Cursor.new(self, position) end
@return [Array<Range>]
# File lib/solargraph/source.rb, line 237 def error_ranges @error_ranges ||= [] end
Finish synchronizing a source that was updated via start_synchronize. This method returns self if the source is already synchronized. Otherwise it parses the AST and returns a new synchronized Source.
@return [Source]
# File lib/solargraph/source.rb, line 124 def finish_synchronize return self if synchronized? synced = Source.new(@code, filename) if synced.parsed? synced.version = version return synced end synced = Source.new(@repaired, filename) synced.error_ranges.concat (error_ranges + last_updater.changes.map(&:range)) synced.code = @code synced.synchronized = true synced.version = version synced end
Get an array of ranges that can be folded, e.g., the range of a class definition or an if condition.
See FOLDING_NODE_TYPES for the list of node types that can be folded.
@return [Array<Range>]
# File lib/solargraph/source.rb, line 287 def folding_ranges @folding_ranges ||= begin result = [] inner_folding_ranges node, result result.concat foldable_comment_block_ranges result end end
@param l1 [Integer] @param c1 [Integer] @param l2 [Integer] @param c2 [Integer] @return [String]
# File lib/solargraph/source.rb, line 66 def from_to l1, c1, l2, c2 b = Solargraph::Position.line_char_to_offset(@code, l1, c1) e = Solargraph::Position.line_char_to_offset(@code, l2, c2) @code[b..e-1] end
A location representing the file in its entirety.
@return [Location]
# File lib/solargraph/source.rb, line 264 def location st = Position.new(0, 0) en = Position.from_offset(code, code.length) range = Range.new(st, en) Location.new(filename, range) end
Get the nearest node that contains the specified index.
@param line [Integer] @param column [Integer] @return [AST::Node]
# File lib/solargraph/source.rb, line 77 def node_at(line, column) tree_at(line, column).first end
@return [Boolean]
# File lib/solargraph/source.rb, line 171 def parsed? @parsed end
@param name [String] @return [Array<Location>]
# File lib/solargraph/source.rb, line 232 def references name Parser.references self, name end
# File lib/solargraph/source.rb, line 175 def repaired? @is_repaired ||= (@code != @repaired) end
Start synchronizing the source. This method updates the code without parsing a new AST. The resulting Source object will be marked not synchronized (#synchronized? == false).
@param updater [Source::Updater] @return [Source]
# File lib/solargraph/source.rb, line 101 def start_synchronize updater raise 'Invalid synchronization' unless updater.filename == filename real_code = updater.write(@code) src = Source.allocate src.filename = filename src.code = real_code src.version = updater.version src.parsed = parsed? src.repaired = updater.repair(@repaired) src.synchronized = false src.node = @node src.comments = @comments src.error_ranges = error_ranges src.last_updater = updater return src.finish_synchronize unless real_code.lines.length == @code.lines.length src end
@param position [Position] @return [Boolean]
# File lib/solargraph/source.rb, line 181 def string_at? position if Parser.rubyvm? string_ranges.each do |range| if synchronized? return true if range.include?(position) || range.ending == position else return true if last_updater && last_updater.changes.one? && range.contain?(last_updater.changes.first.range.start) end end false else return false if Position.to_offset(code, position) >= code.length string_nodes.each do |node| range = Range.from_node(node) next if range.ending.line < position.line break if range.ending.line > position.line return true if node.type == :str && range.include?(position) && range.start != position return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position if node.type == :dstr inner = node_at(position.line, position.column) next if inner.nil? inner_range = Range.from_node(inner) next unless range.include?(inner_range.ending) return true if inner.type == :str inner_code = at(Solargraph::Range.new(inner_range.start, position)) return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') || (inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}')) end break if range.ending.line > position.line end false end end
# File lib/solargraph/source.rb, line 215 def string_ranges @string_ranges ||= Parser.string_ranges(node) end
Synchronize the Source with an update. This method applies changes to the code, parses the new code's AST, and returns the resulting Source object.
@param updater [Source::Updater] @return [Source]
# File lib/solargraph/source.rb, line 144 def synchronize updater raise 'Invalid synchronization' unless updater.filename == filename real_code = updater.write(@code) if real_code == @code @version = updater.version return self end synced = Source.new(real_code, filename) if synced.parsed? synced.version = updater.version return synced end incr_code = updater.repair(@repaired) synced = Source.new(incr_code, filename) synced.error_ranges.concat (error_ranges + updater.changes.map(&:range)) synced.code = real_code synced.version = updater.version synced end
# File lib/solargraph/source.rb, line 296 def synchronized? @synchronized = true if @synchronized.nil? @synchronized end
Get an array of nodes containing the specified index, starting with the nearest node and ending with the root.
@param line [Integer] @param column [Integer] @return [Array<AST::Node>]
# File lib/solargraph/source.rb, line 87 def tree_at(line, column) # offset = Position.line_char_to_offset(@code, line, column) position = Position.new(line, column) stack = [] inner_tree_at @node, position, stack stack end
Protected Instance Methods
@param val [String] @return [String]
# File lib/solargraph/source.rb, line 461 def code=(val) @code_lines= nil @code = val end
Private Instance Methods
@return [Array<String>]
# File lib/solargraph/source.rb, line 490 def code_lines @code_lines ||= code.lines end
# File lib/solargraph/source.rb, line 444 def colonized range, position, node node.type == :COLON2 && range.ending.line == position.line && range.ending.character == position.character - 2 && code[Position.to_offset(code, Position.new(position.line, position.character - 2)), 2] == '::' end
@return [Array<Range>]
# File lib/solargraph/source.rb, line 389 def comment_ranges @comment_ranges ||= @comments.values.map(&:range) end
# File lib/solargraph/source.rb, line 325 def first_not_empty_from line cursor = line cursor += 1 while cursor < code_lines.length && code_lines[cursor].strip.empty? cursor = line if cursor > code_lines.length - 1 cursor end
Get an array of foldable comment block ranges. Blocks are excluded if they are less than 3 lines long.
@return [Array<Range>]
# File lib/solargraph/source.rb, line 397 def foldable_comment_block_ranges return [] unless synchronized? result = [] grouped = [] comments.keys.each do |l| if grouped.empty? || l == grouped.last + 1 grouped.push l else result.push Range.from_to(grouped.first, 0, grouped.last, 0) unless grouped.length < 3 grouped = [l] end end result.push Range.from_to(grouped.first, 0, grouped.last, 0) unless grouped.length < 3 result end
@param top [Parser::AST::Node] @param result [Array<Range>] @param parent [Symbol] @return [void]
# File lib/solargraph/source.rb, line 336 def inner_folding_ranges top, result = [], parent = nil return unless Parser.is_ast_node?(top) if FOLDING_NODE_TYPES.include?(top.type) # @todo Smelly exception for hash's first-level array in RubyVM unless [:ARRAY, :LIST].include?(top.type) && parent == :HASH range = Range.from_node(top) if result.empty? || range.start.line > result.last.start.line result.push range unless range.ending.line - range.start.line < 2 end end end top.children.each do |child| inner_folding_ranges(child, result, top.type) end end
@param node [Parser::AST::Node] @param position [Position] @param stack [Array<Parser::AST::Node>] @return [void]
# File lib/solargraph/source.rb, line 431 def inner_tree_at node, position, stack return if node.nil? here = Range.from_node(node) if here.contain?(position) || colonized(here, position, node) stack.unshift node node.children.each do |c| next unless Parser.is_ast_node?(c) next if !Parser.rubyvm? && c.loc.expression.nil? inner_tree_at(c, position, stack) end end end
@return [Array<Parser::AST::Node>]
# File lib/solargraph/source.rb, line 384 def string_nodes @string_nodes ||= string_nodes_in(@node) end
@param n [Parser::AST::Node] @return [Array<Parser::AST::Node>]
# File lib/solargraph/source.rb, line 415 def string_nodes_in n result = [] if Parser.is_ast_node?(n) if n.type == :str || n.type == :dstr || n.type == :STR || n.type == :DSTR result.push n else n.children.each{ |c| result.concat string_nodes_in(c) } end end result end
A hash of line numbers and their associated comments.
@return [Hash{Integer => Array<String>}]
# File lib/solargraph/source.rb, line 379 def stringified_comments @stringified_comments ||= {} end
Get a string representation of an array of comments.
@param comments [String] @return [String]
# File lib/solargraph/source.rb, line 356 def stringify_comment_array comments ctxt = String.new('') started = false skip = nil comments.lines.each { |l| # Trim the comment and minimum leading whitespace p = l.force_encoding('UTF-8').encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '') if p.strip.empty? next unless started ctxt.concat p else here = p.index(/[^ \t]/) skip = here if skip.nil? || here < skip ctxt.concat p[skip..-1] end started = true } ctxt end