solargraph 0.40.0 → 0.41.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +33 -0
- data/README.md +15 -0
- data/SPONSORS.md +1 -0
- data/lib/.rubocop.yml +1 -1
- data/lib/solargraph.rb +8 -7
- data/lib/solargraph/api_map.rb +52 -75
- data/lib/solargraph/api_map/store.rb +5 -0
- data/lib/solargraph/bench.rb +16 -19
- data/lib/solargraph/compat.rb +15 -1
- data/lib/solargraph/diagnostics/rubocop.rb +10 -2
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +19 -20
- data/lib/solargraph/language_server/host.rb +74 -1
- data/lib/solargraph/language_server/message/completion_item/resolve.rb +1 -0
- data/lib/solargraph/language_server/message/extended/environment.rb +3 -3
- data/lib/solargraph/language_server/message/initialize.rb +30 -35
- data/lib/solargraph/language_server/message/text_document/formatting.rb +69 -21
- data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
- data/lib/solargraph/library.rb +94 -24
- data/lib/solargraph/parser/legacy/node_methods.rb +9 -0
- data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -1
- data/lib/solargraph/parser/rubyvm/node_methods.rb +18 -1
- data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +11 -12
- data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +1 -6
- data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +1 -1
- data/lib/solargraph/source.rb +1 -1
- data/lib/solargraph/source/chain/head.rb +0 -16
- data/lib/solargraph/source/source_chainer.rb +1 -0
- data/lib/solargraph/source_map/mapper.rb +0 -5
- data/lib/solargraph/type_checker.rb +49 -39
- data/lib/solargraph/type_checker/checks.rb +9 -5
- data/lib/solargraph/type_checker/rules.rb +5 -1
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace/config.rb +19 -3
- data/lib/solargraph/yard_map/core_fills.rb +1 -0
- data/solargraph.gemspec +1 -1
- metadata +4 -4
@@ -98,6 +98,7 @@ module Solargraph
|
|
98
98
|
|
99
99
|
def convert_hash node
|
100
100
|
return {} unless Parser.is_ast_node?(node) && node.type == :hash
|
101
|
+
return convert_hash(node.children[0].children[0]) if splatted_hash?(node)
|
101
102
|
result = {}
|
102
103
|
node.children.each do |pair|
|
103
104
|
result[pair.children[0].children[0]] = Solargraph::Parser.chain(pair.children[1])
|
@@ -118,6 +119,14 @@ module Solargraph
|
|
118
119
|
result
|
119
120
|
end
|
120
121
|
|
122
|
+
def splatted_hash? node
|
123
|
+
Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat
|
124
|
+
end
|
125
|
+
|
126
|
+
def splatted_call? node
|
127
|
+
Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat && node.children[0].children[0].type != :hash
|
128
|
+
end
|
129
|
+
|
121
130
|
# @todo Temporarily here for testing. Move to Solargraph::Parser.
|
122
131
|
def call_nodes_from node
|
123
132
|
return [] unless node.is_a?(::Parser::AST::Node)
|
@@ -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) }
|
@@ -90,7 +90,9 @@ module Solargraph
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def convert_hash node
|
93
|
-
return {} unless node?(node) && node.type == :HASH
|
93
|
+
return {} unless node?(node) && node.type == :HASH
|
94
|
+
return convert_hash(node.children[0].children[1]) if splatted_hash?(node)
|
95
|
+
return {} unless node?(node.children[0])
|
94
96
|
result = {}
|
95
97
|
index = 0
|
96
98
|
until index > node.children[0].children.length - 2
|
@@ -103,6 +105,21 @@ module Solargraph
|
|
103
105
|
result
|
104
106
|
end
|
105
107
|
|
108
|
+
def splatted_hash? node
|
109
|
+
splatted_node?(node) && node.children[0].children[1].type == :HASH
|
110
|
+
end
|
111
|
+
|
112
|
+
def splatted_node? node
|
113
|
+
node?(node.children[0]) &&
|
114
|
+
[:ARRAY, :LIST].include?(node.children[0].type) &&
|
115
|
+
node.children[0].children[0].nil? &&
|
116
|
+
node?(node.children[0].children[1])
|
117
|
+
end
|
118
|
+
|
119
|
+
def splatted_call? node
|
120
|
+
splatted_node?(node) && node.children[0].children[1].type != :HASH
|
121
|
+
end
|
122
|
+
|
106
123
|
def node? node
|
107
124
|
node.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
108
125
|
end
|
@@ -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,
|
@@ -55,19 +54,19 @@ module Solargraph
|
|
55
54
|
)
|
56
55
|
region.closure.parameters.push locals.last
|
57
56
|
end
|
58
|
-
if node.children.last
|
59
|
-
locals.push Solargraph::Pin::Parameter.new(
|
60
|
-
location: region.closure.location,
|
61
|
-
closure: region.closure,
|
62
|
-
comments: comments_for(node),
|
63
|
-
name: node.children.last.to_s,
|
64
|
-
presence: region.closure.location.range,
|
65
|
-
decl: :blockarg
|
66
|
-
)
|
67
|
-
region.closure.parameters.push locals.last
|
68
|
-
end
|
69
57
|
end
|
70
58
|
process_children
|
59
|
+
if node.children.last
|
60
|
+
locals.push Solargraph::Pin::Parameter.new(
|
61
|
+
location: region.closure.location,
|
62
|
+
closure: region.closure,
|
63
|
+
comments: comments_for(node),
|
64
|
+
name: node.children.last.to_s,
|
65
|
+
presence: region.closure.location.range,
|
66
|
+
decl: :blockarg
|
67
|
+
)
|
68
|
+
region.closure.parameters.push locals.last
|
69
|
+
end
|
71
70
|
end
|
72
71
|
|
73
72
|
private
|
@@ -16,12 +16,7 @@ module Solargraph
|
|
16
16
|
presence: region.closure.location.range,
|
17
17
|
decl: :optarg
|
18
18
|
)
|
19
|
-
|
20
|
-
if idx
|
21
|
-
region.closure.parameters.insert idx, locals.last
|
22
|
-
else
|
23
|
-
region.closure.parameters.push locals.last
|
24
|
-
end
|
19
|
+
region.closure.parameters.push locals.last
|
25
20
|
node.children[1] && NodeProcessor.process(node.children[1], region, pins, locals)
|
26
21
|
end
|
27
22
|
end
|
@@ -226,7 +226,7 @@ module Solargraph
|
|
226
226
|
|
227
227
|
# @return [void]
|
228
228
|
def process_private_constant
|
229
|
-
|
229
|
+
return unless Parser.is_ast_node?(node.children.last)
|
230
230
|
node.children.last.children[0..-2].each do |child|
|
231
231
|
if [:LIT, :STR].include?(child.type)
|
232
232
|
cn = child.children[0].to_s
|
data/lib/solargraph/source.rb
CHANGED
@@ -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
|
@@ -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
|
|
@@ -95,7 +95,7 @@ module Solargraph
|
|
95
95
|
result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
|
96
96
|
end
|
97
97
|
elsif rules.validate_tags?
|
98
|
-
unless pin.node.nil? || declared.void? ||
|
98
|
+
unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
|
99
99
|
inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
|
100
100
|
if inferred.undefined?
|
101
101
|
unless rules.ignore_all_undefined? || external?(pin)
|
@@ -111,7 +111,7 @@ module Solargraph
|
|
111
111
|
result
|
112
112
|
end
|
113
113
|
|
114
|
-
def
|
114
|
+
def virtual_pin? pin
|
115
115
|
pin.location && source_map.source.comment_at?(pin.location.range.ending)
|
116
116
|
end
|
117
117
|
|
@@ -129,10 +129,10 @@ module Solargraph
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
end
|
132
|
-
params.each_pair do |name,
|
133
|
-
type =
|
132
|
+
params.each_pair do |name, data|
|
133
|
+
type = data[:qualified]
|
134
134
|
if type.undefined?
|
135
|
-
result.push Problem.new(pin.location, "Unresolved type #{
|
135
|
+
result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
|
136
136
|
end
|
137
137
|
end
|
138
138
|
result
|
@@ -266,12 +266,12 @@ module Solargraph
|
|
266
266
|
result.concat kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, idx
|
267
267
|
break
|
268
268
|
else
|
269
|
-
ptype = params[par.name]
|
269
|
+
ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
|
270
270
|
if ptype.nil?
|
271
271
|
# @todo Some level (strong, I guess) should require the param here
|
272
272
|
else
|
273
273
|
argtype = argchain.infer(api_map, block_pin, locals)
|
274
|
-
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
274
|
+
if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
|
275
275
|
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
276
276
|
end
|
277
277
|
end
|
@@ -299,19 +299,19 @@ module Solargraph
|
|
299
299
|
result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
|
300
300
|
else
|
301
301
|
if argchain
|
302
|
-
|
303
|
-
if
|
302
|
+
data = params[par.name]
|
303
|
+
if data.nil?
|
304
304
|
# @todo Some level (strong, I guess) should require the param here
|
305
305
|
else
|
306
|
+
ptype = data[:qualified]
|
307
|
+
next if ptype.undefined?
|
306
308
|
argtype = argchain.infer(api_map, block_pin, locals)
|
307
309
|
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
308
310
|
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
309
311
|
end
|
310
312
|
end
|
311
|
-
|
312
|
-
|
313
|
-
result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
|
314
|
-
end
|
313
|
+
elsif par.decl == :kwarg
|
314
|
+
result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
|
315
315
|
end
|
316
316
|
end
|
317
317
|
end
|
@@ -321,31 +321,34 @@ module Solargraph
|
|
321
321
|
def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
|
322
322
|
result = []
|
323
323
|
kwargs.each_pair do |pname, argchain|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
330
|
-
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
|
331
|
-
end
|
324
|
+
next unless params.key?(pname.to_s)
|
325
|
+
ptype = params[pname.to_s][:qualified]
|
326
|
+
argtype = argchain.infer(api_map, block_pin, locals)
|
327
|
+
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
328
|
+
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
|
332
329
|
end
|
333
330
|
end
|
334
331
|
result
|
335
332
|
end
|
336
333
|
|
334
|
+
# @param [Pin::Method]
|
335
|
+
# @return [Hash]
|
337
336
|
def param_hash(pin)
|
338
337
|
tags = pin.docstring.tags(:param)
|
339
338
|
return {} if tags.empty?
|
340
339
|
result = {}
|
341
340
|
tags.each do |tag|
|
342
341
|
next if tag.types.nil? || tag.types.empty?
|
343
|
-
result[tag.name.to_s] =
|
342
|
+
result[tag.name.to_s] = {
|
343
|
+
tagged: tag.types.join(', '),
|
344
|
+
qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
|
345
|
+
}
|
344
346
|
end
|
345
347
|
result
|
346
348
|
end
|
347
349
|
|
348
350
|
# @param [Array<Pin::Method>]
|
351
|
+
# @return [Hash]
|
349
352
|
def first_param_hash(pins)
|
350
353
|
pins.each do |pin|
|
351
354
|
result = param_hash(pin)
|
@@ -413,24 +416,28 @@ module Solargraph
|
|
413
416
|
end
|
414
417
|
settled_kwargs = 0
|
415
418
|
unless unchecked.empty?
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
419
|
+
if Parser.is_ast_node?(unchecked.last.node) && splatted_call?(unchecked.last.node)
|
420
|
+
settled_kwargs = pin.parameters.count(&:keyword?)
|
421
|
+
else
|
422
|
+
kwargs = convert_hash(unchecked.last.node)
|
423
|
+
if pin.parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
|
424
|
+
if kwargs.empty?
|
425
|
+
add_params += 1
|
426
|
+
else
|
427
|
+
unchecked.pop
|
428
|
+
pin.parameters.each do |param|
|
429
|
+
next unless param.keyword?
|
430
|
+
if kwargs.key?(param.name.to_sym)
|
431
|
+
kwargs.delete param.name.to_sym
|
432
|
+
settled_kwargs += 1
|
433
|
+
elsif param.decl == :kwarg
|
434
|
+
return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
|
435
|
+
end
|
436
|
+
end
|
437
|
+
kwargs.clear if pin.parameters.any?(&:kwrestarg?)
|
438
|
+
unless kwargs.empty?
|
439
|
+
return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
|
429
440
|
end
|
430
|
-
end
|
431
|
-
kwargs.clear if pin.parameters.any?(&:kwrestarg?)
|
432
|
-
unless kwargs.empty?
|
433
|
-
return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
|
434
441
|
end
|
435
442
|
end
|
436
443
|
end
|
@@ -443,6 +450,9 @@ module Solargraph
|
|
443
450
|
if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
|
444
451
|
return []
|
445
452
|
end
|
453
|
+
if req + add_params + 1 == unchecked.length && splatted_call?(unchecked.last.node) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
|
454
|
+
return []
|
455
|
+
end
|
446
456
|
return [Problem.new(location, "Too many arguments to #{pin.path}")]
|
447
457
|
elsif unchecked.length < req - settled_kwargs && (arguments.empty? || !arguments.last.splat?)
|
448
458
|
return [Problem.new(location, "Not enough arguments to #{pin.path}")]
|
@@ -1,5 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Solargraph
|
2
4
|
class TypeChecker
|
5
|
+
# Helper methods for performing type checks
|
6
|
+
#
|
3
7
|
module Checks
|
4
8
|
module_function
|
5
9
|
|
@@ -62,7 +66,7 @@ module Solargraph
|
|
62
66
|
# @param inferred [ComplexType]
|
63
67
|
# @return [Boolean]
|
64
68
|
def duck_types_match? api_map, expected, inferred
|
65
|
-
raise ArgumentError,
|
69
|
+
raise ArgumentError, 'Expected type must be duck type' unless expected.duck_type?
|
66
70
|
expected.each do |exp|
|
67
71
|
next unless exp.duck_type?
|
68
72
|
quack = exp.to_s[1..-1]
|
@@ -71,7 +75,7 @@ module Solargraph
|
|
71
75
|
true
|
72
76
|
end
|
73
77
|
|
74
|
-
# @param type [ComplexType]
|
78
|
+
# @param type [ComplexType::UniqueType]
|
75
79
|
# @return [String]
|
76
80
|
def fuzz type
|
77
81
|
if type.parameters?
|
@@ -82,13 +86,13 @@ module Solargraph
|
|
82
86
|
end
|
83
87
|
|
84
88
|
# @param api_map [ApiMap]
|
85
|
-
# @param cls1 [ComplexType]
|
86
|
-
# @param cls2 [ComplexType]
|
89
|
+
# @param cls1 [ComplexType::UniqueType]
|
90
|
+
# @param cls2 [ComplexType::UniqueType]
|
87
91
|
# @return [Boolean]
|
88
92
|
def either_way?(api_map, cls1, cls2)
|
89
93
|
f1 = fuzz(cls1)
|
90
94
|
f2 = fuzz(cls2)
|
91
|
-
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)
|
92
96
|
end
|
93
97
|
end
|
94
98
|
end
|
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Solargraph
|
2
4
|
class TypeChecker
|
5
|
+
# Definitions of type checking rules to be performed at various levels
|
6
|
+
#
|
3
7
|
class Rules
|
4
8
|
LEVELS = {
|
5
9
|
normal: 0,
|
6
10
|
typed: 1,
|
7
11
|
strict: 2,
|
8
12
|
strong: 3
|
9
|
-
}
|
13
|
+
}.freeze
|
10
14
|
|
11
15
|
# @return [Symbol]
|
12
16
|
attr_reader :level
|
data/lib/solargraph/version.rb
CHANGED
@@ -85,6 +85,13 @@ module Solargraph
|
|
85
85
|
raw_data['reporters']
|
86
86
|
end
|
87
87
|
|
88
|
+
# A hash of options supported by the formatter
|
89
|
+
#
|
90
|
+
# @return [Hash]
|
91
|
+
def formatter
|
92
|
+
raw_data['formatter']
|
93
|
+
end
|
94
|
+
|
88
95
|
# An array of plugins to require.
|
89
96
|
#
|
90
97
|
# @return [Array<String>]
|
@@ -144,6 +151,14 @@ module Solargraph
|
|
144
151
|
'require' => [],
|
145
152
|
'domains' => [],
|
146
153
|
'reporters' => %w[rubocop require_not_found],
|
154
|
+
'formatter' => {
|
155
|
+
'rubocop' => {
|
156
|
+
'cops' => 'safe',
|
157
|
+
'except' => [],
|
158
|
+
'only' => [],
|
159
|
+
'extra_args' =>[]
|
160
|
+
}
|
161
|
+
},
|
147
162
|
'require_paths' => [],
|
148
163
|
'plugins' => [],
|
149
164
|
'max_files' => MAX_FILES
|
@@ -155,9 +170,10 @@ module Solargraph
|
|
155
170
|
# @param globs [Array<String>]
|
156
171
|
# @return [Array<String>]
|
157
172
|
def process_globs globs
|
158
|
-
result =
|
159
|
-
|
160
|
-
|
173
|
+
result = globs.flat_map do |glob|
|
174
|
+
Dir[File.join directory, glob]
|
175
|
+
.map{ |f| f.gsub(/\\/, '/') }
|
176
|
+
.select { |f| File.file?(f) }
|
161
177
|
end
|
162
178
|
result
|
163
179
|
end
|