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.
Files changed (252) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +28 -0
  5. data/.yardopts +2 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE +21 -0
  8. data/README.md +104 -0
  9. data/Rakefile +14 -0
  10. data/SPONSORS.md +9 -0
  11. data/bin/solargraph +5 -0
  12. data/lib/.rubocop.yml +21 -0
  13. data/lib/solargraph.rb +66 -0
  14. data/lib/solargraph/api_map.rb +745 -0
  15. data/lib/solargraph/api_map/bundler_methods.rb +27 -0
  16. data/lib/solargraph/api_map/cache.rb +66 -0
  17. data/lib/solargraph/api_map/source_to_yard.rb +81 -0
  18. data/lib/solargraph/api_map/store.rb +267 -0
  19. data/lib/solargraph/bundle.rb +26 -0
  20. data/lib/solargraph/complex_type.rb +213 -0
  21. data/lib/solargraph/complex_type/type_methods.rb +127 -0
  22. data/lib/solargraph/complex_type/unique_type.rb +75 -0
  23. data/lib/solargraph/convention.rb +38 -0
  24. data/lib/solargraph/convention/base.rb +25 -0
  25. data/lib/solargraph/convention/gemfile.rb +18 -0
  26. data/lib/solargraph/convention/gemspec.rb +25 -0
  27. data/lib/solargraph/convention/rspec.rb +24 -0
  28. data/lib/solargraph/converters/dd.rb +12 -0
  29. data/lib/solargraph/converters/dl.rb +12 -0
  30. data/lib/solargraph/converters/dt.rb +12 -0
  31. data/lib/solargraph/converters/misc.rb +1 -0
  32. data/lib/solargraph/core_fills.rb +159 -0
  33. data/lib/solargraph/diagnostics.rb +55 -0
  34. data/lib/solargraph/diagnostics/base.rb +29 -0
  35. data/lib/solargraph/diagnostics/require_not_found.rb +37 -0
  36. data/lib/solargraph/diagnostics/rubocop.rb +90 -0
  37. data/lib/solargraph/diagnostics/rubocop_helpers.rb +64 -0
  38. data/lib/solargraph/diagnostics/severities.rb +15 -0
  39. data/lib/solargraph/diagnostics/type_check.rb +54 -0
  40. data/lib/solargraph/diagnostics/update_errors.rb +41 -0
  41. data/lib/solargraph/documentor.rb +76 -0
  42. data/lib/solargraph/environ.rb +40 -0
  43. data/lib/solargraph/language_server.rb +19 -0
  44. data/lib/solargraph/language_server/completion_item_kinds.rb +35 -0
  45. data/lib/solargraph/language_server/error_codes.rb +20 -0
  46. data/lib/solargraph/language_server/host.rb +741 -0
  47. data/lib/solargraph/language_server/host/cataloger.rb +56 -0
  48. data/lib/solargraph/language_server/host/diagnoser.rb +81 -0
  49. data/lib/solargraph/language_server/host/dispatch.rb +112 -0
  50. data/lib/solargraph/language_server/host/sources.rb +156 -0
  51. data/lib/solargraph/language_server/message.rb +92 -0
  52. data/lib/solargraph/language_server/message/base.rb +85 -0
  53. data/lib/solargraph/language_server/message/cancel_request.rb +13 -0
  54. data/lib/solargraph/language_server/message/client.rb +11 -0
  55. data/lib/solargraph/language_server/message/client/register_capability.rb +15 -0
  56. data/lib/solargraph/language_server/message/completion_item.rb +11 -0
  57. data/lib/solargraph/language_server/message/completion_item/resolve.rb +57 -0
  58. data/lib/solargraph/language_server/message/exit_notification.rb +13 -0
  59. data/lib/solargraph/language_server/message/extended.rb +21 -0
  60. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +95 -0
  61. data/lib/solargraph/language_server/message/extended/document.rb +20 -0
  62. data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -0
  63. data/lib/solargraph/language_server/message/extended/download_core.rb +23 -0
  64. data/lib/solargraph/language_server/message/extended/environment.rb +25 -0
  65. data/lib/solargraph/language_server/message/extended/search.rb +20 -0
  66. data/lib/solargraph/language_server/message/initialize.rb +153 -0
  67. data/lib/solargraph/language_server/message/initialized.rb +26 -0
  68. data/lib/solargraph/language_server/message/method_not_found.rb +16 -0
  69. data/lib/solargraph/language_server/message/method_not_implemented.rb +14 -0
  70. data/lib/solargraph/language_server/message/shutdown.rb +13 -0
  71. data/lib/solargraph/language_server/message/text_document.rb +28 -0
  72. data/lib/solargraph/language_server/message/text_document/base.rb +19 -0
  73. data/lib/solargraph/language_server/message/text_document/code_action.rb +17 -0
  74. data/lib/solargraph/language_server/message/text_document/completion.rb +57 -0
  75. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -0
  76. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -0
  77. data/lib/solargraph/language_server/message/text_document/did_close.rb +15 -0
  78. data/lib/solargraph/language_server/message/text_document/did_open.rb +15 -0
  79. data/lib/solargraph/language_server/message/text_document/did_save.rb +17 -0
  80. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +23 -0
  81. data/lib/solargraph/language_server/message/text_document/folding_range.rb +26 -0
  82. data/lib/solargraph/language_server/message/text_document/formatting.rb +78 -0
  83. data/lib/solargraph/language_server/message/text_document/hover.rb +44 -0
  84. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +34 -0
  85. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +11 -0
  86. data/lib/solargraph/language_server/message/text_document/references.rb +16 -0
  87. data/lib/solargraph/language_server/message/text_document/rename.rb +19 -0
  88. data/lib/solargraph/language_server/message/text_document/signature_help.rb +29 -0
  89. data/lib/solargraph/language_server/message/workspace.rb +14 -0
  90. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -0
  91. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +33 -0
  92. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -0
  93. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -0
  94. data/lib/solargraph/language_server/message_types.rb +14 -0
  95. data/lib/solargraph/language_server/request.rb +24 -0
  96. data/lib/solargraph/language_server/symbol_kinds.rb +36 -0
  97. data/lib/solargraph/language_server/transport.rb +13 -0
  98. data/lib/solargraph/language_server/transport/adapter.rb +56 -0
  99. data/lib/solargraph/language_server/transport/data_reader.rb +72 -0
  100. data/lib/solargraph/language_server/uri_helpers.rb +49 -0
  101. data/lib/solargraph/library.rb +414 -0
  102. data/lib/solargraph/location.rb +37 -0
  103. data/lib/solargraph/logging.rb +27 -0
  104. data/lib/solargraph/page.rb +83 -0
  105. data/lib/solargraph/parser.rb +26 -0
  106. data/lib/solargraph/parser/comment_ripper.rb +52 -0
  107. data/lib/solargraph/parser/legacy.rb +12 -0
  108. data/lib/solargraph/parser/legacy/class_methods.rb +109 -0
  109. data/lib/solargraph/parser/legacy/flawed_builder.rb +16 -0
  110. data/lib/solargraph/parser/legacy/node_chainer.rb +118 -0
  111. data/lib/solargraph/parser/legacy/node_methods.rb +300 -0
  112. data/lib/solargraph/parser/legacy/node_processors.rb +54 -0
  113. data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +23 -0
  114. data/lib/solargraph/parser/legacy/node_processors/args_node.rb +35 -0
  115. data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +15 -0
  116. data/lib/solargraph/parser/legacy/node_processors/block_node.rb +22 -0
  117. data/lib/solargraph/parser/legacy/node_processors/casgn_node.rb +25 -0
  118. data/lib/solargraph/parser/legacy/node_processors/cvasgn_node.rb +23 -0
  119. data/lib/solargraph/parser/legacy/node_processors/def_node.rb +63 -0
  120. data/lib/solargraph/parser/legacy/node_processors/defs_node.rb +36 -0
  121. data/lib/solargraph/parser/legacy/node_processors/gvasgn_node.rb +23 -0
  122. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +38 -0
  123. data/lib/solargraph/parser/legacy/node_processors/lvasgn_node.rb +28 -0
  124. data/lib/solargraph/parser/legacy/node_processors/namespace_node.rb +39 -0
  125. data/lib/solargraph/parser/legacy/node_processors/orasgn_node.rb +16 -0
  126. data/lib/solargraph/parser/legacy/node_processors/resbody_node.rb +36 -0
  127. data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +21 -0
  128. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +234 -0
  129. data/lib/solargraph/parser/legacy/node_processors/sym_node.rb +18 -0
  130. data/lib/solargraph/parser/node_methods.rb +43 -0
  131. data/lib/solargraph/parser/node_processor.rb +43 -0
  132. data/lib/solargraph/parser/node_processor/base.rb +77 -0
  133. data/lib/solargraph/parser/region.rb +66 -0
  134. data/lib/solargraph/parser/rubyvm.rb +40 -0
  135. data/lib/solargraph/parser/rubyvm/class_methods.rb +150 -0
  136. data/lib/solargraph/parser/rubyvm/node_chainer.rb +135 -0
  137. data/lib/solargraph/parser/rubyvm/node_methods.rb +284 -0
  138. data/lib/solargraph/parser/rubyvm/node_processors.rb +61 -0
  139. data/lib/solargraph/parser/rubyvm/node_processors/alias_node.rb +23 -0
  140. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +62 -0
  141. data/lib/solargraph/parser/rubyvm/node_processors/begin_node.rb +15 -0
  142. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +22 -0
  143. data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +22 -0
  144. data/lib/solargraph/parser/rubyvm/node_processors/cvasgn_node.rb +23 -0
  145. data/lib/solargraph/parser/rubyvm/node_processors/def_node.rb +64 -0
  146. data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +57 -0
  147. data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +23 -0
  148. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +38 -0
  149. data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +39 -0
  150. data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +20 -0
  151. data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +27 -0
  152. data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +39 -0
  153. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +31 -0
  154. data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +15 -0
  155. data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +45 -0
  156. data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +21 -0
  157. data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +15 -0
  158. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +255 -0
  159. data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +18 -0
  160. data/lib/solargraph/parser/snippet.rb +13 -0
  161. data/lib/solargraph/pin.rb +39 -0
  162. data/lib/solargraph/pin/attribute.rb +49 -0
  163. data/lib/solargraph/pin/base.rb +296 -0
  164. data/lib/solargraph/pin/base_method.rb +141 -0
  165. data/lib/solargraph/pin/base_variable.rb +84 -0
  166. data/lib/solargraph/pin/block.rb +48 -0
  167. data/lib/solargraph/pin/class_variable.rb +8 -0
  168. data/lib/solargraph/pin/closure.rb +37 -0
  169. data/lib/solargraph/pin/common.rb +70 -0
  170. data/lib/solargraph/pin/constant.rb +43 -0
  171. data/lib/solargraph/pin/conversions.rb +97 -0
  172. data/lib/solargraph/pin/documenting.rb +110 -0
  173. data/lib/solargraph/pin/duck_method.rb +16 -0
  174. data/lib/solargraph/pin/global_variable.rb +8 -0
  175. data/lib/solargraph/pin/instance_variable.rb +30 -0
  176. data/lib/solargraph/pin/keyword.rb +15 -0
  177. data/lib/solargraph/pin/keyword_param.rb +8 -0
  178. data/lib/solargraph/pin/local_variable.rb +21 -0
  179. data/lib/solargraph/pin/localized.rb +43 -0
  180. data/lib/solargraph/pin/method.rb +111 -0
  181. data/lib/solargraph/pin/method_alias.rb +31 -0
  182. data/lib/solargraph/pin/namespace.rb +85 -0
  183. data/lib/solargraph/pin/parameter.rb +206 -0
  184. data/lib/solargraph/pin/proxy_type.rb +29 -0
  185. data/lib/solargraph/pin/reference.rb +14 -0
  186. data/lib/solargraph/pin/reference/extend.rb +10 -0
  187. data/lib/solargraph/pin/reference/include.rb +10 -0
  188. data/lib/solargraph/pin/reference/override.rb +29 -0
  189. data/lib/solargraph/pin/reference/prepend.rb +10 -0
  190. data/lib/solargraph/pin/reference/require.rb +14 -0
  191. data/lib/solargraph/pin/reference/superclass.rb +10 -0
  192. data/lib/solargraph/pin/singleton.rb +11 -0
  193. data/lib/solargraph/pin/symbol.rb +47 -0
  194. data/lib/solargraph/pin/yard_pin.rb +12 -0
  195. data/lib/solargraph/pin/yard_pin/constant.rb +25 -0
  196. data/lib/solargraph/pin/yard_pin/method.rb +65 -0
  197. data/lib/solargraph/pin/yard_pin/namespace.rb +27 -0
  198. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +26 -0
  199. data/lib/solargraph/position.rb +112 -0
  200. data/lib/solargraph/range.rb +95 -0
  201. data/lib/solargraph/server_methods.rb +16 -0
  202. data/lib/solargraph/shell.rb +221 -0
  203. data/lib/solargraph/source.rb +533 -0
  204. data/lib/solargraph/source/chain.rb +172 -0
  205. data/lib/solargraph/source/chain/block_variable.rb +13 -0
  206. data/lib/solargraph/source/chain/call.rb +203 -0
  207. data/lib/solargraph/source/chain/class_variable.rb +13 -0
  208. data/lib/solargraph/source/chain/constant.rb +75 -0
  209. data/lib/solargraph/source/chain/global_variable.rb +13 -0
  210. data/lib/solargraph/source/chain/head.rb +35 -0
  211. data/lib/solargraph/source/chain/instance_variable.rb +13 -0
  212. data/lib/solargraph/source/chain/link.rb +67 -0
  213. data/lib/solargraph/source/chain/literal.rb +23 -0
  214. data/lib/solargraph/source/chain/or.rb +23 -0
  215. data/lib/solargraph/source/chain/variable.rb +13 -0
  216. data/lib/solargraph/source/chain/z_super.rb +184 -0
  217. data/lib/solargraph/source/change.rb +79 -0
  218. data/lib/solargraph/source/cursor.rb +164 -0
  219. data/lib/solargraph/source/encoding_fixes.rb +23 -0
  220. data/lib/solargraph/source/source_chainer.rb +189 -0
  221. data/lib/solargraph/source/updater.rb +54 -0
  222. data/lib/solargraph/source_map.rb +170 -0
  223. data/lib/solargraph/source_map/clip.rb +190 -0
  224. data/lib/solargraph/source_map/completion.rb +23 -0
  225. data/lib/solargraph/source_map/mapper.rb +199 -0
  226. data/lib/solargraph/stdlib_fills.rb +32 -0
  227. data/lib/solargraph/type_checker.rb +498 -0
  228. data/lib/solargraph/type_checker/checks.rb +95 -0
  229. data/lib/solargraph/type_checker/param_def.rb +35 -0
  230. data/lib/solargraph/type_checker/problem.rb +32 -0
  231. data/lib/solargraph/type_checker/rules.rb +53 -0
  232. data/lib/solargraph/version.rb +5 -0
  233. data/lib/solargraph/views/_method.erb +62 -0
  234. data/lib/solargraph/views/_name_type_tag.erb +10 -0
  235. data/lib/solargraph/views/_namespace.erb +24 -0
  236. data/lib/solargraph/views/document.erb +23 -0
  237. data/lib/solargraph/views/environment.erb +58 -0
  238. data/lib/solargraph/views/layout.erb +44 -0
  239. data/lib/solargraph/views/search.erb +11 -0
  240. data/lib/solargraph/workspace.rb +209 -0
  241. data/lib/solargraph/workspace/config.rb +215 -0
  242. data/lib/solargraph/yard_map.rb +420 -0
  243. data/lib/solargraph/yard_map/cache.rb +19 -0
  244. data/lib/solargraph/yard_map/core_docs.rb +170 -0
  245. data/lib/solargraph/yard_map/core_gen.rb +76 -0
  246. data/lib/solargraph/yard_map/mapper.rb +71 -0
  247. data/lib/solargraph/yard_map/rdoc_to_yard.rb +136 -0
  248. data/lib/yard-solargraph.rb +30 -0
  249. data/solargraph.gemspec +41 -0
  250. data/travis-bundler.rb +11 -0
  251. data/yardoc/2.2.2.tar.gz +0 -0
  252. 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