solargraph 0.39.7
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +28 -0
- data/.yardopts +2 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/Rakefile +14 -0
- data/SPONSORS.md +9 -0
- data/bin/solargraph +5 -0
- data/lib/.rubocop.yml +21 -0
- data/lib/solargraph.rb +66 -0
- data/lib/solargraph/api_map.rb +745 -0
- data/lib/solargraph/api_map/bundler_methods.rb +27 -0
- data/lib/solargraph/api_map/cache.rb +66 -0
- data/lib/solargraph/api_map/source_to_yard.rb +81 -0
- data/lib/solargraph/api_map/store.rb +267 -0
- data/lib/solargraph/bundle.rb +26 -0
- data/lib/solargraph/complex_type.rb +213 -0
- data/lib/solargraph/complex_type/type_methods.rb +127 -0
- data/lib/solargraph/complex_type/unique_type.rb +75 -0
- data/lib/solargraph/convention.rb +38 -0
- data/lib/solargraph/convention/base.rb +25 -0
- data/lib/solargraph/convention/gemfile.rb +18 -0
- data/lib/solargraph/convention/gemspec.rb +25 -0
- data/lib/solargraph/convention/rspec.rb +24 -0
- data/lib/solargraph/converters/dd.rb +12 -0
- data/lib/solargraph/converters/dl.rb +12 -0
- data/lib/solargraph/converters/dt.rb +12 -0
- data/lib/solargraph/converters/misc.rb +1 -0
- data/lib/solargraph/core_fills.rb +159 -0
- data/lib/solargraph/diagnostics.rb +55 -0
- data/lib/solargraph/diagnostics/base.rb +29 -0
- data/lib/solargraph/diagnostics/require_not_found.rb +37 -0
- data/lib/solargraph/diagnostics/rubocop.rb +90 -0
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +64 -0
- data/lib/solargraph/diagnostics/severities.rb +15 -0
- data/lib/solargraph/diagnostics/type_check.rb +54 -0
- data/lib/solargraph/diagnostics/update_errors.rb +41 -0
- data/lib/solargraph/documentor.rb +76 -0
- data/lib/solargraph/environ.rb +40 -0
- data/lib/solargraph/language_server.rb +19 -0
- data/lib/solargraph/language_server/completion_item_kinds.rb +35 -0
- data/lib/solargraph/language_server/error_codes.rb +20 -0
- data/lib/solargraph/language_server/host.rb +741 -0
- data/lib/solargraph/language_server/host/cataloger.rb +56 -0
- data/lib/solargraph/language_server/host/diagnoser.rb +81 -0
- data/lib/solargraph/language_server/host/dispatch.rb +112 -0
- data/lib/solargraph/language_server/host/sources.rb +156 -0
- data/lib/solargraph/language_server/message.rb +92 -0
- data/lib/solargraph/language_server/message/base.rb +85 -0
- data/lib/solargraph/language_server/message/cancel_request.rb +13 -0
- data/lib/solargraph/language_server/message/client.rb +11 -0
- data/lib/solargraph/language_server/message/client/register_capability.rb +15 -0
- data/lib/solargraph/language_server/message/completion_item.rb +11 -0
- data/lib/solargraph/language_server/message/completion_item/resolve.rb +57 -0
- data/lib/solargraph/language_server/message/exit_notification.rb +13 -0
- data/lib/solargraph/language_server/message/extended.rb +21 -0
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +95 -0
- data/lib/solargraph/language_server/message/extended/document.rb +20 -0
- data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -0
- data/lib/solargraph/language_server/message/extended/download_core.rb +23 -0
- data/lib/solargraph/language_server/message/extended/environment.rb +25 -0
- data/lib/solargraph/language_server/message/extended/search.rb +20 -0
- data/lib/solargraph/language_server/message/initialize.rb +153 -0
- data/lib/solargraph/language_server/message/initialized.rb +26 -0
- data/lib/solargraph/language_server/message/method_not_found.rb +16 -0
- data/lib/solargraph/language_server/message/method_not_implemented.rb +14 -0
- data/lib/solargraph/language_server/message/shutdown.rb +13 -0
- data/lib/solargraph/language_server/message/text_document.rb +28 -0
- data/lib/solargraph/language_server/message/text_document/base.rb +19 -0
- data/lib/solargraph/language_server/message/text_document/code_action.rb +17 -0
- data/lib/solargraph/language_server/message/text_document/completion.rb +57 -0
- data/lib/solargraph/language_server/message/text_document/definition.rb +38 -0
- data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -0
- data/lib/solargraph/language_server/message/text_document/did_close.rb +15 -0
- data/lib/solargraph/language_server/message/text_document/did_open.rb +15 -0
- data/lib/solargraph/language_server/message/text_document/did_save.rb +17 -0
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +23 -0
- data/lib/solargraph/language_server/message/text_document/folding_range.rb +26 -0
- data/lib/solargraph/language_server/message/text_document/formatting.rb +78 -0
- data/lib/solargraph/language_server/message/text_document/hover.rb +44 -0
- data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +34 -0
- data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +11 -0
- data/lib/solargraph/language_server/message/text_document/references.rb +16 -0
- data/lib/solargraph/language_server/message/text_document/rename.rb +19 -0
- data/lib/solargraph/language_server/message/text_document/signature_help.rb +29 -0
- data/lib/solargraph/language_server/message/workspace.rb +14 -0
- data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -0
- data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +33 -0
- data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -0
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -0
- data/lib/solargraph/language_server/message_types.rb +14 -0
- data/lib/solargraph/language_server/request.rb +24 -0
- data/lib/solargraph/language_server/symbol_kinds.rb +36 -0
- data/lib/solargraph/language_server/transport.rb +13 -0
- data/lib/solargraph/language_server/transport/adapter.rb +56 -0
- data/lib/solargraph/language_server/transport/data_reader.rb +72 -0
- data/lib/solargraph/language_server/uri_helpers.rb +49 -0
- data/lib/solargraph/library.rb +414 -0
- data/lib/solargraph/location.rb +37 -0
- data/lib/solargraph/logging.rb +27 -0
- data/lib/solargraph/page.rb +83 -0
- data/lib/solargraph/parser.rb +26 -0
- data/lib/solargraph/parser/comment_ripper.rb +52 -0
- data/lib/solargraph/parser/legacy.rb +12 -0
- data/lib/solargraph/parser/legacy/class_methods.rb +109 -0
- data/lib/solargraph/parser/legacy/flawed_builder.rb +16 -0
- data/lib/solargraph/parser/legacy/node_chainer.rb +118 -0
- data/lib/solargraph/parser/legacy/node_methods.rb +300 -0
- data/lib/solargraph/parser/legacy/node_processors.rb +54 -0
- data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +23 -0
- data/lib/solargraph/parser/legacy/node_processors/args_node.rb +35 -0
- data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +15 -0
- data/lib/solargraph/parser/legacy/node_processors/block_node.rb +22 -0
- data/lib/solargraph/parser/legacy/node_processors/casgn_node.rb +25 -0
- data/lib/solargraph/parser/legacy/node_processors/cvasgn_node.rb +23 -0
- data/lib/solargraph/parser/legacy/node_processors/def_node.rb +63 -0
- data/lib/solargraph/parser/legacy/node_processors/defs_node.rb +36 -0
- data/lib/solargraph/parser/legacy/node_processors/gvasgn_node.rb +23 -0
- data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +38 -0
- data/lib/solargraph/parser/legacy/node_processors/lvasgn_node.rb +28 -0
- data/lib/solargraph/parser/legacy/node_processors/namespace_node.rb +39 -0
- data/lib/solargraph/parser/legacy/node_processors/orasgn_node.rb +16 -0
- data/lib/solargraph/parser/legacy/node_processors/resbody_node.rb +36 -0
- data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +21 -0
- data/lib/solargraph/parser/legacy/node_processors/send_node.rb +234 -0
- data/lib/solargraph/parser/legacy/node_processors/sym_node.rb +18 -0
- data/lib/solargraph/parser/node_methods.rb +43 -0
- data/lib/solargraph/parser/node_processor.rb +43 -0
- data/lib/solargraph/parser/node_processor/base.rb +77 -0
- data/lib/solargraph/parser/region.rb +66 -0
- data/lib/solargraph/parser/rubyvm.rb +40 -0
- data/lib/solargraph/parser/rubyvm/class_methods.rb +150 -0
- data/lib/solargraph/parser/rubyvm/node_chainer.rb +135 -0
- data/lib/solargraph/parser/rubyvm/node_methods.rb +284 -0
- data/lib/solargraph/parser/rubyvm/node_processors.rb +61 -0
- data/lib/solargraph/parser/rubyvm/node_processors/alias_node.rb +23 -0
- data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +62 -0
- data/lib/solargraph/parser/rubyvm/node_processors/begin_node.rb +15 -0
- data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +22 -0
- data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +22 -0
- data/lib/solargraph/parser/rubyvm/node_processors/cvasgn_node.rb +23 -0
- data/lib/solargraph/parser/rubyvm/node_processors/def_node.rb +64 -0
- data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +57 -0
- data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +23 -0
- data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +38 -0
- data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +39 -0
- data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +20 -0
- data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +27 -0
- data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +39 -0
- data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +31 -0
- data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +15 -0
- data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +45 -0
- data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +21 -0
- data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +15 -0
- data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +255 -0
- data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +18 -0
- data/lib/solargraph/parser/snippet.rb +13 -0
- data/lib/solargraph/pin.rb +39 -0
- data/lib/solargraph/pin/attribute.rb +49 -0
- data/lib/solargraph/pin/base.rb +296 -0
- data/lib/solargraph/pin/base_method.rb +141 -0
- data/lib/solargraph/pin/base_variable.rb +84 -0
- data/lib/solargraph/pin/block.rb +48 -0
- data/lib/solargraph/pin/class_variable.rb +8 -0
- data/lib/solargraph/pin/closure.rb +37 -0
- data/lib/solargraph/pin/common.rb +70 -0
- data/lib/solargraph/pin/constant.rb +43 -0
- data/lib/solargraph/pin/conversions.rb +97 -0
- data/lib/solargraph/pin/documenting.rb +110 -0
- data/lib/solargraph/pin/duck_method.rb +16 -0
- data/lib/solargraph/pin/global_variable.rb +8 -0
- data/lib/solargraph/pin/instance_variable.rb +30 -0
- data/lib/solargraph/pin/keyword.rb +15 -0
- data/lib/solargraph/pin/keyword_param.rb +8 -0
- data/lib/solargraph/pin/local_variable.rb +21 -0
- data/lib/solargraph/pin/localized.rb +43 -0
- data/lib/solargraph/pin/method.rb +111 -0
- data/lib/solargraph/pin/method_alias.rb +31 -0
- data/lib/solargraph/pin/namespace.rb +85 -0
- data/lib/solargraph/pin/parameter.rb +206 -0
- data/lib/solargraph/pin/proxy_type.rb +29 -0
- data/lib/solargraph/pin/reference.rb +14 -0
- data/lib/solargraph/pin/reference/extend.rb +10 -0
- data/lib/solargraph/pin/reference/include.rb +10 -0
- data/lib/solargraph/pin/reference/override.rb +29 -0
- data/lib/solargraph/pin/reference/prepend.rb +10 -0
- data/lib/solargraph/pin/reference/require.rb +14 -0
- data/lib/solargraph/pin/reference/superclass.rb +10 -0
- data/lib/solargraph/pin/singleton.rb +11 -0
- data/lib/solargraph/pin/symbol.rb +47 -0
- data/lib/solargraph/pin/yard_pin.rb +12 -0
- data/lib/solargraph/pin/yard_pin/constant.rb +25 -0
- data/lib/solargraph/pin/yard_pin/method.rb +65 -0
- data/lib/solargraph/pin/yard_pin/namespace.rb +27 -0
- data/lib/solargraph/pin/yard_pin/yard_mixin.rb +26 -0
- data/lib/solargraph/position.rb +112 -0
- data/lib/solargraph/range.rb +95 -0
- data/lib/solargraph/server_methods.rb +16 -0
- data/lib/solargraph/shell.rb +221 -0
- data/lib/solargraph/source.rb +533 -0
- data/lib/solargraph/source/chain.rb +172 -0
- data/lib/solargraph/source/chain/block_variable.rb +13 -0
- data/lib/solargraph/source/chain/call.rb +203 -0
- data/lib/solargraph/source/chain/class_variable.rb +13 -0
- data/lib/solargraph/source/chain/constant.rb +75 -0
- data/lib/solargraph/source/chain/global_variable.rb +13 -0
- data/lib/solargraph/source/chain/head.rb +35 -0
- data/lib/solargraph/source/chain/instance_variable.rb +13 -0
- data/lib/solargraph/source/chain/link.rb +67 -0
- data/lib/solargraph/source/chain/literal.rb +23 -0
- data/lib/solargraph/source/chain/or.rb +23 -0
- data/lib/solargraph/source/chain/variable.rb +13 -0
- data/lib/solargraph/source/chain/z_super.rb +184 -0
- data/lib/solargraph/source/change.rb +79 -0
- data/lib/solargraph/source/cursor.rb +164 -0
- data/lib/solargraph/source/encoding_fixes.rb +23 -0
- data/lib/solargraph/source/source_chainer.rb +189 -0
- data/lib/solargraph/source/updater.rb +54 -0
- data/lib/solargraph/source_map.rb +170 -0
- data/lib/solargraph/source_map/clip.rb +190 -0
- data/lib/solargraph/source_map/completion.rb +23 -0
- data/lib/solargraph/source_map/mapper.rb +199 -0
- data/lib/solargraph/stdlib_fills.rb +32 -0
- data/lib/solargraph/type_checker.rb +498 -0
- data/lib/solargraph/type_checker/checks.rb +95 -0
- data/lib/solargraph/type_checker/param_def.rb +35 -0
- data/lib/solargraph/type_checker/problem.rb +32 -0
- data/lib/solargraph/type_checker/rules.rb +53 -0
- data/lib/solargraph/version.rb +5 -0
- data/lib/solargraph/views/_method.erb +62 -0
- data/lib/solargraph/views/_name_type_tag.erb +10 -0
- data/lib/solargraph/views/_namespace.erb +24 -0
- data/lib/solargraph/views/document.erb +23 -0
- data/lib/solargraph/views/environment.erb +58 -0
- data/lib/solargraph/views/layout.erb +44 -0
- data/lib/solargraph/views/search.erb +11 -0
- data/lib/solargraph/workspace.rb +209 -0
- data/lib/solargraph/workspace/config.rb +215 -0
- data/lib/solargraph/yard_map.rb +420 -0
- data/lib/solargraph/yard_map/cache.rb +19 -0
- data/lib/solargraph/yard_map/core_docs.rb +170 -0
- data/lib/solargraph/yard_map/core_gen.rb +76 -0
- data/lib/solargraph/yard_map/mapper.rb +71 -0
- data/lib/solargraph/yard_map/rdoc_to_yard.rb +136 -0
- data/lib/yard-solargraph.rb +30 -0
- data/solargraph.gemspec +41 -0
- data/travis-bundler.rb +11 -0
- data/yardoc/2.2.2.tar.gz +0 -0
- metadata +575 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
class SourceMap
|
|
5
|
+
# A static analysis tool for obtaining definitions, completions,
|
|
6
|
+
# signatures, and type inferences from a cursor.
|
|
7
|
+
#
|
|
8
|
+
class Clip
|
|
9
|
+
# @param api_map [ApiMap]
|
|
10
|
+
# @param cursor [Source::Cursor]
|
|
11
|
+
def initialize api_map, cursor
|
|
12
|
+
@api_map = api_map
|
|
13
|
+
@cursor = cursor
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [Array<Pin::Base>]
|
|
17
|
+
def define
|
|
18
|
+
return [] if cursor.comment? || cursor.chain.literal?
|
|
19
|
+
result = cursor.chain.define(api_map, block, locals)
|
|
20
|
+
result.concat((source_map.pins + source_map.locals).select{ |p| p.name == cursor.word && p.location.range.contain?(cursor.position) }) if result.empty?
|
|
21
|
+
result
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Completion]
|
|
25
|
+
def complete
|
|
26
|
+
return package_completions([]) if !source_map.source.parsed? || cursor.string?
|
|
27
|
+
return package_completions(api_map.get_symbols) if cursor.chain.literal? && cursor.chain.links.last.word == '<Symbol>'
|
|
28
|
+
return Completion.new([], cursor.range) if cursor.chain.literal? || cursor.comment?
|
|
29
|
+
result = []
|
|
30
|
+
result.concat complete_keyword_parameters
|
|
31
|
+
if cursor.chain.constant? || cursor.start_of_constant?
|
|
32
|
+
full = cursor.chain.links.first.word
|
|
33
|
+
type = if cursor.chain.undefined?
|
|
34
|
+
cursor.chain.base.infer(api_map, context_pin, locals)
|
|
35
|
+
else
|
|
36
|
+
if full.include?('::') && cursor.chain.links.length == 1
|
|
37
|
+
ComplexType.try_parse(full.split('::')[0..-2].join('::'))
|
|
38
|
+
elsif cursor.chain.links.length > 1
|
|
39
|
+
ComplexType.try_parse(full)
|
|
40
|
+
else
|
|
41
|
+
ComplexType::UNDEFINED
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
if type.undefined?
|
|
45
|
+
if full.include?('::')
|
|
46
|
+
result.concat api_map.get_constants(full, *gates)
|
|
47
|
+
else
|
|
48
|
+
result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) #.select { |pin| pin.name.start_with?(full) }
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
result.concat api_map.get_constants(type.namespace, cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
type = cursor.chain.base.infer(api_map, block, locals)
|
|
55
|
+
result.concat api_map.get_complex_type_methods(type, block.binder.namespace, cursor.chain.links.length == 1)
|
|
56
|
+
if cursor.chain.links.length == 1
|
|
57
|
+
if cursor.word.start_with?('@@')
|
|
58
|
+
return package_completions(api_map.get_class_variable_pins(context_pin.full_context.namespace))
|
|
59
|
+
elsif cursor.word.start_with?('@')
|
|
60
|
+
return package_completions(api_map.get_instance_variable_pins(block.binder.namespace, block.binder.scope))
|
|
61
|
+
elsif cursor.word.start_with?('$')
|
|
62
|
+
return package_completions(api_map.get_global_variable_pins)
|
|
63
|
+
end
|
|
64
|
+
result.concat locals
|
|
65
|
+
result.concat api_map.get_constants(context_pin.context.namespace, *gates)
|
|
66
|
+
result.concat api_map.get_methods(block.binder.namespace, scope: block.binder.scope, visibility: [:public, :private, :protected])
|
|
67
|
+
result.concat api_map.get_methods('Kernel')
|
|
68
|
+
result.concat ApiMap.keywords
|
|
69
|
+
result.concat yielded_self_pins
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
package_completions(result)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Array<Pin::Base>]
|
|
76
|
+
def signify
|
|
77
|
+
return [] unless cursor.argument?
|
|
78
|
+
chain = Parser.chain(cursor.recipient_node, cursor.filename)
|
|
79
|
+
chain.define(api_map, context_pin, locals).select { |pin| pin.is_a?(Pin::Method) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [ComplexType]
|
|
83
|
+
def infer
|
|
84
|
+
result = cursor.chain.infer(api_map, block, locals)
|
|
85
|
+
return result unless result.tag == 'self'
|
|
86
|
+
ComplexType.try_parse(cursor.chain.base.infer(api_map, block, locals).namespace)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Get an array of all the locals that are visible from the cursors's
|
|
90
|
+
# position. Locals can be local variables, method parameters, or block
|
|
91
|
+
# parameters. The array starts with the nearest local pin.
|
|
92
|
+
#
|
|
93
|
+
# @return [Array<Solargraph::Pin::Base>]
|
|
94
|
+
def locals
|
|
95
|
+
loc_pos = context_pin.location.range.contain?(cursor.position) ? cursor.position : context_pin.location.range.ending
|
|
96
|
+
adj_pos = Position.new(loc_pos.line, (loc_pos.column.zero? ? 0 : loc_pos.column - 1))
|
|
97
|
+
@locals ||= source_map.locals.select { |pin|
|
|
98
|
+
pin.visible_from?(block, adj_pos)
|
|
99
|
+
}.reverse
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def gates
|
|
103
|
+
block.gates
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def in_block?
|
|
107
|
+
return @in_block unless @in_block.nil?
|
|
108
|
+
@in_block = begin
|
|
109
|
+
tree = cursor.source.tree_at(cursor.position.line, cursor.position.column)
|
|
110
|
+
Parser.is_ast_node?(tree[1]) && [:block, :ITER].include?(tree[1].type)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
# @return [ApiMap]
|
|
117
|
+
attr_reader :api_map
|
|
118
|
+
|
|
119
|
+
# @return [Source::Cursor]
|
|
120
|
+
attr_reader :cursor
|
|
121
|
+
|
|
122
|
+
# @return [SourceMap]
|
|
123
|
+
def source_map
|
|
124
|
+
@source_map ||= api_map.source_map(cursor.filename)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Solargraph::Pin::Base]
|
|
128
|
+
def block
|
|
129
|
+
@block ||= source_map.locate_block_pin(cursor.node_position.line, cursor.node_position.character)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The context at the current position.
|
|
133
|
+
#
|
|
134
|
+
# @return [Pin::Base]
|
|
135
|
+
def context_pin
|
|
136
|
+
@context_pin ||= source_map.locate_named_path_pin(cursor.node_position.line, cursor.node_position.character)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return [Array<Pin::Base>]
|
|
140
|
+
def yielded_self_pins
|
|
141
|
+
return [] unless block.is_a?(Pin::Block) && block.receiver
|
|
142
|
+
chain = Parser.chain(block.receiver, source_map.source.filename)
|
|
143
|
+
receiver_pin = chain.define(api_map, context_pin, locals).first
|
|
144
|
+
return [] if receiver_pin.nil?
|
|
145
|
+
result = []
|
|
146
|
+
ys = receiver_pin.docstring.tag(:yieldpublic)
|
|
147
|
+
unless ys.nil? || ys.types.empty?
|
|
148
|
+
ysct = ComplexType.try_parse(*ys.types).qualify(api_map, receiver_pin.context.namespace)
|
|
149
|
+
result.concat api_map.get_complex_type_methods(ysct, '', false)
|
|
150
|
+
end
|
|
151
|
+
result
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @return [Array<Pin::KeywordParam]
|
|
155
|
+
def complete_keyword_parameters
|
|
156
|
+
return [] unless cursor.argument? && cursor.chain.links.one? && cursor.word =~ /^[a-z0-9_]*:?$/
|
|
157
|
+
pins = signify
|
|
158
|
+
result = []
|
|
159
|
+
done = []
|
|
160
|
+
pins.each do |pin|
|
|
161
|
+
pin.parameters.each do |param|
|
|
162
|
+
next if done.include?(param.name)
|
|
163
|
+
done.push param.name
|
|
164
|
+
next unless param.keyword?
|
|
165
|
+
result.push Pin::KeywordParam.new(pin.location, "#{param.name}:")
|
|
166
|
+
end
|
|
167
|
+
if !pin.parameters.empty? && pin.parameters.last.kwrestarg?
|
|
168
|
+
pin.docstring.tags(:param).each do |tag|
|
|
169
|
+
next if done.include?(tag.name)
|
|
170
|
+
done.push tag.name
|
|
171
|
+
result.push Pin::KeywordParam.new(pin.location, "#{tag.name}:")
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
result
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @param result [Array<Pin::Base>]
|
|
179
|
+
# @return [Completion]
|
|
180
|
+
def package_completions result
|
|
181
|
+
frag_start = cursor.start_of_word.to_s.downcase
|
|
182
|
+
filtered = result.uniq(&:name).select { |s|
|
|
183
|
+
s.name.downcase.start_with?(frag_start) &&
|
|
184
|
+
(!s.is_a?(Pin::BaseMethod) || s.name.match(/^[a-z0-9_]+(\!|\?|=)?$/i))
|
|
185
|
+
}
|
|
186
|
+
Completion.new(filtered, cursor.range)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
class SourceMap
|
|
5
|
+
# The result of a completion request containing the pins that describe
|
|
6
|
+
# completion options and the range to be replaced.
|
|
7
|
+
#
|
|
8
|
+
class Completion
|
|
9
|
+
# @return [Array<Solargraph::Pin::Base>]
|
|
10
|
+
attr_reader :pins
|
|
11
|
+
|
|
12
|
+
# @return [Solargraph::Range]
|
|
13
|
+
attr_reader :range
|
|
14
|
+
|
|
15
|
+
# @param pins [Array<Solargraph::Pin::Base>]
|
|
16
|
+
# @param range [Solargraph::Range]
|
|
17
|
+
def initialize pins, range
|
|
18
|
+
@pins = pins
|
|
19
|
+
@range = range
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
class SourceMap
|
|
5
|
+
# The Mapper generates pins and other data for SourceMaps.
|
|
6
|
+
#
|
|
7
|
+
# This class is used internally by the SourceMap class. Users should not
|
|
8
|
+
# normally need to call it directly.
|
|
9
|
+
#
|
|
10
|
+
class Mapper
|
|
11
|
+
# include Source::NodeMethods
|
|
12
|
+
|
|
13
|
+
private_class_method :new
|
|
14
|
+
|
|
15
|
+
MACRO_REGEXP = /(@\!method|@\!attribute|@\!domain|@\!macro|@\!parse|@\!override)/.freeze
|
|
16
|
+
|
|
17
|
+
# Generate the data.
|
|
18
|
+
#
|
|
19
|
+
# @param source [Source]
|
|
20
|
+
# @return [Array]
|
|
21
|
+
def map source
|
|
22
|
+
@source = source
|
|
23
|
+
@filename = source.filename
|
|
24
|
+
@code = source.code
|
|
25
|
+
@comments = source.comments
|
|
26
|
+
@pins, @locals = Parser.map(source)
|
|
27
|
+
process_comment_directives
|
|
28
|
+
[@pins, @locals]
|
|
29
|
+
# rescue Exception => e
|
|
30
|
+
# Solargraph.logger.warn "Error mapping #{source.filename}: [#{e.class}] #{e.message}"
|
|
31
|
+
# Solargraph.logger.warn e.backtrace.join("\n")
|
|
32
|
+
# [[], []]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param filename [String]
|
|
36
|
+
# @param code [String]
|
|
37
|
+
# @return [Array]
|
|
38
|
+
def unmap filename, code
|
|
39
|
+
s = Position.new(0, 0)
|
|
40
|
+
e = Position.from_offset(code, code.length)
|
|
41
|
+
location = Location.new(filename, Range.new(s, e))
|
|
42
|
+
[[Pin::Namespace.new(location: location, name: '')], []]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
# @param source [Source]
|
|
47
|
+
# @return [Array]
|
|
48
|
+
def map source
|
|
49
|
+
return new.unmap(source.filename, source.code) unless source.parsed?
|
|
50
|
+
new.map source
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Array<Solargraph::Pin::Base>]
|
|
55
|
+
def pins
|
|
56
|
+
@pins ||= []
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def closure_at(position)
|
|
60
|
+
@pins.select{|pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position)}.last
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def process_comment source_position, comment_position, comment
|
|
64
|
+
return unless comment =~ MACRO_REGEXP
|
|
65
|
+
cmnt = remove_inline_comment_hashes(comment)
|
|
66
|
+
parse = Solargraph::Source.parse_docstring(cmnt)
|
|
67
|
+
last_line = 0
|
|
68
|
+
# @param d [YARD::Tags::Directive]
|
|
69
|
+
parse.directives.each do |d|
|
|
70
|
+
line_num = find_directive_line_number(cmnt, d.tag.tag_name, last_line)
|
|
71
|
+
pos = Solargraph::Position.new(comment_position.line + line_num - 1, comment_position.column)
|
|
72
|
+
process_directive(source_position, pos, d)
|
|
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
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @param comment [String]
|
|
83
|
+
# @return [Integer]
|
|
84
|
+
def find_directive_line_number comment, tag, start
|
|
85
|
+
# Avoid overruning the index
|
|
86
|
+
return start unless start < comment.lines.length
|
|
87
|
+
num = comment.lines[start..-1].find_index do |line|
|
|
88
|
+
# Legacy method directives might be `@method` instead of `@!method`
|
|
89
|
+
# @todo Legacy syntax should probably emit a warning
|
|
90
|
+
line.include?("@!#{tag}") || (tag == 'method' && line.include?("@#{tag}"))
|
|
91
|
+
end
|
|
92
|
+
num.to_i + start
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param source_position [Position]
|
|
96
|
+
# @param comment_position [Position]
|
|
97
|
+
# @param directive [YARD::Tags::Directive]
|
|
98
|
+
def process_directive source_position, comment_position, directive
|
|
99
|
+
docstring = Solargraph::Source.parse_docstring(directive.tag.text).to_docstring
|
|
100
|
+
location = Location.new(@filename, Range.new(comment_position, comment_position))
|
|
101
|
+
case directive.tag.tag_name
|
|
102
|
+
when 'method'
|
|
103
|
+
namespace = closure_at(source_position) || @pins.first
|
|
104
|
+
if namespace.location.range.start.line < comment_position.line
|
|
105
|
+
namespace = closure_at(comment_position)
|
|
106
|
+
end
|
|
107
|
+
region = Parser::Region.new(source: @source, closure: namespace)
|
|
108
|
+
begin
|
|
109
|
+
src_node = Parser.parse("def #{directive.tag.name};end", @filename, location.range.start.line)
|
|
110
|
+
gen_pin = Parser.process_node(src_node, region).first.last
|
|
111
|
+
return if gen_pin.nil?
|
|
112
|
+
# Move the location to the end of the line so it gets recognized
|
|
113
|
+
# as originating from a comment
|
|
114
|
+
shifted = Solargraph::Position.new(comment_position.line, @code.lines[comment_position.line].to_s.chomp.length)
|
|
115
|
+
# @todo: Smelly instance variable access
|
|
116
|
+
gen_pin.instance_variable_set(:@comments, docstring.all.to_s)
|
|
117
|
+
gen_pin.instance_variable_set(:@location, Solargraph::Location.new(@filename, Range.new(shifted, shifted)))
|
|
118
|
+
gen_pin.instance_variable_set(:@explicit, false)
|
|
119
|
+
@pins.push gen_pin
|
|
120
|
+
rescue Parser::SyntaxError => e
|
|
121
|
+
# @todo Handle error in directive
|
|
122
|
+
end
|
|
123
|
+
when 'attribute'
|
|
124
|
+
return if directive.tag.name.nil?
|
|
125
|
+
namespace = closure_at(source_position)
|
|
126
|
+
t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('')
|
|
127
|
+
if t.nil? || t.include?('r')
|
|
128
|
+
pins.push Solargraph::Pin::Attribute.new(
|
|
129
|
+
location: location,
|
|
130
|
+
closure: namespace,
|
|
131
|
+
name: directive.tag.name,
|
|
132
|
+
comments: docstring.all.to_s,
|
|
133
|
+
access: :reader,
|
|
134
|
+
scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
|
|
135
|
+
visibility: :public,
|
|
136
|
+
explicit: false
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
if t.nil? || t.include?('w')
|
|
140
|
+
pins.push Solargraph::Pin::Attribute.new(
|
|
141
|
+
location: location,
|
|
142
|
+
closure: namespace,
|
|
143
|
+
name: "#{directive.tag.name}=",
|
|
144
|
+
comments: docstring.all.to_s,
|
|
145
|
+
access: :writer,
|
|
146
|
+
scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
|
|
147
|
+
visibility: :public
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
when 'parse'
|
|
151
|
+
ns = closure_at(source_position)
|
|
152
|
+
region = Parser::Region.new(source: @source, closure: ns)
|
|
153
|
+
begin
|
|
154
|
+
node = Parser.parse(directive.tag.text, @filename, comment_position.line)
|
|
155
|
+
# @todo These pins may need to be marked not explicit
|
|
156
|
+
Parser.process_node(node, region, @pins)
|
|
157
|
+
rescue Parser::SyntaxError => e
|
|
158
|
+
# @todo Handle parser errors in !parse directives
|
|
159
|
+
end
|
|
160
|
+
when 'domain'
|
|
161
|
+
namespace = closure_at(source_position)
|
|
162
|
+
namespace.domains.concat directive.tag.types unless directive.tag.types.nil?
|
|
163
|
+
when 'override'
|
|
164
|
+
pins.push Pin::Reference::Override.new(location, directive.tag.name, docstring.tags)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def remove_inline_comment_hashes comment
|
|
169
|
+
ctxt = ''
|
|
170
|
+
num = nil
|
|
171
|
+
started = false
|
|
172
|
+
comment.lines.each { |l|
|
|
173
|
+
# Trim the comment and minimum leading whitespace
|
|
174
|
+
p = l.gsub(/^#/, '')
|
|
175
|
+
if num.nil? && !p.strip.empty?
|
|
176
|
+
num = p.index(/[^ ]/)
|
|
177
|
+
started = true
|
|
178
|
+
elsif started && !p.strip.empty?
|
|
179
|
+
cur = p.index(/[^ ]/)
|
|
180
|
+
num = cur if cur < num
|
|
181
|
+
end
|
|
182
|
+
ctxt += "#{p[num..-1]}" if started
|
|
183
|
+
}
|
|
184
|
+
ctxt
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# @return [void]
|
|
188
|
+
def process_comment_directives
|
|
189
|
+
return unless @code =~ MACRO_REGEXP
|
|
190
|
+
code_lines = @code.lines
|
|
191
|
+
@source.associated_comments.each do |line, comments|
|
|
192
|
+
src_pos = line ? Position.new(line, code_lines[line].to_s.chomp.index(/[^\s]/) || 0) : Position.new(code_lines.length, 0)
|
|
193
|
+
com_pos = Position.new(line - 1, 0)
|
|
194
|
+
process_comment(src_pos, com_pos, comments)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
# Overrides for the Ruby stdlib.
|
|
5
|
+
#
|
|
6
|
+
# The YardMap uses this module to add type information to stdlib methods.
|
|
7
|
+
#
|
|
8
|
+
module StdlibFills
|
|
9
|
+
Override = Pin::Reference::Override
|
|
10
|
+
|
|
11
|
+
LIBS = {
|
|
12
|
+
'benchmark' => [
|
|
13
|
+
Override.method_return('Benchmark.measure', 'Benchmark::Tms')
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
'pathname' => [
|
|
17
|
+
Override.method_return('Pathname#join', 'Pathname'),
|
|
18
|
+
Override.method_return('Pathname#basename', 'Pathname'),
|
|
19
|
+
Override.method_return('Pathname#dirname', 'Pathname'),
|
|
20
|
+
Override.method_return('Pathname#cleanpath', 'Pathname'),
|
|
21
|
+
Override.method_return('Pathname#children', 'Array<Pathname>'),
|
|
22
|
+
Override.method_return('Pathname#entries', 'Array<Pathname>')
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# @param path [String]
|
|
27
|
+
# @return [Array<Pin::Reference::Override>]
|
|
28
|
+
def self.get path
|
|
29
|
+
LIBS[path] || []
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
# A static analysis tool for validating data types.
|
|
5
|
+
#
|
|
6
|
+
class TypeChecker
|
|
7
|
+
autoload :Problem, 'solargraph/type_checker/problem'
|
|
8
|
+
autoload :ParamDef, 'solargraph/type_checker/param_def'
|
|
9
|
+
autoload :Rules, 'solargraph/type_checker/rules'
|
|
10
|
+
autoload :Checks, 'solargraph/type_checker/checks'
|
|
11
|
+
|
|
12
|
+
include Checks
|
|
13
|
+
include Parser::NodeMethods
|
|
14
|
+
|
|
15
|
+
# @return [String]
|
|
16
|
+
attr_reader :filename
|
|
17
|
+
|
|
18
|
+
# @return [Rules]
|
|
19
|
+
attr_reader :rules
|
|
20
|
+
|
|
21
|
+
# @return [ApiMap]
|
|
22
|
+
attr_reader :api_map
|
|
23
|
+
|
|
24
|
+
# @param filename [String]
|
|
25
|
+
# @param api_map [ApiMap]
|
|
26
|
+
# @param level [Symbol]
|
|
27
|
+
def initialize filename, api_map: nil, level: :normal
|
|
28
|
+
@filename = filename
|
|
29
|
+
# @todo Smarter directory resolution
|
|
30
|
+
@api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename))
|
|
31
|
+
@rules = Rules.new(level)
|
|
32
|
+
@marked_ranges = []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [SourceMap]
|
|
36
|
+
def source_map
|
|
37
|
+
@source_map ||= api_map.source_map(filename)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Array<Problem>]
|
|
41
|
+
def problems
|
|
42
|
+
@problems ||= begin
|
|
43
|
+
method_tag_problems
|
|
44
|
+
.concat variable_type_tag_problems
|
|
45
|
+
.concat const_problems
|
|
46
|
+
.concat call_problems
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
# @param filename [String]
|
|
52
|
+
# @return [self]
|
|
53
|
+
def load filename, level = :normal
|
|
54
|
+
source = Solargraph::Source.load(filename)
|
|
55
|
+
api_map = Solargraph::ApiMap.new
|
|
56
|
+
api_map.map(source)
|
|
57
|
+
new(filename, api_map: api_map, level: level)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @param code [String]
|
|
61
|
+
# @param filename [String, nil]
|
|
62
|
+
# @return [self]
|
|
63
|
+
def load_string code, filename = nil, level = :normal
|
|
64
|
+
source = Solargraph::Source.load_string(code, filename)
|
|
65
|
+
api_map = Solargraph::ApiMap.new
|
|
66
|
+
api_map.map(source)
|
|
67
|
+
new(filename, api_map: api_map, level: level)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# @return [Array<Problem>]
|
|
74
|
+
def method_tag_problems
|
|
75
|
+
result = []
|
|
76
|
+
# @param pin [Pin::BaseMethod]
|
|
77
|
+
source_map.pins.select { |pin| pin.is_a?(Pin::BaseMethod) }.each do |pin|
|
|
78
|
+
result.concat method_return_type_problems_for(pin)
|
|
79
|
+
result.concat method_param_type_problems_for(pin)
|
|
80
|
+
end
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @param pin [Pin::BaseMethod]
|
|
85
|
+
# @return [Array<Problem>]
|
|
86
|
+
def method_return_type_problems_for pin
|
|
87
|
+
result = []
|
|
88
|
+
declared = pin.typify(api_map).self_to(pin.full_context.namespace)
|
|
89
|
+
if declared.undefined?
|
|
90
|
+
if pin.return_type.undefined? && rules.require_type_tags?
|
|
91
|
+
result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin)
|
|
92
|
+
elsif pin.return_type.defined?
|
|
93
|
+
result.push Problem.new(pin.location, "Unresolved return type #{pin.return_type} for #{pin.path}", pin: pin)
|
|
94
|
+
elsif rules.must_tag_or_infer? && pin.probe(api_map).undefined?
|
|
95
|
+
result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
|
|
96
|
+
end
|
|
97
|
+
elsif rules.validate_tags?
|
|
98
|
+
unless pin.node.nil? || declared.void? || macro_pin?(pin) || abstract?(pin)
|
|
99
|
+
inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
|
|
100
|
+
if inferred.undefined?
|
|
101
|
+
unless rules.ignore_all_undefined? || external?(pin)
|
|
102
|
+
result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin)
|
|
103
|
+
end
|
|
104
|
+
else
|
|
105
|
+
unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
|
|
106
|
+
result.push Problem.new(pin.location, "Declared return type #{declared} does not match inferred type #{inferred} for #{pin.path}", pin: pin)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
result
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def macro_pin? pin
|
|
115
|
+
pin.location && source_map.source.comment_at?(pin.location.range.ending)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @param pin [Pin::BaseMethod]
|
|
119
|
+
# @return [Array<Problem>]
|
|
120
|
+
def method_param_type_problems_for pin
|
|
121
|
+
stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
|
|
122
|
+
params = first_param_hash(stack)
|
|
123
|
+
result = []
|
|
124
|
+
if rules.require_type_tags?
|
|
125
|
+
pin.parameters.each do |par|
|
|
126
|
+
break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
|
|
127
|
+
unless params[par.name]
|
|
128
|
+
result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
params.each_pair do |name, tag|
|
|
133
|
+
type = tag.qualify(api_map, pin.full_context.namespace)
|
|
134
|
+
if type.undefined?
|
|
135
|
+
result.push Problem.new(pin.location, "Unresolved type #{tag} for #{name} param on #{pin.path}", pin: pin)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
result
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def ignored_pins
|
|
142
|
+
@ignored_pins ||= []
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @return [Array<Problem>]
|
|
146
|
+
def variable_type_tag_problems
|
|
147
|
+
result = []
|
|
148
|
+
all_variables.each do |pin|
|
|
149
|
+
if pin.return_type.defined?
|
|
150
|
+
# @todo Somwhere in here we still need to determine if the variable is defined by an external call
|
|
151
|
+
declared = pin.typify(api_map)
|
|
152
|
+
if declared.defined?
|
|
153
|
+
if rules.validate_tags?
|
|
154
|
+
inferred = pin.probe(api_map)
|
|
155
|
+
if inferred.undefined?
|
|
156
|
+
next if rules.ignore_all_undefined?
|
|
157
|
+
# next unless internal?(pin) # @todo This might be redundant for variables
|
|
158
|
+
if declared_externally?(pin)
|
|
159
|
+
ignored_pins.push pin
|
|
160
|
+
else
|
|
161
|
+
result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin)
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
|
|
165
|
+
result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
elsif declared_externally?(pin)
|
|
169
|
+
ignored_pins.push pin
|
|
170
|
+
end
|
|
171
|
+
elsif !pin.is_a?(Pin::Parameter)
|
|
172
|
+
result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
|
|
173
|
+
end
|
|
174
|
+
else
|
|
175
|
+
# @todo Check if the variable is defined by an external call
|
|
176
|
+
inferred = pin.probe(api_map)
|
|
177
|
+
if inferred.undefined? && declared_externally?(pin)
|
|
178
|
+
ignored_pins.push pin
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
result
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @return [Array<Pin::BaseVariable>]
|
|
186
|
+
def all_variables
|
|
187
|
+
source_map.pins.select { |pin| pin.is_a?(Pin::BaseVariable) } +
|
|
188
|
+
source_map.locals.select { |pin| pin.is_a?(Pin::LocalVariable) }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def const_problems
|
|
192
|
+
return [] unless rules.validate_consts?
|
|
193
|
+
result = []
|
|
194
|
+
Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
|
|
195
|
+
rng = Solargraph::Range.from_node(const)
|
|
196
|
+
chain = Solargraph::Parser.chain(const, filename)
|
|
197
|
+
block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
|
|
198
|
+
location = Location.new(filename, rng)
|
|
199
|
+
locals = source_map.locals_at(location)
|
|
200
|
+
pins = chain.define(api_map, block_pin, locals)
|
|
201
|
+
if pins.empty?
|
|
202
|
+
result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
|
|
203
|
+
@marked_ranges.push location.range
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
result
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def call_problems
|
|
210
|
+
result = []
|
|
211
|
+
Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
|
|
212
|
+
rng = Solargraph::Range.from_node(call)
|
|
213
|
+
next if @marked_ranges.any? { |d| d.contain?(rng.start) }
|
|
214
|
+
chain = Solargraph::Parser.chain(call, filename)
|
|
215
|
+
block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
|
|
216
|
+
location = Location.new(filename, rng)
|
|
217
|
+
locals = source_map.locals_at(location)
|
|
218
|
+
type = chain.infer(api_map, block_pin, locals)
|
|
219
|
+
if type.undefined? && !rules.ignore_all_undefined?
|
|
220
|
+
base = chain
|
|
221
|
+
missing = chain
|
|
222
|
+
found = nil
|
|
223
|
+
closest = ComplexType::UNDEFINED
|
|
224
|
+
until base.links.first.undefined?
|
|
225
|
+
found = base.define(api_map, block_pin, locals).first
|
|
226
|
+
break if found
|
|
227
|
+
missing = base
|
|
228
|
+
base = base.base
|
|
229
|
+
end
|
|
230
|
+
closest = found.typify(api_map) if found
|
|
231
|
+
if !found || closest.defined? || internal?(found)
|
|
232
|
+
unless ignored_pins.include?(found)
|
|
233
|
+
result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
|
|
234
|
+
@marked_ranges.push rng
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
result.concat argument_problems_for(chain, api_map, block_pin, locals, location)
|
|
239
|
+
end
|
|
240
|
+
result
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def argument_problems_for chain, api_map, block_pin, locals, location
|
|
244
|
+
result = []
|
|
245
|
+
base = chain
|
|
246
|
+
until base.links.length == 1 && base.undefined?
|
|
247
|
+
pins = base.define(api_map, block_pin, locals)
|
|
248
|
+
if pins.first.is_a?(Pin::BaseMethod)
|
|
249
|
+
# @type [Pin::BaseMethod]
|
|
250
|
+
pin = pins.first
|
|
251
|
+
ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
|
|
252
|
+
arity_problems_for(pin, fake_args_for(block_pin), location)
|
|
253
|
+
else
|
|
254
|
+
arity_problems_for(pin, base.links.last.arguments, location)
|
|
255
|
+
end
|
|
256
|
+
unless ap.empty?
|
|
257
|
+
result.concat ap
|
|
258
|
+
break
|
|
259
|
+
end
|
|
260
|
+
break unless rules.validate_calls?
|
|
261
|
+
params = first_param_hash(pins)
|
|
262
|
+
pin.parameters.each_with_index do |par, idx|
|
|
263
|
+
argchain = base.links.last.arguments[idx]
|
|
264
|
+
if argchain.nil? && par.decl == :arg
|
|
265
|
+
result.push Problem.new(location, "Not enough arguments to #{pin.path}")
|
|
266
|
+
break
|
|
267
|
+
end
|
|
268
|
+
if argchain
|
|
269
|
+
if par.decl != :arg
|
|
270
|
+
result.concat kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, idx
|
|
271
|
+
break
|
|
272
|
+
else
|
|
273
|
+
ptype = params[par.name]
|
|
274
|
+
if ptype.nil?
|
|
275
|
+
# @todo Some level (strong, I guess) should require the param here
|
|
276
|
+
else
|
|
277
|
+
argtype = argchain.infer(api_map, block_pin, locals)
|
|
278
|
+
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
|
279
|
+
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
elsif par.rest?
|
|
284
|
+
next
|
|
285
|
+
elsif par.decl == :kwarg
|
|
286
|
+
result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
|
|
287
|
+
break
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
base = base.base
|
|
292
|
+
end
|
|
293
|
+
result
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, first
|
|
297
|
+
result = []
|
|
298
|
+
kwargs = convert_hash(argchain.node)
|
|
299
|
+
pin.parameters[first..-1].each_with_index do |par, cur|
|
|
300
|
+
idx = first + cur
|
|
301
|
+
argchain = kwargs[par.name.to_sym]
|
|
302
|
+
if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
|
|
303
|
+
result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
|
|
304
|
+
else
|
|
305
|
+
if argchain
|
|
306
|
+
ptype = params[par.name]
|
|
307
|
+
if ptype.nil?
|
|
308
|
+
# @todo Some level (strong, I guess) should require the param here
|
|
309
|
+
else
|
|
310
|
+
argtype = argchain.infer(api_map, block_pin, locals)
|
|
311
|
+
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
|
312
|
+
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
else
|
|
316
|
+
if par.decl == :kwarg
|
|
317
|
+
# @todo Problem: missing required keyword argument
|
|
318
|
+
result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
result
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
|
|
327
|
+
result = []
|
|
328
|
+
kwargs.each_pair do |pname, argchain|
|
|
329
|
+
ptype = params[pname.to_s]
|
|
330
|
+
if ptype.nil?
|
|
331
|
+
# Probably nothing to do here. All of these args should be optional.
|
|
332
|
+
else
|
|
333
|
+
argtype = argchain.infer(api_map, block_pin, locals)
|
|
334
|
+
if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
|
|
335
|
+
result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
result
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def param_hash(pin)
|
|
343
|
+
tags = pin.docstring.tags(:param)
|
|
344
|
+
return {} if tags.empty?
|
|
345
|
+
result = {}
|
|
346
|
+
tags.each do |tag|
|
|
347
|
+
next if tag.types.nil? || tag.types.empty?
|
|
348
|
+
result[tag.name.to_s] = Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
|
|
349
|
+
end
|
|
350
|
+
result
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# @param [Array<Pin::Method>]
|
|
354
|
+
def first_param_hash(pins)
|
|
355
|
+
pins.each do |pin|
|
|
356
|
+
result = param_hash(pin)
|
|
357
|
+
return result unless result.empty?
|
|
358
|
+
end
|
|
359
|
+
{}
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# @param pin [Pin::Base]
|
|
363
|
+
def internal? pin
|
|
364
|
+
pin.location && api_map.bundled?(pin.location.filename)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# @param pin [Pin::Base]
|
|
368
|
+
def external? pin
|
|
369
|
+
!internal? pin
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def declared_externally? pin
|
|
373
|
+
return true if pin.assignment.nil?
|
|
374
|
+
chain = Solargraph::Parser.chain(pin.assignment, filename)
|
|
375
|
+
rng = Solargraph::Range.from_node(pin.assignment)
|
|
376
|
+
block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
|
|
377
|
+
location = Location.new(filename, Range.from_node(pin.assignment))
|
|
378
|
+
locals = source_map.locals_at(location)
|
|
379
|
+
type = chain.infer(api_map, block_pin, locals)
|
|
380
|
+
if type.undefined? && !rules.ignore_all_undefined?
|
|
381
|
+
base = chain
|
|
382
|
+
missing = chain
|
|
383
|
+
found = nil
|
|
384
|
+
closest = ComplexType::UNDEFINED
|
|
385
|
+
until base.links.first.undefined?
|
|
386
|
+
found = base.define(api_map, block_pin, locals).first
|
|
387
|
+
break if found
|
|
388
|
+
missing = base
|
|
389
|
+
base = base.base
|
|
390
|
+
end
|
|
391
|
+
closest = found.typify(api_map) if found
|
|
392
|
+
if !found || closest.defined? || internal?(found)
|
|
393
|
+
return false
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
true
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# @param pin [Pin::BaseMethod]
|
|
400
|
+
def arity_problems_for(pin, arguments, location)
|
|
401
|
+
return [] unless pin.explicit?
|
|
402
|
+
return [] if pin.parameters.empty? && arguments.empty?
|
|
403
|
+
if pin.parameters.empty?
|
|
404
|
+
# Functions tagged param_tuple accepts two arguments (e.g., Hash#[]=)
|
|
405
|
+
return [] if pin.docstring.tag(:param_tuple) && arguments.length == 2
|
|
406
|
+
return [] if arguments.length == 1 && arguments.last.links.last.is_a?(Source::Chain::BlockVariable)
|
|
407
|
+
return [Problem.new(location, "Too many arguments to #{pin.path}")]
|
|
408
|
+
end
|
|
409
|
+
unchecked = arguments.clone
|
|
410
|
+
add_params = 0
|
|
411
|
+
if unchecked.empty? && pin.parameters.any? { |param| param.decl == :kwarg }
|
|
412
|
+
return [Problem.new(location, "Missing keyword arguments to #{pin.path}")]
|
|
413
|
+
end
|
|
414
|
+
unless unchecked.empty?
|
|
415
|
+
kwargs = convert_hash(unchecked.last.node)
|
|
416
|
+
# unless kwargs.empty?
|
|
417
|
+
if pin.parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
|
|
418
|
+
if kwargs.empty?
|
|
419
|
+
add_params += 1
|
|
420
|
+
else
|
|
421
|
+
unchecked.pop
|
|
422
|
+
pin.parameters.each do |param|
|
|
423
|
+
next unless param.keyword?
|
|
424
|
+
if kwargs.key?(param.name.to_sym)
|
|
425
|
+
kwargs.delete param.name.to_sym
|
|
426
|
+
elsif param.decl == :kwarg
|
|
427
|
+
return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
kwargs.clear if pin.parameters.any?(&:kwrestarg?)
|
|
431
|
+
unless kwargs.empty?
|
|
432
|
+
return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
# end
|
|
437
|
+
end
|
|
438
|
+
req = required_param_count(pin)
|
|
439
|
+
if req + add_params < unchecked.length
|
|
440
|
+
return [] if pin.parameters.any?(&:rest?)
|
|
441
|
+
opt = optional_param_count(pin)
|
|
442
|
+
return [] if unchecked.length <= req + opt
|
|
443
|
+
if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
|
|
444
|
+
return []
|
|
445
|
+
end
|
|
446
|
+
return [Problem.new(location, "Too many arguments to #{pin.path}")]
|
|
447
|
+
elsif unchecked.length < req && (arguments.empty? || !arguments.last.splat?)
|
|
448
|
+
return [Problem.new(location, "Not enough arguments to #{pin.path}")]
|
|
449
|
+
end
|
|
450
|
+
[]
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# @param pin [Pin::BaseMethod]
|
|
454
|
+
def required_param_count(pin)
|
|
455
|
+
count = 0
|
|
456
|
+
pin.parameters.each do |param|
|
|
457
|
+
break unless param.decl == :arg
|
|
458
|
+
count += 1
|
|
459
|
+
end
|
|
460
|
+
count
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# @param pin [Pin::BaseMethod]
|
|
464
|
+
def optional_param_count(pin)
|
|
465
|
+
count = 0
|
|
466
|
+
pin.parameters.each do |param|
|
|
467
|
+
next unless param.decl == :optarg
|
|
468
|
+
count += 1
|
|
469
|
+
end
|
|
470
|
+
count
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def abstract? pin
|
|
474
|
+
pin.docstring.has_tag?(:abstract) ||
|
|
475
|
+
(pin.closure && pin.closure.docstring.has_tag?(:abstract))
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def fake_args_for(pin)
|
|
479
|
+
args = []
|
|
480
|
+
with_opts = false
|
|
481
|
+
with_block = false
|
|
482
|
+
pin.parameters.each do |pin|
|
|
483
|
+
if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl)
|
|
484
|
+
with_opts = true
|
|
485
|
+
elsif pin.decl == :block
|
|
486
|
+
with_block = true
|
|
487
|
+
elsif pin.decl == :restarg
|
|
488
|
+
args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true)
|
|
489
|
+
else
|
|
490
|
+
args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)])
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
args.push Solargraph::Parser.chain_string('{}') if with_opts
|
|
494
|
+
args.push Solargraph::Parser.chain_string('&') if with_block
|
|
495
|
+
args
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
end
|