Class: Solargraph::Source

Inherits:
Object
  • Object
show all
Includes:
EncodingFixes
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/chain/literal.rb,
lib/solargraph/source/chain/z_super.rb,
lib/solargraph/source/chain/constant.rb,
lib/solargraph/source/chain/variable.rb,
lib/solargraph/source/encoding_fixes.rb,
lib/solargraph/source/source_chainer.rb,
lib/solargraph/source/chain/block_variable.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 Classes: Chain, Change, Cursor, SourceChainer, Updater

Constant Summary collapse

FOLDING_NODE_TYPES =
if Parser.rubyvm?
  %i[
    CLASS SCLASS MODULE DEFN DEFS IF WHILE UNLESS ITER STR
  ].freeze
else
  %i[
    class sclass module def defs if str dstr array while unless kwbegin hash block
  ].freeze
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from EncodingFixes

normalize

Constructor Details

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

Returns a new instance of Source.

Parameters:

  • code (String)
  • filename (String) (defaults to: nil)
  • version (Integer) (defaults to: 0)


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

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)
    @node, @comments = Solargraph::Parser.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

Returns:

  • (String)


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

def code
  @code
end

#commentsHash{Integer => Array<String>}

Returns:

  • (Hash{Integer => Array<String>})


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

def comments
  @comments
end

#error_rangesArray<Range>

Returns:



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

def error_ranges
  @error_ranges ||= []
end

#filenameString

Returns:

  • (String)


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

def filename
  @filename
end

#nodeParser::AST::Node

Returns:

  • (Parser::AST::Node)


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

def node
  @node
end

#versionInteger

TODO:

Deprecate?

Returns:

  • (Integer)


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

def version
  @version
end

Class Method Details

.load(filename) ⇒ Solargraph::Source

Parameters:

  • filename (String)

Returns:



509
510
511
512
513
514
# File 'lib/solargraph/source.rb', line 509

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

Parameters:

  • code (String)
  • filename (String) (defaults to: nil)
  • version (Integer) (defaults to: 0)

Returns:



520
521
522
# File 'lib/solargraph/source.rb', line 520

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

.parse_docstring(comments) ⇒ YARD::DocstringParser

Parameters:

  • comments (String)

Returns:

  • (YARD::DocstringParser)


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

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

Instance Method Details

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

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

Returns:

  • (Hash{Integer => Array<Parser::Source::Comment>})


318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/solargraph/source.rb', line 318

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

#at(range) ⇒ String

Parameters:

Returns:

  • (String)


71
72
73
# File 'lib/solargraph/source.rb', line 71

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

#code_for(node) ⇒ String

Parameters:

  • node (Parser::AST::Node)

Returns:

  • (String)


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

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

#comment_at?(position) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


235
236
237
238
239
240
241
242
# File 'lib/solargraph/source.rb', line 235

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

Parameters:

  • node (Parser::AST::Node)

Returns:

  • (String)


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

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

#cursor_at(position) ⇒ Source::Cursor

Parameters:

Returns:



180
181
182
# File 'lib/solargraph/source.rb', line 180

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.

Returns:



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

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.

Returns:



301
302
303
304
305
306
307
308
# File 'lib/solargraph/source.rb', line 301

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

Parameters:

  • l1 (Integer)
  • c1 (Integer)
  • l2 (Integer)
  • c2 (Integer)

Returns:

  • (String)


80
81
82
83
84
# File 'lib/solargraph/source.rb', line 80

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.

Returns:



278
279
280
281
282
283
# File 'lib/solargraph/source.rb', line 278

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.

Parameters:

  • line (Integer)
  • column (Integer)

Returns:

  • (AST::Node)


91
92
93
# File 'lib/solargraph/source.rb', line 91

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

#parsed?Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/solargraph/source.rb', line 185

def parsed?
  @parsed
end

#references(name) ⇒ Array<Location>

Parameters:

  • name (String)

Returns:



246
247
248
# File 'lib/solargraph/source.rb', line 246

def references name
  Parser.references self, name
end

#repaired?Boolean

Returns:

  • (Boolean)


189
190
191
# File 'lib/solargraph/source.rb', line 189

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

Parameters:

Returns:



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

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

Parameters:

Returns:

  • (Boolean)


195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/solargraph/source.rb', line 195

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

#string_rangesObject



229
230
231
# File 'lib/solargraph/source.rb', line 229

def string_ranges
  @string_ranges ||= Parser.string_ranges(node)
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.

Parameters:

Returns:



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

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

Returns:

  • (Boolean)


310
311
312
313
# File 'lib/solargraph/source.rb', line 310

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.

Parameters:

  • line (Integer)
  • column (Integer)

Returns:

  • (Array<AST::Node>)


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

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