solargraph 0.40.4 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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