class Solargraph::Source


A Ruby file that has been parsed into an AST.

def associated_comments

Returns:
  • (Hash{Integer => String}) -
def associated_comments
  @associated_comments ||= begin
    result = {}
    buffer = String.new('')
    # @type [Integer, nil]

    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

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 changes

def changes
  @changes ||= []
end

def code

Returns:
  • (String) -
def code
  finalize
  @code
end

def code=(val)

Returns:
  • (String) -

Parameters:
  • val (String) --
def code=(val)
  @code_lines = nil
  @finalized = false
  @code = val
end

def code_for(node)

Returns:
  • (String) -

Parameters:
  • node (Parser::AST::Node) --
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

def code_lines

Returns:
  • (Array) -
def code_lines
  @code_lines ||= code.lines
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.values.map(&:range)
end

def comments

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

def comments_for node

Returns:
  • (String, nil) -

Parameters:
  • node (Parser::AST::Node) --
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

def cursor_at position

Returns:
  • (Source::Cursor) -

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

def error_ranges

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

def finalize

def finalize
  return if @finalized && changes.empty?
  changes.each do |change|
    @code = change.write(@code)
  end
  @finalized = true
  begin
    @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename)
    @parsed = true
    @repaired = @code
  rescue Parser::SyntaxError, EncodingError => e
    @node = nil
    @comments = {}
    @parsed = false
  ensure
    @code.freeze
  end
  if !@parsed && !changes.empty?
    changes.each do |change|
      @repaired = change.repair(@repaired)
    end
    error_ranges.concat(changes.map(&:range))
    begin
      @node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename)
      @parsed = true
    rescue Parser::SyntaxError, EncodingError => e
      @node = nil
      @comments = {}
      @parsed = false
    end
  elsif @parsed
    error_ranges.clear
  end
  changes.clear
end

def first_not_empty_from line

Returns:
  • (Integer) -

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

def foldable_comment_block_ranges

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

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, nil) --
  • code (String) --
def initialize code, filename = nil, version = 0
  @code = normalize(code)
  @repaired = code
  @filename = filename
  @version = version
end

def inner_folding_ranges top, result = [], parent = nil

Returns:
  • (void) -

Parameters:
  • parent (Symbol, nil) --
  • result (Array) --
  • top (Parser::AST::Node) --
def inner_folding_ranges top, result = [], parent = nil
  return unless Parser.is_ast_node?(top)
  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, top.type)
  end
end

def inner_tree_at node, position, stack

Returns:
  • (void) -

Parameters:
  • stack (Array) --
  • position (Position) --
  • node (Parser::AST::Node, nil) --
def inner_tree_at node, position, stack
  return if node.nil?
  here = Range.from_node(node)
  if here.contain?(position)
    stack.unshift node
    node.children.each do |c|
      next unless Parser.is_ast_node?(c)
      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, nil) --
  • 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

Returns:
  • (Parser::AST::Node, nil) -
def node
  finalize
  @node
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_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'))
rescue StandardError => e
  Solargraph.logger.info "YARD failed to parse docstring: [#{e.class}] #{e.message}"
  Solargraph.logger.debug "Unparsed comment: #{comments}"
  YARD::Docstring.parser
end

def parsed?

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

def references name

Returns:
  • (Array) -

Parameters:
  • name (String) --
def references name
  Parser.references self, name
end

def repaired

def repaired
  finalize
  @repaired
end

def repaired?

def repaired?
  code != @repaired
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
    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

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, nil) --
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

def string_ranges

Returns:
  • (::Array) -
def string_ranges
  @string_ranges ||= Parser.string_ranges(node)
end

def stringified_comments

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

def stringify_comment_array comments

Returns:
  • (String) -

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

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
  Source.new(@code, filename, updater.version).tap do |src|
    src.repaired = @repaired
    src.error_ranges.concat error_ranges
    src.changes.concat(changes + updater.changes)
  end
end

def synchronized?

def synchronized?
  true
end

def tree_at(line, column)

Returns:
  • (Array) -

Parameters:
  • column (Integer) --
  • line (Integer) --
def tree_at(line, column)
  position = Position.new(line, column)
  stack = []
  inner_tree_at node, position, stack
  stack
end