solargraph 0.43.2 → 0.44.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/lib/solargraph/complex_type.rb +4 -0
  4. data/lib/solargraph/diagnostics/rubocop_helpers.rb +1 -1
  5. data/lib/solargraph/documentor.rb +10 -12
  6. data/lib/solargraph/language_server/host.rb +8 -4
  7. data/lib/solargraph/language_server/message/initialize.rb +7 -0
  8. data/lib/solargraph/language_server/message/initialized.rb +1 -0
  9. data/lib/solargraph/language_server/message/text_document/document_highlight.rb +16 -0
  10. data/lib/solargraph/language_server/message/text_document.rb +18 -18
  11. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +1 -0
  12. data/lib/solargraph/language_server/message.rb +2 -1
  13. data/lib/solargraph/library.rb +23 -20
  14. data/lib/solargraph/parser/legacy/class_methods.rb +9 -3
  15. data/lib/solargraph/parser/legacy/node_chainer.rb +17 -0
  16. data/lib/solargraph/parser/legacy/node_methods.rb +7 -0
  17. data/lib/solargraph/parser/legacy/node_processors/block_node.rb +22 -2
  18. data/lib/solargraph/parser/rubyvm/class_methods.rb +7 -2
  19. data/lib/solargraph/parser/rubyvm/node_chainer.rb +5 -0
  20. data/lib/solargraph/parser/rubyvm/node_methods.rb +23 -14
  21. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +22 -2
  22. data/lib/solargraph/pin/block.rb +19 -9
  23. data/lib/solargraph/pin/documenting.rb +1 -1
  24. data/lib/solargraph/pin/local_variable.rb +2 -14
  25. data/lib/solargraph/pin/namespace.rb +2 -2
  26. data/lib/solargraph/source/chain/link.rb +4 -0
  27. data/lib/solargraph/source/chain/q_call.rb +11 -0
  28. data/lib/solargraph/source/chain.rb +14 -1
  29. data/lib/solargraph/source.rb +1 -4
  30. data/lib/solargraph/source_map/clip.rb +5 -5
  31. data/lib/solargraph/source_map/mapper.rb +4 -4
  32. data/lib/solargraph/version.rb +1 -1
  33. data/lib/solargraph/workspace.rb +14 -10
  34. data/lib/solargraph/yard_map.rb +6 -2
  35. data/solargraph.gemspec +1 -1
  36. metadata +8 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e671eddad2fe717ad573954a63154bd89adbc12c9899c3f8762bf38a93160657
4
- data.tar.gz: 2224d1457b9e8f7279bfa89b2777fe3d60a0a2283de4047930bebb79034a1ac4
3
+ metadata.gz: 8fd55900ebfdb84f10fa5ba2cb7e5fcc6dc7af0e51efe6a5d230ebaf497ec75c
4
+ data.tar.gz: 859e3d6738c17b1fc9241dbc4ed9e7f4fa117556bb7767915de0178fc2a50721
5
5
  SHA512:
