solargraph 0.56.2 → 0.58.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 +127 -0
- data/.github/workflows/plugins.yml +183 -7
- data/.github/workflows/rspec.yml +55 -5
- data/.github/workflows/typecheck.yml +6 -3
- data/.gitignore +5 -0
- data/.overcommit.yml +72 -0
- data/.rspec +1 -0
- data/.rubocop.yml +66 -0
- data/.rubocop_todo.yml +1279 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +69 -0
- data/README.md +8 -4
- data/Rakefile +125 -13
- data/bin/solargraph +8 -5
- data/lib/solargraph/api_map/cache.rb +3 -2
- data/lib/solargraph/api_map/constants.rb +279 -0
- data/lib/solargraph/api_map/index.rb +49 -31
- data/lib/solargraph/api_map/source_to_yard.rb +13 -4
- data/lib/solargraph/api_map/store.rb +144 -26
- data/lib/solargraph/api_map.rb +217 -245
- data/lib/solargraph/bench.rb +1 -0
- data/lib/solargraph/complex_type/type_methods.rb +6 -0
- data/lib/solargraph/complex_type/unique_type.rb +19 -12
- data/lib/solargraph/complex_type.rb +24 -3
- 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 +4 -2
- 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 +5 -3
- data/lib/solargraph/doc_map.rb +44 -13
- data/lib/solargraph/environ.rb +9 -2
- data/lib/solargraph/equality.rb +1 -0
- data/lib/solargraph/gem_pins.rb +21 -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 +12 -5
- 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 +19 -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 +4 -1
- data/lib/solargraph/library.rb +11 -18
- data/lib/solargraph/location.rb +3 -0
- data/lib/solargraph/logging.rb +11 -2
- data/lib/solargraph/page.rb +3 -0
- data/lib/solargraph/parser/comment_ripper.rb +8 -1
- data/lib/solargraph/parser/flow_sensitive_typing.rb +33 -5
- 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 +3 -13
- 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 +5 -16
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +1 -0
- 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/opasgn_node.rb +64 -8
- data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +12 -3
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +36 -16
- data/lib/solargraph/parser/region.rb +3 -0
- data/lib/solargraph/parser/snippet.rb +2 -0
- data/lib/solargraph/pin/base.rb +77 -14
- data/lib/solargraph/pin/base_variable.rb +6 -5
- data/lib/solargraph/pin/block.rb +3 -2
- data/lib/solargraph/pin/callable.rb +14 -1
- data/lib/solargraph/pin/closure.rb +5 -7
- data/lib/solargraph/pin/common.rb +6 -2
- data/lib/solargraph/pin/constant.rb +2 -0
- data/lib/solargraph/pin/local_variable.rb +1 -2
- data/lib/solargraph/pin/method.rb +28 -9
- data/lib/solargraph/pin/method_alias.rb +3 -0
- data/lib/solargraph/pin/parameter.rb +24 -10
- data/lib/solargraph/pin/proxy_type.rb +5 -1
- data/lib/solargraph/pin/reference/override.rb +15 -1
- data/lib/solargraph/pin/reference/superclass.rb +5 -0
- data/lib/solargraph/pin/reference.rb +17 -0
- data/lib/solargraph/pin/search.rb +6 -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 +3 -0
- data/lib/solargraph/range.rb +5 -0
- data/lib/solargraph/rbs_map/conversions.rb +29 -6
- data/lib/solargraph/rbs_map/core_fills.rb +18 -0
- data/lib/solargraph/rbs_map/core_map.rb +14 -7
- data/lib/solargraph/rbs_map.rb +14 -1
- data/lib/solargraph/shell.rb +85 -1
- data/lib/solargraph/source/chain/call.rb +7 -3
- data/lib/solargraph/source/chain/constant.rb +3 -66
- data/lib/solargraph/source/chain/if.rb +1 -1
- data/lib/solargraph/source/chain/link.rb +11 -2
- data/lib/solargraph/source/chain/or.rb +1 -1
- data/lib/solargraph/source/chain.rb +11 -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 +6 -3
- data/lib/solargraph/source_map/clip.rb +18 -26
- data/lib/solargraph/source_map/data.rb +4 -0
- data/lib/solargraph/source_map/mapper.rb +2 -2
- data/lib/solargraph/source_map.rb +28 -16
- data/lib/solargraph/type_checker/param_def.rb +2 -0
- data/lib/solargraph/type_checker/rules.rb +30 -8
- data/lib/solargraph/type_checker.rb +301 -186
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace/config.rb +21 -5
- data/lib/solargraph/workspace/require_paths.rb +97 -0
- data/lib/solargraph/workspace.rb +30 -67
- data/lib/solargraph/yard_map/mapper/to_method.rb +4 -3
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
- data/lib/solargraph/yard_map/to_method.rb +2 -1
- data/lib/solargraph/yardoc.rb +39 -3
- data/lib/solargraph.rb +2 -0
- data/rbs/fills/bundler/0/bundler.rbs +4271 -0
- data/rbs/fills/open3/0/open3.rbs +172 -0
- data/rbs/fills/rubygems/0/basic_specification.rbs +326 -0
- data/rbs/fills/rubygems/0/errors.rbs +364 -0
- data/rbs/fills/rubygems/0/spec_fetcher.rbs +107 -0
- data/rbs/fills/rubygems/0/specification.rbs +1753 -0
- data/rbs/fills/{tuple.rbs → tuple/tuple.rbs} +2 -3
- data/rbs_collection.yaml +4 -4
- data/sig/shims/ast/0/node.rbs +5 -0
- data/sig/shims/ast/2.4/.rbs_meta.yaml +9 -0
- data/sig/shims/ast/2.4/ast.rbs +73 -0
- data/sig/shims/parser/3.2.0.1/builders/default.rbs +195 -0
- data/sig/shims/parser/3.2.0.1/manifest.yaml +7 -0
- data/sig/shims/parser/3.2.0.1/parser.rbs +201 -0
- data/sig/shims/parser/3.2.0.1/polyfill.rbs +4 -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 +26 -5
- metadata +181 -13
- data/lib/.rubocop.yml +0 -22
- data/lib/solargraph/parser/node_methods.rb +0 -97
|
@@ -21,14 +21,23 @@ module Solargraph
|
|
|
21
21
|
# @return [ApiMap]
|
|
22
22
|
attr_reader :api_map
|
|
23
23
|
|
|
24
|
-
# @param filename [String]
|
|
24
|
+
# @param filename [String, nil]
|
|
25
25
|
# @param api_map [ApiMap, nil]
|
|
26
|
-
# @param level [Symbol]
|
|
27
|
-
|
|
26
|
+
# @param level [Symbol] Don't complain about anything above this level
|
|
27
|
+
# @param workspace [Workspace, nil] Workspace to use for loading
|
|
28
|
+
# type checker rules modified by user config
|
|
29
|
+
# @param type_checker_rules [Hash{Symbol => Symbol}] Overrides for
|
|
30
|
+
# type checker rules - e.g., :report_undefined => :strong
|
|
31
|
+
# @param rules [Rules] Type checker rules object
|
|
32
|
+
def initialize filename,
|
|
33
|
+
api_map: nil,
|
|
34
|
+
level: :normal,
|
|
35
|
+
workspace: filename ? Workspace.new(File.dirname(filename)) : nil,
|
|
36
|
+
rules: workspace ? workspace.rules(level) : Rules.new(level, {})
|
|
28
37
|
@filename = filename
|
|
29
38
|
# @todo Smarter directory resolution
|
|
30
39
|
@api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename))
|
|
31
|
-
@rules =
|
|
40
|
+
@rules = rules
|
|
32
41
|
# @type [Array<Range>]
|
|
33
42
|
@marked_ranges = []
|
|
34
43
|
end
|
|
@@ -38,15 +47,20 @@ module Solargraph
|
|
|
38
47
|
@source_map ||= api_map.source_map(filename)
|
|
39
48
|
end
|
|
40
49
|
|
|
50
|
+
# @return [Source]
|
|
51
|
+
def source
|
|
52
|
+
@source_map.source
|
|
53
|
+
end
|
|
54
|
+
|
|
41
55
|
# @return [Array<Problem>]
|
|
42
56
|
def problems
|
|
43
57
|
@problems ||= begin
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
all = method_tag_problems
|
|
59
|
+
.concat(variable_type_tag_problems)
|
|
60
|
+
.concat(const_problems)
|
|
61
|
+
.concat(call_problems)
|
|
62
|
+
unignored = without_ignored(all)
|
|
63
|
+
unignored.concat(unneeded_sgignore_problems)
|
|
50
64
|
end
|
|
51
65
|
end
|
|
52
66
|
|
|
@@ -91,7 +105,7 @@ module Solargraph
|
|
|
91
105
|
def method_return_type_problems_for pin
|
|
92
106
|
return [] if pin.is_a?(Pin::MethodAlias)
|
|
93
107
|
result = []
|
|
94
|
-
declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, pin.
|
|
108
|
+
declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, *pin.gates)
|
|
95
109
|
if declared.undefined?
|
|
96
110
|
if pin.return_type.undefined? && rules.require_type_tags?
|
|
97
111
|
if pin.attribute?
|
|
@@ -140,40 +154,39 @@ module Solargraph
|
|
|
140
154
|
|
|
141
155
|
# @param pin [Pin::Base]
|
|
142
156
|
def virtual_pin? pin
|
|
143
|
-
pin.location &&
|
|
157
|
+
pin.location && source.comment_at?(pin.location.range.ending)
|
|
144
158
|
end
|
|
145
159
|
|
|
146
160
|
# @param pin [Pin::Method]
|
|
147
161
|
# @return [Array<Problem>]
|
|
148
162
|
def method_param_type_problems_for pin
|
|
149
163
|
stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
|
|
150
|
-
params = first_param_hash(stack)
|
|
151
164
|
result = []
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
pin.signatures.each do |sig|
|
|
166
|
+
params = param_details_from_stack(sig, stack)
|
|
167
|
+
if rules.require_type_tags?
|
|
168
|
+
sig.parameters.each do |par|
|
|
169
|
+
break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
|
|
170
|
+
unless params[par.name]
|
|
171
|
+
if pin.attribute?
|
|
172
|
+
inferred = pin.probe(api_map).self_to_type(pin.full_context)
|
|
173
|
+
if inferred.undefined?
|
|
174
|
+
result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
|
|
175
|
+
end
|
|
176
|
+
else
|
|
160
177
|
result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
|
|
161
178
|
end
|
|
162
|
-
else
|
|
163
|
-
result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
|
|
164
179
|
end
|
|
165
180
|
end
|
|
166
|
-
end
|
|
167
181
|
end
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
|
|
182
|
+
# @param name [String]
|
|
183
|
+
# @param data [Hash{Symbol => BasicObject}]
|
|
184
|
+
params.each_pair do |name, data|
|
|
185
|
+
# @type [ComplexType]
|
|
186
|
+
type = data[:qualified]
|
|
187
|
+
if type.undefined?
|
|
188
|
+
result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
|
|
189
|
+
end
|
|
177
190
|
end
|
|
178
191
|
end
|
|
179
192
|
result
|
|
@@ -191,7 +204,7 @@ module Solargraph
|
|
|
191
204
|
if pin.return_type.defined?
|
|
192
205
|
declared = pin.typify(api_map)
|
|
193
206
|
next if declared.duck_type?
|
|
194
|
-
if declared.defined?
|
|
207
|
+
if declared.defined? && pin.assignment
|
|
195
208
|
if rules.validate_tags?
|
|
196
209
|
inferred = pin.probe(api_map)
|
|
197
210
|
if inferred.undefined?
|
|
@@ -212,7 +225,7 @@ module Solargraph
|
|
|
212
225
|
elsif !pin.is_a?(Pin::Parameter) && !resolved_constant?(pin)
|
|
213
226
|
result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
|
|
214
227
|
end
|
|
215
|
-
|
|
228
|
+
elsif pin.assignment
|
|
216
229
|
inferred = pin.probe(api_map)
|
|
217
230
|
if inferred.undefined? && declared_externally?(pin)
|
|
218
231
|
ignored_pins.push pin
|
|
@@ -231,13 +244,14 @@ module Solargraph
|
|
|
231
244
|
def const_problems
|
|
232
245
|
return [] unless rules.validate_consts?
|
|
233
246
|
result = []
|
|
234
|
-
Solargraph::Parser::NodeMethods.const_nodes_from(
|
|
247
|
+
Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const|
|
|
235
248
|
rng = Solargraph::Range.from_node(const)
|
|
236
249
|
chain = Solargraph::Parser.chain(const, filename)
|
|
237
|
-
|
|
250
|
+
closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column)
|
|
251
|
+
closure_pin.rebind(api_map)
|
|
238
252
|
location = Location.new(filename, rng)
|
|
239
253
|
locals = source_map.locals_at(location)
|
|
240
|
-
pins = chain.define(api_map,
|
|
254
|
+
pins = chain.define(api_map, closure_pin, locals)
|
|
241
255
|
if pins.empty?
|
|
242
256
|
result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
|
|
243
257
|
@marked_ranges.push location.range
|
|
@@ -249,21 +263,29 @@ module Solargraph
|
|
|
249
263
|
# @return [Array<Problem>]
|
|
250
264
|
def call_problems
|
|
251
265
|
result = []
|
|
252
|
-
Solargraph::Parser::NodeMethods.call_nodes_from(
|
|
266
|
+
Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call|
|
|
253
267
|
rng = Solargraph::Range.from_node(call)
|
|
254
268
|
next if @marked_ranges.any? { |d| d.contain?(rng.start) }
|
|
255
269
|
chain = Solargraph::Parser.chain(call, filename)
|
|
256
|
-
|
|
270
|
+
closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column)
|
|
271
|
+
namespace_pin = closure_pin
|
|
272
|
+
if call.type == :block
|
|
273
|
+
# blocks in the AST include the method call as well, so the
|
|
274
|
+
# node returned by #call_nodes_from needs to be backed out
|
|
275
|
+
# one closure
|
|
276
|
+
closure_pin = closure_pin.closure
|
|
277
|
+
end
|
|
278
|
+
closure_pin.rebind(api_map)
|
|
257
279
|
location = Location.new(filename, rng)
|
|
258
280
|
locals = source_map.locals_at(location)
|
|
259
|
-
type = chain.infer(api_map,
|
|
281
|
+
type = chain.infer(api_map, closure_pin, locals)
|
|
260
282
|
if type.undefined? && !rules.ignore_all_undefined?
|
|
261
283
|
base = chain
|
|
262
284
|
missing = chain
|
|
263
285
|
found = nil
|
|
264
286
|
closest = ComplexType::UNDEFINED
|
|
265
287
|
until base.links.first.undefined?
|
|
266
|
-
found = base.define(api_map,
|
|
288
|
+
found = base.define(api_map, closure_pin, locals).first
|
|
267
289
|
break if found
|
|
268
290
|
missing = base
|
|
269
291
|
base = base.base
|
|
@@ -272,145 +294,160 @@ module Solargraph
|
|
|
272
294
|
# @todo remove the internal_or_core? check at a higher-than-strict level
|
|
273
295
|
if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
|
|
274
296
|
unless closest.generic? || ignored_pins.include?(found)
|
|
275
|
-
|
|
297
|
+
if closest.defined?
|
|
298
|
+
result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}")
|
|
299
|
+
else
|
|
300
|
+
result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
|
|
301
|
+
end
|
|
276
302
|
@marked_ranges.push rng
|
|
277
303
|
end
|
|
278
304
|
end
|
|
279
305
|
end
|
|
280
|
-
result.concat argument_problems_for(chain, api_map,
|
|
306
|
+
result.concat argument_problems_for(chain, api_map, closure_pin, locals, location)
|
|
281
307
|
end
|
|
282
308
|
result
|
|
283
309
|
end
|
|
284
310
|
|
|
285
311
|
# @param chain [Solargraph::Source::Chain]
|
|
286
312
|
# @param api_map [Solargraph::ApiMap]
|
|
287
|
-
# @param
|
|
313
|
+
# @param closure_pin [Solargraph::Pin::Closure]
|
|
288
314
|
# @param locals [Array<Solargraph::Pin::Base>]
|
|
289
315
|
# @param location [Solargraph::Location]
|
|
290
316
|
# @return [Array<Problem>]
|
|
291
|
-
def argument_problems_for chain, api_map,
|
|
317
|
+
def argument_problems_for chain, api_map, closure_pin, locals, location
|
|
292
318
|
result = []
|
|
293
319
|
base = chain
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
320
|
+
# @type last_base_link [Solargraph::Source::Chain::Call]
|
|
321
|
+
last_base_link = base.links.last
|
|
322
|
+
return [] unless last_base_link.is_a?(Solargraph::Source::Chain::Call)
|
|
323
|
+
|
|
324
|
+
arguments = last_base_link.arguments
|
|
325
|
+
|
|
326
|
+
pins = base.define(api_map, closure_pin, locals)
|
|
327
|
+
|
|
328
|
+
first_pin = pins.first
|
|
329
|
+
if first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map)
|
|
330
|
+
# Do nothing, as we can't find the actual method implementation
|
|
331
|
+
elsif first_pin.is_a?(Pin::Method)
|
|
332
|
+
# @type [Pin::Method]
|
|
333
|
+
pin = first_pin
|
|
334
|
+
ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
|
|
335
|
+
arity_problems_for(pin, fake_args_for(closure_pin), location)
|
|
336
|
+
elsif pin.path == 'Class#new'
|
|
337
|
+
fqns = if base.links.one?
|
|
338
|
+
closure_pin.namespace
|
|
339
|
+
else
|
|
340
|
+
base.base.infer(api_map, closure_pin, locals).namespace
|
|
341
|
+
end
|
|
342
|
+
init = api_map.get_method_stack(fqns, 'initialize').first
|
|
343
|
+
init ? arity_problems_for(init, arguments, location) : []
|
|
344
|
+
else
|
|
345
|
+
arity_problems_for(pin, arguments, location)
|
|
346
|
+
end
|
|
347
|
+
return ap unless ap.empty?
|
|
348
|
+
return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper)
|
|
349
|
+
|
|
350
|
+
all_errors = []
|
|
351
|
+
pin.signatures.sort { |sig| sig.parameters.length }.each do |sig|
|
|
352
|
+
params = param_details_from_stack(sig, pins)
|
|
353
|
+
|
|
354
|
+
signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
|
|
355
|
+
|
|
356
|
+
if signature_errors.empty?
|
|
357
|
+
# we found a signature that works - meaning errors from
|
|
358
|
+
# other signatures don't matter.
|
|
359
|
+
return []
|
|
360
|
+
end
|
|
361
|
+
all_errors.concat signature_errors
|
|
362
|
+
end
|
|
363
|
+
result.concat all_errors
|
|
364
|
+
end
|
|
365
|
+
result
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# @param location [Location]
|
|
369
|
+
# @param locals [Array<Pin::LocalVariable>]
|
|
370
|
+
# @param closure_pin [Pin::Closure]
|
|
371
|
+
# @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}]
|
|
372
|
+
# @param arguments [Array<Source::Chain>]
|
|
373
|
+
# @param sig [Pin::Signature]
|
|
374
|
+
# @param pin [Pin::Method]
|
|
375
|
+
# @param pins [Array<Pin::Method>]
|
|
376
|
+
#
|
|
377
|
+
# @return [Array<Problem>]
|
|
378
|
+
def signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
|
|
379
|
+
errors = []
|
|
380
|
+
# @todo add logic mapping up restarg parameters with
|
|
381
|
+
# arguments (including restarg arguments). Use tuples
|
|
382
|
+
# when possible, and when not, ensure provably
|
|
383
|
+
# incorrect situations are detected.
|
|
384
|
+
sig.parameters.each_with_index do |par, idx|
|
|
385
|
+
return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing
|
|
386
|
+
argchain = arguments[idx]
|
|
387
|
+
if argchain.nil?
|
|
388
|
+
if par.decl == :arg
|
|
389
|
+
final_arg = arguments.last
|
|
390
|
+
if final_arg && final_arg.node.type == :splat
|
|
391
|
+
argchain = final_arg
|
|
392
|
+
return errors
|
|
313
393
|
else
|
|
314
|
-
|
|
394
|
+
errors.push Problem.new(location, "Not enough arguments to #{pin.path}")
|
|
315
395
|
end
|
|
316
|
-
init = api_map.get_method_stack(fqns, 'initialize').first
|
|
317
|
-
init ? arity_problems_for(init, arguments, location) : []
|
|
318
396
|
else
|
|
319
|
-
|
|
397
|
+
final_arg = arguments.last
|
|
398
|
+
argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type)
|
|
320
399
|
end
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
#
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
400
|
+
end
|
|
401
|
+
if argchain
|
|
402
|
+
if par.decl != :arg
|
|
403
|
+
errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx
|
|
404
|
+
next
|
|
405
|
+
else
|
|
406
|
+
if argchain.node.type == :splat && argchain == arguments.last
|
|
407
|
+
final_arg = argchain
|
|
408
|
+
end
|
|
409
|
+
if (final_arg && final_arg.node.type == :splat)
|
|
410
|
+
# The final argument given has been seen and was a
|
|
411
|
+
# splat, which doesn't give us useful types or
|
|
412
|
+
# arities against positional parameters, so let's
|
|
413
|
+
# continue on in case there are any required
|
|
414
|
+
# kwargs we should warn about
|
|
415
|
+
next
|
|
416
|
+
end
|
|
417
|
+
if argchain.node.type == :splat && par != sig.parameters.last
|
|
418
|
+
# we have been given a splat and there are more
|
|
419
|
+
# arguments to come.
|
|
420
|
+
|
|
421
|
+
# @todo Improve this so that we can skip past the
|
|
422
|
+
# rest of the positional parameters here but still
|
|
423
|
+
# process the kwargs
|
|
424
|
+
return errors
|
|
396
425
|
end
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
426
|
+
ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
|
|
427
|
+
ptype = ptype.self_to_type(par.context)
|
|
428
|
+
if ptype.nil?
|
|
429
|
+
# @todo Some level (strong, I guess) should require the param here
|
|
430
|
+
else
|
|
431
|
+
argtype = argchain.infer(api_map, closure_pin, locals)
|
|
432
|
+
argtype = argtype.self_to_type(closure_pin.context)
|
|
433
|
+
if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
|
|
434
|
+
errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
|
435
|
+
return errors
|
|
436
|
+
end
|
|
400
437
|
end
|
|
401
|
-
all_errors.concat errors
|
|
402
438
|
end
|
|
403
|
-
|
|
439
|
+
elsif par.decl == :kwarg
|
|
440
|
+
errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
|
|
441
|
+
next
|
|
404
442
|
end
|
|
405
|
-
base = base.base
|
|
406
443
|
end
|
|
407
|
-
|
|
444
|
+
errors
|
|
408
445
|
end
|
|
409
446
|
|
|
410
447
|
# @param sig [Pin::Signature]
|
|
411
448
|
# @param argchain [Source::Chain]
|
|
412
449
|
# @param api_map [ApiMap]
|
|
413
|
-
# @param
|
|
450
|
+
# @param closure_pin [Pin::Closure]
|
|
414
451
|
# @param locals [Array<Pin::LocalVariable>]
|
|
415
452
|
# @param location [Location]
|
|
416
453
|
# @param pin [Pin::Method]
|
|
@@ -418,13 +455,13 @@ module Solargraph
|
|
|
418
455
|
# @param idx [Integer]
|
|
419
456
|
#
|
|
420
457
|
# @return [Array<Problem>]
|
|
421
|
-
def kwarg_problems_for sig, argchain, api_map,
|
|
458
|
+
def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx
|
|
422
459
|
result = []
|
|
423
460
|
kwargs = convert_hash(argchain.node)
|
|
424
461
|
par = sig.parameters[idx]
|
|
425
462
|
argchain = kwargs[par.name.to_sym]
|
|
426
463
|
if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
|
|
427
|
-
result.concat kwrestarg_problems_for(api_map,
|
|
464
|
+
result.concat kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs)
|
|
428
465
|
else
|
|
429
466
|
if argchain
|
|
430
467
|
data = params[par.name]
|
|
@@ -432,8 +469,11 @@ module Solargraph
|
|
|
432
469
|
# @todo Some level (strong, I guess) should require the param here
|
|
433
470
|
else
|
|
434
471
|
ptype = data[:qualified]
|
|
472
|
+
ptype = ptype.self_to_type(pin.context)
|
|
435
473
|
unless ptype.undefined?
|
|
436
|
-
|
|
474
|
+
# @sg-ignore https://github.com/castwide/solargraph/pull/1127
|
|
475
|
+
argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context)
|
|
476
|
+
# @sg-ignore Unresolved call to defined?
|
|
437
477
|
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
|
438
478
|
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
|
439
479
|
end
|
|
@@ -447,19 +487,21 @@ module Solargraph
|
|
|
447
487
|
end
|
|
448
488
|
|
|
449
489
|
# @param api_map [ApiMap]
|
|
450
|
-
# @param
|
|
490
|
+
# @param closure_pin [Pin::Closure]
|
|
451
491
|
# @param locals [Array<Pin::LocalVariable>]
|
|
452
492
|
# @param location [Location]
|
|
453
493
|
# @param pin [Pin::Method]
|
|
454
494
|
# @param params [Hash{String => [nil, Hash]}]
|
|
455
495
|
# @param kwargs [Hash{Symbol => Source::Chain}]
|
|
456
496
|
# @return [Array<Problem>]
|
|
457
|
-
def kwrestarg_problems_for(api_map,
|
|
497
|
+
def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs)
|
|
458
498
|
result = []
|
|
459
499
|
kwargs.each_pair do |pname, argchain|
|
|
460
500
|
next unless params.key?(pname.to_s)
|
|
461
501
|
ptype = params[pname.to_s][:qualified]
|
|
462
|
-
|
|
502
|
+
ptype = ptype.self_to_type(pin.context)
|
|
503
|
+
argtype = argchain.infer(api_map, closure_pin, locals)
|
|
504
|
+
argtype = argtype.self_to_type(closure_pin.context)
|
|
463
505
|
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
|
464
506
|
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
|
|
465
507
|
end
|
|
@@ -467,9 +509,32 @@ module Solargraph
|
|
|
467
509
|
result
|
|
468
510
|
end
|
|
469
511
|
|
|
470
|
-
# @param
|
|
512
|
+
# @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}]
|
|
513
|
+
# @param pin [Pin::Method, Pin::Signature]
|
|
514
|
+
# @param relevant_pin [Pin::Method, Pin::Signature] the pin which is under inspection
|
|
515
|
+
# @return [void]
|
|
516
|
+
def add_restkwarg_param_tag_details(param_details, pin, relevant_pin)
|
|
517
|
+
# see if we have additional tags to pay attention to from YARD -
|
|
518
|
+
# e.g., kwargs in a **restkwargs splat
|
|
519
|
+
tags = pin.docstring.tags(:param)
|
|
520
|
+
tags.each do |tag|
|
|
521
|
+
next if param_details.key? tag.name.to_s
|
|
522
|
+
next if tag.types.nil?
|
|
523
|
+
details = {
|
|
524
|
+
tagged: tag.types.join(', '),
|
|
525
|
+
qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
|
|
526
|
+
}
|
|
527
|
+
# don't complain about a param that didn't come from the pin we're looking at anyway
|
|
528
|
+
if details[:qualified].defined? ||
|
|
529
|
+
relevant_pin.parameter_names.include?(tag.name.to_s)
|
|
530
|
+
param_details[tag.name.to_s] = details
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# @param pin [Pin::Signature]
|
|
471
536
|
# @return [Hash{String => Hash{Symbol => String, ComplexType}}]
|
|
472
|
-
def
|
|
537
|
+
def signature_param_details(pin)
|
|
473
538
|
# @type [Hash{String => Hash{Symbol => String, ComplexType}}]
|
|
474
539
|
result = {}
|
|
475
540
|
pin.parameters.each do |param|
|
|
@@ -488,36 +553,50 @@ module Solargraph
|
|
|
488
553
|
next if tag.types.nil?
|
|
489
554
|
result[tag.name.to_s] = {
|
|
490
555
|
tagged: tag.types.join(', '),
|
|
491
|
-
qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.
|
|
556
|
+
qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, *pin.closure.gates)
|
|
492
557
|
}
|
|
493
558
|
end
|
|
494
559
|
result
|
|
495
560
|
end
|
|
496
561
|
|
|
497
|
-
#
|
|
562
|
+
# The original signature defines the parameters, but other
|
|
563
|
+
# signatures and method pins can help by adding type information
|
|
564
|
+
#
|
|
565
|
+
# @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}]
|
|
566
|
+
# @param param_names [Array<String>]
|
|
567
|
+
# @param new_param_details [Hash{String => Hash{Symbol => String, ComplexType}}]
|
|
568
|
+
#
|
|
569
|
+
# @return [void]
|
|
570
|
+
def add_to_param_details(param_details, param_names, new_param_details)
|
|
571
|
+
new_param_details.each do |param_name, details|
|
|
572
|
+
next unless param_names.include?(param_name)
|
|
573
|
+
|
|
574
|
+
param_details[param_name] ||= {}
|
|
575
|
+
param_details[param_name][:tagged] ||= details[:tagged]
|
|
576
|
+
param_details[param_name][:qualified] ||= details[:qualified]
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
# @param signature [Pin::Signature]
|
|
581
|
+
# @param method_pin_stack [Array<Pin::Method>]
|
|
498
582
|
# @return [Hash{String => Hash{Symbol => String, ComplexType}}]
|
|
499
|
-
def
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
param_names =
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
# @type [Hash{String => Hash{Symbol => BasicObject}}]
|
|
583
|
+
def param_details_from_stack(signature, method_pin_stack)
|
|
584
|
+
signature_type = signature.typify(api_map)
|
|
585
|
+
signature = signature.proxy signature_type
|
|
586
|
+
param_details = signature_param_details(signature)
|
|
587
|
+
param_names = signature.parameter_names
|
|
588
|
+
|
|
589
|
+
method_pin_stack.each do |method_pin|
|
|
590
|
+
add_restkwarg_param_tag_details(param_details, method_pin, signature)
|
|
508
591
|
|
|
509
592
|
# documentation of types in superclasses should fail back to
|
|
510
593
|
# subclasses if the subclass hasn't documented something
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
results[param_name] ||= {}
|
|
516
|
-
results[param_name][:tagged] ||= details[:tagged]
|
|
517
|
-
results[param_name][:qualified] ||= details[:qualified]
|
|
594
|
+
method_pin.signatures.each do |sig|
|
|
595
|
+
add_restkwarg_param_tag_details(param_details, sig, signature)
|
|
596
|
+
add_to_param_details param_details, param_names, signature_param_details(sig)
|
|
518
597
|
end
|
|
519
598
|
end
|
|
520
|
-
|
|
599
|
+
param_details
|
|
521
600
|
end
|
|
522
601
|
|
|
523
602
|
# @param pin [Pin::Base]
|
|
@@ -540,20 +619,21 @@ module Solargraph
|
|
|
540
619
|
|
|
541
620
|
# @param pin [Pin::BaseVariable]
|
|
542
621
|
def declared_externally? pin
|
|
543
|
-
|
|
622
|
+
raise "No assignment found" if pin.assignment.nil?
|
|
623
|
+
|
|
544
624
|
chain = Solargraph::Parser.chain(pin.assignment, filename)
|
|
545
625
|
rng = Solargraph::Range.from_node(pin.assignment)
|
|
546
|
-
|
|
626
|
+
closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column)
|
|
547
627
|
location = Location.new(filename, Range.from_node(pin.assignment))
|
|
548
628
|
locals = source_map.locals_at(location)
|
|
549
|
-
type = chain.infer(api_map,
|
|
629
|
+
type = chain.infer(api_map, closure_pin, locals)
|
|
550
630
|
if type.undefined? && !rules.ignore_all_undefined?
|
|
551
631
|
base = chain
|
|
552
632
|
missing = chain
|
|
553
633
|
found = nil
|
|
554
634
|
closest = ComplexType::UNDEFINED
|
|
555
635
|
until base.links.first.undefined?
|
|
556
|
-
found = base.define(api_map,
|
|
636
|
+
found = base.define(api_map, closure_pin, locals).first
|
|
557
637
|
break if found
|
|
558
638
|
missing = base
|
|
559
639
|
base = base.base
|
|
@@ -646,7 +726,6 @@ module Solargraph
|
|
|
646
726
|
# @param parameters [Enumerable<Pin::Parameter>]
|
|
647
727
|
# @todo need to use generic types in method to choose correct
|
|
648
728
|
# signature and generate Integer as return type
|
|
649
|
-
# @sg-ignore
|
|
650
729
|
# @return [Integer]
|
|
651
730
|
def required_param_count(parameters)
|
|
652
731
|
parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
|
|
@@ -687,12 +766,48 @@ module Solargraph
|
|
|
687
766
|
args
|
|
688
767
|
end
|
|
689
768
|
|
|
769
|
+
# @return [Set<Integer>]
|
|
770
|
+
def sg_ignore_lines_processed
|
|
771
|
+
@sg_ignore_lines_processed ||= Set.new
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
# @return [Set<Integer>]
|
|
775
|
+
def all_sg_ignore_lines
|
|
776
|
+
source.associated_comments.select do |_line, text|
|
|
777
|
+
text.include?('@sg-ignore')
|
|
778
|
+
end.keys.to_set
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
# @return [Array<Integer>]
|
|
782
|
+
def unprocessed_sg_ignore_lines
|
|
783
|
+
(all_sg_ignore_lines - sg_ignore_lines_processed).to_a.sort
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
# @return [Array<Problem>]
|
|
787
|
+
def unneeded_sgignore_problems
|
|
788
|
+
return [] unless rules.validate_sg_ignores?
|
|
789
|
+
|
|
790
|
+
unprocessed_sg_ignore_lines.map do |line|
|
|
791
|
+
Problem.new(
|
|
792
|
+
Location.new(filename, Range.from_to(line, 0, line, 0)),
|
|
793
|
+
'Unneeded @sg-ignore comment'
|
|
794
|
+
)
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
|
|
690
798
|
# @param problems [Array<Problem>]
|
|
691
799
|
# @return [Array<Problem>]
|
|
692
800
|
def without_ignored problems
|
|
693
801
|
problems.reject do |problem|
|
|
694
|
-
node =
|
|
695
|
-
node &&
|
|
802
|
+
node = source.node_at(problem.location.range.start.line, problem.location.range.start.column)
|
|
803
|
+
ignored = node && source.comments_for(node)&.include?('@sg-ignore')
|
|
804
|
+
unless !ignored || all_sg_ignore_lines.include?(problem.location.range.start.line)
|
|
805
|
+
# :nocov:
|
|
806
|
+
Solargraph.assert_or_log(:sg_ignore) { "@sg-ignore accounting issue - node is #{node}" }
|
|
807
|
+
# :nocov:
|
|
808
|
+
end
|
|
809
|
+
sg_ignore_lines_processed.add problem.location.range.start.line if ignored
|
|
810
|
+
ignored
|
|
696
811
|
end
|
|
697
812
|
end
|
|
698
813
|
end
|