solargraph 0.46.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +41 -41
  3. data/.gitignore +9 -9
  4. data/.rspec +2 -2
  5. data/.travis.yml +19 -19
  6. data/CHANGELOG.md +1123 -1115
  7. data/Gemfile +0 -0
  8. data/LICENSE +0 -0
  9. data/README.md +128 -128
  10. data/Rakefile +0 -0
  11. data/SPONSORS.md +17 -18
  12. data/bin/solargraph +0 -0
  13. data/lib/solargraph/api_map/bundler_methods.rb +22 -22
  14. data/lib/solargraph/api_map/cache.rb +70 -70
  15. data/lib/solargraph/api_map/source_to_yard.rb +81 -81
  16. data/lib/solargraph/api_map/store.rb +256 -256
  17. data/lib/solargraph/api_map.rb +686 -686
  18. data/lib/solargraph/bench.rb +27 -27
  19. data/lib/solargraph/compat.rb +37 -37
  20. data/lib/solargraph/complex_type/type_methods.rb +130 -130
  21. data/lib/solargraph/complex_type/unique_type.rb +75 -75
  22. data/lib/solargraph/complex_type.rb +221 -221
  23. data/lib/solargraph/convention/base.rb +33 -33
  24. data/lib/solargraph/convention/gemfile.rb +15 -15
  25. data/lib/solargraph/convention/gemspec.rb +22 -22
  26. data/lib/solargraph/convention/rspec.rb +30 -30
  27. data/lib/solargraph/convention.rb +47 -47
  28. data/lib/solargraph/converters/dd.rb +12 -12
  29. data/lib/solargraph/converters/dl.rb +12 -12
  30. data/lib/solargraph/converters/dt.rb +12 -12
  31. data/lib/solargraph/converters/misc.rb +1 -1
  32. data/lib/solargraph/diagnostics/base.rb +29 -29
  33. data/lib/solargraph/diagnostics/require_not_found.rb +53 -53
  34. data/lib/solargraph/diagnostics/rubocop.rb +98 -98
  35. data/lib/solargraph/diagnostics/rubocop_helpers.rb +63 -63
  36. data/lib/solargraph/diagnostics/severities.rb +15 -15
  37. data/lib/solargraph/diagnostics/type_check.rb +54 -54
  38. data/lib/solargraph/diagnostics/update_errors.rb +41 -41
  39. data/lib/solargraph/diagnostics.rb +55 -55
  40. data/lib/solargraph/documentor.rb +76 -76
  41. data/lib/solargraph/environ.rb +45 -45
  42. data/lib/solargraph/language_server/completion_item_kinds.rb +35 -35
  43. data/lib/solargraph/language_server/error_codes.rb +20 -20
  44. data/lib/solargraph/language_server/host/cataloger.rb +56 -56
  45. data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
  46. data/lib/solargraph/language_server/host/dispatch.rb +111 -111
  47. data/lib/solargraph/language_server/host/message_worker.rb +59 -59
  48. data/lib/solargraph/language_server/host/sources.rb +156 -156
  49. data/lib/solargraph/language_server/host.rb +865 -865
  50. data/lib/solargraph/language_server/message/base.rb +89 -89
  51. data/lib/solargraph/language_server/message/cancel_request.rb +13 -13
  52. data/lib/solargraph/language_server/message/client/register_capability.rb +15 -15
  53. data/lib/solargraph/language_server/message/client.rb +11 -11
  54. data/lib/solargraph/language_server/message/completion_item/resolve.rb +58 -58
  55. data/lib/solargraph/language_server/message/completion_item.rb +11 -11
  56. data/lib/solargraph/language_server/message/exit_notification.rb +13 -13
  57. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +100 -100
  58. data/lib/solargraph/language_server/message/extended/document.rb +20 -20
  59. data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -32
  60. data/lib/solargraph/language_server/message/extended/download_core.rb +23 -23
  61. data/lib/solargraph/language_server/message/extended/environment.rb +25 -25
  62. data/lib/solargraph/language_server/message/extended/search.rb +20 -20
  63. data/lib/solargraph/language_server/message/extended.rb +21 -21
  64. data/lib/solargraph/language_server/message/initialize.rb +162 -162
  65. data/lib/solargraph/language_server/message/initialized.rb +27 -27
  66. data/lib/solargraph/language_server/message/method_not_found.rb +16 -16
  67. data/lib/solargraph/language_server/message/method_not_implemented.rb +14 -14
  68. data/lib/solargraph/language_server/message/shutdown.rb +13 -13
  69. data/lib/solargraph/language_server/message/text_document/base.rb +19 -19
  70. data/lib/solargraph/language_server/message/text_document/code_action.rb +17 -17
  71. data/lib/solargraph/language_server/message/text_document/completion.rb +59 -59
  72. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -38
  73. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -15
  74. data/lib/solargraph/language_server/message/text_document/did_close.rb +15 -15
  75. data/lib/solargraph/language_server/message/text_document/did_open.rb +15 -15
  76. data/lib/solargraph/language_server/message/text_document/did_save.rb +17 -17
  77. data/lib/solargraph/language_server/message/text_document/document_highlight.rb +16 -16
  78. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +23 -23
  79. data/lib/solargraph/language_server/message/text_document/folding_range.rb +26 -26
  80. data/lib/solargraph/language_server/message/text_document/formatting.rb +126 -126
  81. data/lib/solargraph/language_server/message/text_document/hover.rb +56 -54
  82. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +34 -34
  83. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +11 -11
  84. data/lib/solargraph/language_server/message/text_document/references.rb +16 -16
  85. data/lib/solargraph/language_server/message/text_document/rename.rb +19 -19
  86. data/lib/solargraph/language_server/message/text_document/signature_help.rb +29 -29
  87. data/lib/solargraph/language_server/message/text_document.rb +28 -28
  88. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +30 -30
  89. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +33 -33
  90. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
  91. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -23
  92. data/lib/solargraph/language_server/message/workspace.rb +14 -14
  93. data/lib/solargraph/language_server/message.rb +93 -93
  94. data/lib/solargraph/language_server/message_types.rb +14 -14
  95. data/lib/solargraph/language_server/request.rb +24 -24
  96. data/lib/solargraph/language_server/symbol_kinds.rb +36 -36
  97. data/lib/solargraph/language_server/transport/adapter.rb +53 -53
  98. data/lib/solargraph/language_server/transport/data_reader.rb +72 -72
  99. data/lib/solargraph/language_server/transport.rb +13 -13
  100. data/lib/solargraph/language_server/uri_helpers.rb +49 -49
  101. data/lib/solargraph/language_server.rb +19 -19
  102. data/lib/solargraph/library.rb +546 -546
  103. data/lib/solargraph/location.rb +37 -37
  104. data/lib/solargraph/logging.rb +27 -27
  105. data/lib/solargraph/page.rb +83 -83
  106. data/lib/solargraph/parser/comment_ripper.rb +52 -52
  107. data/lib/solargraph/parser/legacy/class_methods.rb +135 -135
  108. data/lib/solargraph/parser/legacy/flawed_builder.rb +16 -16
  109. data/lib/solargraph/parser/legacy/node_chainer.rb +148 -148
  110. data/lib/solargraph/parser/legacy/node_methods.rb +325 -325
  111. data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +23 -23
  112. data/lib/solargraph/parser/legacy/node_processors/args_node.rb +35 -35
  113. data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +15 -15
  114. data/lib/solargraph/parser/legacy/node_processors/block_node.rb +42 -42
  115. data/lib/solargraph/parser/legacy/node_processors/casgn_node.rb +25 -25
  116. data/lib/solargraph/parser/legacy/node_processors/cvasgn_node.rb +23 -23
  117. data/lib/solargraph/parser/legacy/node_processors/def_node.rb +63 -63
  118. data/lib/solargraph/parser/legacy/node_processors/defs_node.rb +36 -36
  119. data/lib/solargraph/parser/legacy/node_processors/gvasgn_node.rb +23 -23
  120. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +38 -38
  121. data/lib/solargraph/parser/legacy/node_processors/lvasgn_node.rb +28 -28
  122. data/lib/solargraph/parser/legacy/node_processors/namespace_node.rb +39 -39
  123. data/lib/solargraph/parser/legacy/node_processors/orasgn_node.rb +16 -16
  124. data/lib/solargraph/parser/legacy/node_processors/resbody_node.rb +36 -36
  125. data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +21 -21
  126. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +257 -257
  127. data/lib/solargraph/parser/legacy/node_processors/sym_node.rb +18 -18
  128. data/lib/solargraph/parser/legacy/node_processors.rb +54 -54
  129. data/lib/solargraph/parser/legacy.rb +12 -12
  130. data/lib/solargraph/parser/node_methods.rb +43 -43
  131. data/lib/solargraph/parser/node_processor/base.rb +77 -77
  132. data/lib/solargraph/parser/node_processor.rb +43 -43
  133. data/lib/solargraph/parser/region.rb +66 -66
  134. data/lib/solargraph/parser/rubyvm/class_methods.rb +144 -144
  135. data/lib/solargraph/parser/rubyvm/node_chainer.rb +160 -160
  136. data/lib/solargraph/parser/rubyvm/node_methods.rb +315 -315
  137. data/lib/solargraph/parser/rubyvm/node_processors/alias_node.rb +23 -23
  138. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +85 -85
  139. data/lib/solargraph/parser/rubyvm/node_processors/begin_node.rb +15 -15
  140. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +42 -42
  141. data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +22 -22
  142. data/lib/solargraph/parser/rubyvm/node_processors/cvasgn_node.rb +23 -23
  143. data/lib/solargraph/parser/rubyvm/node_processors/def_node.rb +63 -63
  144. data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +57 -57
  145. data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +23 -23
  146. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +38 -38
  147. data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +39 -39
  148. data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +20 -20
  149. data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +27 -27
  150. data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +39 -39
  151. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +26 -26
  152. data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +15 -15
  153. data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +45 -45
  154. data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +21 -21
  155. data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +15 -15
  156. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +277 -277
  157. data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +18 -18
  158. data/lib/solargraph/parser/rubyvm/node_processors.rb +63 -63
  159. data/lib/solargraph/parser/rubyvm.rb +40 -40
  160. data/lib/solargraph/parser/snippet.rb +13 -13
  161. data/lib/solargraph/parser.rb +26 -26
  162. data/lib/solargraph/pin/base.rb +296 -296
  163. data/lib/solargraph/pin/base_variable.rb +84 -84
  164. data/lib/solargraph/pin/block.rb +73 -72
  165. data/lib/solargraph/pin/class_variable.rb +8 -8
  166. data/lib/solargraph/pin/closure.rb +37 -37
  167. data/lib/solargraph/pin/common.rb +70 -70
  168. data/lib/solargraph/pin/constant.rb +43 -43
  169. data/lib/solargraph/pin/conversions.rb +96 -96
  170. data/lib/solargraph/pin/documenting.rb +105 -105
  171. data/lib/solargraph/pin/duck_method.rb +16 -16
  172. data/lib/solargraph/pin/global_variable.rb +8 -8
  173. data/lib/solargraph/pin/instance_variable.rb +30 -30
  174. data/lib/solargraph/pin/keyword.rb +15 -15
  175. data/lib/solargraph/pin/keyword_param.rb +8 -8
  176. data/lib/solargraph/pin/local_variable.rb +55 -55
  177. data/lib/solargraph/pin/method.rb +245 -245
  178. data/lib/solargraph/pin/method_alias.rb +31 -31
  179. data/lib/solargraph/pin/namespace.rb +91 -91
  180. data/lib/solargraph/pin/parameter.rb +201 -201
  181. data/lib/solargraph/pin/proxy_type.rb +29 -29
  182. data/lib/solargraph/pin/reference/extend.rb +10 -10
  183. data/lib/solargraph/pin/reference/include.rb +10 -10
  184. data/lib/solargraph/pin/reference/override.rb +29 -29
  185. data/lib/solargraph/pin/reference/prepend.rb +10 -10
  186. data/lib/solargraph/pin/reference/require.rb +14 -14
  187. data/lib/solargraph/pin/reference/superclass.rb +10 -10
  188. data/lib/solargraph/pin/reference.rb +14 -14
  189. data/lib/solargraph/pin/search.rb +56 -56
  190. data/lib/solargraph/pin/singleton.rb +11 -11
  191. data/lib/solargraph/pin/symbol.rb +47 -47
  192. data/lib/solargraph/pin.rb +37 -37
  193. data/lib/solargraph/position.rb +100 -100
  194. data/lib/solargraph/range.rb +95 -95
  195. data/lib/solargraph/server_methods.rb +16 -16
  196. data/lib/solargraph/shell.rb +230 -226
  197. data/lib/solargraph/source/chain/block_variable.rb +13 -13
  198. data/lib/solargraph/source/chain/call.rb +204 -204
  199. data/lib/solargraph/source/chain/class_variable.rb +13 -13
  200. data/lib/solargraph/source/chain/constant.rb +75 -75
  201. data/lib/solargraph/source/chain/global_variable.rb +13 -13
  202. data/lib/solargraph/source/chain/hash.rb +28 -28
  203. data/lib/solargraph/source/chain/head.rb +19 -19
  204. data/lib/solargraph/source/chain/instance_variable.rb +13 -13
  205. data/lib/solargraph/source/chain/link.rb +71 -71
  206. data/lib/solargraph/source/chain/literal.rb +23 -23
  207. data/lib/solargraph/source/chain/or.rb +23 -23
  208. data/lib/solargraph/source/chain/q_call.rb +11 -11
  209. data/lib/solargraph/source/chain/variable.rb +13 -13
  210. data/lib/solargraph/source/chain/z_super.rb +30 -30
  211. data/lib/solargraph/source/chain.rb +164 -164
  212. data/lib/solargraph/source/change.rb +79 -79
  213. data/lib/solargraph/source/cursor.rb +164 -164
  214. data/lib/solargraph/source/source_chainer.rb +191 -191
  215. data/lib/solargraph/source/updater.rb +54 -54
  216. data/lib/solargraph/source.rb +524 -522
  217. data/lib/solargraph/source_map/clip.rb +224 -224
  218. data/lib/solargraph/source_map/completion.rb +23 -23
  219. data/lib/solargraph/source_map/mapper.rb +239 -212
  220. data/lib/solargraph/source_map.rb +180 -180
  221. data/lib/solargraph/type_checker/checks.rb +99 -99
  222. data/lib/solargraph/type_checker/param_def.rb +35 -35
  223. data/lib/solargraph/type_checker/problem.rb +32 -32
  224. data/lib/solargraph/type_checker/rules.rb +57 -57
  225. data/lib/solargraph/type_checker.rb +543 -543
  226. data/lib/solargraph/version.rb +5 -5
  227. data/lib/solargraph/views/environment.erb +58 -58
  228. data/lib/solargraph/workspace/config.rb +231 -231
  229. data/lib/solargraph/workspace.rb +215 -215
  230. data/lib/solargraph/yard_map/cache.rb +19 -19
  231. data/lib/solargraph/yard_map/core_docs.rb +170 -170
  232. data/lib/solargraph/yard_map/core_fills.rb +208 -208
  233. data/lib/solargraph/yard_map/core_gen.rb +76 -76
  234. data/lib/solargraph/yard_map/helpers.rb +16 -16
  235. data/lib/solargraph/yard_map/mapper/to_constant.rb +25 -25
  236. data/lib/solargraph/yard_map/mapper/to_method.rb +78 -78
  237. data/lib/solargraph/yard_map/mapper/to_namespace.rb +27 -27
  238. data/lib/solargraph/yard_map/mapper.rb +77 -77
  239. data/lib/solargraph/yard_map/rdoc_to_yard.rb +140 -140
  240. data/lib/solargraph/yard_map/stdlib_fills.rb +43 -43
  241. data/lib/solargraph/yard_map/to_method.rb +79 -79
  242. data/lib/solargraph/yard_map.rb +460 -460
  243. data/lib/solargraph.rb +69 -69
  244. data/lib/yard-solargraph.rb +33 -33
  245. data/solargraph.gemspec +0 -0
  246. metadata +12 -12
