class Solargraph::Source


A Ruby file that has been parsed into an AST.

def associated_comments

Returns:
  • (Hash{Integer => Array}) -
def associated_comments
  @associated_comments ||= begin
    result = {}
    Parser::Source::Comment.associate_locations(node, comments).each_pair do |loc, all|
      block = all.select { |l| l.loc.line < loc.line }
      next if block.empty?
      result[loc.line] ||= []
      result[loc.line].concat block
    end
    result
  end
end

def at range

Returns:
  • (String) -

Parameters:
  • range (Solargraph::Range) --
def at range
  from_to range.start.line, range.start.character, range.ending.line, range.ending.character
end

def code_for(node)

Returns:
  • (String) -

Parameters:
  • node (Parser::AST::Node) --
def code_for(node)
  b = Position.line_char_to_offset(@code, node.location.line, node.location.column)
  e = Position.line_char_to_offset(@code, node.location.last_line, node.location.last_column)
  frag = code[b..e-1].to_s
  frag.strip.gsub(/,$/, '')
end

def comment_at? position

Returns:
  • (Boolean) -

Parameters:
  • position (Position) --
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

def comment_ranges

Returns:
  • (Array) -
def comment_ranges
  @comment_ranges ||= @comments.map do |cmnt|
    Range.from_expr(cmnt.loc.expression)
  end
end

def comments_for node

Returns:
  • (String) -

Parameters:
  • node (Parser::AST::Node) --
def comments_for node
  stringified_comments[node.loc.line] ||= begin
    arr = associated_comments[node.loc.line]
    arr ? stringify_comment_array(arr) : nil
  end
end

def cursor_at position

Returns:
  • (Source::Cursor) -

Parameters:
  • position (Position) --
def cursor_at position
  Cursor.new(self, position)
end

def error_ranges

Returns:
  • (Array) -
def error_ranges
  @error_ranges ||= []
end

def finish_synchronize

Returns:
  • (Source) -
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

def foldable_comment_block_ranges

Returns:
  • (Array) -
def foldable_comment_block_ranges
  return [] unless synchronized?
  result = []
  grouped = []
  # @param cmnt [Parser::Source::Comment]

  @comments.each do |cmnt|
    if cmnt.document?
      result.push Range.from_expr(cmnt.loc.expression)
    elsif code.lines[cmnt.loc.expression.line].strip.start_with?('#')
      if grouped.empty? || cmnt.loc.expression.line == grouped.last.loc.expression.line + 1
        grouped.push cmnt
      else
        result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
        grouped = [cmnt]
      end
    else
      unless grouped.length < 3
        result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0)
      end
      grouped.clear
    end
  end
  result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
  result
end

def folding_ranges

Returns:
  • (Array) -
def folding_ranges
  @folding_ranges ||= begin
    result = []
    inner_folding_ranges node, result
    result.concat foldable_comment_block_ranges
    result
  end
end

def from_to l1, c1, l2, c2

Returns:
  • (String) -

Parameters:
  • c2 (Integer) --
  • l2 (Integer) --
  • c1 (Integer) --
  • l1 (Integer) --
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

def initialize code, filename = nil, version = 0

Parameters:
  • version (Integer) --
  • filename (String) --
  • code (String) --
def initialize code, filename = nil, version = 0
  @code = normalize(code)
  @repaired = code
  @filename = filename
  @version = version
  @domains = []
  begin
    @node, @comments = Source.parse_with_comments(@code, filename)
    @parsed = true
  rescue Parser::SyntaxError, EncodingError => e
    # @todo 100% whitespace results in a nil node, so there's no reason to parse it.

    #   We still need to determine whether the resulting node should be nil or a dummy

    #   node with a location that encompasses the range.

    # @node, @comments = Source.parse_with_comments(@code.gsub(/[^\s]/, ' '), filename)

    @node = nil
    @comments = []
    @parsed = false
  rescue Exception => e
    Solargraph.logger.warn "[#{e.class}] #{e.message}"
    Solargraph.logger.warn e.backtrace.join("\n")
    raise "Error parsing #{filename || '(source)'}: [#{e.class}] #{e.message}"
  ensure
    @code.freeze
  end
end

def inner_folding_ranges top, result = []

Returns:
  • (void) -

Parameters:
  • result (Array) --
  • top (Parser::AST::Node) --
def inner_folding_ranges top, result = []
  return unless top.is_a?(Parser::AST::Node)
  if FOLDING_NODE_TYPES.include?(top.type)
    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
  top.children.each do |child|
    inner_folding_ranges(child, result)
  end
end

def inner_node_references name, top

Returns:
  • (Array) -

Parameters:
  • top (AST::Node) --
  • name (String) --
def inner_node_references name, top
  result = []
  if top.is_a?(AST::Node) && top.to_s.include?(":#{name}")
    result.push top if top.children.any? { |c| c.to_s == name }
    top.children.each { |c| result.concat inner_node_references(name, c) }
  end
  result
end

def inner_tree_at node, position, stack

Returns:
  • (void) -

Parameters:
  • stack (Array) --
  • position (Position) --
  • node (Parser::AST::Node) --