6
- metadata.gz: 76ffe1331d8020eed92c39d2c4886ce7d01e4143a8b8264ac1bf4011335fabfd32a48f16a7099f889fed2e7c485e2e9cbf09714f677cd6e9396381032522f67a
7
- data.tar.gz: 9c6da25c26ed209af38be4a0bc8d7a6ce826fb3012bce650089e982f829f4c8b14807ed0f7d1ecdeacd9b737bedd9d682440a9701a864313a83e60182771ade0
6
+ metadata.gz: fb53ed887a2d25be28655486de35cb87deac7a604eae688bc20be8c34a696dc86d9dbcf6d069f908316078d653e06f4f00154e23eae411876a8f5cd191dfa51d
7
+ data.tar.gz: f7e65200040c8a2bfd7f73e4ad7aa718dc7378fcb99850802fcd2b7ab212b59cf6ae215e21dcd7f1158291551ab211bc836113e9c06d389f6c17fb69de27457f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 0.44.2 - November 23, 2021
2
+ - Scope local variables in class_eval blocks (#503)
3
+ - Fix invalid UTF-8 in node comments (#504)
4
+
5
+ ## 0.44.1 - November 18, 2021
6
+ - Chain nil safety navigation operator (#420)
7
+ - Update closure and context for class_eval receiver (#487)
8
+ - SourceMap::Mapper handles invalid byte sequences (#474)
9
+ - Special handle var= references, which may use as var = (have space) (#498)
10
+ - Rebind define_method to self (#494)
11
+
12
+ ## 0.44.0 - September 27, 2021
13
+ - Allow opening parenthesis, space, and comma when using Diff::LCS (#465)
14
+ - Support textDocument/documentHighlight
15
+ - Library#references_from performs shallow matches
16
+
17
+ ## 0.43.3 - September 25, 2021
18
+ - Avoid Dir.chdir in Bundler processes (#481)
19
+ - Check stdlib for gems with empty yardocs
20
+ - Library#maybe_map checks for source in workspace
21
+
1
22
  ## 0.43.2 - September 23, 2021
2
23
  - Synchronize server requests (#461)
3
24
 
@@ -87,6 +87,10 @@ module Solargraph
87
87
  ComplexType.parse(*result.map(&:tag))
88
88
  end
89
89
 
90
+ def nullable?
91
+ @items.any?(&:nil_type?)
92
+ end
93
+
90
94
  private
91
95
 
92
96
  # @todo This is a quick and dirty hack that forces `self` keywords
@@ -31,7 +31,7 @@ module Solargraph
31
31
  # @param code [String]
32
32
  # @return [Array(Array<String>, Array<String>)]
33
33
  def generate_options filename, code
34
- args = ['-f', 'j', filename]
34
+ args = ['-f', 'j', '--force-exclusion', filename]
35
35
  base_options = RuboCop::Options.new
36
36
  options, paths = base_options.parse(args)
37
37
  options[:stdin] = code
@@ -59,18 +59,16 @@ module Solargraph
59
59
  # @return [Hash]
60
60
  def self.specs_from_bundle directory
61
61
  Solargraph.with_clean_env do
62
- Dir.chdir directory do
63
- cmd = [
64
- 'bundle', 'exec', 'ruby', '-e',
65
- "require 'bundler'; require 'json'; puts Bundler.definition.specs_for([:default]).map { |spec| [spec.name, spec.version] }.to_h.to_json"
66
- ]
67
- o, e, s = Open3.capture3(*cmd)
68
- if s.success?
69
- o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
70
- else
71
- Solargraph.logger.warn e
72
- raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
73
- end
62
+ cmd = [
63
+ 'ruby', '-e',
64
+ "require 'bundler'; require 'json'; Dir.chdir('#{directory}') { puts Bundler.definition.specs_for([:default]).map { |spec| [spec.name, spec.version] }.to_h.to_json }"
65
+ ]
66
+ o, e, s = Open3.capture3(*cmd)
67
+ if s.success?
68
+ o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
69
+ else
70
+ Solargraph.logger.warn e
71
+ raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
74
72
  end
75
73
  end
76
74
  end
@@ -18,7 +18,6 @@ module Solargraph
18
18
  autoload :Dispatch, 'solargraph/language_server/host/dispatch'
19
19
  autoload :MessageWorker, 'solargraph/language_server/host/message_worker'
20
20
 
21
-
22
21
  include UriHelpers
23
22
  include Logging
24
23
  include Dispatch
@@ -543,10 +542,11 @@ module Solargraph
543
542
  # @param line [Integer]
544
543
  # @param column [Integer]
545
544
  # @param strip [Boolean] Strip special characters from variable names
545
+ # @param only [Boolean] If true, search current file only
546
546
  # @return [Array<Solargraph::Range>]
547
- def references_from uri, line, column, strip: true
547
+ def references_from uri, line, column, strip: true, only: false
548
548
  library = library_for(uri)
549
- library.references_from(uri_to_file(uri), line, column, strip: strip)
549
+ library.references_from(uri_to_file(uri), line, column, strip: strip, only: only)
550
550
  end
551
551
 
552
552
  # @param query [String]
@@ -632,6 +632,7 @@ module Solargraph
632
632
  'diagnostics' => false,
633
633
  'formatting' => false,
634
634
  'folding' => true,
635
+ 'highlights' => true,
635
636
  'logLevel' => 'warn'
636
637
  }
637
638
  end
@@ -716,7 +717,7 @@ module Solargraph
716
717
  return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
717
718
  # @type [Diff::LCS::Change]
718
719
  diff = diffs.first.first
719
- return change unless diff.adding? && ['.', ':'].include?(diff.element)
720
+ return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element)
720
721
  position = Solargraph::Position.from_offset(source.code, diff.position)
721
722
  {
722
723
  'range' => {
@@ -784,6 +785,9 @@ module Solargraph
784
785
  },
785
786
  'textDocument/codeAction' => {
786
787
  codeActionProvider: true
788
+ },
789
+ 'textDocument/documentHighlight' => {
790
+ documentHighlightProvider: true
787
791
  }
788
792
  }
789
793
  end
@@ -36,6 +36,7 @@ module Solargraph
36
36
  result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
37
37
  result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
38
38
  result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
39
+ result[:capabilities].merge! static_highlights unless dynamic_registration_for?('textDocument', 'documentHighlight')
39
40
  # @todo Temporarily disabled
40
41
  # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
41
42
  set_result result
@@ -138,6 +139,12 @@ module Solargraph
138
139
  }
139
140
  end
140
141
 
142
+ def static_highlights
143
+ {
144
+ documentHighlightProvider: true
145
+ }
146
+ end
147
+
141
148
  # @param section [String]
142
149
  # @param capability [String]
143
150
  # @return [Boolean]
@@ -17,6 +17,7 @@ module Solargraph
17
17
  textDocument/rename
18
18
  textDocument/prepareRename
19
19
  textDocument/foldingRange
20
+ textDocument/documentHighlight
20
21
  workspace/symbol
21
22
  ]
22
23
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph::LanguageServer::Message::TextDocument
4
+ class DocumentHighlight < Base
5
+ def process
6
+ locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character'], strip: true, only: true)
7
+ result = locs.map do |loc|
8
+ {
9
+ range: loc.range.to_hash,
10
+ kind: 1
11
+ }
12
+ end
13
+ set_result result
14
+ end
15
+ end
16
+ end
@@ -4,24 +4,24 @@ module Solargraph
4
4
  module LanguageServer
5
5
  module Message
6
6
  module TextDocument
7
- autoload :Base, 'solargraph/language_server/message/text_document/base'
8
- autoload :Completion, 'solargraph/language_server/message/text_document/completion'
9
- autoload :DidOpen, 'solargraph/language_server/message/text_document/did_open'
10
- autoload :DidChange, 'solargraph/language_server/message/text_document/did_change'
11
- autoload :DidClose, 'solargraph/language_server/message/text_document/did_close'
12
- autoload :DidSave, 'solargraph/language_server/message/text_document/did_save'
13
- autoload :Hover, 'solargraph/language_server/message/text_document/hover'
14
- autoload :SignatureHelp, 'solargraph/language_server/message/text_document/signature_help'
15
- autoload :DiagnosticsQueue, 'solargraph/language_server/message/text_document/diagnostics_queue'
16
- autoload :OnTypeFormatting, 'solargraph/language_server/message/text_document/on_type_formatting'
17
- autoload :Definition, 'solargraph/language_server/message/text_document/definition'
18
- autoload :DocumentSymbol, 'solargraph/language_server/message/text_document/document_symbol'
19
- autoload :Formatting, 'solargraph/language_server/message/text_document/formatting'
20
- autoload :References, 'solargraph/language_server/message/text_document/references'
21
- autoload :Rename, 'solargraph/language_server/message/text_document/rename'
22
- autoload :PrepareRename, 'solargraph/language_server/message/text_document/prepare_rename'
23
- autoload :FoldingRange, 'solargraph/language_server/message/text_document/folding_range'
24
- autoload :CodeAction, 'solargraph/language_server/message/text_document/code_action'
7
+ autoload :Base, 'solargraph/language_server/message/text_document/base'
8
+ autoload :Completion, 'solargraph/language_server/message/text_document/completion'
9
+ autoload :DidOpen, 'solargraph/language_server/message/text_document/did_open'
10
+ autoload :DidChange, 'solargraph/language_server/message/text_document/did_change'
11
+ autoload :DidClose, 'solargraph/language_server/message/text_document/did_close'
12
+ autoload :DidSave, 'solargraph/language_server/message/text_document/did_save'
13
+ autoload :Hover, 'solargraph/language_server/message/text_document/hover'
14
+ autoload :SignatureHelp, 'solargraph/language_server/message/text_document/signature_help'
15
+ autoload :DiagnosticsQueue, 'solargraph/language_server/message/text_document/diagnostics_queue'
16
+ autoload :OnTypeFormatting, 'solargraph/language_server/message/text_document/on_type_formatting'
17
+ autoload :Definition, 'solargraph/language_server/message/text_document/definition'
18
+ autoload :DocumentSymbol, 'solargraph/language_server/message/text_document/document_symbol'
19
+ autoload :Formatting, 'solargraph/language_server/message/text_document/formatting'
20
+ autoload :References, 'solargraph/language_server/message/text_document/references'
21
+ autoload :Rename, 'solargraph/language_server/message/text_document/rename'
22
+ autoload :PrepareRename, 'solargraph/language_server/message/text_document/prepare_rename'
23
+ autoload :FoldingRange, 'solargraph/language_server/message/text_document/folding_range'
24
+ autoload :DocumentHighlight, 'solargraph/language_server/message/text_document/document_highlight'
25
25
  end
26
26
  end
27
27
  end
@@ -22,6 +22,7 @@ module Solargraph::LanguageServer::Message::Workspace
22
22
  (host.options['definitions'] ? y : n).push('textDocument/definition')
23
23
  (host.options['references'] ? y : n).push('textDocument/references')
24
24
  (host.options['folding'] ? y : n).push('textDocument/folding')
25
+ (host.options['highlights'] ? y : n).push('textDocument/documentHighlight')
25
26
  host.register_capabilities y
26
27
  host.unregister_capabilities n
27
28
  end
@@ -73,7 +73,8 @@ module Solargraph
73
73
  register 'textDocument/rename', TextDocument::Rename
74
74
  register 'textDocument/prepareRename', TextDocument::PrepareRename
75
75
  register 'textDocument/foldingRange', TextDocument::FoldingRange
76
- register 'textDocument/codeAction', TextDocument::CodeAction
76
+ # register 'textDocument/codeAction', TextDocument::CodeAction
77
+ register 'textDocument/documentHighlight', TextDocument::DocumentHighlight
77
78
  register 'workspace/didChangeWatchedFiles', Workspace::DidChangeWatchedFiles
78
79
  register 'workspace/didChangeConfiguration', Workspace::DidChangeConfiguration
79
80
  register 'workspace/didChangeWorkspaceFolders', Workspace::DidChangeWorkspaceFolders
@@ -216,33 +216,35 @@ module Solargraph
216
216
  # @param line [Integer]
217
217
  # @param column [Integer]
218
218
  # @param strip [Boolean] Strip special characters from variable names
219
+ # @param only [Boolean] Search for references in the current file only
219
220
  # @return [Array<Solargraph::Range>]
220
221
  # @todo Take a Location instead of filename/line/column
221
- def references_from filename, line, column, strip: false
222
+ def references_from filename, line, column, strip: false, only: false
222
223
  cursor = api_map.cursor_at(filename, Position.new(line, column))
223
224
  clip = api_map.clip(cursor)
224
- pins = clip.define
225
- return [] if pins.empty?
225
+ pin = clip.define.first
226
+ return [] unless pin
226
227
  result = []
227
- pins.uniq.each do |pin|
228
- (workspace.sources + (@current ? [@current] : [])).uniq(&:filename).each do |source|
229
- found = source.references(pin.name)
230
- found.select! do |loc|
231
- referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
232
- # HACK: The additional location comparison is necessary because
233
- # Clip#define can return proxies for parameter pins
234
- referenced.any? { |r| r == pin || r.location == pin.location }
235
- end
236
- # HACK: for language clients that exclude special characters from the start of variable names
237
- if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
238
- found.map! do |loc|
239
- Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
240
- end
228
+ files = if only
229
+ [api_map.source_map(filename)]
230
+ else
231
+ (workspace.sources + (@current ? [@current] : []))
232
+ end
233
+ files.uniq(&:filename).each do |source|
234
+ found = source.references(pin.name)
235
+ found.select! do |loc|
236
+ referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first
237
+ referenced && referenced.path == pin.path
238
+ end
239
+ # HACK: for language clients that exclude special characters from the start of variable names
240
+ if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
241
+ found.map! do |loc|
242
+ Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
241
243
  end
242
- result.concat(found.sort do |a, b|
243
- a.range.start.line <=> b.range.start.line
244
- end)
245
244
  end
245
+ result.concat(found.sort do |a, b|
246
+ a.range.start.line <=> b.range.start.line
247
+ end)
246
248
  end
247
249
  result.uniq
248
250
  end
@@ -518,6 +520,7 @@ module Solargraph
518
520
 
519
521
  def maybe_map source
520
522
  return unless source
523
+ return unless @current == source || workspace.has_file?(source.filename)
521
524
  if source_map_hash.key?(source.filename)
522
525
  return if source_map_hash[source.filename].code == source.code &&
523
526
  source_map_hash[source.filename].source.synchronized? &&
@@ -48,10 +48,16 @@ module Solargraph
48
48
  end
49
49
 
50
50
  def references source, name
51
+ if name.end_with?("=")
52
+ reg = /#{Regexp.escape name[0..-2]}\s*=/
53
+ extract_offset = ->(code, offset) { reg.match(code, offset).offset(0) }
54
+ else
55
+ extract_offset = ->(code, offset) { [soff = code.index(name, offset), soff + name.length] }
56
+ end
51
57
  inner_node_references(name, source.node).map do |n|
52
- offset = Position.to_offset(source.code, NodeMethods.get_node_start_position(n))
53
- soff = source.code.index(name, offset)
54
- eoff = soff + name.length
58
+ rng = Range.from_node(n)
59
+ offset = Position.to_offset(source.code, rng.start)
60
+ soff, eoff = extract_offset[source.code, offset]
55
61
  Location.new(
56
62
  source.filename,
57
63
  Range.new(
@@ -71,6 +71,23 @@ module Solargraph
71
71
  else
72
72
  raise "No idea what to do with #{n}"
73
73
  end
74
+ elsif n.type == :csend
75
+ if n.children[0].is_a?(::Parser::AST::Node)
76
+ result.concat generate_links(n.children[0])
77
+ args = []
78
+ n.children[2..-1].each do |c|
79
+ args.push NodeChainer.chain(c)
80
+ end
81
+ result.push Chain::QCall.new(n.children[1].to_s, args, @in_block > 0 || block_passed?(n))
82
+ elsif n.children[0].nil?
83
+ args = []
84
+ n.children[2..-1].each do |c|
85
+ args.push NodeChainer.chain(c)
86
+ end
87
+ result.push Chain::QCall.new(n.children[1].to_s, args, @in_block > 0 || block_passed?(n))
88
+ else
89
+ raise "No idea what to do with #{n}"
90
+ end
74
91
  elsif n.type == :self
75
92
  result.push Chain::Head.new('self')
76
93
  elsif n.type == :zsuper
@@ -178,6 +178,7 @@ module Solargraph
178
178
 
179
179
  # @param cursor [Solargraph::Source::Cursor]
180
180
  def find_recipient_node cursor
181
+ return repaired_find_recipient_node(cursor) if cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '('
181
182
  source = cursor.source
182
183
  position = cursor.position
183
184
  offset = cursor.offset
@@ -210,6 +211,12 @@ module Solargraph
210
211
  nil
211
212
  end
212
213
 
214
+ def repaired_find_recipient_node cursor
215
+ cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1])
216
+ node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first
217
+ return node if node && node.type == :send
218
+ end
219
+
213
220
  module DeepInference
214
221
  class << self
215
222
  CONDITIONAL = [:if, :unless]
@@ -5,16 +5,36 @@ module Solargraph
5
5
  module Legacy
6
6
  module NodeProcessors
7
7
  class BlockNode < Parser::NodeProcessor::Base
8
+ include Legacy::NodeMethods
9
+
8
10
  def process
11
+ location = get_node_location(node)
12
+ parent = if other_class_eval?
13
+ Solargraph::Pin::Namespace.new(
14
+ location: location,
15
+ type: :class,
16
+ name: unpack_name(node.children[0].children[0])
17
+ )
18
+ else
19
+ region.closure
20
+ end
9
21
  pins.push Solargraph::Pin::Block.new(
10
- location: get_node_location(node),
11
- closure: region.closure,
22
+ location: location,
23
+ closure: parent,
12
24
  receiver: node.children[0],
13
25
  comments: comments_for(node),
14
26
  scope: region.scope || region.closure.context.scope
15
27
  )
16
28
  process_children region.update(closure: pins.last)
17
29
  end
30
+
31
+ private
32
+
33
+ def other_class_eval?
34
+ node.children[0].type == :send &&
35
+ node.children[0].children[1] == :class_eval &&
36
+ [:cbase, :const].include?(node.children[0].children[0]&.type)
37
+ end
18
38
  end
19
39
  end
20
40
  end
@@ -40,11 +40,16 @@ module Solargraph
40
40
  # end
41
41
 
42
42
  def references source, name
43
+ if name.end_with?("=")
44
+ reg = /#{Regexp.escape name[0..-2]}\s*=/
45
+ extract_offset = ->(code, offset) { reg.match(code, offset).offset(0) }
46
+ else
47
+ extract_offset = ->(code, offset) { [soff = code.index(name, offset), soff + name.length] }
48
+ end
43
49
  inner_node_references(name, source.node).map do |n|
44
50
  rng = Range.from_node(n)
45
51
  offset = Position.to_offset(source.code, rng.start)
46
- soff = source.code.index(name, offset)
47
- eoff = soff + name.length
52
+ soff, eoff = extract_offset[source.code, offset]
48
53
  Location.new(
49
54
  source.filename,
50
55
  Range.new(
@@ -60,6 +60,11 @@ module Solargraph
60
60
  result.concat generate_links(c)
61
61
  end
62
62
  result.push Chain::Call.new(n.children[-2].to_s, node_to_argchains(n.children.last), @in_block > 0 || block_passed?(n))
63
+ elsif n.type == :QCALL
64
+ n.children[0..-3].each do |c|
65
+ result.concat generate_links(c)
66
+ end
67
+ result.push Chain::QCall.new(n.children[-2].to_s, node_to_argchains(n.children.last), @in_block > 0 || block_passed?(n))
63
68
  elsif n.type == :ATTRASGN
64
69
  result.concat generate_links(n.children[0])
65
70
  result.push Chain::Call.new(n.children[1].to_s, node_to_argchains(n.children[2]), @in_block > 0 || block_passed?(n))
@@ -141,36 +141,45 @@ module Solargraph
141
141
  class << self
142
142
  protected
143
143
 
144
+ # @param cursor [Source::Cursor]
145
+ # @return [RubyVM::AbstractSyntaxTree::Node, nil]
144
146
  def synchronized_find_recipient_node cursor
147
+ cursor = maybe_adjust_cursor(cursor)
145
148
  source = cursor.source
146
149
  position = cursor.position
147
150
  offset = cursor.offset
148
151
  tree = source.tree_at(position.line, position.column)
149
- tree.shift while tree.first && [:FCALL, :VCALL, :CALL].include?(tree.first.type) && !source.code_for(tree.first).strip.end_with?(')')
152
+ .select { |n| [:FCALL, :VCALL, :CALL].include?(n.type) }
153
+ unless source.repaired?
154
+ tree.shift while tree.first && !source.code_for(tree.first).strip.end_with?(')')
155
+ end
156
+ return tree.first if source.repaired? || source.code[0..offset-1] =~ /\(\s*$/
150
157
  tree.each do |node|
151
- if [:FCALL, :VCALL, :CALL].include?(node.type)
152
- args = node.children.find { |c| Parser.is_ast_node?(c) && [:ARRAY, :ZARRAY, :LIST].include?(c.type) }
153
- if args
154
- match = source.code[0..offset-1].match(/,[^\)]*\z/)
155
- rng = Solargraph::Range.from_node(args)
156
- if match
157
- rng = Solargraph::Range.new(rng.start, position)
158
- end
159
- return node if rng.contain?(position)
160
- elsif source.code[0..offset-1] =~ /\(\s*$/
161
- break unless source.code_for(node).strip.end_with?(')')
162
- return node
158
+ args = node.children.find { |c| Parser.is_ast_node?(c) && [:ARRAY, :ZARRAY, :LIST].include?(c.type) }
159
+ if args
160
+ match = source.code[0..offset-1].match(/,[^\)]*\z/)
161
+ rng = Solargraph::Range.from_node(args)
162
+ if match
163
+ rng = Solargraph::Range.new(rng.start, position)
163
164
  end
165
+ return node if rng.contain?(position)
164
166
  end
165
167
  end
166
168
  nil
167
169
  end
168
170
 
171
+ # @param cursor [Source::Cursor]
172
+ # @return [Source::Cursor]
173
+ def maybe_adjust_cursor cursor
174
+ return cursor unless (cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '(') || [',', ' '].include?(cursor.source.code[cursor.offset - 1])
175
+ cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1])
176
+ end
177
+
169
178
  def unsynchronized_find_recipient_node cursor
170
179
  source = cursor.source
171
180
  position = cursor.position
172
181
  offset = cursor.offset
173
- if source.code[0..offset-1] =~ /\([A-Zaz0-9_\s]*\z$/ #&& source.code[offset] == ')'
182
+ if source.code[0..offset-1] =~ /\([A-Zaz0-9_\s]*\z$/
174
183
  tree = source.tree_at(position.line, position.column - 1)
175
184
  if tree.first && [:FCALL, :VCALL, :CALL].include?(tree.first.type)
176
185
  return tree.first
@@ -5,16 +5,36 @@ module Solargraph
5
5
  module Rubyvm
6
6
  module NodeProcessors
7
7
  class BlockNode < Parser::NodeProcessor::Base
8
+ include NodeMethods
9
+
8
10
  def process
11
+ location = get_node_location(node)
12
+ parent = if other_class_eval?
13
+ Solargraph::Pin::Namespace.new(
14
+ location: location,
15
+ type: :class,
16
+ name: unpack_name(node.children[0].children[0])
17
+ )
18
+ else
19
+ region.closure
20
+ end
9
21
  pins.push Solargraph::Pin::Block.new(
10
- location: get_node_location(node),
11
- closure: region.closure,
22
+ location: location,
23
+ closure: parent,
12
24
  receiver: node.children[0],
13
25
  comments: comments_for(node),
14
26
  scope: region.scope || region.closure.context.scope
15
27
  )
16
28
  process_children region.update(closure: pins.last)
17
29
  end
30
+
31
+ private
32
+
33
+ def other_class_eval?
34
+ node.children[0].type == :CALL &&
35
+ node.children[0].children[1] == :class_eval &&
36
+ [:COLON2, :CONST].include?(node.children[0].children[0].type)
37
+ end
18
38
  end
19
39
  end
20
40
  end
@@ -8,9 +8,10 @@ module Solargraph
8
8
  # @return [Parser::AST::Node]
9
9
  attr_reader :receiver
10
10
 
11
- def initialize receiver: nil, args: [], **splat
11
+ def initialize receiver: nil, args: [], context: nil, **splat
12
12
  super(**splat)
13
13
  @receiver = receiver
14
+ @context = context
14
15
  @parameters = args
15
16
  end
16
17
 
@@ -44,15 +45,24 @@ module Solargraph
44
45
  return nil unless api_map.rebindable_method_names.include?(word)
45
46
  chain = Parser.chain(receiver, location.filename)
46
47
  locals = api_map.source_map(location.filename).locals_at(location)
47
- if ['instance_eval', 'instance_exec', 'class_eval', 'class_exec', 'module_eval', 'module_exec'].include?(chain.links.last.word)
48
+ links_last_word = chain.links.last.word
49
+ if %w[instance_eval instance_exec class_eval class_exec module_eval module_exec].include?(links_last_word)
48
50
  return chain.base.infer(api_map, self, locals)
49
- else
50
- receiver_pin = chain.define(api_map, self, locals).first
51
- if receiver_pin && receiver_pin.docstring
52
- ys = receiver_pin.docstring.tag(:yieldself)
53
- if ys && ys.types && !ys.types.empty?
54
- return ComplexType.try_parse(*ys.types).qualify(api_map, receiver_pin.context.namespace)
55
- end
51
+ end
52
+ if 'define_method' == links_last_word and chain.define(api_map, self, locals).first&.path == 'Module#define_method' # change class type to instance type
53
+ if chain.links.size > 1 # Class.define_method
54
+ ty = chain.base.infer(api_map, self, locals)
55
+ return Solargraph::ComplexType.parse(ty.namespace)
56
+ else # define_method without self
57
+ return Solargraph::ComplexType.parse(closure.binder.namespace)
58
+ end
59
+ end
60
+ # other case without early return, read block yieldself tags
61
+ receiver_pin = chain.define(api_map, self, locals).first
62
+ if receiver_pin && receiver_pin.docstring
63
+ ys = receiver_pin.docstring.tag(:yieldself)
64
+ if ys && ys.types && !ys.types.empty?
65
+ return ComplexType.try_parse(*ys.types).qualify(api_map, receiver_pin.context.namespace)
56
66
  end
57
67
  end
58
68
  nil
@@ -80,7 +80,7 @@ module Solargraph
80
80
  end
81
81
  end
82
82
  end
83
- sections.map(&:to_s).join
83
+ sections.map(&:to_s).join.strip
84
84
  end
85
85
  end
86
86
 
@@ -18,19 +18,7 @@ module Solargraph
18
18
  true
19
19
  end
20
20
 
21
- # @param other [Pin::Base] The caller's block
22
- # @param position [Position, Array(Integer, Integer)] The caller's position
23
- # @return [Boolean]
24
- def visible_from?(other, position)
25
- position = Position.normalize(position)
26
- other.filename == filename &&
27
- match_tags(other.full_context.tag, full_context.tag) &&
28
- (other == closure ||
29
- (closure.location.range.contain?(other.location.range.start) && closure.location.range.contain?(other.location.range.ending))
30
- ) &&
31
- presence.contain?(position)
32
- end
33
-
21
+ # @param other_closure [Pin::Closure]
34
22
  # @param other_loc [Location]
35
23
  def visible_at?(other_closure, other_loc)
36
24
  return true if location.filename == other_loc.filename &&
@@ -53,7 +41,7 @@ module Solargraph
53
41
  end
54
42
 
55
43
  def match_named_closure needle, haystack
56
- return true if needle == haystack
44
+ return true if needle == haystack || haystack.is_a?(Pin::Block)
57
45
  cursor = haystack
58
46
  until cursor.nil?
59
47
  return true if needle.path == cursor.path
@@ -9,8 +9,8 @@ module Solargraph
9
9
  # @return [::Symbol] :class or :module
10
10
  attr_reader :type
11
11
 
12
- # @param type [Symbol] :class or :module
13
- # @param visibility [Symbol] :public or :private
12
+ # @param type [::Symbol] :class or :module
13
+ # @param visibility [::Symbol] :public or :private
14
14
  # @param gates [Array<String>]
15
15
  def initialize type: :class, visibility: :public, gates: [''], **splat
16
16
  # super(location, namespace, name, comments)
@@ -51,6 +51,10 @@ module Solargraph
51
51
  clone.mark_head(false)
52
52
  end
53
53
 
54
+ def nullable?
55
+ false
56
+ end
57
+
54
58
  protected
55
59
 
56
60
  # Mark whether this link is the head of a chain
@@ -0,0 +1,11 @@
1
+ module Solargraph
2
+ class Source
3
+ class Chain
4
+ class QCall < Call
5
+ def nullable?
6
+ true
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -11,6 +11,7 @@ module Solargraph
11
11
  class Chain
12
12
  autoload :Link, 'solargraph/source/chain/link'
13
13
  autoload :Call, 'solargraph/source/chain/call'
14
+ autoload :QCall, 'solargraph/source/chain/q_call'
14
15
  autoload :Variable, 'solargraph/source/chain/variable'
15
16
  autoload :ClassVariable, 'solargraph/source/chain/class_variable'
16
17
  autoload :Constant, 'solargraph/source/chain/constant'
@@ -76,7 +77,8 @@ module Solargraph
76
77
  # @return [ComplexType]
77
78
  def infer api_map, name_pin, locals
78
79
  pins = define(api_map, name_pin, locals)
79
- infer_first_defined(pins, links.last.last_context, api_map)
80
+ type = infer_first_defined(pins, links.last.last_context, api_map)
81
+ maybe_nil(type)
80
82
  end
81
83
 
82
84
  # @return [Boolean]
@@ -101,6 +103,10 @@ module Solargraph
101
103
  @splat
102
104
  end
103
105
 
106
+ def nullable?
107
+ links.any?(&:nullable?)
108
+ end
109
+
104
110
  private
105
111
 
106
112
  # @param pins [Array<Pin::Base>]
@@ -146,6 +152,13 @@ module Solargraph
146
152
  return type if context.nil? || context.return_type.undefined?
147
153
  type.self_to(context.return_type.namespace)
148
154
  end
155
+
156
+ # @param type [ComplexType]
157
+ def maybe_nil type
158
+ return type if type.undefined? || type.void? || type.nullable?
159
+ return type unless nullable?
160
+ ComplexType.try_parse("#{type}, nil")
161
+ end
149
162
  end
150
163
  end
151
164
  end
@@ -42,7 +42,6 @@ module Solargraph
42
42
  @version = version
43
43
  @domains = []
44
44
  begin
45
- # @node, @comments = Source.parse_with_comments(@code, filename)
46
45
  @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename)
47
46
  @parsed = true
48
47
  rescue Parser::SyntaxError, EncodingError => e
@@ -336,7 +335,6 @@ module Solargraph
336
335
  # @param parent [Symbol]
337
336
  # @return [void]
338
337
  def inner_folding_ranges top, result = [], parent = nil
339
- # return unless top.is_a?(::Parser::AST::Node)
340
338
  return unless Parser.is_ast_node?(top)
341
339
  if FOLDING_NODE_TYPES.include?(top.type)
342
340
  # @todo Smelly exception for hash's first-level array in RubyVM
@@ -362,7 +360,7 @@ module Solargraph
362
360
  skip = nil
363
361
  comments.lines.each { |l|
364
362
  # Trim the comment and minimum leading whitespace
365
- p = l.gsub(/^#+/, '')
363
+ p = l.encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '')
366
364
  if p.strip.empty?
367
365
  next unless started
368
366
  ctxt.concat p
@@ -433,7 +431,6 @@ module Solargraph
433
431
  # @return [void]
434
432
  def inner_tree_at node, position, stack
435
433
  return if node.nil?
436
- # here = Range.from_to(node.loc.expression.line, node.loc.expression.column, node.loc.expression.last_line, node.loc.expression.last_column)
437
434
  here = Range.from_node(node)
438
435
  if here.contain?(position) || colonized(here, position, node)
439
436
  stack.unshift node
@@ -53,11 +53,7 @@ module Solargraph
53
53
  #
54
54
  # @return [Array<Solargraph::Pin::Base>]
55
55
  def locals
56
- loc_pos = context_pin.location.range.contain?(cursor.position) ? cursor.position : context_pin.location.range.ending
57
- adj_pos = Position.new(loc_pos.line, (loc_pos.column.zero? ? 0 : loc_pos.column - 1))
58
- @locals ||= source_map.locals.select { |pin|
59
- pin.visible_from?(block, adj_pos)
60
- }.reverse
56
+ @locals ||= source_map.locals_at(location)
61
57
  end
62
58
 
63
59
  def gates
@@ -92,6 +88,10 @@ module Solargraph
92
88
  @source_map ||= api_map.source_map(cursor.filename)
93
89
  end
94
90
 
91
+ def location
92
+ Location.new(source_map.filename, Solargraph::Range.new(cursor.position, cursor.position))
93
+ end
94
+
95
95
  # @return [Solargraph::Pin::Base]
96
96
  def block
97
97
  @block ||= source_map.locate_block_pin(cursor.node_position.line, cursor.node_position.character)
@@ -61,7 +61,7 @@ module Solargraph
61
61
  end
62
62
 
63
63
  def process_comment source_position, comment_position, comment
64
- return unless comment =~ MACRO_REGEXP
64
+ return unless comment.encode('UTF-8', invalid: :replace, replace: '?') =~ MACRO_REGEXP
65
65
  cmnt = remove_inline_comment_hashes(comment)
66
66
  parse = Solargraph::Source.parse_docstring(cmnt)
67
67
  last_line = 0
@@ -169,7 +169,7 @@ module Solargraph
169
169
  # @todo Handle parser errors in !parse directives
170
170
  end
171
171
  when 'domain'
172
- namespace = closure_at(source_position)
172
+ namespace = closure_at(source_position) || Pin::ROOT_PIN
173
173
  namespace.domains.concat directive.tag.types unless directive.tag.types.nil?
174
174
  when 'override'
175
175
  pins.push Pin::Reference::Override.new(location, directive.tag.name, docstring.tags)
@@ -182,7 +182,7 @@ module Solargraph
182
182
  started = false
183
183
  comment.lines.each { |l|
184
184
  # Trim the comment and minimum leading whitespace
185
- p = l.gsub(/^#/, '')
185
+ p = l.encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#/, '')
186
186
  if num.nil? && !p.strip.empty?
187
187
  num = p.index(/[^ ]/)
188
188
  started = true
@@ -197,7 +197,7 @@ module Solargraph
197
197
 
198
198
  # @return [void]
199
199
  def process_comment_directives
200
- return unless @code =~ MACRO_REGEXP
200
+ return unless @code.encode('UTF-8', invalid: :replace, replace: '?') =~ MACRO_REGEXP
201
201
  code_lines = @code.lines
202
202
  @source.associated_comments.each do |line, comments|
203
203
  src_pos = line ? Position.new(line, code_lines[line].to_s.chomp.index(/[^\s]/) || 0) : Position.new(code_lines.length, 0)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.43.2'
4
+ VERSION = '0.44.2'
5
5
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
4
+ require 'rubygems'
5
+
3
6
  module Solargraph
4
7
  # A workspace consists of the files in a project's directory and the
5
8
  # project's configuration. It provides a Source for each file to be used
@@ -168,19 +171,20 @@ module Solargraph
168
171
  # HACK: Evaluating gemspec files violates the goal of not running
169
172
  # workspace code, but this is how Gem::Specification.load does it
170
173
  # anyway.
171
- Dir.chdir base do
174
+ cmd = ['ruby', '-e', "require 'rubygems'; require 'json'; spec = eval(File.read('#{file}'), TOPLEVEL_BINDING, '#{file}'); return unless Gem::Specification === spec; puts({name: spec.name, paths: spec.require_paths}.to_json)"]
175
+ o, e, s = Open3.capture3(*cmd)
176
+ if s.success?
172
177
  begin
173
- # @type [Gem::Specification]
174
- spec = eval(File.read(file), TOPLEVEL_BINDING, file)
175
- next unless Gem::Specification === spec
176
- @gemnames.push spec.name
177
- result.concat(spec.require_paths.map { |path| File.join(base, path) })
178
- rescue RuntimeError, ScriptError, Errno::ENOENT => e
179
- # Don't die if we have an error during eval-ing a gem spec.
180
- # Concat the default lib directory instead.
178
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
179
+ next if hash.empty?
180
+ @gemnames.push hash['name']
181
+ result.concat(hash['paths'].map { |path| File.join(base, path) })
182
+ rescue StandardError => e
181
183
  Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}"
182
- result.push File.join(base, 'lib')
183
184
  end
185
+ else
186
+ Solargraph.logger.warn "Error reading #{file}"
187
+ Solargraph.logger.warn e
184
188
  end
185
189
  end
186
190
  result.concat(config.require_paths.map { |p| File.join(directory, p) })
@@ -10,6 +10,8 @@ module Solargraph
10
10
  # stdlib, and gems.
11
11
  #
12
12
  class YardMap
13
+ class NoYardocError < StandardError; end
14
+
13
15
  autoload :Cache, 'solargraph/yard_map/cache'
14
16
  autoload :CoreDocs, 'solargraph/yard_map/core_docs'
15
17
  autoload :CoreGen, 'solargraph/yard_map/core_gen'
@@ -86,7 +88,7 @@ module Solargraph
86
88
  @rebindable_method_names ||= pins_by_class(Pin::Method)
87
89
  .select { |pin| pin.comments && pin.comments.include?('@yieldself') }
88
90
  .map(&:name)
89
- .concat(['instance_eval', 'instance_exec', 'class_eval', 'class_exec', 'module_eval', 'module_exec'])
91
+ .concat(['instance_eval', 'instance_exec', 'class_eval', 'class_exec', 'module_eval', 'module_exec', 'define_method'])
90
92
  .to_set
91
93
  end
92
94
 
@@ -224,7 +226,7 @@ module Solargraph
224
226
  result.concat process_yardoc yd, spec
225
227
  result.concat add_gem_dependencies(spec) if with_dependencies?
226
228
  end
227
- rescue Gem::LoadError => e
229
+ rescue Gem::LoadError, NoYardocError => e
228
230
  base = r.split('/').first
229
231
  next if from_std.include?(base)
230
232
  from_std.push base
@@ -299,6 +301,7 @@ module Solargraph
299
301
  result = Marshal.load(dump)
300
302
  return result unless result.nil? || result.empty?
301
303
  Solargraph.logger.warn "Empty cache for #{spec.name} #{spec.version}. Reloading"
304
+ File.unlink ser
302
305
  rescue StandardError => e
303
306
  Solargraph.logger.warn "Error loading pin cache: [#{e.class}] #{e.message}"
304
307
  File.unlink ser
@@ -315,6 +318,7 @@ module Solargraph
315
318
  Solargraph.logger.info "Loading #{spec.name} #{spec.version} from #{y}"
316
319
  load_yardoc y
317
320
  result = Mapper.new(YARD::Registry.all, spec).map
321
+ raise NoYardocError, "Yardoc at #{y} is empty" if result.empty?
318
322
  if spec
319
323
  ser = File.join(CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}.ser")
320
324
  file = File.open(ser, 'wb')
data/solargraph.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |s|
34
34
  s.add_runtime_dependency 'tilt', '~> 2.0'
35
35
  s.add_runtime_dependency 'yard', '~> 0.9', '>= 0.9.24'
36
36
 
37
- s.add_development_dependency 'pry', '~> 0.11.3'
37
+ s.add_development_dependency 'pry'
38
38
  s.add_development_dependency 'public_suffix', '~> 3.1'
39
39
  s.add_development_dependency 'rspec', '~> 3.5', '>= 3.5.0'
40
40
  s.add_development_dependency 'simplecov', '~> 0.14'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.43.2
4
+ version: 0.44.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-23 00:00:00.000000000 Z
11
+ date: 2021-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backport
@@ -222,16 +222,16 @@ dependencies:
222
222
  name: pry
223
223
  requirement: !ruby/object:Gem::Requirement
224
224
  requirements:
225
- - - "~>"
225
+ - - ">="
226
226
  - !ruby/object:Gem::Version
227
- version: 0.11.3
227
+ version: '0'
228
228
  type: :development
229
229
  prerelease: false
230
230
  version_requirements: !ruby/object:Gem::Requirement
231
231
  requirements:
232
- - - "~>"
232
+ - - ">="
233
233
  - !ruby/object:Gem::Version
234
- version: 0.11.3
234
+ version: '0'
235
235
  - !ruby/object:Gem::Dependency
236
236
  name: public_suffix
237
237
  requirement: !ruby/object:Gem::Requirement
@@ -381,6 +381,7 @@ files:
381
381
  - lib/solargraph/language_server/message/text_document/did_close.rb
382
382
  - lib/solargraph/language_server/message/text_document/did_open.rb
383
383
  - lib/solargraph/language_server/message/text_document/did_save.rb
384
+ - lib/solargraph/language_server/message/text_document/document_highlight.rb
384
385
  - lib/solargraph/language_server/message/text_document/document_symbol.rb
385
386
  - lib/solargraph/language_server/message/text_document/folding_range.rb
386
387
  - lib/solargraph/language_server/message/text_document/formatting.rb
@@ -509,6 +510,7 @@ files:
509
510
  - lib/solargraph/source/chain/link.rb
510
511
  - lib/solargraph/source/chain/literal.rb
511
512
  - lib/solargraph/source/chain/or.rb
513
+ - lib/solargraph/source/chain/q_call.rb
512
514
  - lib/solargraph/source/chain/variable.rb
513
515
  - lib/solargraph/source/chain/z_super.rb
514
516
  - lib/solargraph/source/change.rb