@@ -1,543 +1,543 @@
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
- without_ignored(
44
- method_tag_problems
45
- .concat variable_type_tag_problems
46
- .concat const_problems
47
- .concat call_problems
48
- )
49
- end
50
- end
51
-
52
- class << self
53
- # @param filename [String]
54
- # @return [self]
55
- def load filename, level = :normal
56
- source = Solargraph::Source.load(filename)
57
- api_map = Solargraph::ApiMap.new
58
- api_map.map(source)
59
- new(filename, api_map: api_map, level: level)
60
- end
61
-
62
- # @param code [String]
63
- # @param filename [String, nil]
64
- # @return [self]
65
- def load_string code, filename = nil, level = :normal
66
- source = Solargraph::Source.load_string(code, filename)
67
- api_map = Solargraph::ApiMap.new
68
- api_map.map(source)
69
- new(filename, api_map: api_map, level: level)
70
- end
71
- end
72
-
73
- private
74
-
75
- # @return [Array<Problem>]
76
- def method_tag_problems
77
- result = []
78
- # @param pin [Pin::Method]
79
- source_map.pins_by_class(Pin::Method).each do |pin|
80
- result.concat method_return_type_problems_for(pin)
81
- result.concat method_param_type_problems_for(pin)
82
- end
83
- result
84
- end
85
-
86
- # @param pin [Pin::Method]
87
- # @return [Array<Problem>]
88
- def method_return_type_problems_for pin
89
- return [] if pin.is_a?(Pin::MethodAlias)
90
- result = []
91
- declared = pin.typify(api_map).self_to(pin.full_context.namespace)
92
- if declared.undefined?
93
- if pin.return_type.undefined? && rules.require_type_tags?
94
- result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin)
95
- elsif pin.return_type.defined? && !resolved_constant?(pin)
96
- result.push Problem.new(pin.location, "Unresolved return type #{pin.return_type} for #{pin.path}", pin: pin)
97
- elsif rules.must_tag_or_infer? && pin.probe(api_map).undefined?
98
- result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
99
- end
100
- elsif rules.validate_tags?
101
- unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
102
- inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
103
- if inferred.undefined?
104
- unless rules.ignore_all_undefined? || external?(pin)
105
- result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin)
106
- end
107
- else
108
- unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
109
- result.push Problem.new(pin.location, "Declared return type #{declared} does not match inferred type #{inferred} for #{pin.path}", pin: pin)
110
- end
111
- end
112
- end
113
- end
114
- result
115
- end
116
-
117
- # @todo This is not optimal. A better solution would probably be to mix
118
- # namespace alias into types at the ApiMap level.
119
- #
120
- # @param pin [Pin::Base]
121
- # @return [Boolean]
122
- def resolved_constant? pin
123
- api_map.get_constants('', pin.binder.tag)
124
- .select { |p| p.name == pin.return_type.namespace }
125
- .any? do |p|
126
- inferred = p.infer(api_map)
127
- ['Class', 'Module'].include?(inferred.name)
128
- end
129
- end
130
-
131
- def virtual_pin? pin
132
- pin.location && source_map.source.comment_at?(pin.location.range.ending)
133
- end
134
-
135
- # @param pin [Pin::Method]
136
- # @return [Array<Problem>]
137
- def method_param_type_problems_for pin
138
- stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
139
- params = first_param_hash(stack)
140
- result = []
141
- if rules.require_type_tags?
142
- pin.parameters.each do |par|
143
- break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
144
- unless params[par.name]
145
- result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
146
- end
147
- end
148
- end
149
- params.each_pair do |name, data|
150
- type = data[:qualified]
151
- if type.undefined?
152
- result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
153
- end
154
- end
155
- result
156
- end
157
-
158
- def ignored_pins
159
- @ignored_pins ||= []
160
- end
161
-
162
- # @return [Array<Problem>]
163
- def variable_type_tag_problems
164
- result = []
165
- all_variables.each do |pin|
166
- if pin.return_type.defined?
167
- declared = pin.typify(api_map)
168
- next if declared.duck_type?
169
- if declared.defined?
170
- if rules.validate_tags?
171
- inferred = pin.probe(api_map)
172
- if inferred.undefined?
173
- next if rules.ignore_all_undefined?
174
- if declared_externally?(pin)
175
- ignored_pins.push pin
176
- else
177
- result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin)
178
- end
179
- else
180
- unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
181
- result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin)
182
- end
183
- end
184
- elsif declared_externally?(pin)
185
- ignored_pins.push pin
186
- end
187
- elsif !pin.is_a?(Pin::Parameter)
188
- result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
189
- end
190
- else
191
- inferred = pin.probe(api_map)
192
- if inferred.undefined? && declared_externally?(pin)
193
- ignored_pins.push pin
194
- end
195
- end
196
- end
197
- result
198
- end
199
-
200
- # @return [Array<Pin::BaseVariable>]
201
- def all_variables
202
- source_map.pins_by_class(Pin::BaseVariable) + source_map.locals.select { |pin| pin.is_a?(Pin::LocalVariable) }
203
- end
204
-
205
- def const_problems
206
- return [] unless rules.validate_consts?
207
- result = []
208
- Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
209
- rng = Solargraph::Range.from_node(const)
210
- chain = Solargraph::Parser.chain(const, filename)
211
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
212
- location = Location.new(filename, rng)
213
- locals = source_map.locals_at(location)
214
- pins = chain.define(api_map, block_pin, locals)
215
- if pins.empty?
216
- result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
217
- @marked_ranges.push location.range
218
- end
219
- end
220
- result
221
- end
222
-
223
- def call_problems
224
- result = []
225
- Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
226
- rng = Solargraph::Range.from_node(call)
227
- next if @marked_ranges.any? { |d| d.contain?(rng.start) }
228
- chain = Solargraph::Parser.chain(call, filename)
229
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
230
- location = Location.new(filename, rng)
231
- locals = source_map.locals_at(location)
232
- type = chain.infer(api_map, block_pin, locals)
233
- if type.undefined? && !rules.ignore_all_undefined?
234
- base = chain
235
- missing = chain
236
- found = nil
237
- closest = ComplexType::UNDEFINED
238
- until base.links.first.undefined?
239
- found = base.define(api_map, block_pin, locals).first
240
- break if found
241
- missing = base
242
- base = base.base
243
- end
244
- closest = found.typify(api_map) if found
245
- if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
246
- unless ignored_pins.include?(found)
247
- result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
248
- @marked_ranges.push rng
249
- end
250
- end
251
- end
252
- result.concat argument_problems_for(chain, api_map, block_pin, locals, location)
253
- end
254
- result
255
- end
256
-
257
- def argument_problems_for chain, api_map, block_pin, locals, location
258
- result = []
259
- base = chain
260
- until base.links.length == 1 && base.undefined?
261
- pins = base.define(api_map, block_pin, locals)
262
- if pins.first.is_a?(Pin::Method)
263
- # @type [Pin::Method]
264
- pin = pins.first
265
- ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
266
- arity_problems_for(pin, fake_args_for(block_pin), location)
267
- else
268
- arity_problems_for(pin, base.links.last.arguments, location)
269
- end
270
- unless ap.empty?
271
- result.concat ap
272
- break
273
- end
274
- break unless rules.validate_calls?
275
- params = first_param_hash(pins)
276
- pin.parameters.each_with_index do |par, idx|
277
- argchain = base.links.last.arguments[idx]
278
- if argchain.nil? && par.decl == :arg
279
- result.push Problem.new(location, "Not enough arguments to #{pin.path}")
280
- break
281
- end
282
- if argchain
283
- if par.decl != :arg
284
- result.concat kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, idx
285
- break
286
- else
287
- ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
288
- if ptype.nil?
289
- # @todo Some level (strong, I guess) should require the param here
290
- else
291
- argtype = argchain.infer(api_map, block_pin, locals)
292
- if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
293
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
294
- end
295
- end
296
- end
297
- elsif par.rest?
298
- next
299
- elsif par.decl == :kwarg
300
- result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
301
- break
302
- end
303
- end
304
- end
305
- base = base.base
306
- end
307
- result
308
- end
309
-
310
- def kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, first
311
- result = []
312
- kwargs = convert_hash(argchain.node)
313
- pin.parameters[first..-1].each_with_index do |par, cur|
314
- idx = first + cur
315
- argchain = kwargs[par.name.to_sym]
316
- if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
317
- result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
318
- else
319
- if argchain
320
- data = params[par.name]
321
- if data.nil?
322
- # @todo Some level (strong, I guess) should require the param here
323
- else
324
- ptype = data[:qualified]
325
- next if ptype.undefined?
326
- argtype = argchain.infer(api_map, block_pin, locals)
327
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
328
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
329
- end
330
- end
331
- elsif par.decl == :kwarg
332
- result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
333
- end
334
- end
335
- end
336
- result
337
- end
338
-
339
- def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
340
- result = []
341
- kwargs.each_pair do |pname, argchain|
342
- next unless params.key?(pname.to_s)
343
- ptype = params[pname.to_s][:qualified]
344
- argtype = argchain.infer(api_map, block_pin, locals)
345
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
346
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
347
- end
348
- end
349
- result
350
- end
351
-
352
- # @param [Pin::Method]
353
- # @return [Hash]
354
- def param_hash(pin)
355
- tags = pin.docstring.tags(:param)
356
- return {} if tags.empty?
357
- result = {}
358
- tags.each do |tag|
359
- next if tag.types.nil? || tag.types.empty?
360
- result[tag.name.to_s] = {
361
- tagged: tag.types.join(', '),
362
- qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
363
- }
364
- end
365
- result
366
- end
367
-
368
- # @param [Array<Pin::Method>]
369
- # @return [Hash]
370
- def first_param_hash(pins)
371
- pins.each do |pin|
372
- result = param_hash(pin)
373
- return result unless result.empty?
374
- end
375
- {}
376
- end
377
-
378
- # @param pin [Pin::Base]
379
- def internal? pin
380
- return false if pin.nil?
381
- pin.location && api_map.bundled?(pin.location.filename)
382
- end
383
-
384
- def internal_or_core? pin
385
- internal?(pin) || api_map.yard_map.core_pins.include?(pin) || api_map.yard_map.stdlib_pins.include?(pin)
386
- end
387
-
388
- # @param pin [Pin::Base]
389
- def external? pin
390
- !internal? pin
391
- end
392
-
393
- def declared_externally? pin
394
- return true if pin.assignment.nil?
395
- chain = Solargraph::Parser.chain(pin.assignment, filename)
396
- rng = Solargraph::Range.from_node(pin.assignment)
397
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
398
- location = Location.new(filename, Range.from_node(pin.assignment))
399
- locals = source_map.locals_at(location)
400
- type = chain.infer(api_map, block_pin, locals)
401
- if type.undefined? && !rules.ignore_all_undefined?
402
- base = chain
403
- missing = chain
404
- found = nil
405
- closest = ComplexType::UNDEFINED
406
- until base.links.first.undefined?
407
- found = base.define(api_map, block_pin, locals).first
408
- break if found
409
- missing = base
410
- base = base.base
411
- end
412
- closest = found.typify(api_map) if found
413
- if !found || closest.defined? || internal?(found)
414
- return false
415
- end
416
- end
417
- true
418
- end
419
-
420
- # @param pin [Pin::Method]
421
- def arity_problems_for(pin, arguments, location)
422
- ([pin] + pin.overloads).map do |p|
423
- result = pin_arity_problems_for(p, arguments, location)
424
- return [] if result.empty?
425
- result
426
- end.flatten.uniq(&:message)
427
- end
428
-
429
- # @param pin [Pin::Method]
430
- def pin_arity_problems_for(pin, arguments, location)
431
- return [] unless pin.explicit?
432
- return [] if pin.parameters.empty? && arguments.empty?
433
- if pin.parameters.empty?
434
- # Functions tagged param_tuple accepts two arguments (e.g., Hash#[]=)
435
- return [] if pin.docstring.tag(:param_tuple) && arguments.length == 2
436
- return [] if arguments.length == 1 && arguments.last.links.last.is_a?(Source::Chain::BlockVariable)
437
- return [Problem.new(location, "Too many arguments to #{pin.path}")]
438
- end
439
- unchecked = arguments.clone
440
- add_params = 0
441
- if unchecked.empty? && pin.parameters.any? { |param| param.decl == :kwarg }
442
- return [Problem.new(location, "Missing keyword arguments to #{pin.path}")]
443
- end
444
- settled_kwargs = 0
445
- unless unchecked.empty?
446
- if any_splatted_call?(unchecked.map(&:node))
447
- settled_kwargs = pin.parameters.count(&:keyword?)
448
- else
449
- kwargs = convert_hash(unchecked.last.node)
450
- if pin.parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
451
- if kwargs.empty?
452
- add_params += 1
453
- else
454
- unchecked.pop
455
- pin.parameters.each do |param|
456
- next unless param.keyword?
457
- if kwargs.key?(param.name.to_sym)
458
- kwargs.delete param.name.to_sym
459
- settled_kwargs += 1
460
- elsif param.decl == :kwarg
461
- return [] if arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash) && arguments.last.links.last.splatted?
462
- return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
463
- end
464
- end
465
- kwargs.clear if pin.parameters.any?(&:kwrestarg?)
466
- unless kwargs.empty?
467
- return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
468
- end
469
- end
470
- end
471
- end
472
- end
473
- req = required_param_count(pin)
474
- if req + add_params < unchecked.length
475
- return [] if pin.parameters.any?(&:rest?)
476
- opt = optional_param_count(pin)
477
- return [] if unchecked.length <= req + opt
478
- if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
479
- return []
480
- end
481
- if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
482
- return []
483
- end
484
- return [] if arguments.length - req == pin.parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length
485
- return [Problem.new(location, "Too many arguments to #{pin.path}")]
486
- elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash)))
487
- # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs.
488
- # See https://github.com/castwide/solargraph/issues/418
489
- unless arguments.empty? && pin.path == 'Kernel#raise'
490
- return [Problem.new(location, "Not enough arguments to #{pin.path}")]
491
- end
492
- end
493
- []
494
- end
495
-
496
- # @param pin [Pin::Method]
497
- def required_param_count(pin)
498
- pin.parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
499
- end
500
-
501
- # @param pin [Pin::Method]
502
- def optional_param_count(pin)
503
- count = 0
504
- pin.parameters.each do |param|
505
- next unless param.decl == :optarg
506
- count += 1
507
- end
508
- count
509
- end
510
-
511
- def abstract? pin
512
- pin.docstring.has_tag?(:abstract) ||
513
- (pin.closure && pin.closure.docstring.has_tag?(:abstract))
514
- end
515
-
516
- def fake_args_for(pin)
517
- args = []
518
- with_opts = false
519
- with_block = false
520
- pin.parameters.each do |pin|
521
- if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl)
522
- with_opts = true
523
- elsif pin.decl == :block
524
- with_block = true
525
- elsif pin.decl == :restarg
526
- args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true)
527
- else
528
- args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)])
529
- end
530
- end
531
- args.push Solargraph::Parser.chain_string('{}') if with_opts
532
- args.push Solargraph::Parser.chain_string('&') if with_block
533
- args
534
- end
535
-
536
- def without_ignored problems
537
- problems.reject do |problem|
538
- node = source_map.source.node_at(problem.location.range.start.line, problem.location.range.start.column)
539
- source_map.source.comments_for(node)&.include?('@sg-ignore')
540
- end
541
- end
542
- end
543
- end
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
+ without_ignored(
44
+ method_tag_problems
45
+ .concat variable_type_tag_problems
46
+ .concat const_problems
47
+ .concat call_problems
48
+ )
49
+ end
50
+ end
51
+
52
+ class << self
53
+ # @param filename [String]
54
+ # @return [self]
55
+ def load filename, level = :normal
56
+ source = Solargraph::Source.load(filename)
57
+ api_map = Solargraph::ApiMap.new
58
+ api_map.map(source)
59
+ new(filename, api_map: api_map, level: level)
60
+ end
61
+
62
+ # @param code [String]
63
+ # @param filename [String, nil]
64
+ # @return [self]
65
+ def load_string code, filename = nil, level = :normal
66
+ source = Solargraph::Source.load_string(code, filename)
67
+ api_map = Solargraph::ApiMap.new
68
+ api_map.map(source)
69
+ new(filename, api_map: api_map, level: level)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ # @return [Array<Problem>]
76
+ def method_tag_problems
77
+ result = []
78
+ # @param pin [Pin::Method]
79
+ source_map.pins_by_class(Pin::Method).each do |pin|
80
+ result.concat method_return_type_problems_for(pin)
81
+ result.concat method_param_type_problems_for(pin)
82
+ end
83
+ result
84
+ end
85
+
86
+ # @param pin [Pin::Method]
87
+ # @return [Array<Problem>]
88
+ def method_return_type_problems_for pin
89
+ return [] if pin.is_a?(Pin::MethodAlias)
90
+ result = []
91
+ declared = pin.typify(api_map).self_to(pin.full_context.namespace)
92
+ if declared.undefined?
93
+ if pin.return_type.undefined? && rules.require_type_tags?
94
+ result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin)
95
+ elsif pin.return_type.defined? && !resolved_constant?(pin)
96
+ result.push Problem.new(pin.location, "Unresolved return type #{pin.return_type} for #{pin.path}", pin: pin)
97
+ elsif rules.must_tag_or_infer? && pin.probe(api_map).undefined?
98
+ result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
99
+ end
100
+ elsif rules.validate_tags?
101
+ unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
102
+ inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
103
+ if inferred.undefined?
104
+ unless rules.ignore_all_undefined? || external?(pin)
105
+ result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin)
106
+ end
107
+ else
108
+ unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
109
+ result.push Problem.new(pin.location, "Declared return type #{declared} does not match inferred type #{inferred} for #{pin.path}", pin: pin)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ result
115
+ end
116
+
117
+ # @todo This is not optimal. A better solution would probably be to mix
118
+ # namespace alias into types at the ApiMap level.
119
+ #
120
+ # @param pin [Pin::Base]
121
+ # @return [Boolean]
122
+ def resolved_constant? pin
123
+ api_map.get_constants('', pin.binder.tag)
124
+ .select { |p| p.name == pin.return_type.namespace }
125
+ .any? do |p|
126
+ inferred = p.infer(api_map)
127
+ ['Class', 'Module'].include?(inferred.name)
128
+ end
129
+ end
130
+
131
+ def virtual_pin? pin
132
+ pin.location && source_map.source.comment_at?(pin.location.range.ending)
133
+ end
134
+
135
+ # @param pin [Pin::Method]
136
+ # @return [Array<Problem>]
137
+ def method_param_type_problems_for pin
138
+ stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
139
+ params = first_param_hash(stack)
140
+ result = []
141
+ if rules.require_type_tags?
142
+ pin.parameters.each do |par|
143
+ break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
144
+ unless params[par.name]
145
+ result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
146
+ end
147
+ end
148
+ end
149
+ params.each_pair do |name, data|
150
+ type = data[:qualified]
151
+ if type.undefined?
152
+ result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
153
+ end
154
+ end
155
+ result
156
+ end
157
+
158
+ def ignored_pins
159
+ @ignored_pins ||= []
160
+ end
161
+
162
+ # @return [Array<Problem>]
163
+ def variable_type_tag_problems
164
+ result = []
165
+ all_variables.each do |pin|
166
+ if pin.return_type.defined?
167
+ declared = pin.typify(api_map)
168
+ next if declared.duck_type?
169
+ if declared.defined?
170
+ if rules.validate_tags?
171
+ inferred = pin.probe(api_map)
172
+ if inferred.undefined?
173
+ next if rules.ignore_all_undefined?
174
+ if declared_externally?(pin)
175
+ ignored_pins.push pin
176
+ else
177
+ result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin)
178
+ end
179
+ else
180
+ unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
181
+ result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin)
182
+ end
183
+ end
184
+ elsif declared_externally?(pin)
185
+ ignored_pins.push pin
186
+ end
187
+ elsif !pin.is_a?(Pin::Parameter)
188
+ result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
189
+ end
190
+ else
191
+ inferred = pin.probe(api_map)
192
+ if inferred.undefined? && declared_externally?(pin)
193
+ ignored_pins.push pin
194
+ end
195
+ end
196
+ end
197
+ result
198
+ end
199
+
200
+ # @return [Array<Pin::BaseVariable>]
201
+ def all_variables
202
+ source_map.pins_by_class(Pin::BaseVariable) + source_map.locals.select { |pin| pin.is_a?(Pin::LocalVariable) }
203
+ end
204
+
205
+ def const_problems
206
+ return [] unless rules.validate_consts?
207
+ result = []
208
+ Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
209
+ rng = Solargraph::Range.from_node(const)
210
+ chain = Solargraph::Parser.chain(const, filename)
211
+ block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
212
+ location = Location.new(filename, rng)
213
+ locals = source_map.locals_at(location)
214
+ pins = chain.define(api_map, block_pin, locals)
215
+ if pins.empty?
216
+ result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
217
+ @marked_ranges.push location.range
218
+ end
219
+ end
220
+ result
221
+ end
222
+
223
+ def call_problems
224
+ result = []
225
+ Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
226
+ rng = Solargraph::Range.from_node(call)
227
+ next if @marked_ranges.any? { |d| d.contain?(rng.start) }
228
+ chain = Solargraph::Parser.chain(call, filename)
229
+ block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
230
+ location = Location.new(filename, rng)
231
+ locals = source_map.locals_at(location)
232
+ type = chain.infer(api_map, block_pin, locals)
233
+ if type.undefined? && !rules.ignore_all_undefined?
234
+ base = chain
235
+ missing = chain
236
+ found = nil
237
+ closest = ComplexType::UNDEFINED
238
+ until base.links.first.undefined?
239
+ found = base.define(api_map, block_pin, locals).first
240
+ break if found
241
+ missing = base
242
+ base = base.base
243
+ end
244
+ closest = found.typify(api_map) if found
245
+ if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
246
+ unless ignored_pins.include?(found)
247
+ result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
248
+ @marked_ranges.push rng
249
+ end
250
+ end
251
+ end
252
+ result.concat argument_problems_for(chain, api_map, block_pin, locals, location)
253
+ end
254
+ result
255
+ end
256
+
257
+ def argument_problems_for chain, api_map, block_pin, locals, location
258
+ result = []
259
+ base = chain
260
+ until base.links.length == 1 && base.undefined?
261
+ pins = base.define(api_map, block_pin, locals)
262
+ if pins.first.is_a?(Pin::Method)
263
+ # @type [Pin::Method]
264
+ pin = pins.first
265
+ ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
266
+ arity_problems_for(pin, fake_args_for(block_pin), location)
267
+ else
268
+ arity_problems_for(pin, base.links.last.arguments, location)
269
+ end
270
+ unless ap.empty?
271
+ result.concat ap
272
+ break
273
+ end
274
+ break unless rules.validate_calls?
275
+ params = first_param_hash(pins)
276
+ pin.parameters.each_with_index do |par, idx|
277
+ argchain = base.links.last.arguments[idx]
278
+ if argchain.nil? && par.decl == :arg
279
+ result.push Problem.new(location, "Not enough arguments to #{pin.path}")
280
+ break
281
+ end
282
+ if argchain
283
+ if par.decl != :arg
284
+ result.concat kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, idx
285
+ break
286
+ else
287
+ ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
288
+ if ptype.nil?
289
+ # @todo Some level (strong, I guess) should require the param here
290
+ else
291
+ argtype = argchain.infer(api_map, block_pin, locals)
292
+ if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
293
+ result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
294
+ end
295
+ end
296
+ end
297
+ elsif par.rest?
298
+ next
299
+ elsif par.decl == :kwarg
300
+ result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
301
+ break
302
+ end
303
+ end
304
+ end
305
+ base = base.base
306
+ end
307
+ result
308
+ end
309
+
310
+ def kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, first
311
+ result = []
312
+ kwargs = convert_hash(argchain.node)
313
+ pin.parameters[first..-1].each_with_index do |par, cur|
314
+ idx = first + cur
315
+ argchain = kwargs[par.name.to_sym]
316
+ if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
317
+ result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
318
+ else
319
+ if argchain
320
+ data = params[par.name]
321
+ if data.nil?
322
+ # @todo Some level (strong, I guess) should require the param here
323
+ else
324
+ ptype = data[:qualified]
325
+ next if ptype.undefined?
326
+ argtype = argchain.infer(api_map, block_pin, locals)
327
+ if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
328
+ result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
329
+ end
330
+ end
331
+ elsif par.decl == :kwarg
332
+ result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
333
+ end
334
+ end
335
+ end
336
+ result
337
+ end
338
+
339
+ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
340
+ result = []
341
+ kwargs.each_pair do |pname, argchain|
342
+ next unless params.key?(pname.to_s)
343
+ ptype = params[pname.to_s][:qualified]
344
+ argtype = argchain.infer(api_map, block_pin, locals)
345
+ if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
346
+ result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
347
+ end
348
+ end
349
+ result
350
+ end
351
+
352
+ # @param [Pin::Method]
353
+ # @return [Hash]
354
+ def param_hash(pin)
355
+ tags = pin.docstring.tags(:param)
356
+ return {} if tags.empty?
357
+ result = {}
358
+ tags.each do |tag|
359
+ next if tag.types.nil? || tag.types.empty?
360
+ result[tag.name.to_s] = {
361
+ tagged: tag.types.join(', '),
362
+ qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
363
+ }
364
+ end
365
+ result
366
+ end
367
+
368
+ # @param [Array<Pin::Method>]
369
+ # @return [Hash]
370
+ def first_param_hash(pins)
371
+ pins.each do |pin|
372
+ result = param_hash(pin)
373
+ return result unless result.empty?
374
+ end
375
+ {}
376
+ end
377
+
378
+ # @param pin [Pin::Base]
379
+ def internal? pin
380
+ return false if pin.nil?
381
+ pin.location && api_map.bundled?(pin.location.filename)
382
+ end
383
+
384
+ def internal_or_core? pin
385
+ internal?(pin) || api_map.yard_map.core_pins.include?(pin) || api_map.yard_map.stdlib_pins.include?(pin)
386
+ end
387
+
388
+ # @param pin [Pin::Base]
389
+ def external? pin
390
+ !internal? pin
391
+ end
392
+
393
+ def declared_externally? pin
394
+ return true if pin.assignment.nil?
395
+ chain = Solargraph::Parser.chain(pin.assignment, filename)
396
+ rng = Solargraph::Range.from_node(pin.assignment)
397
+ block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
398
+ location = Location.new(filename, Range.from_node(pin.assignment))
399
+ locals = source_map.locals_at(location)
400
+ type = chain.infer(api_map, block_pin, locals)
401
+ if type.undefined? && !rules.ignore_all_undefined?
402
+ base = chain
403
+ missing = chain
404
+ found = nil
405
+ closest = ComplexType::UNDEFINED
406
+ until base.links.first.undefined?
407
+ found = base.define(api_map, block_pin, locals).first
408
+ break if found
409
+ missing = base
410
+ base = base.base
411
+ end
412
+ closest = found.typify(api_map) if found
413
+ if !found || closest.defined? || internal?(found)
414
+ return false
415
+ end
416
+ end
417
+ true
418
+ end
419
+
420
+ # @param pin [Pin::Method]
421
+ def arity_problems_for(pin, arguments, location)
422
+ ([pin] + pin.overloads).map do |p|
423
+ result = pin_arity_problems_for(p, arguments, location)
424
+ return [] if result.empty?
425
+ result
426
+ end.flatten.uniq(&:message)
427
+ end
428
+
429
+ # @param pin [Pin::Method]
430
+ def pin_arity_problems_for(pin, arguments, location)
431
+ return [] unless pin.explicit?
432
+ return [] if pin.parameters.empty? && arguments.empty?
433
+ if pin.parameters.empty?
434
+ # Functions tagged param_tuple accepts two arguments (e.g., Hash#[]=)
435
+ return [] if pin.docstring.tag(:param_tuple) && arguments.length == 2
436
+ return [] if arguments.length == 1 && arguments.last.links.last.is_a?(Source::Chain::BlockVariable)
437
+ return [Problem.new(location, "Too many arguments to #{pin.path}")]
438
+ end
439
+ unchecked = arguments.clone
440
+ add_params = 0
441
+ if unchecked.empty? && pin.parameters.any? { |param| param.decl == :kwarg }
442
+ return [Problem.new(location, "Missing keyword arguments to #{pin.path}")]
443
+ end
444
+ settled_kwargs = 0
445
+ unless unchecked.empty?
446
+ if any_splatted_call?(unchecked.map(&:node))
447
+ settled_kwargs = pin.parameters.count(&:keyword?)
448
+ else
449
+ kwargs = convert_hash(unchecked.last.node)
450
+ if pin.parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
451
+ if kwargs.empty?
452
+ add_params += 1
453
+ else
454
+ unchecked.pop
455
+ pin.parameters.each do |param|
456
+ next unless param.keyword?
457
+ if kwargs.key?(param.name.to_sym)
458
+ kwargs.delete param.name.to_sym
459
+ settled_kwargs += 1
460
+ elsif param.decl == :kwarg
461
+ return [] if arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash) && arguments.last.links.last.splatted?
462
+ return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
463
+ end
464
+ end
465
+ kwargs.clear if pin.parameters.any?(&:kwrestarg?)
466
+ unless kwargs.empty?
467
+ return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
468
+ end
469
+ end
470
+ end
471
+ end
472
+ end
473
+ req = required_param_count(pin)
474
+ if req + add_params < unchecked.length
475
+ return [] if pin.parameters.any?(&:rest?)
476
+ opt = optional_param_count(pin)
477
+ return [] if unchecked.length <= req + opt
478
+ if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
479
+ return []
480
+ end
481
+ if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
482
+ return []
483
+ end
484
+ return [] if arguments.length - req == pin.parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length
485
+ return [Problem.new(location, "Too many arguments to #{pin.path}")]
486
+ elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash)))
487
+ # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs.
488
+ # See https://github.com/castwide/solargraph/issues/418
489
+ unless arguments.empty? && pin.path == 'Kernel#raise'
490
+ return [Problem.new(location, "Not enough arguments to #{pin.path}")]
491
+ end
492
+ end
493
+ []
494
+ end
495
+
496
+ # @param pin [Pin::Method]
497
+ def required_param_count(pin)
498
+ pin.parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
499
+ end
500
+
501
+ # @param pin [Pin::Method]
502
+ def optional_param_count(pin)
503
+ count = 0
504
+ pin.parameters.each do |param|
505
+ next unless param.decl == :optarg
506
+ count += 1
507
+ end
508
+ count
509
+ end
510
+
511
+ def abstract? pin
512
+ pin.docstring.has_tag?(:abstract) ||
513
+ (pin.closure && pin.closure.docstring.has_tag?(:abstract))
514
+ end
515
+
516
+ def fake_args_for(pin)
517
+ args = []
518
+ with_opts = false
519
+ with_block = false
520
+ pin.parameters.each do |pin|
521
+ if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl)
522
+ with_opts = true
523
+ elsif pin.decl == :block
524
+ with_block = true
525
+ elsif pin.decl == :restarg
526
+ args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true)
527
+ else
528
+ args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)])
529
+ end
530
+ end
531
+ args.push Solargraph::Parser.chain_string('{}') if with_opts
532
+ args.push Solargraph::Parser.chain_string('&') if with_block
533
+ args
534
+ end
535
+
536
+ def without_ignored problems
537
+ problems.reject do |problem|
538
+ node = source_map.source.node_at(problem.location.range.start.line, problem.location.range.start.column)
539
+ source_map.source.comments_for(node)&.include?('@sg-ignore')
540
+ end
541
+ end
542
+ end
543
+ end