solargraph 0.45.0 → 0.46.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 +1115 -1109
  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 +18 -17
  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 -681
  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 -37
  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 +54 -44
  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 -64
  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 +72 -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 -203
  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 -0
  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 -36
  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 +226 -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 +522 -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 +212 -212
  220. data/lib/solargraph/source_map.rb +180 -189
  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 -529
  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 -214
  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 -445
  243. data/lib/solargraph.rb +69 -69
  244. data/lib/yard-solargraph.rb +33 -33
  245. data/solargraph.gemspec +0 -0
  246. metadata +13 -12
@@ -1,529 +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
- 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::Method]
77
- source_map.pins_by_class(Pin::Method).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::Method]
85
- # @return [Array<Problem>]
86
- def method_return_type_problems_for pin
87
- return [] if pin.is_a?(Pin::MethodAlias)
88
- result = []
89
- declared = pin.typify(api_map).self_to(pin.full_context.namespace)
90
- if declared.undefined?
91
- if pin.return_type.undefined? && rules.require_type_tags?
92
- result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin)
93
- elsif pin.return_type.defined? && !resolved_constant?(pin)
94
- result.push Problem.new(pin.location, "Unresolved return type #{pin.return_type} for #{pin.path}", pin: pin)
95
- elsif rules.must_tag_or_infer? && pin.probe(api_map).undefined?
96
- result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
97
- end
98
- elsif rules.validate_tags?
99
- unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
100
- inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
101
- if inferred.undefined?
102
- unless rules.ignore_all_undefined? || external?(pin)
103
- result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin)
104
- end
105
- else
106
- unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
107
- result.push Problem.new(pin.location, "Declared return type #{declared} does not match inferred type #{inferred} for #{pin.path}", pin: pin)
108
- end
109
- end
110
- end
111
- end
112
- result
113
- end
114
-
115
- # @todo This is not optimal. A better solution would probably be to mix
116
- # namespace alias into types at the ApiMap level.
117
- #
118
- # @param pin [Pin::Base]
119
- # @return [Boolean]
120
- def resolved_constant? pin
121
- api_map.get_constants('', pin.binder.tag).any? { |pin| pin.name == pin.return_type.namespace && ['Class', 'Module'].include?(pin.return_type.name) }
122
- end
123
-
124
- def virtual_pin? pin
125
- pin.location && source_map.source.comment_at?(pin.location.range.ending)
126
- end
127
-
128
- # @param pin [Pin::Method]
129
- # @return [Array<Problem>]
130
- def method_param_type_problems_for pin
131
- stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
132
- params = first_param_hash(stack)
133
- result = []
134
- if rules.require_type_tags?
135
- pin.parameters.each do |par|
136
- break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
137
- unless params[par.name]
138
- result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
139
- end
140
- end
141
- end
142
- params.each_pair do |name, data|
143
- type = data[:qualified]
144
- if type.undefined?
145
- result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
146
- end
147
- end
148
- result
149
- end
150
-
151
- def ignored_pins
152
- @ignored_pins ||= []
153
- end
154
-
155
- # @return [Array<Problem>]
156
- def variable_type_tag_problems
157
- result = []
158
- all_variables.each do |pin|
159
- if pin.return_type.defined?
160
- declared = pin.typify(api_map)
161
- next if declared.duck_type?
162
- if declared.defined?
163
- if rules.validate_tags?
164
- inferred = pin.probe(api_map)
165
- if inferred.undefined?
166
- next if rules.ignore_all_undefined?
167
- if declared_externally?(pin)
168
- ignored_pins.push pin
169
- else
170
- result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin)
171
- end
172
- else
173
- unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
174
- result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin)
175
- end
176
- end
177
- elsif declared_externally?(pin)
178
- ignored_pins.push pin
179
- end
180
- elsif !pin.is_a?(Pin::Parameter)
181
- result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
182
- end
183
- else
184
- inferred = pin.probe(api_map)
185
- if inferred.undefined? && declared_externally?(pin)
186
- ignored_pins.push pin
187
- end
188
- end
189
- end
190
- result
191
- end
192
-
193
- # @return [Array<Pin::BaseVariable>]
194
- def all_variables
195
- source_map.pins_by_class(Pin::BaseVariable) + source_map.locals.select { |pin| pin.is_a?(Pin::LocalVariable) }
196
- end
197
-
198
- def const_problems
199
- return [] unless rules.validate_consts?
200
- result = []
201
- Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
202
- rng = Solargraph::Range.from_node(const)
203
- chain = Solargraph::Parser.chain(const, filename)
204
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
205
- location = Location.new(filename, rng)
206
- locals = source_map.locals_at(location)
207
- pins = chain.define(api_map, block_pin, locals)
208
- if pins.empty?
209
- result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
210
- @marked_ranges.push location.range
211
- end
212
- end
213
- result
214
- end
215
-
216
- def call_problems
217
- result = []
218
- Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
219
- rng = Solargraph::Range.from_node(call)
220
- next if @marked_ranges.any? { |d| d.contain?(rng.start) }
221
- chain = Solargraph::Parser.chain(call, filename)
222
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
223
- location = Location.new(filename, rng)
224
- locals = source_map.locals_at(location)
225
- type = chain.infer(api_map, block_pin, locals)
226
- if type.undefined? && !rules.ignore_all_undefined?
227
- base = chain
228
- missing = chain
229
- found = nil
230
- closest = ComplexType::UNDEFINED
231
- until base.links.first.undefined?
232
- found = base.define(api_map, block_pin, locals).first
233
- break if found
234
- missing = base
235
- base = base.base
236
- end
237
- closest = found.typify(api_map) if found
238
- if !found || (closest.defined? && internal_or_core?(found))
239
- unless ignored_pins.include?(found)
240
- result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
241
- @marked_ranges.push rng
242
- end
243
- end
244
- end
245
- result.concat argument_problems_for(chain, api_map, block_pin, locals, location)
246
- end
247
- result
248
- end
249
-
250
- def argument_problems_for chain, api_map, block_pin, locals, location
251
- result = []
252
- base = chain
253
- until base.links.length == 1 && base.undefined?
254
- pins = base.define(api_map, block_pin, locals)
255
- if pins.first.is_a?(Pin::Method)
256
- # @type [Pin::Method]
257
- pin = pins.first
258
- ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
259
- arity_problems_for(pin, fake_args_for(block_pin), location)
260
- else
261
- arity_problems_for(pin, base.links.last.arguments, location)
262
- end
263
- unless ap.empty?
264
- result.concat ap
265
- break
266
- end
267
- break unless rules.validate_calls?
268
- params = first_param_hash(pins)
269
- pin.parameters.each_with_index do |par, idx|
270
- argchain = base.links.last.arguments[idx]
271
- if argchain.nil? && par.decl == :arg
272
- result.push Problem.new(location, "Not enough arguments to #{pin.path}")
273
- break
274
- end
275
- if argchain
276
- if par.decl != :arg
277
- result.concat kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, idx
278
- break
279
- else
280
- ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
281
- if ptype.nil?
282
- # @todo Some level (strong, I guess) should require the param here
283
- else
284
- argtype = argchain.infer(api_map, block_pin, locals)
285
- if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
286
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
287
- end
288
- end
289
- end
290
- elsif par.rest?
291
- next
292
- elsif par.decl == :kwarg
293
- result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
294
- break
295
- end
296
- end
297
- end
298
- base = base.base
299
- end
300
- result
301
- end
302
-
303
- def kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, first
304
- result = []
305
- kwargs = convert_hash(argchain.node)
306
- pin.parameters[first..-1].each_with_index do |par, cur|
307
- idx = first + cur
308
- argchain = kwargs[par.name.to_sym]
309
- if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
310
- result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
311
- else
312
- if argchain
313
- data = params[par.name]
314
- if data.nil?
315
- # @todo Some level (strong, I guess) should require the param here
316
- else
317
- ptype = data[:qualified]
318
- next if ptype.undefined?
319
- argtype = argchain.infer(api_map, block_pin, locals)
320
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
321
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
322
- end
323
- end
324
- elsif par.decl == :kwarg
325
- result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
326
- end
327
- end
328
- end
329
- result
330
- end
331
-
332
- def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
333
- result = []
334
- kwargs.each_pair do |pname, argchain|
335
- next unless params.key?(pname.to_s)
336
- ptype = params[pname.to_s][:qualified]
337
- argtype = argchain.infer(api_map, block_pin, locals)
338
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
339
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
340
- end
341
- end
342
- result
343
- end
344
-
345
- # @param [Pin::Method]
346
- # @return [Hash]
347
- def param_hash(pin)
348
- tags = pin.docstring.tags(:param)
349
- return {} if tags.empty?
350
- result = {}
351
- tags.each do |tag|
352
- next if tag.types.nil? || tag.types.empty?
353
- result[tag.name.to_s] = {
354
- tagged: tag.types.join(', '),
355
- qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
356
- }
357
- end
358
- result
359
- end
360
-
361
- # @param [Array<Pin::Method>]
362
- # @return [Hash]
363
- def first_param_hash(pins)
364
- pins.each do |pin|
365
- result = param_hash(pin)
366
- return result unless result.empty?
367
- end
368
- {}
369
- end
370
-
371
- # @param pin [Pin::Base]
372
- def internal? pin
373
- return false if pin.nil?
374
- pin.location && api_map.bundled?(pin.location.filename)
375
- end
376
-
377
- def internal_or_core? pin
378
- internal?(pin) || api_map.yard_map.core_pins.include?(pin) || api_map.yard_map.stdlib_pins.include?(pin)
379
- end
380
-
381
- # @param pin [Pin::Base]
382
- def external? pin
383
- !internal? pin
384
- end
385
-
386
- def declared_externally? pin
387
- return true if pin.assignment.nil?
388
- chain = Solargraph::Parser.chain(pin.assignment, filename)
389
- rng = Solargraph::Range.from_node(pin.assignment)
390
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
391
- location = Location.new(filename, Range.from_node(pin.assignment))
392
- locals = source_map.locals_at(location)
393
- type = chain.infer(api_map, block_pin, locals)
394
- if type.undefined? && !rules.ignore_all_undefined?
395
- base = chain
396
- missing = chain
397
- found = nil
398
- closest = ComplexType::UNDEFINED
399
- until base.links.first.undefined?
400
- found = base.define(api_map, block_pin, locals).first
401
- break if found
402
- missing = base
403
- base = base.base
404
- end
405
- closest = found.typify(api_map) if found
406
- if !found || closest.defined? || internal?(found)
407
- return false
408
- end
409
- end
410
- true
411
- end
412
-
413
- # @param pin [Pin::Method]
414
- def arity_problems_for(pin, arguments, location)
415
- ([pin] + pin.overloads).map do |p|
416
- result = pin_arity_problems_for(p, arguments, location)
417
- return [] if result.empty?
418
- result
419
- end.flatten.uniq(&:message)
420
- end
421
-
422
- # @param pin [Pin::Method]
423
- def pin_arity_problems_for(pin, arguments, location)
424
- return [] unless pin.explicit?
425
- return [] if pin.parameters.empty? && arguments.empty?
426
- if pin.parameters.empty?
427
- # Functions tagged param_tuple accepts two arguments (e.g., Hash#[]=)
428
- return [] if pin.docstring.tag(:param_tuple) && arguments.length == 2
429
- return [] if arguments.length == 1 && arguments.last.links.last.is_a?(Source::Chain::BlockVariable)
430
- return [Problem.new(location, "Too many arguments to #{pin.path}")]
431
- end
432
- unchecked = arguments.clone
433
- add_params = 0
434
- if unchecked.empty? && pin.parameters.any? { |param| param.decl == :kwarg }
435
- return [Problem.new(location, "Missing keyword arguments to #{pin.path}")]
436
- end
437
- settled_kwargs = 0
438
- unless unchecked.empty?
439
- if any_splatted_call?(unchecked.map(&:node))
440
- settled_kwargs = pin.parameters.count(&:keyword?)
441
- else
442
- kwargs = convert_hash(unchecked.last.node)
443
- if pin.parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
444
- if kwargs.empty?
445
- add_params += 1
446
- else
447
- unchecked.pop
448
- pin.parameters.each do |param|
449
- next unless param.keyword?
450
- if kwargs.key?(param.name.to_sym)
451
- kwargs.delete param.name.to_sym
452
- settled_kwargs += 1
453
- elsif param.decl == :kwarg
454
- return [] if arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash) && arguments.last.links.last.splatted?
455
- return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
456
- end
457
- end
458
- kwargs.clear if pin.parameters.any?(&:kwrestarg?)
459
- unless kwargs.empty?
460
- return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
461
- end
462
- end
463
- end
464
- end
465
- end
466
- req = required_param_count(pin)
467
- if req + add_params < unchecked.length
468
- return [] if pin.parameters.any?(&:rest?)
469
- opt = optional_param_count(pin)
470
- return [] if unchecked.length <= req + opt
471
- if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
472
- return []
473
- end
474
- if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
475
- return []
476
- end
477
- return [] if arguments.length - req == pin.parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length
478
- return [Problem.new(location, "Too many arguments to #{pin.path}")]
479
- elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash)))
480
- # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs.
481
- # See https://github.com/castwide/solargraph/issues/418
482
- unless arguments.empty? && pin.path == 'Kernel#raise'
483
- return [Problem.new(location, "Not enough arguments to #{pin.path}")]
484
- end
485
- end
486
- []
487
- end
488
-
489
- # @param pin [Pin::Method]
490
- def required_param_count(pin)
491
- pin.parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
492
- end
493
-
494
- # @param pin [Pin::Method]
495
- def optional_param_count(pin)
496
- count = 0
497
- pin.parameters.each do |param|
498
- next unless param.decl == :optarg
499
- count += 1
500
- end
501
- count
502
- end
503
-
504
- def abstract? pin
505
- pin.docstring.has_tag?(:abstract) ||
506
- (pin.closure && pin.closure.docstring.has_tag?(:abstract))
507
- end
508
-
509
- def fake_args_for(pin)
510
- args = []
511
- with_opts = false
512
- with_block = false
513
- pin.parameters.each do |pin|
514
- if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl)
515
- with_opts = true
516
- elsif pin.decl == :block
517
- with_block = true
518
- elsif pin.decl == :restarg
519
- args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true)
520
- else
521
- args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)])
522
- end
523
- end
524
- args.push Solargraph::Parser.chain_string('{}') if with_opts
525
- args.push Solargraph::Parser.chain_string('&') if with_block
526
- args
527
- end
528
- end
529
- 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