solargraph 0.56.2 → 0.57.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 +4 -4
- data/.github/workflows/linting.yml +125 -0
- data/.github/workflows/plugins.yml +148 -6
- data/.github/workflows/rspec.yml +39 -4
- data/.github/workflows/typecheck.yml +5 -2
- data/.gitignore +5 -0
- data/.overcommit.yml +72 -0
- data/.rspec +1 -0
- data/.rubocop.yml +66 -0
- data/.rubocop_todo.yml +2627 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +42 -0
- data/README.md +8 -4
- data/Rakefile +125 -13
- data/lib/solargraph/api_map/cache.rb +3 -2
- data/lib/solargraph/api_map/constants.rb +218 -0
- data/lib/solargraph/api_map/index.rb +20 -26
- data/lib/solargraph/api_map/source_to_yard.rb +10 -4
- data/lib/solargraph/api_map/store.rb +126 -18
- data/lib/solargraph/api_map.rb +212 -234
- data/lib/solargraph/bench.rb +1 -0
- data/lib/solargraph/complex_type/type_methods.rb +1 -0
- data/lib/solargraph/complex_type/unique_type.rb +7 -7
- data/lib/solargraph/complex_type.rb +5 -1
- data/lib/solargraph/convention/active_support_concern.rb +111 -0
- data/lib/solargraph/convention/base.rb +17 -0
- data/lib/solargraph/convention/data_definition/data_assignment_node.rb +1 -0
- data/lib/solargraph/convention/data_definition/data_definition_node.rb +3 -1
- data/lib/solargraph/convention/data_definition.rb +2 -1
- data/lib/solargraph/convention/gemspec.rb +1 -1
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +1 -0
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +3 -1
- data/lib/solargraph/convention/struct_definition.rb +36 -13
- data/lib/solargraph/convention.rb +31 -2
- data/lib/solargraph/diagnostics/rubocop.rb +6 -1
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +1 -1
- data/lib/solargraph/doc_map.rb +40 -12
- data/lib/solargraph/environ.rb +9 -2
- data/lib/solargraph/gem_pins.rb +17 -11
- data/lib/solargraph/language_server/host/dispatch.rb +2 -0
- data/lib/solargraph/language_server/host/message_worker.rb +3 -0
- data/lib/solargraph/language_server/host.rb +2 -1
- data/lib/solargraph/language_server/message/base.rb +2 -1
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -1
- data/lib/solargraph/language_server/message/text_document/definition.rb +2 -0
- data/lib/solargraph/language_server/message/text_document/formatting.rb +16 -2
- data/lib/solargraph/language_server/message/text_document/type_definition.rb +1 -0
- data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +2 -0
- data/lib/solargraph/language_server/progress.rb +8 -0
- data/lib/solargraph/language_server/request.rb +1 -0
- data/lib/solargraph/library.rb +8 -15
- data/lib/solargraph/location.rb +2 -0
- data/lib/solargraph/logging.rb +11 -2
- data/lib/solargraph/page.rb +4 -0
- data/lib/solargraph/parser/comment_ripper.rb +8 -1
- data/lib/solargraph/parser/flow_sensitive_typing.rb +32 -4
- data/lib/solargraph/parser/node_methods.rb +2 -2
- data/lib/solargraph/parser/node_processor/base.rb +1 -1
- data/lib/solargraph/parser/node_processor.rb +6 -2
- data/lib/solargraph/parser/parser_gem/class_methods.rb +1 -1
- data/lib/solargraph/parser/parser_gem/flawed_builder.rb +1 -0
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +3 -1
- data/lib/solargraph/parser/parser_gem/node_methods.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +3 -2
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +2 -0
- data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +3 -0
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +35 -14
- data/lib/solargraph/parser/region.rb +3 -0
- data/lib/solargraph/parser/snippet.rb +2 -0
- data/lib/solargraph/pin/base.rb +50 -8
- data/lib/solargraph/pin/base_variable.rb +1 -2
- data/lib/solargraph/pin/callable.rb +9 -0
- data/lib/solargraph/pin/closure.rb +2 -0
- data/lib/solargraph/pin/common.rb +6 -2
- data/lib/solargraph/pin/constant.rb +2 -0
- data/lib/solargraph/pin/delegated_method.rb +1 -0
- data/lib/solargraph/pin/local_variable.rb +4 -1
- data/lib/solargraph/pin/method.rb +8 -5
- data/lib/solargraph/pin/method_alias.rb +3 -0
- data/lib/solargraph/pin/parameter.rb +18 -8
- data/lib/solargraph/pin/proxy_type.rb +1 -0
- data/lib/solargraph/pin/reference/override.rb +15 -1
- data/lib/solargraph/pin/reference/superclass.rb +5 -0
- data/lib/solargraph/pin/reference.rb +26 -0
- data/lib/solargraph/pin/search.rb +3 -1
- data/lib/solargraph/pin/signature.rb +2 -0
- data/lib/solargraph/pin/symbol.rb +5 -0
- data/lib/solargraph/pin_cache.rb +64 -4
- data/lib/solargraph/position.rb +2 -0
- data/lib/solargraph/range.rb +1 -0
- data/lib/solargraph/rbs_map/conversions.rb +7 -5
- data/lib/solargraph/rbs_map/core_map.rb +3 -0
- data/lib/solargraph/rbs_map.rb +15 -2
- data/lib/solargraph/shell.rb +3 -0
- data/lib/solargraph/source/chain/link.rb +10 -1
- data/lib/solargraph/source/chain.rb +9 -2
- data/lib/solargraph/source/change.rb +2 -2
- data/lib/solargraph/source/cursor.rb +2 -3
- data/lib/solargraph/source/source_chainer.rb +1 -1
- data/lib/solargraph/source.rb +5 -2
- data/lib/solargraph/source_map/clip.rb +1 -1
- data/lib/solargraph/source_map/data.rb +4 -0
- data/lib/solargraph/source_map/mapper.rb +4 -2
- data/lib/solargraph/source_map.rb +21 -14
- data/lib/solargraph/type_checker/param_def.rb +2 -0
- data/lib/solargraph/type_checker/rules.rb +8 -0
- data/lib/solargraph/type_checker.rb +173 -120
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace/config.rb +0 -2
- data/lib/solargraph/workspace/require_paths.rb +98 -0
- data/lib/solargraph/workspace.rb +16 -48
- data/lib/solargraph/yard_map/mapper/to_method.rb +2 -2
- data/lib/solargraph/yardoc.rb +16 -3
- data/lib/solargraph.rb +2 -0
- data/rbs/fills/tuple.rbs +2 -3
- data/sig/shims/parser/3.2.0.1/builders/default.rbs +195 -0
- data/sig/shims/thor/1.2.0.1/.rbs_meta.yaml +9 -0
- data/sig/shims/thor/1.2.0.1/manifest.yaml +7 -0
- data/sig/shims/thor/1.2.0.1/thor.rbs +17 -0
- data/solargraph.gemspec +14 -4
- metadata +123 -9
- data/lib/.rubocop.yml +0 -22
@@ -38,15 +38,20 @@ module Solargraph
|
|
38
38
|
@source_map ||= api_map.source_map(filename)
|
39
39
|
end
|
40
40
|
|
41
|
+
# @return [Source]
|
42
|
+
def source
|
43
|
+
@source_map.source
|
44
|
+
end
|
45
|
+
|
41
46
|
# @return [Array<Problem>]
|
42
47
|
def problems
|
43
48
|
@problems ||= begin
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
all = method_tag_problems
|
50
|
+
.concat(variable_type_tag_problems)
|
51
|
+
.concat(const_problems)
|
52
|
+
.concat(call_problems)
|
53
|
+
unignored = without_ignored(all)
|
54
|
+
unignored.concat(unneeded_sgignore_problems)
|
50
55
|
end
|
51
56
|
end
|
52
57
|
|
@@ -140,7 +145,7 @@ module Solargraph
|
|
140
145
|
|
141
146
|
# @param pin [Pin::Base]
|
142
147
|
def virtual_pin? pin
|
143
|
-
pin.location &&
|
148
|
+
pin.location && source.comment_at?(pin.location.range.ending)
|
144
149
|
end
|
145
150
|
|
146
151
|
# @param pin [Pin::Method]
|
@@ -231,7 +236,7 @@ module Solargraph
|
|
231
236
|
def const_problems
|
232
237
|
return [] unless rules.validate_consts?
|
233
238
|
result = []
|
234
|
-
Solargraph::Parser::NodeMethods.const_nodes_from(
|
239
|
+
Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const|
|
235
240
|
rng = Solargraph::Range.from_node(const)
|
236
241
|
chain = Solargraph::Parser.chain(const, filename)
|
237
242
|
block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
|
@@ -249,7 +254,7 @@ module Solargraph
|
|
249
254
|
# @return [Array<Problem>]
|
250
255
|
def call_problems
|
251
256
|
result = []
|
252
|
-
Solargraph::Parser::NodeMethods.call_nodes_from(
|
257
|
+
Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call|
|
253
258
|
rng = Solargraph::Range.from_node(call)
|
254
259
|
next if @marked_ranges.any? { |d| d.contain?(rng.start) }
|
255
260
|
chain = Solargraph::Parser.chain(call, filename)
|
@@ -272,7 +277,11 @@ module Solargraph
|
|
272
277
|
# @todo remove the internal_or_core? check at a higher-than-strict level
|
273
278
|
if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
|
274
279
|
unless closest.generic? || ignored_pins.include?(found)
|
275
|
-
|
280
|
+
if closest.defined?
|
281
|
+
result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}")
|
282
|
+
else
|
283
|
+
result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
|
284
|
+
end
|
276
285
|
@marked_ranges.push rng
|
277
286
|
end
|
278
287
|
end
|
@@ -284,127 +293,136 @@ module Solargraph
|
|
284
293
|
|
285
294
|
# @param chain [Solargraph::Source::Chain]
|
286
295
|
# @param api_map [Solargraph::ApiMap]
|
287
|
-
# @param
|
296
|
+
# @param closure_pin [Solargraph::Pin::Closure]
|
288
297
|
# @param locals [Array<Solargraph::Pin::Base>]
|
289
298
|
# @param location [Solargraph::Location]
|
290
299
|
# @return [Array<Problem>]
|
291
|
-
def argument_problems_for chain, api_map,
|
300
|
+
def argument_problems_for chain, api_map, closure_pin, locals, location
|
292
301
|
result = []
|
293
302
|
base = chain
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
303
|
+
# @type last_base_link [Solargraph::Source::Chain::Call]
|
304
|
+
last_base_link = base.links.last
|
305
|
+
return [] unless last_base_link.is_a?(Solargraph::Source::Chain::Call)
|
306
|
+
|
307
|
+
arguments = last_base_link.arguments
|
308
|
+
|
309
|
+
pins = base.define(api_map, closure_pin, locals)
|
310
|
+
|
311
|
+
first_pin = pins.first
|
312
|
+
unresolvable = first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map)
|
313
|
+
if !unresolvable && first_pin.is_a?(Pin::Method)
|
314
|
+
# @type [Pin::Method]
|
315
|
+
pin = first_pin
|
316
|
+
ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
|
317
|
+
arity_problems_for(pin, fake_args_for(closure_pin), location)
|
318
|
+
elsif pin.path == 'Class#new'
|
319
|
+
fqns = if base.links.one?
|
320
|
+
closure_pin.namespace
|
321
|
+
else
|
322
|
+
base.base.infer(api_map, closure_pin, locals).namespace
|
323
|
+
end
|
324
|
+
init = api_map.get_method_stack(fqns, 'initialize').first
|
325
|
+
|
326
|
+
init ? arity_problems_for(init, arguments, location) : []
|
327
|
+
else
|
328
|
+
arity_problems_for(pin, arguments, location)
|
329
|
+
end
|
330
|
+
return ap unless ap.empty?
|
331
|
+
return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper)
|
332
|
+
|
333
|
+
params = first_param_hash(pins)
|
334
|
+
|
335
|
+
all_errors = []
|
336
|
+
pin.signatures.sort { |sig| sig.parameters.length }.each do |sig|
|
337
|
+
signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
|
338
|
+
if signature_errors.empty?
|
339
|
+
# we found a signature that works - meaning errors from
|
340
|
+
# other signatures don't matter.
|
341
|
+
return []
|
342
|
+
end
|
343
|
+
all_errors.concat signature_errors
|
344
|
+
end
|
345
|
+
result.concat all_errors
|
346
|
+
end
|
347
|
+
result
|
348
|
+
end
|
349
|
+
|
350
|
+
# @param location [Location]
|
351
|
+
# @param locals [Array<Pin::LocalVariable>]
|
352
|
+
# @param closure_pin [Pin::Closure]
|
353
|
+
# @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}]
|
354
|
+
# @param arguments [Array<Source::Chain>]
|
355
|
+
# @param sig [Pin::Signature]
|
356
|
+
# @param pin [Pin::Method]
|
357
|
+
# @param pins [Array<Pin::Method>]
|
358
|
+
#
|
359
|
+
# @return [Array<Problem>]
|
360
|
+
def signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
|
361
|
+
errors = []
|
362
|
+
# @todo add logic mapping up restarg parameters with
|
363
|
+
# arguments (including restarg arguments). Use tuples
|
364
|
+
# when possible, and when not, ensure provably
|
365
|
+
# incorrect situations are detected.
|
366
|
+
sig.parameters.each_with_index do |par, idx|
|
367
|
+
return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing
|
368
|
+
argchain = arguments[idx]
|
369
|
+
if argchain.nil?
|
370
|
+
if par.decl == :arg
|
371
|
+
final_arg = arguments.last
|
372
|
+
if final_arg && final_arg.node.type == :splat
|
373
|
+
argchain = final_arg
|
374
|
+
return errors
|
313
375
|
else
|
314
|
-
|
376
|
+
errors.push Problem.new(location, "Not enough arguments to #{pin.path}")
|
315
377
|
end
|
316
|
-
init = api_map.get_method_stack(fqns, 'initialize').first
|
317
|
-
init ? arity_problems_for(init, arguments, location) : []
|
318
378
|
else
|
319
|
-
|
320
|
-
|
321
|
-
unless ap.empty?
|
322
|
-
result.concat ap
|
323
|
-
break
|
379
|
+
final_arg = arguments.last
|
380
|
+
argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type)
|
324
381
|
end
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
#
|
336
|
-
#
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
final_arg = arguments.last
|
342
|
-
if final_arg && final_arg.node.type == :splat
|
343
|
-
argchain = final_arg
|
344
|
-
next # don't try to apply the type of the splat - unlikely to be specific enough
|
345
|
-
else
|
346
|
-
errors.push Problem.new(location, "Not enough arguments to #{pin.path}")
|
347
|
-
next
|
348
|
-
end
|
349
|
-
else
|
350
|
-
final_arg = arguments.last
|
351
|
-
argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type)
|
352
|
-
end
|
353
|
-
end
|
354
|
-
if argchain
|
355
|
-
if par.decl != :arg
|
356
|
-
errors.concat kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, params, idx
|
357
|
-
next
|
358
|
-
else
|
359
|
-
if argchain.node.type == :splat && argchain == arguments.last
|
360
|
-
final_arg = argchain
|
361
|
-
end
|
362
|
-
if (final_arg && final_arg.node.type == :splat)
|
363
|
-
# The final argument given has been seen and was a
|
364
|
-
# splat, which doesn't give us useful types or
|
365
|
-
# arities against positional parameters, so let's
|
366
|
-
# continue on in case there are any required
|
367
|
-
# kwargs we should warn about
|
368
|
-
next
|
369
|
-
end
|
370
|
-
|
371
|
-
if argchain.node.type == :splat && par != sig.parameters.last
|
372
|
-
# we have been given a splat and there are more
|
373
|
-
# arguments to come.
|
374
|
-
|
375
|
-
# @todo Improve this so that we can skip past the
|
376
|
-
# rest of the positional parameters here but still
|
377
|
-
# process the kwargs
|
378
|
-
break
|
379
|
-
end
|
380
|
-
ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
|
381
|
-
ptype = ptype.self_to_type(par.context)
|
382
|
-
if ptype.nil?
|
383
|
-
# @todo Some level (strong, I guess) should require the param here
|
384
|
-
else
|
385
|
-
argtype = argchain.infer(api_map, block_pin, locals)
|
386
|
-
if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
|
387
|
-
errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
388
|
-
next
|
389
|
-
end
|
390
|
-
end
|
391
|
-
end
|
392
|
-
elsif par.decl == :kwarg
|
393
|
-
errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
|
394
|
-
next
|
395
|
-
end
|
382
|
+
end
|
383
|
+
if argchain
|
384
|
+
if par.decl != :arg
|
385
|
+
errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx
|
386
|
+
next
|
387
|
+
else
|
388
|
+
if argchain.node.type == :splat && argchain == arguments.last
|
389
|
+
final_arg = argchain
|
390
|
+
end
|
391
|
+
if (final_arg && final_arg.node.type == :splat)
|
392
|
+
# The final argument given has been seen and was a
|
393
|
+
# splat, which doesn't give us useful types or
|
394
|
+
# arities against positional parameters, so let's
|
395
|
+
# continue on in case there are any required
|
396
|
+
# kwargs we should warn about
|
397
|
+
next
|
396
398
|
end
|
397
|
-
if
|
398
|
-
|
399
|
-
|
399
|
+
if argchain.node.type == :splat && par != sig.parameters.last
|
400
|
+
# we have been given a splat and there are more
|
401
|
+
# arguments to come.
|
402
|
+
|
403
|
+
# @todo Improve this so that we can skip past the
|
404
|
+
# rest of the positional parameters here but still
|
405
|
+
# process the kwargs
|
406
|
+
return errors
|
407
|
+
end
|
408
|
+
ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
|
409
|
+
ptype = ptype.self_to_type(par.context)
|
410
|
+
if ptype.nil?
|
411
|
+
# @todo Some level (strong, I guess) should require the param here
|
412
|
+
else
|
413
|
+
argtype = argchain.infer(api_map, closure_pin, locals)
|
414
|
+
if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
|
415
|
+
errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
416
|
+
return errors
|
417
|
+
end
|
400
418
|
end
|
401
|
-
all_errors.concat errors
|
402
419
|
end
|
403
|
-
|
420
|
+
elsif par.decl == :kwarg
|
421
|
+
errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
|
422
|
+
next
|
404
423
|
end
|
405
|
-
base = base.base
|
406
424
|
end
|
407
|
-
|
425
|
+
errors
|
408
426
|
end
|
409
427
|
|
410
428
|
# @param sig [Pin::Signature]
|
@@ -646,7 +664,6 @@ module Solargraph
|
|
646
664
|
# @param parameters [Enumerable<Pin::Parameter>]
|
647
665
|
# @todo need to use generic types in method to choose correct
|
648
666
|
# signature and generate Integer as return type
|
649
|
-
# @sg-ignore
|
650
667
|
# @return [Integer]
|
651
668
|
def required_param_count(parameters)
|
652
669
|
parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
|
@@ -687,12 +704,48 @@ module Solargraph
|
|
687
704
|
args
|
688
705
|
end
|
689
706
|
|
707
|
+
# @return [Set<Integer>]
|
708
|
+
def sg_ignore_lines_processed
|
709
|
+
@sg_ignore_lines_processed ||= Set.new
|
710
|
+
end
|
711
|
+
|
712
|
+
# @return [Set<Integer>]
|
713
|
+
def all_sg_ignore_lines
|
714
|
+
source.associated_comments.select do |_line, text|
|
715
|
+
text.include?('@sg-ignore')
|
716
|
+
end.keys.to_set
|
717
|
+
end
|
718
|
+
|
719
|
+
# @return [Array<Integer>]
|
720
|
+
def unprocessed_sg_ignore_lines
|
721
|
+
(all_sg_ignore_lines - sg_ignore_lines_processed).to_a.sort
|
722
|
+
end
|
723
|
+
|
724
|
+
# @return [Array<Problem>]
|
725
|
+
def unneeded_sgignore_problems
|
726
|
+
return [] unless rules.validate_sg_ignores?
|
727
|
+
|
728
|
+
unprocessed_sg_ignore_lines.map do |line|
|
729
|
+
Problem.new(
|
730
|
+
Location.new(filename, Range.from_to(line, 0, line, 0)),
|
731
|
+
'Unneeded @sg-ignore comment'
|
732
|
+
)
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
690
736
|
# @param problems [Array<Problem>]
|
691
737
|
# @return [Array<Problem>]
|
692
738
|
def without_ignored problems
|
693
739
|
problems.reject do |problem|
|
694
|
-
node =
|
695
|
-
node &&
|
740
|
+
node = source.node_at(problem.location.range.start.line, problem.location.range.start.column)
|
741
|
+
ignored = node && source.comments_for(node)&.include?('@sg-ignore')
|
742
|
+
unless !ignored || all_sg_ignore_lines.include?(problem.location.range.start.line)
|
743
|
+
# :nocov:
|
744
|
+
Solargraph.assert_or_log(:sg_ignore) { "@sg-ignore accounting issue - node is #{node}" }
|
745
|
+
# :nocov:
|
746
|
+
end
|
747
|
+
sg_ignore_lines_processed.add problem.location.range.start.line if ignored
|
748
|
+
ignored
|
696
749
|
end
|
697
750
|
end
|
698
751
|
end
|
data/lib/solargraph/version.rb
CHANGED
@@ -90,7 +90,6 @@ module Solargraph
|
|
90
90
|
|
91
91
|
# A hash of options supported by the formatter
|
92
92
|
#
|
93
|
-
# @sg-ignore pending https://github.com/castwide/solargraph/pull/905
|
94
93
|
# @return [Hash]
|
95
94
|
def formatter
|
96
95
|
raw_data['formatter']
|
@@ -105,7 +104,6 @@ module Solargraph
|
|
105
104
|
|
106
105
|
# The maximum number of files to parse from the workspace.
|
107
106
|
#
|
108
|
-
# @sg-ignore pending https://github.com/castwide/solargraph/pull/905
|
109
107
|
# @return [Integer]
|
110
108
|
def max_files
|
111
109
|
raw_data['max_files']
|
@@ -0,0 +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
|
+
# @sg-ignore Unresolved call to capture3 on Module<Open3>
|
80
|
+
o, e, s = Open3.capture3(*cmd)
|
81
|
+
if s.success?
|
82
|
+
begin
|
83
|
+
hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
|
84
|
+
return [] if hash.empty?
|
85
|
+
hash['paths'].map { |path| File.join(base, path) }
|
86
|
+
rescue StandardError => e
|
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
|
data/lib/solargraph/workspace.rb
CHANGED
@@ -10,15 +10,11 @@ module Solargraph
|
|
10
10
|
#
|
11
11
|
class Workspace
|
12
12
|
autoload :Config, 'solargraph/workspace/config'
|
13
|
+
autoload :RequirePaths, 'solargraph/workspace/require_paths'
|
13
14
|
|
14
15
|
# @return [String]
|
15
16
|
attr_reader :directory
|
16
17
|
|
17
|
-
# The require paths associated with the workspace.
|
18
|
-
#
|
19
|
-
# @return [Array<String>]
|
20
|
-
attr_reader :require_paths
|
21
|
-
|
22
18
|
# @return [Array<String>]
|
23
19
|
attr_reader :gemnames
|
24
20
|
alias source_gems gemnames
|
@@ -32,10 +28,17 @@ module Solargraph
|
|
32
28
|
@server = server
|
33
29
|
load_sources
|
34
30
|
@gemnames = []
|
35
|
-
@require_paths = generate_require_paths
|
36
31
|
require_plugins
|
37
32
|
end
|
38
33
|
|
34
|
+
# The require paths associated with the workspace.
|
35
|
+
#
|
36
|
+
# @return [Array<String>]
|
37
|
+
def require_paths
|
38
|
+
# @todo are the semantics of '*' the same as '', meaning 'don't send back any require paths'?
|
39
|
+
@require_paths ||= RequirePaths.new(directory_or_nil, config).generate
|
40
|
+
end
|
41
|
+
|
39
42
|
# @return [Solargraph::Workspace::Config]
|
40
43
|
def config
|
41
44
|
@config ||= Solargraph::Workspace::Config.new(directory)
|
@@ -133,6 +136,7 @@ module Solargraph
|
|
133
136
|
@gem_rbs_collection ||= read_rbs_collection_path
|
134
137
|
end
|
135
138
|
|
139
|
+
# @return [String, nil]
|
136
140
|
def rbs_collection_config_path
|
137
141
|
@rbs_collection_config_path ||= begin
|
138
142
|
unless directory.empty? || directory == '*'
|
@@ -155,6 +159,12 @@ module Solargraph
|
|
155
159
|
server['commandPath'] || 'solargraph'
|
156
160
|
end
|
157
161
|
|
162
|
+
# @return [String, nil]
|
163
|
+
def directory_or_nil
|
164
|
+
return nil if directory.empty? || directory == '*'
|
165
|
+
directory
|
166
|
+
end
|
167
|
+
|
158
168
|
# True if the workspace has a root Gemfile.
|
159
169
|
#
|
160
170
|
# @todo Handle projects with custom Bundler/Gemfile setups (see DocMap#gemspecs_required_from_bundler)
|
@@ -192,48 +202,6 @@ module Solargraph
|
|
192
202
|
end
|
193
203
|
end
|
194
204
|
|
195
|
-
# Generate require paths from gemspecs if they exist or assume the default
|
196
|
-
# lib directory.
|
197
|
-
#
|
198
|
-
# @return [Array<String>]
|
199
|
-
def generate_require_paths
|
200
|
-
return configured_require_paths unless gemspec?
|
201
|
-
result = []
|
202
|
-
gemspecs.each do |file|
|
203
|
-
base = File.dirname(file)
|
204
|
-
# HACK: Evaluating gemspec files violates the goal of not running
|
205
|
-
# workspace code, but this is how Gem::Specification.load does it
|
206
|
-
# anyway.
|
207
|
-
cmd = ['ruby', '-e', "require 'rubygems'; require 'json'; spec = eval(File.read('#{file}'), TOPLEVEL_BINDING, '#{file}'); return unless Gem::Specification === spec; puts({name: spec.name, paths: spec.require_paths}.to_json)"]
|
208
|
-
o, e, s = Open3.capture3(*cmd)
|
209
|
-
if s.success?
|
210
|
-
begin
|
211
|
-
hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
|
212
|
-
next if hash.empty?
|
213
|
-
@gemnames.push hash['name']
|
214
|
-
result.concat(hash['paths'].map { |path| File.join(base, path) })
|
215
|
-
rescue StandardError => e
|
216
|
-
Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}"
|
217
|
-
end
|
218
|
-
else
|
219
|
-
Solargraph.logger.warn "Error reading #{file}"
|
220
|
-
Solargraph.logger.warn e
|
221
|
-
end
|
222
|
-
end
|
223
|
-
result.concat(config.require_paths.map { |p| File.join(directory, p) })
|
224
|
-
result.push File.join(directory, 'lib') if result.empty?
|
225
|
-
result
|
226
|
-
end
|
227
|
-
|
228
|
-
# Get additional require paths defined in the configuration.
|
229
|
-
#
|
230
|
-
# @return [Array<String>]
|
231
|
-
def configured_require_paths
|
232
|
-
return ['lib'] if directory.empty?
|
233
|
-
return [File.join(directory, 'lib')] if config.require_paths.empty?
|
234
|
-
config.require_paths.map { |p| File.join(directory, p) }
|
235
|
-
end
|
236
|
-
|
237
205
|
# @return [void]
|
238
206
|
def require_plugins
|
239
207
|
config.plugins.each do |plugin|
|
@@ -27,8 +27,8 @@ module Solargraph
|
|
27
27
|
final_scope = scope || code_object.scope
|
28
28
|
override_key = [closure.path, final_scope, name]
|
29
29
|
final_visibility = VISIBILITY_OVERRIDE[override_key]
|
30
|
-
final_visibility ||= VISIBILITY_OVERRIDE[
|
31
|
-
final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name)
|
30
|
+
final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]]
|
31
|
+
final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym)
|
32
32
|
final_visibility ||= visibility
|
33
33
|
final_visibility ||= :private if code_object.module_function? && final_scope == :instance
|
34
34
|
final_visibility ||= :public if code_object.module_function? && final_scope == :class
|
data/lib/solargraph/yardoc.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'open3'
|
4
|
+
|
3
5
|
module Solargraph
|
4
6
|
# Methods for caching and loading YARD documentation for gems.
|
5
7
|
#
|
@@ -9,15 +11,25 @@ module Solargraph
|
|
9
11
|
# Build and cache a gem's yardoc and return the path. If the cache already
|
10
12
|
# exists, do nothing and return the path.
|
11
13
|
#
|
14
|
+
# @param yard_plugins [Array<String>] The names of YARD plugins to use.
|
12
15
|
# @param gemspec [Gem::Specification]
|
13
16
|
# @return [String] The path to the cached yardoc.
|
14
|
-
def cache(gemspec)
|
17
|
+
def cache(yard_plugins, gemspec)
|
15
18
|
path = PinCache.yardoc_path gemspec
|
16
19
|
return path if cached?(gemspec)
|
17
20
|
|
18
21
|
Solargraph.logger.info "Caching yardoc for #{gemspec.name} #{gemspec.version}"
|
19
|
-
|
20
|
-
|
22
|
+
cmd = "yardoc --db #{path} --no-output --plugin solargraph"
|
23
|
+
yard_plugins.each { |plugin| cmd << " --plugin #{plugin}" }
|
24
|
+
Solargraph.logger.debug { "Running: #{cmd}" }
|
25
|
+
# @todo set these up to run in parallel
|
26
|
+
#
|
27
|
+
# @sg-ignore RBS gem doesn't reflect that Open3.* also include
|
28
|
+
# kwopts from Process.spawn()
|
29
|
+
stdout_and_stderr_str, status = Open3.capture2e(cmd, chdir: gemspec.gem_dir)
|
30
|
+
unless status.success?
|
31
|
+
Solargraph.logger.warn { "YARD failed running #{cmd.inspect} in #{gemspec.gem_dir}" }
|
32
|
+
Solargraph.logger.info stdout_and_stderr_str
|
21
33
|
end
|
22
34
|
path
|
23
35
|
end
|
@@ -32,6 +44,7 @@ module Solargraph
|
|
32
44
|
|
33
45
|
# True if another process is currently building the yardoc cache.
|
34
46
|
#
|
47
|
+
# @param gemspec [Gem::Specification]
|
35
48
|
def processing?(gemspec)
|
36
49
|
yardoc = File.join(PinCache.yardoc_path(gemspec), 'processing')
|
37
50
|
File.exist?(yardoc)
|
data/lib/solargraph.rb
CHANGED
@@ -53,6 +53,8 @@ module Solargraph
|
|
53
53
|
dir = File.dirname(__FILE__)
|
54
54
|
VIEWS_PATH = File.join(dir, 'solargraph', 'views')
|
55
55
|
|
56
|
+
CHDIR_MUTEX = Mutex.new
|
57
|
+
|
56
58
|
# @param type [Symbol] Type of assert.
|
57
59
|
def self.asserts_on?(type)
|
58
60
|
if ENV['SOLARGRAPH_ASSERTS'].nil? || ENV['SOLARGRAPH_ASSERTS'].empty?
|