solargraph 0.40.4 → 0.41.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eff7d664014ae6d25990957afb4328baafe88ff76ef892db8b3d50791860845e
4
- data.tar.gz: 8038c884a63ce269f1a0faaf635e4c295fadd61c456ba2665c84a17b4f03a031
3
+ metadata.gz: eed08215481576c83b9ff5014648c0fa5fe7e458eb59eabcf86632496e4a9f32
4
+ data.tar.gz: 8f036f65304ecf1401504a84a49bc34ed6d9be97308be8a1e080be884af100fc
5
5
  SHA512:
6
- metadata.gz: a7b95e2ba342b191642a1d5ea228243a60b9211f6c05cab2acf09ddcaab3efb25cef488ca910964e0ac1c32edacc2f0ced4eef09fa819b03d2919af4ac734ca7
7
- data.tar.gz: 6990c07591d43fdb583484c00fc5221d228850fa75e12b5cd2632413a21c1984f634ce2655371d9be7016a035e23fe622469418779bf3285f03a087a38c394ae
6
+ metadata.gz: '024920c42f1f1e37f604b616481bca07b91ec52ed9fd315e6e12a6cc0ab7c0a88ef9ecd827866ab52d66d2f43b679a97c68d3877b88adc5092fc9c2dc2dca5a1'
7
+ data.tar.gz: 02c6de30fdac85feedbee8e8b3cbd35ea846a9cbb3a5cb8c476053208a63ae5781a45382fcca43e760875f3f5f911675bdec3f8ba318b122ef6ea0c42f735a29
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.41.0 - May 30, 2021
2
+ - Chain constant at last double colon with more than two nested namespaces
3
+ - Fill Integer#times return type (#440)
4
+ - Validate included modules in type checks (#424)
5
+ - Faster language server initialization
6
+ - Server response to initialize is near immediate
7
+ - Workspace is mapped in a background thread
8
+ - Supported clients report mapping progress
9
+ - Log RuboCop corrections at info level (#426)
10
+ - Allow configuring the version of RuboCop to require (#430)
11
+ - Fix source of diagnostic (#434)
12
+ - Fix file argument in RuboCop (#435)
13
+ - Config ignores directories with .rb extension (#423)
14
+
1
15
  ## 0.40.4 - March 3, 2021
2
16
  - Fix optarg and blockarg ordering
3
17
  - Override specialization for #initialize
data/README.md CHANGED
@@ -79,6 +79,21 @@ Run `bundle install` and use `bundle exec yard gems` to generate the documentati
79
79
 
80
80
  In order to make sure you're using the correct dependencies, you can start the language server with Bundler. In VS Code, there's a `solargraph.useBundler` option. Other clients will vary, but the command you probably want to run is `bundle exec solargraph socket` or `bundle exec solargraph stdio`.
81
81
 
82
+ ### Rubocop Version
83
+
84
+ If you have multiple versions of [`rubocop`](https://rubygems.org/gems/rubocop) installed and you would like to choose a version other than the latest to use, this specific version can be configured.
85
+
86
+ In `.solargraph.yml`:
87
+
88
+ ```yaml
89
+ ---
90
+ reporters:
91
+ - rubocop:version=0.61.0 # diagnostics
92
+ formatter:
93
+ rubocop:
94
+ version: 0.61.0 # formatting
95
+ ```
96
+
82
97
  ### Integrating Other Editors
83
98
 
84
99
  The [language server protocol](https://microsoft.github.io/language-server-protocol/specification) is the recommended way for integrating Solargraph into editors and IDEs. Clients can connect using either stdio or TCP. Language client developers should refer to [https://solargraph.org/guides/language-server](https://solargraph.org/guides/language-server).
data/SPONSORS.md CHANGED
@@ -13,3 +13,4 @@ The following people and organizations provide funding or other resources. [Beco
13
13
  - Emily Strickland
14
14
  - Tom de Grunt
15
15
  - Akira Yamada
16
+ - Jared White
data/lib/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  Layout/EndOfLine:
2
- EnforcedStyle: lf
2
+ Enabled: false
3
3
  Style/MethodDefParentheses:
4
4
  Enabled: false
5
5
  Layout/EmptyLineAfterGuardClause:
data/lib/solargraph.rb CHANGED
@@ -9,13 +9,14 @@ require 'solargraph/version'
9
9
  # static analysis, and language server libraries.
10
10
  #
11
11
  module Solargraph
12
- class InvalidOffsetError < RangeError; end
13
- class DiagnosticsError < RuntimeError; end
14
- class FileNotFoundError < RuntimeError; end
15
- class SourceNotAvailableError < StandardError; end
16
- class ComplexTypeError < StandardError; end
17
- class WorkspaceTooLargeError < RuntimeError; end
18
- class BundleNotFoundError < StandardError; end
12
+ class InvalidOffsetError < RangeError; end
13
+ class DiagnosticsError < RuntimeError; end
14
+ class FileNotFoundError < RuntimeError; end
15
+ class SourceNotAvailableError < StandardError; end
16
+ class ComplexTypeError < StandardError; end
17
+ class WorkspaceTooLargeError < RuntimeError; end
18
+ class BundleNotFoundError < StandardError; end
19
+ class InvalidRubocopVersionError < RuntimeError; end
19
20
 
20
21
  autoload :Position, 'solargraph/position'
21
22
  autoload :Range, 'solargraph/range'
@@ -33,7 +33,12 @@ module Solargraph
33
33
  # @param pins [Array<Pin::Base>]
34
34
  # @return [self]
35
35
  def index pins
36
- catalog Bench.new(pins: pins)
36
+ # @todo This implementation is incomplete. It should probably create a
37
+ # Bench.
38
+ @source_map_hash = {}
39
+ implicit.clear
40
+ cache.clear
41
+ @store = Store.new(yard_map.pins + pins)
37
42
  self
38
43
  end
39
44
 
@@ -42,97 +47,55 @@ module Solargraph
42
47
  # @param source [Source]
43
48
  # @return [self]
44
49
  def map source
45
- catalog Bench.new(opened: [source])
50
+ map = Solargraph::SourceMap.map(source)
51
+ catalog Bench.new(source_maps: [map])
46
52
  self
47
53
  end
48
54
 
49
- # @param name [String]
50
- # @return [YARD::Tags::MacroDirective, nil]
51
- def named_macro name
52
- store.named_macros[name]
53
- end
54
-
55
55
  # Catalog a bench.
56
56
  #
57
57
  # @param bench [Bench]
58
- # @return [self]
59
58
  def catalog bench
60
- new_map_hash = {}
61
- # Bench always needs to be merged if it adds or removes sources
62
- merged = (bench.sources.length == source_map_hash.values.length)
63
- bench.sources.each do |source|
64
- if source_map_hash.key?(source.filename)
65
- if source_map_hash[source.filename].code == source.code &&
66
- source_map_hash[source.filename].source.synchronized? &&
67
- source.synchronized?
68
- new_map_hash[source.filename] = source_map_hash[source.filename]
69
- elsif !source.synchronized?
70
- new_map_hash[source.filename] = source_map_hash[source.filename]
71
- # @todo Smelly instance variable access
72
- new_map_hash[source.filename].instance_variable_set(:@source, source)
73
- else
74
- map = Solargraph::SourceMap.map(source)
75
- if source_map_hash[source.filename].try_merge!(map)
76
- new_map_hash[source.filename] = source_map_hash[source.filename]
77
- else
78
- new_map_hash[source.filename] = map
79
- merged = false
80
- end
81
- end
82
- else
83
- map = Solargraph::SourceMap.map(source)
84
- new_map_hash[source.filename] = map
85
- merged = false
86
- end
87
- end
88
- return self if bench.pins.empty? && @store && merged
89
59
  implicit.clear
60
+ @cache.clear
61
+ @source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
90
62
  pins = []
91
- reqs = Set.new
92
- # @param map [SourceMap]
93
- new_map_hash.each_value do |map|
63
+ @required = Set.new
64
+ local_path_hash.clear
65
+ source_map_hash.each_value do |map|
94
66
  pins.concat map.pins
95
- reqs.merge map.requires.map(&:name)
96
- end
97
- pins.concat bench.pins
98
- reqs.merge bench.workspace.config.required
99
- @required = reqs
100
- bench.sources.each do |src|
101
- implicit.merge new_map_hash[src.filename].environ
67
+ @required.merge map.requires.map(&:name)
68
+ implicit.merge map.environ
102
69
  end
103
- # implicit.merge Convention.for_global(self)
104
- local_path_hash.clear
105
- unless bench.workspace.require_paths.empty?
106
- file_keys = new_map_hash.keys
107
- workspace_path = Pathname.new(bench.workspace.directory)
108
- reqs.delete_if do |r|
109
- bench.workspace.require_paths.any? do |base|
110
- pn = workspace_path.join(base, "#{r}.rb").to_s
111
- if file_keys.include? pn
112
- local_path_hash[r] = pn
113
- true
114
- else
115
- false
116
- end
117
- end
70
+ @required.merge implicit.requires
71
+ external_requires = []
72
+ @required.each do |req|
73
+ result = bench.load_paths.find do |path|
74
+ full = Pathname.new(path).join("#{req}.rb").to_s
75
+ @source_map_hash.key?(full)
76
+ end
77
+ if result
78
+ local_path_hash[req] = Pathname.new(result).join("#{req}.rb").to_s
79
+ else
80
+ external_requires.push req unless result
118
81
  end
119
82
  end
120
- reqs.merge implicit.requires
121
- br = reqs.include?('bundler/require') ? require_from_bundle(bench.workspace.directory) : {}
122
- reqs.merge br.keys
123
- yard_map.change(reqs.to_a, br, bench.workspace.gemnames)
124
- new_store = Store.new(yard_map.pins + implicit.pins + pins)
125
- @cache.clear
126
- @source_map_hash = new_map_hash
127
- @store = new_store
83
+ br = @required.include?('bundler/require') ? bench.gemnames.to_h { |gs| [gs.name, gs] } : {}
84
+ @required.merge br.keys
85
+ yard_map.change(external_requires, br, bench.gemnames)
86
+ @store = Store.new(yard_map.pins + implicit.pins + pins)
128
87
  @unresolved_requires = yard_map.unresolved_requires
129
- workspace_filenames.clear
130
- workspace_filenames.concat bench.workspace.filenames
131
88
  @rebindable_method_names = nil
132
89
  store.block_pins.each { |blk| blk.rebind(self) }
133
90
  self
134
91
  end
135
92
 
93
+ # @param name [String]
94
+ # @return [YARD::Tags::MacroDirective, nil]
95
+ def named_macro name
96
+ store.named_macros[name]
97
+ end
98
+
136
99
  def required
137
100
  @required ||= Set.new
138
101
  end
@@ -173,7 +136,10 @@ module Solargraph
173
136
  def self.load directory
174
137
  api_map = new
175
138
  workspace = Solargraph::Workspace.new(directory)
176
- api_map.catalog Bench.new(workspace: workspace)
139
+ # api_map.catalog Bench.new(workspace: workspace)
140
+ library = Library.new(workspace)
141
+ library.map!
142
+ api_map.catalog library.bench
177
143
  api_map
178
144
  end
179
145
 
@@ -531,6 +497,15 @@ module Solargraph
531
497
  @yard_map ||= YardMap.new
532
498
  end
533
499
 
500
+ # Check if the host class includes the specified module.
501
+ #
502
+ # @param host [String] The class
503
+ # @param mod [String] The module
504
+ # @return [Boolean]
505
+ def type_include?(host, mod)
506
+ store.get_includes(host).include?(mod)
507
+ end
508
+
534
509
  private
535
510
 
536
511
  # @return [Array<String>]
@@ -544,7 +519,9 @@ module Solargraph
544
519
  attr_reader :source_map_hash
545
520
 
546
521
  # @return [ApiMap::Store]
547
- attr_reader :store
522
+ def store
523
+ @store ||= Store.new
524
+ end
548
525
 
549
526
  # @return [Solargraph::ApiMap::Cache]
550
527
  attr_reader :cache
@@ -1,30 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Solargraph
4
- # An aggregation of a workspace and additional sources to be cataloged in an
5
- # ApiMap.
6
+ # A container of source maps and gem specs to be cataloged in an ApiMap.
6
7
  #
7
8
  class Bench
8
- # @return [Workspace]
9
- attr_reader :workspace
10
-
11
- # @return [Array<Source>]
12
- attr_reader :opened
9
+ # @return [Set<SourceMap>]
10
+ attr_reader :source_maps
13
11
 
14
- # @return [Array<Pin::Base>]
15
- attr_reader :pins
12
+ # @return [Set<String>]
13
+ attr_reader :load_paths
16
14
 
17
- # @param workspace [Workspace]
18
- # @param opened [Array<Source>]
19
- def initialize workspace: Workspace.new, opened: [], pins: []
20
- @workspace = workspace
21
- @opened = opened
22
- @pins = pins
23
- end
15
+ # @return [Set<String>]
16
+ attr_reader :gemnames
24
17
 
25
- # @return [Array<Source>]
26
- def sources
27
- @sources ||= (opened + workspace.sources).uniq(&:filename)
18
+ # @param source_maps [Array<SourceMap>, Set<SourceMap>]
19
+ # @param load_paths [Array<String>, Set<String>]
20
+ # @param gemnames [Array<String>, Set<String>]
21
+ def initialize source_maps: [], load_paths: [], gemnames: []
22
+ @source_maps = source_maps.to_set
23
+ @load_paths = load_paths.to_set
24
+ @gemnames = gemnames.to_set
28
25
  end
29
26
  end
30
27
  end
@@ -1,9 +1,23 @@
1
+ unless Hash.method_defined?(:transform_keys)
2
+ class Hash
3
+ def transform_keys &block
4
+ result = {}
5
+ each_pair do |k, v|
6
+ result[block.call(k)] = v
7
+ end
8
+ result
9
+ end
10
+ end
11
+ end
12
+
1
13
  unless Hash.method_defined?(:transform_values)
2
14
  class Hash
3
15
  def transform_values &block
16
+ result = {}
4
17
  each_pair do |k, v|
5
- self[k] = block.call(v)
18
+ result[k] = block.call(v)
6
19
  end
20
+ result
7
21
  end
8
22
  end
9
23
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubocop'
4
3
  require 'stringio'
5
4
 
6
5
  module Solargraph
@@ -23,6 +22,7 @@ module Solargraph
23
22
  # @param _api_map [Solargraph::ApiMap]
24
23
  # @return [Array<Hash>]
25
24
  def diagnose source, _api_map
25
+ require_rubocop(rubocop_version)
26
26
  options, paths = generate_options(source.filename, source.code)
27
27
  store = RuboCop::ConfigStore.new
28
28
  runner = RuboCop::Runner.new(options, store)
@@ -36,6 +36,13 @@ module Solargraph
36
36
 
37
37
  private
38
38
 
39
+ # Extracts the rubocop version from _args_
40
+ #
41
+ # @return [String]
42
+ def rubocop_version
43
+ args.find { |a| a =~ /version=/ }.to_s.split('=').last
44
+ end
45
+
39
46
  # @param resp [Hash]
40
47
  # @return [Array<Hash>]
41
48
  def make_array resp
@@ -57,7 +64,8 @@ module Solargraph
57
64
  range: offense_range(off).to_hash,
58
65
  # 1 = Error, 2 = Warning, 3 = Information, 4 = Hint
59
66
  severity: SEVERITIES[off['severity']],
60
- source: off['cop_name'],
67
+ source: 'rubocop',
68
+ code: off['cop_name'],
61
69
  message: off['message'].gsub(/^#{off['cop_name']}\:/, '')
62
70
  }
63
71
  end
@@ -7,6 +7,24 @@ module Solargraph
7
7
  module RubocopHelpers
8
8
  module_function
9
9
 
10
+ # Requires a specific version of rubocop, or the latest installed version
11
+ # if _version_ is `nil`.
12
+ #
13
+ # @param version [String]
14
+ # @raise [InvalidRubocopVersionError] if _version_ is not installed
15
+ def require_rubocop(version = nil)
16
+ begin
17
+ gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path
18
+ gem_lib_path = File.join(gem_path, 'lib')
19
+ $LOAD_PATH.unshift(gem_lib_path) unless $LOAD_PATH.include?(gem_lib_path)
20
+ rescue Gem::MissingSpecVersionError => e
21
+ raise InvalidRubocopVersionError,
22
+ "could not find '#{e.name}' (#{e.requirement}) - "\
23
+ "did find: [#{e.specs.map { |s| s.version.version }.join(', ')}]"
24
+ end
25
+ require 'rubocop'
26
+ end
27
+
10
28
  # Generate command-line options for the specified filename and code.
11
29
  #
12
30
  # @param filename [String]
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'observer'
4
4
  require 'set'
5
+ require 'securerandom'
5
6
 
6
7
  module Solargraph
7
8
  module LanguageServer
@@ -192,7 +193,7 @@ module Solargraph
192
193
  def diagnose uri
193
194
  if sources.include?(uri)
194
195
  library = library_for(uri)
195
- if library.synchronized?
196
+ if library.mapped? && library.synchronized?
196
197
  logger.info "Diagnosing #{uri}"
197
198
  begin
198
199
  results = library.diagnose uri_to_file(uri)
@@ -277,6 +278,7 @@ module Solargraph
277
278
  begin
278
279
  lib = Solargraph::Library.load(path, name)
279
280
  libraries.push lib
281
+ async_library_map lib
280
282
  rescue WorkspaceTooLargeError => e
281
283
  send_notification 'window/showMessage', {
282
284
  'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
@@ -631,6 +633,7 @@ module Solargraph
631
633
 
632
634
  # @return [void]
633
635
  def catalog
636
+ return unless libraries.all?(&:mapped?)
634
637
  libraries.each(&:catalog)
635
638
  end
636
639
 
@@ -741,6 +744,71 @@ module Solargraph
741
744
  def prepare_rename?
742
745
  client_capabilities['rename'] && client_capabilities['rename']['prepareSupport']
743
746
  end
747
+
748
+ def client_supports_progress?
749
+ client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
750
+ end
751
+
752
+ # @param library [Library]
753
+ # @return [void]
754
+ def async_library_map library
755
+ return if library.mapped?
756
+ Thread.new do
757
+ if client_supports_progress?
758
+ uuid = SecureRandom.uuid
759
+ send_request 'window/workDoneProgress/create', {
760
+ token: uuid
761
+ } do |response|
762
+ do_async_library_map library, response.nil? ? uuid : nil
763
+ end
764
+ else
765
+ do_async_library_map library
766
+ end
767
+ end
768
+ end
769
+
770
+ def do_async_library_map library, uuid = nil
771
+ total = library.workspace.sources.length
772
+ if uuid
773
+ send_notification '$/progress', {
774
+ token: uuid,
775
+ value: {
776
+ kind: 'begin',
777
+ title: "Mapping workspace",
778
+ message: "0/#{total} files",
779
+ cancellable: false,
780
+ percentage: 0
781
+ }
782
+ }
783
+ end
784
+ pct = 0
785
+ mod = 10
786
+ while library.next_map
787
+ next unless uuid
788
+ cur = ((library.source_map_hash.keys.length.to_f / total.to_f) * 100).to_i
789
+ if cur > pct && cur % mod == 0
790
+ pct = cur
791
+ send_notification '$/progress', {
792
+ token: uuid,
793
+ value: {
794
+ kind: 'report',
795
+ cancellable: false,
796
+ message: "#{library.source_map_hash.keys.length}/#{total} files",
797
+ percentage: pct
798
+ }
799
+ }
800
+ end
801
+ end
802
+ if uuid
803
+ send_notification '$/progress', {
804
+ token: uuid,
805
+ value: {
806
+ kind: 'end',
807
+ message: 'Mapping complete'
808
+ }
809
+ }
810
+ end
811
+ end
744
812
  end
745
813
  end
746
814
  end
@@ -21,6 +21,7 @@ module Solargraph
21
21
  docs = pins
22
22
  .reject { |pin| pin.documentation.empty? && pin.return_type.undefined? }
23
23
  result = params
24
+ .transform_keys(&:to_sym)
24
25
  .merge(pins.first.resolve_completion_item)
25
26
  .merge(documentation: markup_content(join_docs(docs)))
26
27
  result[:detail] = pins.first.detail
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Make sure the environment page can report RuboCop's version
4
- require 'rubocop'
5
-
6
3
  module Solargraph
7
4
  module LanguageServer
8
5
  module Message
@@ -12,6 +9,9 @@ module Solargraph
12
9
  #
13
10
  class Environment < Base
14
11
  def process
12
+ # Make sure the environment page can report RuboCop's version
13
+ require 'rubocop'
14
+
15
15
  page = Solargraph::Page.new(host.options['viewsPath'])
16
16
  content = page.render('environment', layout: true, locals: { config: host.options, folders: host.folders })
17
17
  set_result(
@@ -1,49 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'benchmark'
4
-
5
3
  module Solargraph
6
4
  module LanguageServer
7
5
  module Message
8
6
  class Initialize < Base
9
7
  def process
10
- bm = Benchmark.measure {
11
- host.configure params['initializationOptions']
12
- host.client_capabilities = params['capabilities']
13
- if support_workspace_folders?
14
- host.prepare_folders params['workspaceFolders']
15
- elsif params['rootUri']
16
- host.prepare UriHelpers.uri_to_file(params['rootUri'])
17
- else
18
- host.prepare params['rootPath']
19
- end
20
- result = {
21
- capabilities: {
22
- textDocumentSync: 2, # @todo What should this be?
23
- workspace: {
24
- workspaceFolders: {
25
- supported: true,
26
- changeNotifications: true
27
- }
8
+ host.configure params['initializationOptions']
9
+ host.client_capabilities = params['capabilities']
10
+ if support_workspace_folders?
11
+ host.prepare_folders params['workspaceFolders']
12
+ elsif params['rootUri']
13
+ host.prepare UriHelpers.uri_to_file(params['rootUri'])
14
+ else
15
+ host.prepare params['rootPath']
16
+ end
17
+ result = {
18
+ capabilities: {
19
+ textDocumentSync: 2, # @todo What should this be?
20
+ workspace: {
21
+ workspaceFolders: {
22
+ supported: true,
23
+ changeNotifications: true
28
24
  }
29
25
  }
30
26
  }
31
- result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
32
- result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
33
- # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
34
- result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
35
- result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
36
- result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
37
- result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
38
- result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
39
- result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
40
- result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
41
- result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
42
- # @todo Temporarily disabled
43
- # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
44
- set_result result
45
27
  }
46
- Solargraph.logger.unknown "Solargraph initialized (#{bm.real} seconds)"
28
+ result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
29
+ result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
30
+ # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
31
+ result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
32
+ result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
33
+ result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
34
+ result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
35
+ result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
36
+ result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
37
+ result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
38
+ result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
39
+ # @todo Temporarily disabled
40
+ # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
41
+ set_result result
47
42
  end
48
43
 
49
44
  private
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubocop'
4
3
  require 'securerandom'
5
4
  require 'tmpdir'
6
5
 
@@ -11,21 +10,22 @@ module Solargraph
11
10
  class Formatting < Base
12
11
  include Solargraph::Diagnostics::RubocopHelpers
13
12
 
14
- class BlankRubocopFormatter < ::RuboCop::Formatter::BaseFormatter; end
15
-
16
13
  def process
17
14
  file_uri = params['textDocument']['uri']
18
15
  config = config_for(file_uri)
19
16
  original = host.read_text(file_uri)
20
17
  args = cli_args(file_uri, config)
21
18
 
19
+ require_rubocop(config['version'])
22
20
  options, paths = RuboCop::Options.new.parse(args)
23
21
  options[:stdin] = original
24
- redirect_stdout do
22
+ corrections = redirect_stdout do
25
23
  RuboCop::Runner.new(options, RuboCop::ConfigStore.new).run(paths)
26
24
  end
27
25
  result = options[:stdin]
28
26
 
27
+ log_corrections(corrections)
28
+
29
29
  format original, result
30
30
  rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
31
31
  set_error(Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}")
@@ -33,6 +33,17 @@ module Solargraph
33
33
 
34
34
  private
35
35
 
36
+ def log_corrections(corrections)
37
+ corrections = corrections&.strip
38
+ return if corrections&.empty?
39
+
40
+ Solargraph.logger.info('Formatting result:')
41
+ corrections.each_line do |line|
42
+ next if line.strip.empty?
43
+ Solargraph.logger.info(line.strip)
44
+ end
45
+ end
46
+
36
47
  def config_for(file_uri)
37
48
  conf = host.formatter_config(file_uri)
38
49
  return {} unless conf.is_a?(Hash)
@@ -40,12 +51,12 @@ module Solargraph
40
51
  conf['rubocop'] || {}
41
52
  end
42
53
 
43
- def cli_args file, config
54
+ def cli_args file_uri, config
55
+ file = UriHelpers.uri_to_file(file_uri)
44
56
  args = [
45
57
  config['cops'] == 'all' ? '--auto-correct-all' : '--auto-correct',
46
58
  '--cache', 'false',
47
- '--format', 'Solargraph::LanguageServer::Message::' \
48
- 'TextDocument::Formatting::BlankRubocopFormatter',
59
+ '--format', formatter_class(config).name,
49
60
  ]
50
61
 
51
62
  ['except', 'only'].each do |arg|
@@ -57,6 +68,16 @@ module Solargraph
57
68
  args + [file]
58
69
  end
59
70
 
71
+ def formatter_class(config)
72
+ if self.class.const_defined?('BlankRubocopFormatter')
73
+ BlankRubocopFormatter
74
+ else
75
+ require_rubocop(config['version'])
76
+ klass = Class.new(::RuboCop::Formatter::BaseFormatter)
77
+ self.class.const_set 'BlankRubocopFormatter', klass
78
+ end
79
+ end
80
+
60
81
  def cop_list(value)
61
82
  value = value.join(',') if value.respond_to?(:join)
62
83
  return nil if value == '' || !value.is_a?(String)
@@ -17,7 +17,7 @@ module Solargraph
17
17
  if !this_link.nil? && this_link != last_link
18
18
  parts.push this_link
19
19
  end
20
- parts.push pin.detail.gsub(':', '\\:') unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
20
+ parts.push "`" + pin.detail.gsub(':', '\\:') + "`" unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
21
21
  parts.push pin.documentation unless pin.documentation.nil? || pin.documentation.empty?
22
22
  unless parts.empty?
23
23
  data = parts.join("\n\n")
@@ -20,9 +20,7 @@ module Solargraph
20
20
  def initialize workspace = Solargraph::Workspace.new, name = nil
21
21
  @workspace = workspace
22
22
  @name = name
23
- api_map.catalog bench
24
- @synchronized = true
25
- @catalog_mutex = Mutex.new
23
+ @synchronized = false
26
24
  end
27
25
 
28
26
  def inspect
@@ -48,9 +46,13 @@ module Solargraph
48
46
  # @return [void]
49
47
  def attach source
50
48
  mutex.synchronize do
51
- @synchronized = (@current == source) if synchronized?
49
+ if @current && @current.filename != source.filename && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
50
+ source_map_hash.delete @current.filename
51
+ @synchronized = false
52
+ end
52
53
  @current = source
53
- catalog
54
+ maybe_map @current
55
+ api_map.catalog bench unless synchronized?
54
56
  end
55
57
  end
56
58
 
@@ -110,9 +112,9 @@ module Solargraph
110
112
  mutex.synchronize do
111
113
  next if File.directory?(filename) || !File.exist?(filename)
112
114
  next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
113
- @synchronized = false
114
115
  source = Solargraph::Source.load_string(File.read(filename), filename)
115
116
  workspace.merge(source)
117
+ maybe_map source
116
118
  result = true
117
119
  end
118
120
  result
@@ -158,6 +160,8 @@ module Solargraph
158
160
  position = Position.new(line, column)
159
161
  cursor = Source::Cursor.new(read(filename), position)
160
162
  api_map.clip(cursor).complete
163
+ rescue FileNotFoundError => e
164
+ handle_file_not_found filename, e
161
165
  end
162
166
 
163
167
  # Get definition suggestions for the expression at the specified file and
@@ -186,6 +190,8 @@ module Solargraph
186
190
  else
187
191
  api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
188
192
  end
193
+ rescue FileNotFoundError => e
194
+ handle_file_not_found(filename, e)
189
195
  end
190
196
 
191
197
  # Get signature suggestions for the method at the specified file and
@@ -260,14 +266,12 @@ module Solargraph
260
266
  # @param query [String]
261
267
  # @return [Array<YARD::CodeObjects::Base>]
262
268
  def document query
263
- catalog
264
269
  api_map.document query
265
270
  end
266
271
 
267
272
  # @param query [String]
268
273
  # @return [Array<String>]
269
274
  def search query
270
- catalog
271
275
  api_map.search query
272
276
  end
273
277
 
@@ -276,7 +280,6 @@ module Solargraph
276
280
  # @param query [String]
277
281
  # @return [Array<Pin::Base>]
278
282
  def query_symbols query
279
- catalog
280
283
  api_map.query_symbols query
281
284
  end
282
285
 
@@ -295,10 +298,13 @@ module Solargraph
295
298
  # @param path [String]
296
299
  # @return [Array<Solargraph::Pin::Base>]
297
300
  def path_pins path
298
- catalog
299
301
  api_map.get_path_suggestions(path)
300
302
  end
301
303
 
304
+ def source_maps
305
+ source_map_hash.values
306
+ end
307
+
302
308
  # Get the current text of a file in the library.
303
309
  #
304
310
  # @param filename [String]
@@ -318,7 +324,6 @@ module Solargraph
318
324
  # be an option to do so.
319
325
  #
320
326
  return [] unless open?(filename)
321
- catalog
322
327
  result = []
323
328
  source = read(filename)
324
329
  repargs = {}
@@ -346,7 +351,7 @@ module Solargraph
346
351
  #
347
352
  # @return [void]
348
353
  def catalog
349
- @catalog_mutex.synchronize do
354
+ mutex.synchronize do
350
355
  break if synchronized?
351
356
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
352
357
  api_map.catalog bench
@@ -355,6 +360,16 @@ module Solargraph
355
360
  end
356
361
  end
357
362
 
363
+ def bench
364
+ source_maps = @current ? [@current] : []
365
+ source_maps.concat source_map_hash.values
366
+ Bench.new(
367
+ source_maps: source_maps,
368
+ load_paths: workspace.require_paths,
369
+ gemnames: workspace.gemnames
370
+ )
371
+ end
372
+
358
373
  # Get an array of foldable ranges for the specified file.
359
374
  #
360
375
  # @deprecated The library should not need to handle folding ranges. The
@@ -381,14 +396,49 @@ module Solargraph
381
396
  # @param source [Source]
382
397
  # @return [Boolean] True if the source was merged into the workspace.
383
398
  def merge source
399
+ Logging.logger.debug "Merging source: #{source.filename}"
384
400
  result = false
385
401
  mutex.synchronize do
386
402
  result = workspace.merge(source)
387
- @synchronized = !result if synchronized?
403
+ maybe_map source
388
404
  end
405
+ # catalog
389
406
  result
390
407
  end
391
408
 
409
+ def source_map_hash
410
+ @source_map_hash ||= {}
411
+ end
412
+
413
+ def mapped?
414
+ (workspace.filenames - source_map_hash.keys).empty?
415
+ end
416
+
417
+ def next_map
418
+ return false if mapped?
419
+ mutex.synchronize do
420
+ @synchronized = false
421
+ src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
422
+ if src
423
+ Logging.logger.debug "Mapping #{src.filename}"
424
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
425
+ else
426
+ false
427
+ end
428
+ end
429
+ end
430
+
431
+ def map!
432
+ workspace.sources.each do |src|
433
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
434
+ end
435
+ self
436
+ end
437
+
438
+ def pins
439
+ @pins ||= []
440
+ end
441
+
392
442
  private
393
443
 
394
444
  # @return [Mutex]
@@ -401,14 +451,6 @@ module Solargraph
401
451
  @api_map ||= Solargraph::ApiMap.new
402
452
  end
403
453
 
404
- # @return [Bench]
405
- def bench
406
- Bench.new(
407
- workspace: workspace,
408
- opened: @current ? [@current] : []
409
- )
410
- end
411
-
412
454
  # Get the source for an open file or create a new source if the file
413
455
  # exists on disk. Sources created from disk are not added to the open
414
456
  # workspace files, i.e., the version on disk remains the authoritative
@@ -422,5 +464,35 @@ module Solargraph
422
464
  raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
423
465
  workspace.source(filename)
424
466
  end
467
+
468
+ def handle_file_not_found filename, error
469
+ if workspace.source(filename)
470
+ Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap"
471
+ nil
472
+ else
473
+ raise error
474
+ end
475
+ end
476
+
477
+ def maybe_map source
478
+ if source_map_hash.key?(source.filename)
479
+ return if source_map_hash[source.filename].code == source.code &&
480
+ source_map_hash[source.filename].source.synchronized? &&
481
+ source.synchronized?
482
+ if source.synchronized?
483
+ new_map = Solargraph::SourceMap.map(source)
484
+ unless source_map_hash[source.filename].try_merge!(new_map)
485
+ source_map_hash[source.filename] = new_map
486
+ @synchronized = false
487
+ end
488
+ else
489
+ # @todo Smelly instance variable access
490
+ source_map_hash[source.filename].instance_variable_set(:@source, source)
491
+ end
492
+ else
493
+ source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
494
+ @synchronized = false
495
+ end
496
+ end
425
497
  end
426
498
  end
@@ -109,7 +109,6 @@ module Solargraph
109
109
  end
110
110
 
111
111
  def node_to_argchains node
112
- # @todo Process array, splat, argscat
113
112
  return [] unless Parser.is_ast_node?(node)
114
113
  if [:ZARRAY, :ARRAY, :LIST].include?(node.type)
115
114
  node.children[0..-2].map { |c| NodeChainer.chain(c) }
@@ -32,7 +32,6 @@ module Solargraph
32
32
  region.closure.parameters.push locals.last
33
33
  end
34
34
  end
35
- # @todo Optional args, keyword args, etc.
36
35
  if node.children[6]
37
36
  locals.push Solargraph::Pin::Parameter.new(
38
37
  location: region.closure.location,
@@ -13,22 +13,6 @@ module Solargraph
13
13
  # return super_pins(api_map, name_pin) if word == 'super'
14
14
  []
15
15
  end
16
-
17
- # @todo This is temporary. Chain heads need to handle arguments to
18
- # `super`.
19
- # def arguments
20
- # []
21
- # end
22
-
23
- private
24
-
25
- # # @param api_map [ApiMap]
26
- # # @param name_pin [Pin::Base]
27
- # # @return [Array<Pin::Base>]
28
- # def super_pins api_map, name_pin
29
- # pins = api_map.get_method_stack(name_pin.namespace, name_pin.name, scope: name_pin.scope)
30
- # pins.reject{|p| p.path == name_pin.path}
31
- # end
32
16
  end
33
17
  end
34
18
  end
@@ -34,6 +34,7 @@ module Solargraph
34
34
  # Special handling for files that end with an integer and a period
35
35
  return Chain.new([Chain::Literal.new('Integer'), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/
36
36
  return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
37
+ return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i)
37
38
  begin
38
39
  return Chain.new([]) if phrase.end_with?('..')
39
40
  node = nil
@@ -64,7 +65,7 @@ module Solargraph
64
65
  elsif end_of_phrase.strip == '::'
65
66
  chain.links.push Chain::UNDEFINED_CONSTANT
66
67
  end
67
- elsif chain.links.last.is_a?(Source::Chain::Constant) && end_of_phrase.strip == '::' && !source.code[Position.to_offset(source.code, position)].match?(/[a-z]/i)
68
+ elsif chain.links.last.is_a?(Source::Chain::Constant) && end_of_phrase.strip == '::'
68
69
  chain.links.push Source::Chain::UNDEFINED_CONSTANT
69
70
  end
70
71
  chain
@@ -71,11 +71,6 @@ module Solargraph
71
71
  pos = Solargraph::Position.new(comment_position.line + line_num - 1, comment_position.column)
72
72
  process_directive(source_position, pos, d)
73
73
  last_line = line_num + 1
74
- # @todo The below call assumes the topmost comment line. The above
75
- # process occasionally emits incorrect comment positions due to
76
- # blank lines in comment blocks, but at least it processes all the
77
- # directives.
78
- # process_directive(source_position, comment_position, d)
79
74
  end
80
75
  end
81
76
 
@@ -75,7 +75,7 @@ module Solargraph
75
75
  true
76
76
  end
77
77
 
78
- # @param type [ComplexType]
78
+ # @param type [ComplexType::UniqueType]
79
79
  # @return [String]
80
80
  def fuzz type
81
81
  if type.parameters?
@@ -86,13 +86,13 @@ module Solargraph
86
86
  end
87
87
 
88
88
  # @param api_map [ApiMap]
89
- # @param cls1 [ComplexType]
90
- # @param cls2 [ComplexType]
89
+ # @param cls1 [ComplexType::UniqueType]
90
+ # @param cls2 [ComplexType::UniqueType]
91
91
  # @return [Boolean]
92
92
  def either_way?(api_map, cls1, cls2)
93
93
  f1 = fuzz(cls1)
94
94
  f2 = fuzz(cls2)
95
- api_map.super_and_sub?(f1, f2) || api_map.super_and_sub?(f2, f1)
95
+ api_map.type_include?(f1, f2) || api_map.super_and_sub?(f1, f2) || api_map.super_and_sub?(f2, f1)
96
96
  end
97
97
  end
98
98
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.40.4'
4
+ VERSION = '0.41.0'
5
5
  end
@@ -170,9 +170,10 @@ module Solargraph
170
170
  # @param globs [Array<String>]
171
171
  # @return [Array<String>]
172
172
  def process_globs globs
173
- result = []
174
- globs.each do |glob|
175
- result.concat Dir[File.join directory, glob].map{ |f| f.gsub(/\\/, '/') }
173
+ result = globs.flat_map do |glob|
174
+ Dir[File.join directory, glob]
175
+ .map{ |f| f.gsub(/\\/, '/') }
176
+ .select { |f| File.file?(f) }
176
177
  end
177
178
  result
178
179
  end
@@ -111,6 +111,7 @@ module Solargraph
111
111
  @param y [Numeric]
112
112
  @return [Numeric]
113
113
  )),
114
+ Override.method_return('Integer#times', 'Enumerator', delete: [:overload]),
114
115
 
115
116
  Override.method_return('Kernel#puts', 'nil'),
116
117
 
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.40.4
4
+ version: 0.41.0
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-03-03 00:00:00.000000000 Z
11
+ date: 2021-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backport