def inner_tree_at node, position, stack
  return if node.nil?
  here = Range.from_to(node.loc.expression.line, node.loc.expression.column, node.loc.expression.last_line, node.loc.expression.last_column)
  if here.contain?(position)
    stack.unshift node
    node.children.each do |c|
      next unless c.is_a?(AST::Node)
      next if c.loc.expression.nil?
      inner_tree_at(c, position, stack)
    end
  end
end

def load filename

Returns:
  • (Solargraph::Source) -

Parameters:
  • filename (String) --
def load filename
  file = File.open(filename)
  code = file.read
  file.close
  Source.load_string(code, filename)
end

def load_string code, filename = nil, version = 0

Returns:
  • (Solargraph::Source) -

Parameters:
  • version (Integer) --
  • filename (String) --
  • code (String) --
def load_string code, filename = nil, version = 0
  Source.new code, filename, version
end

def location

Returns:
  • (Location) -
def location
  st = Position.new(0, 0)
  en = Position.from_offset(code, code.length)
  range = Range.new(st, en)
  Location.new(filename, range)
end

def node_at(line, column)

Returns:
  • (AST::Node) -

Parameters:
  • column (Integer) --
  • line (Integer) --
def node_at(line, column)
  tree_at(line, column).first
end

def parse code, filename = nil, line = 0

Returns:
  • (Parser::AST::Node) -

Parameters:
  • line (Integer) --
  • filename (String, nil) --
  • code (String) --
def parse code, filename = nil, line = 0
  buffer = Parser::Source::Buffer.new(filename, line)
  buffer.source = code
  parser.parse(buffer)
end

def parse_docstring comments

Returns:
  • (YARD::DocstringParser) -

Parameters:
  • comments (String) --
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

def parse_with_comments code, filename = nil

Returns:
  • (Array(Parser::AST::Node, Array)) -

Parameters:
  • filename (String) --
  • code (String) --
def parse_with_comments code, filename = nil
  buffer = Parser::Source::Buffer.new(filename, 0)
  buffer.source = code
  parser.parse_with_comments(buffer)
end

def parsed?

Returns:
  • (Boolean) -
def parsed?
  @parsed
end

def parser

Returns:
  • (Parser::Base) -
def parser
  # @todo Consider setting an instance variable. We might not need to

  #   recreate the parser every time we use it.

  parser = Parser::CurrentRuby.new(FlawedBuilder.new)
  parser.diagnostics.all_errors_are_fatal = true
  parser.diagnostics.ignore_warnings      = true
  parser
end

def references name

Returns:
  • (Array) -

Parameters:
  • name (String) --
def references name
  inner_node_references(name, node).map do |n|
    offset = Position.to_offset(code, get_node_start_position(n))
    soff = code.index(name, offset)
    eoff = soff + name.length
    Location.new(
      filename,
      Range.new(
        Position.from_offset(code, soff),
        Position.from_offset(code, eoff)
      )
    )
  end
end

def repaired?

def repaired?
  @is_repaired ||= (@code != @repaired)
end

def start_synchronize updater

Returns:
  • (Source) -

Parameters:
  • updater (Source::Updater) --
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

def string_at? position

Returns:
  • (Boolean) -

Parameters:
  • position (Position) --
def string_at? position
  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
    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

def string_nodes

Returns:
  • (Array) -
def string_nodes
  @string_nodes ||= string_nodes_in(@node)
end

def string_nodes_in n

Returns:
  • (Array) -

Parameters:
  • n (Parser::AST::Node) --
def string_nodes_in n
  result = []
  if n.is_a?(Parser::AST::Node)
    if n.type == :str || n.type == :dstr
      result.push n
    else
      n.children.each{ |c| result.concat string_nodes_in(c) }
    end
  end
  result
end

def stringified_comments

Returns:
  • (Hash{Integer => Array}) -
def stringified_comments
  @stringified_comments ||= {}
end

def stringify_comment_array comments

Returns:
  • (String) -

Parameters:
  • comments (Array) --
def stringify_comment_array comments
  ctxt = ''
  num = nil
  started = false
  last_line = nil
  comments.each { |l|
    # Trim the comment and minimum leading whitespace

    p = l.text.gsub(/^#+/, '')
    if num.nil? and !p.strip.empty?
      num = p.index(/[^ ]/)
      started = true
    elsif started and !p.strip.empty?
      cur = p.index(/[^ ]/)
      num = cur if cur < num
    end
    # Include blank lines between comments

    ctxt += ("\n" * (l.loc.first_line - last_line - 1)) unless last_line.nil? || l.loc.first_line - last_line <= 0
    ctxt += "#{p[num..-1]}\n" if started
    last_line = l.loc.last_line if last_line.nil? || l.loc.last_line > last_line
  }
  ctxt
end

def synchronize updater

Returns:
  • (Source) -

Parameters:
  • updater (Source::Updater) --
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

def synchronized?

def synchronized?
  @synchronized = true if @synchronized.nil?
  @synchronized
end

def tree_at(line, column)

Returns:
  • (Array) -

Parameters:
  • column (Integer) --
  • line (Integer) --
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