solargraph 0.40.1 → 0.41.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/CHANGELOG.md +32 -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 +19 -18
- 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 +68 -18
- data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
- data/lib/solargraph/library.rb +94 -21
- data/lib/solargraph/parser/legacy/node_methods.rb +4 -0
- data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -1
- data/lib/solargraph/parser/rubyvm/node_methods.rb +9 -2
- 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
@@ -123,6 +123,10 @@ module Solargraph
|
|
123
123
|
Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat
|
124
124
|
end
|
125
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
|
+
|
126
130
|
# @todo Temporarily here for testing. Move to Solargraph::Parser.
|
127
131
|
def call_nodes_from node
|
128
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) }
|
@@ -106,11 +106,18 @@ module Solargraph
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def splatted_hash? node
|
109
|
+
splatted_node?(node) && node.children[0].children[1].type == :HASH
|
110
|
+
end
|
111
|
+
|
112
|
+
def splatted_node? node
|
109
113
|
node?(node.children[0]) &&
|
110
114
|
[:ARRAY, :LIST].include?(node.children[0].type) &&
|
111
115
|
node.children[0].children[0].nil? &&
|
112
|
-
node?(node.children[0].children[1])
|
113
|
-
|
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
|
114
121
|
end
|
115
122
|
|
116
123
|
def node? node
|
@@ -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
|