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