Class: Solargraph::Source

Inherits:
Object
  • Object
show all
Includes:
EncodingFixes, NodeMethods
Defined in:
lib/solargraph/source.rb,
lib/solargraph/source/chain.rb,
lib/solargraph/source/change.rb,
lib/solargraph/source/cursor.rb,
lib/solargraph/source/updater.rb,
lib/solargraph/source/chain/or.rb,
lib/solargraph/source/chain/call.rb,
lib/solargraph/source/chain/head.rb,
lib/solargraph/source/chain/link.rb,
lib/solargraph/source/node_chainer.rb,
lib/solargraph/source/node_methods.rb,
lib/solargraph/source/chain/literal.rb,
lib/solargraph/source/chain/constant.rb,
lib/solargraph/source/chain/variable.rb,
lib/solargraph/source/encoding_fixes.rb,
lib/solargraph/source/flawed_builder.rb,
lib/solargraph/source/source_chainer.rb,
lib/solargraph/source/chain/class_variable.rb,
lib/solargraph/source/chain/global_variable.rb,
lib/solargraph/source/chain/instance_variable.rb

Overview

A Ruby file that has been parsed into an AST.

Defined Under Namespace

Modules: EncodingFixes, NodeMethods Classes: Chain, Change, Cursor, NodeChainer, SourceChainer, Updater

Constant Summary collapse

FOLDING_NODE_TYPES =
i[
  class sclass module def defs if str dstr array while unless kwbegin hash block
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from NodeMethods

const_from, drill_signature, get_node_end_position, get_node_start_position, infer_literal_node_type, pack_name, resolve_node_signature, returns_from, unpack_name

Methods included from EncodingFixes

normalize

Constructor Details

#initialize(code, filename = nil, version = 0) ⇒ Source


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/solargraph/source.rb', line 41

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

Instance Attribute Details

#codeString


26
27
28
# File 'lib/solargraph/source.rb', line 26

def code
  @code
end

#commentsArray<Parser::Source::Comment>


32
33
34
# File 'lib/solargraph/source.rb', line 32

def comments
  @comments
end

#error_rangesArray<Range>


244
245
246
# File 'lib/solargraph/source.rb', line 244

def error_ranges
  @error_ranges ||= []
end

#filenameString


23
24
25
# File 'lib/solargraph/source.rb', line 23

def filename
  @filename
end

#nodeParser::AST::Node


29
30
31
# File 'lib/solargraph/source.rb', line 29

def node
  @node
end

#versionInteger


36
37
38
# File 'lib/solargraph/source.rb', line 36

def version
  @version
end

Class Method Details

.load(filename) ⇒ Solargraph::Source


488
489
490
491
492
493
# File 'lib/solargraph/source.rb', line 488

def load filename
  file = File.open(filename)
  code = file.read
  file.close
  Source.load_string(code, filename)
end

.load_string(code, filename = nil, version = 0) ⇒ Solargraph::Source


499
500
501
# File 'lib/solargraph/source.rb', line 499

def load_string code, filename = nil, version = 0
  Source.new code, filename, version
end

.parse(code, filename = nil, line = 0) ⇒ Parser::AST::Node


516
517
518
519
520
# File 'lib/solargraph/source.rb', line 516

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

.parse_docstring(comments) ⇒ YARD::DocstringParser


534
535
536
537
538
# File 'lib/solargraph/source.rb', line 534

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

.parse_with_comments(code, filename = nil) ⇒ Array(Parser::AST::Node, Array<Parser::Source::Comment>)


506
507
508
509
510
# File 'lib/solargraph/source.rb', line 506

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

.parserParser::Base


523
524
525
526
527
528
529
530
# File 'lib/solargraph/source.rb', line 523

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

Instance Method Details

#associated_commentsHash{Integer => Array<Parser::Source::Comment>}

Get a hash of comments grouped by the line numbers of the associated code.


303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/solargraph/source.rb', line 303

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

#at(range) ⇒ String


69
70
71
# File 'lib/solargraph/source.rb', line 69

def at range
  from_to range.start.line, range.start.character, range.ending.line, range.ending.character
end

#code_for(node) ⇒ String


250
251
252
253
254
255
# File 'lib/solargraph/source.rb', line 250

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

#comment_at?(position) ⇒ Boolean


217
218
219
220
221
222
223
224
# File 'lib/solargraph/source.rb', line 217

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

#comments_for(node) ⇒ String


259
260
261
262
263
264
# File 'lib/solargraph/source.rb', line 259

def comments_for node
  stringified_comments[node.loc.line] ||= begin
    arr = associated_comments[node.loc.line]
    arr ? stringify_comment_array(arr) : nil
  end
end

#cursor_at(position) ⇒ Source::Cursor


178
179
180
# File 'lib/solargraph/source.rb', line 178

def cursor_at position
  Cursor.new(self, position)
end

#finish_synchronizeSource

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.


136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/solargraph/source.rb', line 136

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

#folding_rangesArray<Range>

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.


286
287
288
289
290
291
292
293
# File 'lib/solargraph/source.rb', line 286

def folding_ranges
  @folding_ranges ||= begin
    result = []
    inner_folding_ranges node, result
    result.concat foldable_comment_block_ranges
    result
  end
end

#from_to(l1, c1, l2, c2) ⇒ String


78
79
80
81
82
# File 'lib/solargraph/source.rb', line 78

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

#locationLocation

A location representing the file in its entirety.


269
270
271
272
273
274
# File 'lib/solargraph/source.rb', line 269

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

#node_at(line, column) ⇒ AST::Node

Get the nearest node that contains the specified index.


89
90
91
# File 'lib/solargraph/source.rb', line 89

def node_at(line, column)
  tree_at(line, column).first
end

#parsed?Boolean


183
184
185
# File 'lib/solargraph/source.rb', line 183

def parsed?
  @parsed
end

#references(name) ⇒ Array<Location>


228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/solargraph/source.rb', line 228

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

#repaired?Boolean


187
188
189
# File 'lib/solargraph/source.rb', line 187

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

#start_synchronize(updater) ⇒ Source

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).


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/solargraph/source.rb', line 113

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

#string_at?(position) ⇒ Boolean


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/solargraph/source.rb', line 193

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

#synchronize(updater) ⇒ Source

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.


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/solargraph/source.rb', line 156

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

#synchronized?Boolean


295
296
297
298
# File 'lib/solargraph/source.rb', line 295

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

#tree_at(line, column) ⇒ Array<AST::Node>

Get an array of nodes containing the specified index, starting with the nearest node and ending with the root.


99
100
101
102
103
104
105
# File 'lib/solargraph/source.rb', line 99

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