solargraph 0.58.1 → 0.59.0.dev.1
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 +4 -4
- data/.envrc +3 -0
- data/.github/workflows/linting.yml +4 -5
- data/.github/workflows/plugins.yml +40 -36
- data/.github/workflows/rspec.yml +45 -13
- data/.github/workflows/typecheck.yml +2 -2
- data/.rubocop_todo.yml +27 -49
- data/README.md +3 -3
- data/Rakefile +1 -0
- data/lib/solargraph/api_map/cache.rb +110 -110
- data/lib/solargraph/api_map/constants.rb +289 -279
- data/lib/solargraph/api_map/index.rb +204 -193
- data/lib/solargraph/api_map/source_to_yard.rb +109 -97
- data/lib/solargraph/api_map/store.rb +387 -384
- data/lib/solargraph/api_map.rb +1000 -945
- data/lib/solargraph/complex_type/conformance.rb +176 -0
- data/lib/solargraph/complex_type/type_methods.rb +242 -228
- data/lib/solargraph/complex_type/unique_type.rb +632 -482
- data/lib/solargraph/complex_type.rb +549 -444
- data/lib/solargraph/convention/data_definition/data_definition_node.rb +93 -91
- data/lib/solargraph/convention/data_definition.rb +108 -105
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +62 -61
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +103 -102
- data/lib/solargraph/convention/struct_definition.rb +168 -164
- data/lib/solargraph/diagnostics/require_not_found.rb +54 -53
- data/lib/solargraph/diagnostics/rubocop.rb +119 -118
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +70 -68
- data/lib/solargraph/diagnostics/type_check.rb +56 -55
- data/lib/solargraph/doc_map.rb +200 -439
- data/lib/solargraph/equality.rb +34 -34
- data/lib/solargraph/gem_pins.rb +97 -98
- data/lib/solargraph/language_server/host/dispatch.rb +131 -130
- data/lib/solargraph/language_server/host/message_worker.rb +113 -112
- data/lib/solargraph/language_server/host/sources.rb +100 -99
- data/lib/solargraph/language_server/host.rb +883 -878
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +109 -114
- data/lib/solargraph/language_server/message/extended/document.rb +24 -23
- data/lib/solargraph/language_server/message/text_document/completion.rb +58 -56
- data/lib/solargraph/language_server/message/text_document/definition.rb +42 -40
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +28 -26
- data/lib/solargraph/language_server/message/text_document/formatting.rb +150 -148
- data/lib/solargraph/language_server/message/text_document/hover.rb +60 -58
- data/lib/solargraph/language_server/message/text_document/signature_help.rb +25 -24
- data/lib/solargraph/language_server/message/text_document/type_definition.rb +27 -25
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +25 -23
- data/lib/solargraph/library.rb +729 -683
- data/lib/solargraph/location.rb +87 -82
- data/lib/solargraph/logging.rb +57 -37
- data/lib/solargraph/parser/comment_ripper.rb +76 -69
- data/lib/solargraph/parser/flow_sensitive_typing.rb +483 -255
- data/lib/solargraph/parser/node_processor/base.rb +122 -92
- data/lib/solargraph/parser/node_processor.rb +63 -62
- data/lib/solargraph/parser/parser_gem/class_methods.rb +167 -149
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +191 -166
- data/lib/solargraph/parser/parser_gem/node_methods.rb +506 -486
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
- data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +61 -59
- data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +24 -15
- data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
- data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +60 -53
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +53 -23
- data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +41 -40
- data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +30 -29
- data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +61 -59
- data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
- data/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +22 -0
- data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
- data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +39 -38
- data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +53 -52
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +296 -291
- data/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +23 -0
- data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +33 -29
- data/lib/solargraph/parser/parser_gem/node_processors.rb +74 -70
- data/lib/solargraph/parser/region.rb +75 -69
- data/lib/solargraph/parser/snippet.rb +17 -17
- data/lib/solargraph/pin/base.rb +761 -729
- data/lib/solargraph/pin/base_variable.rb +418 -126
- data/lib/solargraph/pin/block.rb +126 -104
- data/lib/solargraph/pin/breakable.rb +13 -9
- data/lib/solargraph/pin/callable.rb +278 -231
- data/lib/solargraph/pin/closure.rb +68 -72
- data/lib/solargraph/pin/common.rb +94 -79
- data/lib/solargraph/pin/compound_statement.rb +55 -0
- data/lib/solargraph/pin/conversions.rb +124 -123
- data/lib/solargraph/pin/delegated_method.rb +131 -120
- data/lib/solargraph/pin/documenting.rb +115 -114
- data/lib/solargraph/pin/instance_variable.rb +38 -34
- data/lib/solargraph/pin/keyword.rb +16 -20
- data/lib/solargraph/pin/local_variable.rb +31 -75
- data/lib/solargraph/pin/method.rb +720 -672
- data/lib/solargraph/pin/method_alias.rb +42 -34
- data/lib/solargraph/pin/namespace.rb +121 -115
- data/lib/solargraph/pin/parameter.rb +338 -275
- data/lib/solargraph/pin/proxy_type.rb +40 -39
- data/lib/solargraph/pin/reference/override.rb +47 -47
- data/lib/solargraph/pin/reference/superclass.rb +17 -15
- data/lib/solargraph/pin/reference.rb +41 -39
- data/lib/solargraph/pin/search.rb +62 -61
- data/lib/solargraph/pin/signature.rb +69 -61
- data/lib/solargraph/pin/symbol.rb +53 -53
- data/lib/solargraph/pin/until.rb +18 -18
- data/lib/solargraph/pin/while.rb +18 -18
- data/lib/solargraph/pin.rb +46 -44
- data/lib/solargraph/pin_cache.rb +665 -245
- data/lib/solargraph/position.rb +118 -119
- data/lib/solargraph/range.rb +112 -112
- data/lib/solargraph/rbs_map/conversions.rb +846 -823
- data/lib/solargraph/rbs_map/core_map.rb +65 -58
- data/lib/solargraph/rbs_map/stdlib_map.rb +72 -43
- data/lib/solargraph/rbs_map.rb +217 -163
- data/lib/solargraph/shell.rb +397 -352
- data/lib/solargraph/source/chain/call.rb +372 -337
- data/lib/solargraph/source/chain/constant.rb +28 -26
- data/lib/solargraph/source/chain/hash.rb +35 -34
- data/lib/solargraph/source/chain/if.rb +29 -28
- data/lib/solargraph/source/chain/instance_variable.rb +34 -13
- data/lib/solargraph/source/chain/literal.rb +53 -48
- data/lib/solargraph/source/chain/or.rb +31 -23
- data/lib/solargraph/source/chain.rb +294 -291
- data/lib/solargraph/source/change.rb +89 -82
- data/lib/solargraph/source/cursor.rb +172 -166
- data/lib/solargraph/source/source_chainer.rb +204 -194
- data/lib/solargraph/source/updater.rb +59 -55
- data/lib/solargraph/source.rb +524 -498
- data/lib/solargraph/source_map/clip.rb +237 -226
- data/lib/solargraph/source_map/data.rb +37 -34
- data/lib/solargraph/source_map/mapper.rb +282 -259
- data/lib/solargraph/source_map.rb +220 -212
- data/lib/solargraph/type_checker/problem.rb +34 -32
- data/lib/solargraph/type_checker/rules.rb +157 -84
- data/lib/solargraph/type_checker.rb +895 -814
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace/config.rb +257 -255
- data/lib/solargraph/workspace/gemspecs.rb +367 -0
- data/lib/solargraph/workspace/require_paths.rb +98 -97
- data/lib/solargraph/workspace.rb +362 -220
- data/lib/solargraph/yard_map/helpers.rb +45 -44
- data/lib/solargraph/yard_map/mapper/to_method.rb +134 -130
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +32 -31
- data/lib/solargraph/yard_map/mapper.rb +84 -79
- data/lib/solargraph/yardoc.rb +97 -87
- data/lib/solargraph.rb +126 -105
- data/rbs/fills/rubygems/0/dependency.rbs +193 -0
- data/rbs/fills/tuple/tuple.rbs +28 -0
- data/rbs/shims/ast/0/node.rbs +5 -0
- data/rbs/shims/diff-lcs/1.5/diff-lcs.rbs +11 -0
- data/rbs_collection.yaml +1 -1
- data/solargraph.gemspec +2 -1
- metadata +22 -17
- data/lib/solargraph/type_checker/checks.rb +0 -124
- data/lib/solargraph/type_checker/param_def.rb +0 -37
- data/lib/solargraph/yard_map/to_method.rb +0 -89
- data/sig/shims/ast/0/node.rbs +0 -5
- /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
- /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/thor.rbs +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'bundler'
|
|
5
|
+
|
|
6
|
+
module Solargraph
|
|
7
|
+
class Workspace
|
|
8
|
+
# Manages determining which gemspecs are available in a workspace
|
|
9
|
+
class Gemspecs
|
|
10
|
+
include Logging
|
|
11
|
+
|
|
12
|
+
attr_reader :directory, :preferences
|
|
13
|
+
|
|
14
|
+
# @param directory [String, nil] If nil, assume no bundle is present
|
|
15
|
+
# @param preferences [Array<Gem::Specification>]
|
|
16
|
+
def initialize directory, preferences: []
|
|
17
|
+
# @todo an issue with both external bundles and the potential
|
|
18
|
+
# preferences feature is that bundler gives you a 'clean'
|
|
19
|
+
# rubygems environment with only the specified versions
|
|
20
|
+
# installed. Possible alternatives:
|
|
21
|
+
#
|
|
22
|
+
# *) prompt the user to run solargraph outside of bundler
|
|
23
|
+
# and treat all bundles as external
|
|
24
|
+
# *) reinstall the needed gems dynamically each time
|
|
25
|
+
# *) manipulate the rubygems/bundler environment
|
|
26
|
+
@directory = directory && File.absolute_path(directory)
|
|
27
|
+
# @todo implement preferences as a config-exposed feature
|
|
28
|
+
@preferences = preferences
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Take the path given to a 'require' statement in a source file
|
|
32
|
+
# and return the Gem::Specifications which will be brought into
|
|
33
|
+
# scope with it, so we can load pins for them.
|
|
34
|
+
#
|
|
35
|
+
# @param require [String] The string sent to 'require' in the code to resolve, e.g. 'rails', 'bundler/require'
|
|
36
|
+
# @return [::Array<Gem::Specification>, nil]
|
|
37
|
+
def resolve_require require
|
|
38
|
+
return nil if require.empty?
|
|
39
|
+
|
|
40
|
+
# This is added in the parser when it sees 'Bundler.require' -
|
|
41
|
+
# see https://bundler.io/guides/bundler_setup.html '
|
|
42
|
+
#
|
|
43
|
+
# @todo handle different arguments to Bundler.require
|
|
44
|
+
return auto_required_gemspecs_from_bundler if require == 'bundler/require'
|
|
45
|
+
|
|
46
|
+
# Determine gem name based on the require path
|
|
47
|
+
file = "lib/#{require}.rb"
|
|
48
|
+
spec_with_path = Gem::Specification.find_by_path(file)
|
|
49
|
+
|
|
50
|
+
all_gemspecs = all_gemspecs_from_bundle
|
|
51
|
+
|
|
52
|
+
gem_names_to_try = [
|
|
53
|
+
spec_with_path&.name,
|
|
54
|
+
require.tr('/', '-'),
|
|
55
|
+
require.split('/').first
|
|
56
|
+
].compact.uniq
|
|
57
|
+
# @param gem_name [String]
|
|
58
|
+
gem_names_to_try.each do |gem_name|
|
|
59
|
+
# @sg-ignore Unresolved call to == on Boolean
|
|
60
|
+
gemspec = all_gemspecs.find { |gemspec| gemspec.name == gem_name }
|
|
61
|
+
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
62
|
+
return [gemspec_or_preference(gemspec)] if gemspec
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
gemspec = Gem::Specification.find_by_name(gem_name)
|
|
66
|
+
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
67
|
+
return [gemspec_or_preference(gemspec)] if gemspec
|
|
68
|
+
rescue Gem::MissingSpecError
|
|
69
|
+
logger.debug do
|
|
70
|
+
"Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# look ourselves just in case this is hanging out somewhere
|
|
75
|
+
# that find_by_path doesn't index
|
|
76
|
+
gemspec = all_gemspecs.find do |spec|
|
|
77
|
+
spec = to_gem_specification(spec) unless spec.respond_to?(:files)
|
|
78
|
+
|
|
79
|
+
# @sg-ignore Translate to something flow sensitive typing understands
|
|
80
|
+
spec&.files&.any? { |gemspec_file| file == gemspec_file }
|
|
81
|
+
end
|
|
82
|
+
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
83
|
+
return [gemspec_or_preference(gemspec)] if gemspec
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @param stdlib_name [String]
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<String>]
|
|
92
|
+
def stdlib_dependencies stdlib_name
|
|
93
|
+
deps = RbsMap::StdlibMap.stdlib_dependencies(stdlib_name, nil) || []
|
|
94
|
+
deps.map { |dep| dep['name'] }.compact
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param name [String]
|
|
98
|
+
# @param version [String, nil]
|
|
99
|
+
# @param out [IO, nil] output stream for logging
|
|
100
|
+
#
|
|
101
|
+
# @return [Gem::Specification, nil]
|
|
102
|
+
def find_gem name, version = nil, out: $stderr
|
|
103
|
+
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
104
|
+
specish = all_gemspecs_from_bundle.find { |specish| specish.name == name && specish.version == version }
|
|
105
|
+
return to_gem_specification specish if specish
|
|
106
|
+
|
|
107
|
+
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
108
|
+
specish = all_gemspecs_from_bundle.find { |specish| specish.name == name }
|
|
109
|
+
# @sg-ignore flow sensitive typing needs to create separate ranges for postfix if
|
|
110
|
+
return to_gem_specification specish if specish
|
|
111
|
+
|
|
112
|
+
resolve_gem_ignoring_local_bundle name, version, out: out
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @param gemspec [Gem::Specification]
|
|
116
|
+
# @param out[IO, nil] output stream for logging
|
|
117
|
+
#
|
|
118
|
+
# @return [Array<Gem::Specification>]
|
|
119
|
+
def fetch_dependencies gemspec, out: $stderr
|
|
120
|
+
gemspecs = all_gemspecs_from_bundle
|
|
121
|
+
|
|
122
|
+
# @type [Hash{String => Gem::Specification}]
|
|
123
|
+
deps_so_far = {}
|
|
124
|
+
|
|
125
|
+
# @param runtime_dep [Gem::Dependency]
|
|
126
|
+
# @param deps [Hash{String => Gem::Specification}]
|
|
127
|
+
gem_dep_gemspecs = only_runtime_dependencies(gemspec).each_with_object(deps_so_far) do |runtime_dep, deps|
|
|
128
|
+
dep = find_gem(runtime_dep.name, runtime_dep.requirement)
|
|
129
|
+
next unless dep
|
|
130
|
+
|
|
131
|
+
fetch_dependencies(dep, out: out).each { |sub_dep| deps[sub_dep.name] ||= sub_dep }
|
|
132
|
+
|
|
133
|
+
deps[dep.name] ||= dep
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# RBS tracks implicit dependencies, like how the YAML standard
|
|
137
|
+
# library implies pulling in the psych library.
|
|
138
|
+
stdlib_deps = RbsMap::StdlibMap.stdlib_dependencies(gemspec.name, gemspec.version) || []
|
|
139
|
+
stdlib_dep_gemspecs = stdlib_deps.map { |dep| find_gem(dep['name'], dep['version']) }.compact
|
|
140
|
+
(gem_dep_gemspecs.values.compact + stdlib_dep_gemspecs).uniq(&:name)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Returns all gemspecs directly depended on by this workspace's
|
|
144
|
+
# bundle (does not include transitive dependencies).
|
|
145
|
+
#
|
|
146
|
+
# @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
|
|
147
|
+
def all_gemspecs_from_bundle
|
|
148
|
+
return [] unless directory
|
|
149
|
+
|
|
150
|
+
@all_gemspecs_from_bundle ||=
|
|
151
|
+
if in_this_bundle?
|
|
152
|
+
all_gemspecs_from_this_bundle
|
|
153
|
+
else
|
|
154
|
+
all_gemspecs_from_external_bundle
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @return [Hash{Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification => Gem::Specification}]
|
|
159
|
+
def self.gem_specification_cache
|
|
160
|
+
@gem_specification_cache ||= {}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
# @param specish [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification]
|
|
166
|
+
#
|
|
167
|
+
# @return [Gem::Specification, nil]
|
|
168
|
+
# @sg-ignore flow sensitive typing needs better handling of ||= on lvars
|
|
169
|
+
def to_gem_specification specish
|
|
170
|
+
# print time including milliseconds
|
|
171
|
+
self.class.gem_specification_cache[specish] ||= case specish
|
|
172
|
+
when Gem::Specification
|
|
173
|
+
specish
|
|
174
|
+
when Bundler::LazySpecification
|
|
175
|
+
# materializing didn't work. Let's look in the local
|
|
176
|
+
# rubygems without bundler's help
|
|
177
|
+
resolve_gem_ignoring_local_bundle specish.name,
|
|
178
|
+
specish.version
|
|
179
|
+
when Bundler::StubSpecification
|
|
180
|
+
# turns a Bundler::StubSpecification into a
|
|
181
|
+
# Gem::StubSpecification if we can
|
|
182
|
+
if specish.respond_to?(:stub)
|
|
183
|
+
# @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName'
|
|
184
|
+
to_gem_specification specish.stub
|
|
185
|
+
else
|
|
186
|
+
# A Bundler::StubSpecification is a Bundler::
|
|
187
|
+
# RemoteSpecification which ought to proxy a Gem::
|
|
188
|
+
# Specification
|
|
189
|
+
specish
|
|
190
|
+
end
|
|
191
|
+
# @sg-ignore Unresolved constant Gem::StubSpecification
|
|
192
|
+
when Gem::StubSpecification
|
|
193
|
+
# @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName'
|
|
194
|
+
specish.to_spec
|
|
195
|
+
else
|
|
196
|
+
raise "Unexpected type while resolving gem: #{specish.class}"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @param command [String] The expression to evaluate in the external bundle
|
|
201
|
+
# @sg-ignore Need a JSON type
|
|
202
|
+
# @yield [undefined, nil]
|
|
203
|
+
def query_external_bundle command
|
|
204
|
+
Solargraph.with_clean_env do
|
|
205
|
+
cmd = [
|
|
206
|
+
'ruby', '-e',
|
|
207
|
+
"require 'bundler'; require 'json'; Dir.chdir('#{directory}') { puts begin; #{command}; end.to_json }"
|
|
208
|
+
]
|
|
209
|
+
o, e, s = Open3.capture3(*cmd)
|
|
210
|
+
if s.success?
|
|
211
|
+
Solargraph.logger.debug "External bundle: #{o}"
|
|
212
|
+
o && !o.empty? ? JSON.parse(o.split("\n").last) : nil
|
|
213
|
+
else
|
|
214
|
+
Solargraph.logger.warn e
|
|
215
|
+
raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @sg-ignore need boolish support for ? methods
|
|
221
|
+
def in_this_bundle?
|
|
222
|
+
Bundler.definition&.lockfile&.to_s&.start_with?(directory)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
|
|
226
|
+
def all_gemspecs_from_this_bundle
|
|
227
|
+
# Find only the gems bundler is now using
|
|
228
|
+
specish_objects = Bundler.definition.locked_gems.specs
|
|
229
|
+
if specish_objects.first.respond_to?(:materialize_for_installation)
|
|
230
|
+
specish_objects = specish_objects.map(&:materialize_for_installation)
|
|
231
|
+
end
|
|
232
|
+
specish_objects.map do |specish|
|
|
233
|
+
if specish.respond_to?(:name) && specish.respond_to?(:version) && specish.respond_to?(:gem_dir)
|
|
234
|
+
# duck type is good enough for outside uses!
|
|
235
|
+
specish
|
|
236
|
+
else
|
|
237
|
+
to_gem_specification(specish)
|
|
238
|
+
end
|
|
239
|
+
end.compact
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
|
|
243
|
+
def auto_required_gemspecs_from_bundler
|
|
244
|
+
return [] unless directory
|
|
245
|
+
|
|
246
|
+
logger.info 'Fetching gemspecs autorequired from Bundler (bundler/require)'
|
|
247
|
+
@auto_required_gemspecs_from_bundler ||=
|
|
248
|
+
if in_this_bundle?
|
|
249
|
+
auto_required_gemspecs_from_this_bundle
|
|
250
|
+
else
|
|
251
|
+
auto_required_gemspecs_from_external_bundle
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
|
|
256
|
+
def auto_required_gemspecs_from_this_bundle
|
|
257
|
+
# Adapted from require() in lib/bundler/runtime.rb
|
|
258
|
+
dep_names = Bundler.definition.dependencies.select do |dep|
|
|
259
|
+
dep.groups.include?(:default) && dep.should_include?
|
|
260
|
+
end.map(&:name)
|
|
261
|
+
|
|
262
|
+
all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) }
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
|
|
266
|
+
def auto_required_gemspecs_from_external_bundle
|
|
267
|
+
@auto_required_gemspecs_from_external_bundle ||=
|
|
268
|
+
begin
|
|
269
|
+
logger.info 'Fetching auto-required gemspecs from Bundler (bundler/require)'
|
|
270
|
+
command =
|
|
271
|
+
'Bundler.definition.dependencies' \
|
|
272
|
+
'.select { |dep| dep.groups.include?(:default) && dep.should_include? }' \
|
|
273
|
+
'.map(&:name)'
|
|
274
|
+
# @sg-ignore
|
|
275
|
+
# @type [Array<String>]
|
|
276
|
+
dep_names = query_external_bundle command
|
|
277
|
+
|
|
278
|
+
all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) }
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# @param gemspec [Gem::Specification]
|
|
283
|
+
# @return [Array<Gem::Dependency>]
|
|
284
|
+
def only_runtime_dependencies gemspec
|
|
285
|
+
unless gemspec.respond_to?(:dependencies) && gemspec.respond_to?(:development_dependencies)
|
|
286
|
+
gemspec = to_gem_specification(gemspec)
|
|
287
|
+
end
|
|
288
|
+
return [] if gemspec.nil?
|
|
289
|
+
|
|
290
|
+
gemspec.dependencies - gemspec.development_dependencies
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# @todo Should this be using Gem::SpecFetcher and pull them automatically?
|
|
294
|
+
#
|
|
295
|
+
# @param name [String]
|
|
296
|
+
# @param version_or_requirement [String, nil]
|
|
297
|
+
# @param out [IO, nil] output stream for logging
|
|
298
|
+
#
|
|
299
|
+
# @return [Gem::Specification, nil]
|
|
300
|
+
def resolve_gem_ignoring_local_bundle name, version_or_requirement = nil, out: $stderr
|
|
301
|
+
Gem::Specification.find_by_name(name, version_or_requirement)
|
|
302
|
+
rescue Gem::MissingSpecError
|
|
303
|
+
begin
|
|
304
|
+
Gem::Specification.find_by_name(name)
|
|
305
|
+
rescue Gem::MissingSpecError
|
|
306
|
+
stdlibmap = RbsMap::StdlibMap.new(name)
|
|
307
|
+
unless stdlibmap.resolved?
|
|
308
|
+
gem_desc = name
|
|
309
|
+
gem_desc += ":#{version_or_requirement}" if version_or_requirement
|
|
310
|
+
out&.puts "Please install the gem #{gem_desc} in Solargraph's Ruby environment"
|
|
311
|
+
end
|
|
312
|
+
nil # either not here or in stdlib
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# @sg-ignore flow sensitive typing needs better handling of ||= on lvars
|
|
317
|
+
# @return [Array<Gem::Specification>]
|
|
318
|
+
def all_gemspecs_from_external_bundle
|
|
319
|
+
@all_gemspecs_from_external_bundle ||=
|
|
320
|
+
begin
|
|
321
|
+
logger.info 'Fetching gemspecs required from external bundle'
|
|
322
|
+
|
|
323
|
+
command = 'specish_objects = Bundler.definition.locked_gems&.specs; ' \
|
|
324
|
+
'if specish_objects.first.respond_to?(:materialize_for_installation);' \
|
|
325
|
+
'specish_objects = specish_objects.map(&:materialize_for_installation);' \
|
|
326
|
+
'end;' \
|
|
327
|
+
'specish_objects.map { |specish| [specish.name, specish.version] }'
|
|
328
|
+
# @type [Array<Gem::Specification>]
|
|
329
|
+
query_external_bundle(command).map do |name, version|
|
|
330
|
+
resolve_gem_ignoring_local_bundle(name, version)
|
|
331
|
+
end.compact
|
|
332
|
+
rescue Solargraph::BundleNotFoundError => e
|
|
333
|
+
Solargraph.logger.info e.message
|
|
334
|
+
# @sg-ignore Need to add nil check here
|
|
335
|
+
Solargraph.logger.debug e.backtrace.join("\n")
|
|
336
|
+
[]
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# @return [Hash{String => Gem::Specification}]
|
|
341
|
+
def preference_map
|
|
342
|
+
@preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# @param gemspec [Gem::Specification]
|
|
346
|
+
#
|
|
347
|
+
# @return [Gem::Specification]
|
|
348
|
+
def gemspec_or_preference gemspec
|
|
349
|
+
return gemspec unless preference_map.key?(gemspec.name)
|
|
350
|
+
return gemspec if gemspec.version == preference_map[gemspec.name].version
|
|
351
|
+
|
|
352
|
+
change_gemspec_version gemspec, preference_map[gemspec.name].version
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# @param gemspec [Gem::Specification]
|
|
356
|
+
# @param version [String]
|
|
357
|
+
# @return [Gem::Specification]
|
|
358
|
+
def change_gemspec_version gemspec, version
|
|
359
|
+
Gem::Specification.find_by_name(gemspec.name, "= #{version}")
|
|
360
|
+
rescue Gem::MissingSpecError
|
|
361
|
+
Solargraph.logger.info "Gem #{gemspec.name} version #{version.inspect} not found. " \
|
|
362
|
+
"Using #{gemspec.version} instead"
|
|
363
|
+
gemspec
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
@@ -1,97 +1,98 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'open3'
|
|
4
|
-
|
|
5
|
-
module Solargraph
|
|
6
|
-
# A workspace consists of the files in a project's directory and the
|
|
7
|
-
# project's configuration. It provides a Source for each file to be used
|
|
8
|
-
# in an associated Library or ApiMap.
|
|
9
|
-
#
|
|
10
|
-
class Workspace
|
|
11
|
-
# Manages determining which gemspecs are available in a workspace
|
|
12
|
-
class RequirePaths
|
|
13
|
-
attr_reader :directory, :config
|
|
14
|
-
|
|
15
|
-
# @param directory [String, nil]
|
|
16
|
-
# @param config [Config, nil]
|
|
17
|
-
def initialize directory, config
|
|
18
|
-
@directory = directory
|
|
19
|
-
@config = config
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Generate require paths from gemspecs if they exist or assume the default
|
|
23
|
-
# lib directory.
|
|
24
|
-
#
|
|
25
|
-
# @return [Array<String>]
|
|
26
|
-
def generate
|
|
27
|
-
result = require_paths_from_gemspec_files
|
|
28
|
-
return configured_require_paths if result.empty?
|
|
29
|
-
result.concat(config.require_paths.map { |p| File.join(directory, p) }) if config
|
|
30
|
-
result
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
# @return [Array<String>]
|
|
36
|
-
def require_paths_from_gemspec_files
|
|
37
|
-
results = []
|
|
38
|
-
gemspec_file_paths.each do |gemspec_file_path|
|
|
39
|
-
results.concat require_path_from_gemspec_file(gemspec_file_path)
|
|
40
|
-
end
|
|
41
|
-
results
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Get an array of all gemspec files in the workspace.
|
|
45
|
-
#
|
|
46
|
-
# @return [Array<String>]
|
|
47
|
-
def gemspec_file_paths
|
|
48
|
-
return [] if directory.nil?
|
|
49
|
-
@gemspec_file_paths ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs|
|
|
50
|
-
config.nil? || config.allow?(gs)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Get additional require paths defined in the configuration.
|
|
55
|
-
#
|
|
56
|
-
# @return [Array<String>]
|
|
57
|
-
def configured_require_paths
|
|
58
|
-
return ['lib'] unless directory
|
|
59
|
-
return [File.join(directory, 'lib')] if !config || config.require_paths.empty?
|
|
60
|
-
config.require_paths.map { |p| File.join(directory, p) }
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Generate require paths from gemspecs if they exist or assume the default
|
|
64
|
-
# lib directory.
|
|
65
|
-
#
|
|
66
|
-
# @param gemspec_file_path [String]
|
|
67
|
-
# @return [Array<String>]
|
|
68
|
-
def require_path_from_gemspec_file gemspec_file_path
|
|
69
|
-
base = File.dirname(gemspec_file_path)
|
|
70
|
-
# HACK: Evaluating gemspec files violates the goal of not running
|
|
71
|
-
# workspace code, but this is how Gem::Specification.load does it
|
|
72
|
-
# anyway.
|
|
73
|
-
cmd = ['ruby', '-e',
|
|
74
|
-
"require 'rubygems'; " \
|
|
75
|
-
"require 'json'; " \
|
|
76
|
-
"spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \
|
|
77
|
-
'return unless Gem::Specification === spec; ' \
|
|
78
|
-
'puts({name: spec.name, paths: spec.require_paths}.to_json)']
|
|
79
|
-
o, e, s = Open3.capture3(*cmd)
|
|
80
|
-
if s.success?
|
|
81
|
-
begin
|
|
82
|
-
hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
|
|
83
|
-
return [] if hash.empty?
|
|
84
|
-
hash['paths'].map { |path| File.join(base, path) }
|
|
85
|
-
rescue StandardError => e
|
|
86
|
-
|
|
87
|
-
[]
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Solargraph.logger.warn
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Solargraph
|
|
6
|
+
# A workspace consists of the files in a project's directory and the
|
|
7
|
+
# project's configuration. It provides a Source for each file to be used
|
|
8
|
+
# in an associated Library or ApiMap.
|
|
9
|
+
#
|
|
10
|
+
class Workspace
|
|
11
|
+
# Manages determining which gemspecs are available in a workspace
|
|
12
|
+
class RequirePaths
|
|
13
|
+
attr_reader :directory, :config
|
|
14
|
+
|
|
15
|
+
# @param directory [String, nil]
|
|
16
|
+
# @param config [Config, nil]
|
|
17
|
+
def initialize directory, config
|
|
18
|
+
@directory = directory
|
|
19
|
+
@config = config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Generate require paths from gemspecs if they exist or assume the default
|
|
23
|
+
# lib directory.
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<String>]
|
|
26
|
+
def generate
|
|
27
|
+
result = require_paths_from_gemspec_files
|
|
28
|
+
return configured_require_paths if result.empty?
|
|
29
|
+
result.concat(config.require_paths.map { |p| File.join(directory, p) }) if config
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# @return [Array<String>]
|
|
36
|
+
def require_paths_from_gemspec_files
|
|
37
|
+
results = []
|
|
38
|
+
gemspec_file_paths.each do |gemspec_file_path|
|
|
39
|
+
results.concat require_path_from_gemspec_file(gemspec_file_path)
|
|
40
|
+
end
|
|
41
|
+
results
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get an array of all gemspec files in the workspace.
|
|
45
|
+
#
|
|
46
|
+
# @return [Array<String>]
|
|
47
|
+
def gemspec_file_paths
|
|
48
|
+
return [] if directory.nil?
|
|
49
|
+
@gemspec_file_paths ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs|
|
|
50
|
+
config.nil? || config.allow?(gs)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get additional require paths defined in the configuration.
|
|
55
|
+
#
|
|
56
|
+
# @return [Array<String>]
|
|
57
|
+
def configured_require_paths
|
|
58
|
+
return ['lib'] unless directory
|
|
59
|
+
return [File.join(directory, 'lib')] if !config || config.require_paths.empty?
|
|
60
|
+
config.require_paths.map { |p| File.join(directory, p) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Generate require paths from gemspecs if they exist or assume the default
|
|
64
|
+
# lib directory.
|
|
65
|
+
#
|
|
66
|
+
# @param gemspec_file_path [String]
|
|
67
|
+
# @return [Array<String>]
|
|
68
|
+
def require_path_from_gemspec_file gemspec_file_path
|
|
69
|
+
base = File.dirname(gemspec_file_path)
|
|
70
|
+
# HACK: Evaluating gemspec files violates the goal of not running
|
|
71
|
+
# workspace code, but this is how Gem::Specification.load does it
|
|
72
|
+
# anyway.
|
|
73
|
+
cmd = ['ruby', '-e',
|
|
74
|
+
"require 'rubygems'; " \
|
|
75
|
+
"require 'json'; " \
|
|
76
|
+
"spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \
|
|
77
|
+
'return unless Gem::Specification === spec; ' \
|
|
78
|
+
'puts({name: spec.name, paths: spec.require_paths}.to_json)']
|
|
79
|
+
o, e, s = Open3.capture3(*cmd)
|
|
80
|
+
if s.success?
|
|
81
|
+
begin
|
|
82
|
+
hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
|
|
83
|
+
return [] if hash.empty?
|
|
84
|
+
hash['paths'].map { |path| File.join(base, path) }
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
# @sg-ignore flow sensitive typing should be able to handle redefinition
|
|
87
|
+
Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}"
|
|
88
|
+
[]
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
Solargraph.logger.warn "Error reading #{gemspec_file_path}"
|
|
92
|
+
Solargraph.logger.warn e
|
|
93
|
+
[]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|