solargraph 0.46.0 → 0.54.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/plugins.yml +40 -0
  4. data/.github/workflows/rspec.yml +37 -41
  5. data/.github/workflows/typecheck.yml +34 -0
  6. data/.gitignore +9 -9
  7. data/.rspec +2 -2
  8. data/.yardopts +2 -2
  9. data/CHANGELOG.md +1338 -1115
  10. data/Gemfile +0 -0
  11. data/LICENSE +1 -1
  12. data/README.md +131 -128
  13. data/Rakefile +0 -0
  14. data/SPONSORS.md +10 -18
  15. data/bin/solargraph +0 -0
  16. data/lib/solargraph/api_map/cache.rb +109 -70
  17. data/lib/solargraph/api_map/index.rb +167 -0
  18. data/lib/solargraph/api_map/source_to_yard.rb +88 -81
  19. data/lib/solargraph/api_map/store.rb +260 -256
  20. data/lib/solargraph/api_map.rb +870 -686
  21. data/lib/solargraph/bench.rb +44 -27
  22. data/lib/solargraph/cache.rb +77 -0
  23. data/lib/solargraph/complex_type/type_methods.rb +217 -130
  24. data/lib/solargraph/complex_type/unique_type.rb +386 -75
  25. data/lib/solargraph/complex_type.rb +394 -221
  26. data/lib/solargraph/convention/base.rb +33 -33
  27. data/lib/solargraph/convention/gemfile.rb +15 -15
  28. data/lib/solargraph/convention/gemspec.rb +22 -22
  29. data/lib/solargraph/convention/rakefile.rb +17 -0
  30. data/lib/solargraph/convention.rb +47 -47
  31. data/lib/solargraph/converters/dd.rb +17 -12
  32. data/lib/solargraph/converters/dl.rb +15 -12
  33. data/lib/solargraph/converters/dt.rb +15 -12
  34. data/lib/solargraph/converters/misc.rb +1 -1
  35. data/lib/solargraph/diagnostics/base.rb +29 -29
  36. data/lib/solargraph/diagnostics/require_not_found.rb +53 -53
  37. data/lib/solargraph/diagnostics/rubocop.rb +113 -98
  38. data/lib/solargraph/diagnostics/rubocop_helpers.rb +66 -63
  39. data/lib/solargraph/diagnostics/severities.rb +15 -15
  40. data/lib/solargraph/diagnostics/type_check.rb +55 -54
  41. data/lib/solargraph/diagnostics/update_errors.rb +41 -41
  42. data/lib/solargraph/diagnostics.rb +55 -55
  43. data/lib/solargraph/doc_map.rb +188 -0
  44. data/lib/solargraph/environ.rb +45 -45
  45. data/lib/solargraph/equality.rb +33 -0
  46. data/lib/solargraph/gem_pins.rb +72 -0
  47. data/lib/solargraph/language_server/completion_item_kinds.rb +35 -35
  48. data/lib/solargraph/language_server/error_codes.rb +20 -20
  49. data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
  50. data/lib/solargraph/language_server/host/dispatch.rb +128 -111
  51. data/lib/solargraph/language_server/host/message_worker.rb +106 -59
  52. data/lib/solargraph/language_server/host/sources.rb +99 -156
  53. data/lib/solargraph/language_server/host.rb +861 -865
  54. data/lib/solargraph/language_server/message/base.rb +96 -89
  55. data/lib/solargraph/language_server/message/cancel_request.rb +13 -13
  56. data/lib/solargraph/language_server/message/client/register_capability.rb +15 -15
  57. data/lib/solargraph/language_server/message/client.rb +11 -11
  58. data/lib/solargraph/language_server/message/completion_item/resolve.rb +60 -58
  59. data/lib/solargraph/language_server/message/completion_item.rb +11 -11
  60. data/lib/solargraph/language_server/message/exit_notification.rb +13 -13
  61. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +112 -100
  62. data/lib/solargraph/language_server/message/extended/document.rb +20 -20
  63. data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -32
  64. data/lib/solargraph/language_server/message/extended/download_core.rb +19 -23
  65. data/lib/solargraph/language_server/message/extended/environment.rb +25 -25
  66. data/lib/solargraph/language_server/message/extended/search.rb +20 -20
  67. data/lib/solargraph/language_server/message/extended.rb +21 -21
  68. data/lib/solargraph/language_server/message/initialize.rb +191 -162
  69. data/lib/solargraph/language_server/message/initialized.rb +28 -27
  70. data/lib/solargraph/language_server/message/method_not_found.rb +16 -16
  71. data/lib/solargraph/language_server/message/method_not_implemented.rb +14 -14
  72. data/lib/solargraph/language_server/message/shutdown.rb +13 -13
  73. data/lib/solargraph/language_server/message/text_document/base.rb +19 -19
  74. data/lib/solargraph/language_server/message/text_document/code_action.rb +17 -17
  75. data/lib/solargraph/language_server/message/text_document/completion.rb +56 -59
  76. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -38
  77. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -15
  78. data/lib/solargraph/language_server/message/text_document/did_close.rb +15 -15
  79. data/lib/solargraph/language_server/message/text_document/did_open.rb +15 -15
  80. data/lib/solargraph/language_server/message/text_document/did_save.rb +17 -17
  81. data/lib/solargraph/language_server/message/text_document/document_highlight.rb +16 -16
  82. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +26 -23
  83. data/lib/solargraph/language_server/message/text_document/folding_range.rb +26 -26
  84. data/lib/solargraph/language_server/message/text_document/formatting.rb +131 -126
  85. data/lib/solargraph/language_server/message/text_document/hover.rb +58 -54
  86. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +34 -34
  87. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +11 -11
  88. data/lib/solargraph/language_server/message/text_document/references.rb +16 -16
  89. data/lib/solargraph/language_server/message/text_document/rename.rb +19 -19
  90. data/lib/solargraph/language_server/message/text_document/signature_help.rb +24 -29
  91. data/lib/solargraph/language_server/message/text_document/type_definition.rb +24 -0
  92. data/lib/solargraph/language_server/message/text_document.rb +28 -28
  93. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +35 -30
  94. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +40 -33
  95. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
  96. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -23
  97. data/lib/solargraph/language_server/message/workspace.rb +14 -14
  98. data/lib/solargraph/language_server/message.rb +94 -93
  99. data/lib/solargraph/language_server/message_types.rb +14 -14
  100. data/lib/solargraph/language_server/progress.rb +135 -0
  101. data/lib/solargraph/language_server/request.rb +24 -24
  102. data/lib/solargraph/language_server/symbol_kinds.rb +36 -36
  103. data/lib/solargraph/language_server/transport/adapter.rb +68 -53
  104. data/lib/solargraph/language_server/transport/data_reader.rb +74 -72
  105. data/lib/solargraph/language_server/transport.rb +13 -13
  106. data/lib/solargraph/language_server/uri_helpers.rb +49 -49
  107. data/lib/solargraph/language_server.rb +20 -19
  108. data/lib/solargraph/library.rb +663 -546
  109. data/lib/solargraph/location.rb +58 -37
  110. data/lib/solargraph/logging.rb +27 -27
  111. data/lib/solargraph/page.rb +89 -83
  112. data/lib/solargraph/parser/comment_ripper.rb +56 -52
  113. data/lib/solargraph/parser/node_methods.rb +83 -43
  114. data/lib/solargraph/parser/node_processor/base.rb +87 -77
  115. data/lib/solargraph/parser/node_processor.rb +45 -43
  116. data/lib/solargraph/parser/{legacy → parser_gem}/class_methods.rb +153 -135
  117. data/lib/solargraph/parser/{legacy → parser_gem}/flawed_builder.rb +18 -16
  118. data/lib/solargraph/parser/{legacy → parser_gem}/node_chainer.rb +164 -148
  119. data/lib/solargraph/parser/parser_gem/node_methods.rb +495 -0
  120. data/lib/solargraph/parser/{rubyvm → parser_gem}/node_processors/alias_node.rb +23 -23
  121. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +57 -0
  122. data/lib/solargraph/parser/{rubyvm → parser_gem}/node_processors/begin_node.rb +15 -15
  123. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/block_node.rb +43 -42
  124. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/casgn_node.rb +35 -25
  125. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/cvasgn_node.rb +23 -23
  126. data/lib/solargraph/parser/{rubyvm → parser_gem}/node_processors/def_node.rb +50 -63
  127. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/defs_node.rb +36 -36
  128. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/gvasgn_node.rb +23 -23
  129. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/ivasgn_node.rb +38 -38
  130. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/lvasgn_node.rb +28 -28
  131. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +53 -0
  132. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/namespace_node.rb +39 -39
  133. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/orasgn_node.rb +16 -16
  134. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/resbody_node.rb +36 -36
  135. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +42 -0
  136. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/send_node.rb +259 -257
  137. data/lib/solargraph/parser/{legacy → parser_gem}/node_processors/sym_node.rb +18 -18
  138. data/lib/solargraph/parser/parser_gem/node_processors.rb +56 -0
  139. data/lib/solargraph/parser/parser_gem.rb +12 -0
  140. data/lib/solargraph/parser/region.rb +66 -66
  141. data/lib/solargraph/parser/snippet.rb +15 -13
  142. data/lib/solargraph/parser.rb +22 -26
  143. data/lib/solargraph/pin/base.rb +378 -296
  144. data/lib/solargraph/pin/base_variable.rb +118 -84
  145. data/lib/solargraph/pin/block.rb +101 -72
  146. data/lib/solargraph/pin/callable.rb +147 -0
  147. data/lib/solargraph/pin/class_variable.rb +8 -8
  148. data/lib/solargraph/pin/closure.rb +57 -37
  149. data/lib/solargraph/pin/common.rb +70 -70
  150. data/lib/solargraph/pin/constant.rb +43 -43
  151. data/lib/solargraph/pin/conversions.rb +123 -96
  152. data/lib/solargraph/pin/delegated_method.rb +101 -0
  153. data/lib/solargraph/pin/documenting.rb +98 -105
  154. data/lib/solargraph/pin/duck_method.rb +16 -16
  155. data/lib/solargraph/pin/global_variable.rb +8 -8
  156. data/lib/solargraph/pin/instance_variable.rb +34 -30
  157. data/lib/solargraph/pin/keyword.rb +15 -15
  158. data/lib/solargraph/pin/keyword_param.rb +8 -8
  159. data/lib/solargraph/pin/local_variable.rb +67 -55
  160. data/lib/solargraph/pin/method.rb +527 -245
  161. data/lib/solargraph/pin/method_alias.rb +31 -31
  162. data/lib/solargraph/pin/namespace.rb +107 -91
  163. data/lib/solargraph/pin/parameter.rb +212 -201
  164. data/lib/solargraph/pin/proxy_type.rb +29 -29
  165. data/lib/solargraph/pin/reference/extend.rb +10 -10
  166. data/lib/solargraph/pin/reference/include.rb +10 -10
  167. data/lib/solargraph/pin/reference/override.rb +29 -29
  168. data/lib/solargraph/pin/reference/prepend.rb +10 -10
  169. data/lib/solargraph/pin/reference/require.rb +14 -14
  170. data/lib/solargraph/pin/reference/superclass.rb +10 -10
  171. data/lib/solargraph/pin/reference.rb +22 -14
  172. data/lib/solargraph/pin/search.rb +56 -56
  173. data/lib/solargraph/pin/signature.rb +17 -0
  174. data/lib/solargraph/pin/singleton.rb +11 -11
  175. data/lib/solargraph/pin/symbol.rb +47 -47
  176. data/lib/solargraph/pin.rb +41 -37
  177. data/lib/solargraph/position.rb +107 -100
  178. data/lib/solargraph/range.rb +98 -95
  179. data/lib/solargraph/rbs_map/conversions.rb +646 -0
  180. data/lib/solargraph/rbs_map/core_fills.rb +50 -0
  181. data/lib/solargraph/rbs_map/core_map.rb +28 -0
  182. data/lib/solargraph/rbs_map/stdlib_map.rb +33 -0
  183. data/lib/solargraph/rbs_map.rb +93 -0
  184. data/lib/solargraph/server_methods.rb +16 -16
  185. data/lib/solargraph/shell.rb +269 -226
  186. data/lib/solargraph/source/chain/array.rb +33 -0
  187. data/lib/solargraph/source/chain/block_symbol.rb +13 -0
  188. data/lib/solargraph/source/chain/block_variable.rb +13 -13
  189. data/lib/solargraph/source/chain/call.rb +303 -204
  190. data/lib/solargraph/source/chain/class_variable.rb +13 -13
  191. data/lib/solargraph/source/chain/constant.rb +89 -75
  192. data/lib/solargraph/source/chain/global_variable.rb +13 -13
  193. data/lib/solargraph/source/chain/hash.rb +33 -28
  194. data/lib/solargraph/source/chain/head.rb +19 -19
  195. data/lib/solargraph/source/chain/if.rb +28 -0
  196. data/lib/solargraph/source/chain/instance_variable.rb +13 -13
  197. data/lib/solargraph/source/chain/link.rb +98 -71
  198. data/lib/solargraph/source/chain/literal.rb +28 -23
  199. data/lib/solargraph/source/chain/or.rb +23 -23
  200. data/lib/solargraph/source/chain/q_call.rb +11 -11
  201. data/lib/solargraph/source/chain/variable.rb +13 -13
  202. data/lib/solargraph/source/chain/z_super.rb +30 -30
  203. data/lib/solargraph/source/chain.rb +252 -164
  204. data/lib/solargraph/source/change.rb +82 -79
  205. data/lib/solargraph/source/cursor.rb +167 -164
  206. data/lib/solargraph/source/source_chainer.rb +194 -191
  207. data/lib/solargraph/source/updater.rb +55 -54
  208. data/lib/solargraph/source.rb +495 -522
  209. data/lib/solargraph/source_map/clip.rb +232 -224
  210. data/lib/solargraph/source_map/completion.rb +23 -23
  211. data/lib/solargraph/source_map/data.rb +30 -0
  212. data/lib/solargraph/source_map/mapper.rb +255 -212
  213. data/lib/solargraph/source_map.rb +217 -180
  214. data/lib/solargraph/type_checker/checks.rb +120 -99
  215. data/lib/solargraph/type_checker/param_def.rb +35 -35
  216. data/lib/solargraph/type_checker/problem.rb +32 -32
  217. data/lib/solargraph/type_checker/rules.rb +62 -57
  218. data/lib/solargraph/type_checker.rb +672 -543
  219. data/lib/solargraph/version.rb +5 -5
  220. data/lib/solargraph/views/environment.erb +56 -58
  221. data/lib/solargraph/workspace/config.rb +239 -231
  222. data/lib/solargraph/workspace.rb +239 -215
  223. data/lib/solargraph/yard_map/cache.rb +25 -19
  224. data/lib/solargraph/yard_map/helpers.rb +16 -16
  225. data/lib/solargraph/yard_map/mapper/to_constant.rb +26 -25
  226. data/lib/solargraph/yard_map/mapper/to_method.rb +94 -78
  227. data/lib/solargraph/yard_map/mapper/to_namespace.rb +28 -27
  228. data/lib/solargraph/yard_map/mapper.rb +78 -77
  229. data/lib/solargraph/yard_map/to_method.rb +86 -79
  230. data/lib/solargraph/yard_map.rb +18 -460
  231. data/lib/solargraph/yard_tags.rb +20 -0
  232. data/lib/solargraph/yardoc.rb +52 -0
  233. data/lib/solargraph.rb +72 -69
  234. data/solargraph.gemspec +21 -10
  235. metadata +184 -115
  236. data/.travis.yml +0 -19
  237. data/lib/solargraph/api_map/bundler_methods.rb +0 -22
  238. data/lib/solargraph/compat.rb +0 -37
  239. data/lib/solargraph/convention/rspec.rb +0 -30
  240. data/lib/solargraph/documentor.rb +0 -76
  241. data/lib/solargraph/language_server/host/cataloger.rb +0 -56
  242. data/lib/solargraph/parser/legacy/node_methods.rb +0 -325
  243. data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +0 -23
  244. data/lib/solargraph/parser/legacy/node_processors/args_node.rb +0 -35
  245. data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +0 -15
  246. data/lib/solargraph/parser/legacy/node_processors/def_node.rb +0 -63
  247. data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +0 -21
  248. data/lib/solargraph/parser/legacy/node_processors.rb +0 -54
  249. data/lib/solargraph/parser/legacy.rb +0 -12
  250. data/lib/solargraph/parser/rubyvm/class_methods.rb +0 -144
  251. data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -160
  252. data/lib/solargraph/parser/rubyvm/node_methods.rb +0 -315
  253. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +0 -85
  254. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +0 -42
  255. data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +0 -22
  256. data/lib/solargraph/parser/rubyvm/node_processors/cvasgn_node.rb +0 -23
  257. data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +0 -57
  258. data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +0 -23
  259. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +0 -38
  260. data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +0 -39
  261. data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +0 -20
  262. data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +0 -27
  263. data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +0 -39
  264. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +0 -26
  265. data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +0 -15
  266. data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +0 -45
  267. data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +0 -21
  268. data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +0 -15
  269. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +0 -277
  270. data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +0 -18
  271. data/lib/solargraph/parser/rubyvm/node_processors.rb +0 -63
  272. data/lib/solargraph/parser/rubyvm.rb +0 -40
  273. data/lib/solargraph/yard_map/core_docs.rb +0 -170
  274. data/lib/solargraph/yard_map/core_fills.rb +0 -208
  275. data/lib/solargraph/yard_map/core_gen.rb +0 -76
  276. data/lib/solargraph/yard_map/rdoc_to_yard.rb +0 -140
  277. data/lib/solargraph/yard_map/stdlib_fills.rb +0 -43
  278. data/lib/yard-solargraph.rb +0 -33
  279. data/yardoc/2.2.2.tar.gz +0 -0
@@ -1,543 +1,672 @@
1
- # frozen_string_literal: true
2
-
3
- module Solargraph
4
- # A static analysis tool for validating data types.
5
- #
6
- class TypeChecker
7
- autoload :Problem, 'solargraph/type_checker/problem'
8
- autoload :ParamDef, 'solargraph/type_checker/param_def'
9
- autoload :Rules, 'solargraph/type_checker/rules'
10
- autoload :Checks, 'solargraph/type_checker/checks'
11
-
12
- include Checks
13
- include Parser::NodeMethods
14
-
15
- # @return [String]
16
- attr_reader :filename
17
-
18
- # @return [Rules]
19
- attr_reader :rules
20
-
21
- # @return [ApiMap]
22
- attr_reader :api_map
23
-
24
- # @param filename [String]
25
- # @param api_map [ApiMap]
26
- # @param level [Symbol]
27
- def initialize filename, api_map: nil, level: :normal
28
- @filename = filename
29
- # @todo Smarter directory resolution
30
- @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename))
31
- @rules = Rules.new(level)
32
- @marked_ranges = []
33
- end
34
-
35
- # @return [SourceMap]
36
- def source_map
37
- @source_map ||= api_map.source_map(filename)
38
- end
39
-
40
- # @return [Array<Problem>]
41
- def problems
42
- @problems ||= begin
43
- without_ignored(
44
- method_tag_problems
45
- .concat variable_type_tag_problems
46
- .concat const_problems
47
- .concat call_problems
48
- )
49
- end
50
- end
51
-
52
- class << self
53
- # @param filename [String]
54
- # @return [self]
55
- def load filename, level = :normal
56
- source = Solargraph::Source.load(filename)
57
- api_map = Solargraph::ApiMap.new
58
- api_map.map(source)
59
- new(filename, api_map: api_map, level: level)
60
- end
61
-
62
- # @param code [String]
63
- # @param filename [String, nil]
64
- # @return [self]
65
- def load_string code, filename = nil, level = :normal
66
- source = Solargraph::Source.load_string(code, filename)
67
- api_map = Solargraph::ApiMap.new
68
- api_map.map(source)
69
- new(filename, api_map: api_map, level: level)
70
- end
71
- end
72
-
73
- private
74
-
75
- # @return [Array<Problem>]
76
- def method_tag_problems
77
- result = []
78
- # @param pin [Pin::Method]
79
- source_map.pins_by_class(Pin::Method).each do |pin|
80
- result.concat method_return_type_problems_for(pin)
81
- result.concat method_param_type_problems_for(pin)
82
- end
83
- result
84
- end
85
-
86
- # @param pin [Pin::Method]
87
- # @return [Array<Problem>]
88
- def method_return_type_problems_for pin
89
- return [] if pin.is_a?(Pin::MethodAlias)
90
- result = []
91
- declared = pin.typify(api_map).self_to(pin.full_context.namespace)
92
- if declared.undefined?
93
- if pin.return_type.undefined? && rules.require_type_tags?
94
- result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin)
95
- elsif pin.return_type.defined? && !resolved_constant?(pin)
96
- result.push Problem.new(pin.location, "Unresolved return type #{pin.return_type} for #{pin.path}", pin: pin)
97
- elsif rules.must_tag_or_infer? && pin.probe(api_map).undefined?
98
- result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
99
- end
100
- elsif rules.validate_tags?
101
- unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
102
- inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
103
- if inferred.undefined?
104
- unless rules.ignore_all_undefined? || external?(pin)
105
- result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin)
106
- end
107
- else
108
- unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
109
- result.push Problem.new(pin.location, "Declared return type #{declared} does not match inferred type #{inferred} for #{pin.path}", pin: pin)
110
- end
111
- end
112
- end
113
- end
114
- result
115
- end
116
-
117
- # @todo This is not optimal. A better solution would probably be to mix
118
- # namespace alias into types at the ApiMap level.
119
- #
120
- # @param pin [Pin::Base]
121
- # @return [Boolean]
122
- def resolved_constant? pin
123
- api_map.get_constants('', pin.binder.tag)
124
- .select { |p| p.name == pin.return_type.namespace }
125
- .any? do |p|
126
- inferred = p.infer(api_map)
127
- ['Class', 'Module'].include?(inferred.name)
128
- end
129
- end
130
-
131
- def virtual_pin? pin
132
- pin.location && source_map.source.comment_at?(pin.location.range.ending)
133
- end
134
-
135
- # @param pin [Pin::Method]
136
- # @return [Array<Problem>]
137
- def method_param_type_problems_for pin
138
- stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
139
- params = first_param_hash(stack)
140
- result = []
141
- if rules.require_type_tags?
142
- pin.parameters.each do |par|
143
- break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
144
- unless params[par.name]
145
- result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
146
- end
147
- end
148
- end
149
- params.each_pair do |name, data|
150
- type = data[:qualified]
151
- if type.undefined?
152
- result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
153
- end
154
- end
155
- result
156
- end
157
-
158
- def ignored_pins
159
- @ignored_pins ||= []
160
- end
161
-
162
- # @return [Array<Problem>]
163
- def variable_type_tag_problems
164
- result = []
165
- all_variables.each do |pin|
166
- if pin.return_type.defined?
167
- declared = pin.typify(api_map)
168
- next if declared.duck_type?
169
- if declared.defined?
170
- if rules.validate_tags?
171
- inferred = pin.probe(api_map)
172
- if inferred.undefined?
173
- next if rules.ignore_all_undefined?
174
- if declared_externally?(pin)
175
- ignored_pins.push pin
176
- else
177
- result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin)
178
- end
179
- else
180
- unless (rules.rank > 1 ? types_match?(api_map, declared, inferred) : any_types_match?(api_map, declared, inferred))
181
- result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin)
182
- end
183
- end
184
- elsif declared_externally?(pin)
185
- ignored_pins.push pin
186
- end
187
- elsif !pin.is_a?(Pin::Parameter)
188
- result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
189
- end
190
- else
191
- inferred = pin.probe(api_map)
192
- if inferred.undefined? && declared_externally?(pin)
193
- ignored_pins.push pin
194
- end
195
- end
196
- end
197
- result
198
- end
199
-
200
- # @return [Array<Pin::BaseVariable>]
201
- def all_variables
202
- source_map.pins_by_class(Pin::BaseVariable) + source_map.locals.select { |pin| pin.is_a?(Pin::LocalVariable) }
203
- end
204
-
205
- def const_problems
206
- return [] unless rules.validate_consts?
207
- result = []
208
- Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
209
- rng = Solargraph::Range.from_node(const)
210
- chain = Solargraph::Parser.chain(const, filename)
211
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
212
- location = Location.new(filename, rng)
213
- locals = source_map.locals_at(location)
214
- pins = chain.define(api_map, block_pin, locals)
215
- if pins.empty?
216
- result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
217
- @marked_ranges.push location.range
218
- end
219
- end
220
- result
221
- end
222
-
223
- def call_problems
224
- result = []
225
- Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
226
- rng = Solargraph::Range.from_node(call)
227
- next if @marked_ranges.any? { |d| d.contain?(rng.start) }
228
- chain = Solargraph::Parser.chain(call, filename)
229
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
230
- location = Location.new(filename, rng)
231
- locals = source_map.locals_at(location)
232
- type = chain.infer(api_map, block_pin, locals)
233
- if type.undefined? && !rules.ignore_all_undefined?
234
- base = chain
235
- missing = chain
236
- found = nil
237
- closest = ComplexType::UNDEFINED
238
- until base.links.first.undefined?
239
- found = base.define(api_map, block_pin, locals).first
240
- break if found
241
- missing = base
242
- base = base.base
243
- end
244
- closest = found.typify(api_map) if found
245
- if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
246
- unless ignored_pins.include?(found)
247
- result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
248
- @marked_ranges.push rng
249
- end
250
- end
251
- end
252
- result.concat argument_problems_for(chain, api_map, block_pin, locals, location)
253
- end
254
- result
255
- end
256
-
257
- def argument_problems_for chain, api_map, block_pin, locals, location
258
- result = []
259
- base = chain
260
- until base.links.length == 1 && base.undefined?
261
- pins = base.define(api_map, block_pin, locals)
262
- if pins.first.is_a?(Pin::Method)
263
- # @type [Pin::Method]
264
- pin = pins.first
265
- ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
266
- arity_problems_for(pin, fake_args_for(block_pin), location)
267
- else
268
- arity_problems_for(pin, base.links.last.arguments, location)
269
- end
270
- unless ap.empty?
271
- result.concat ap
272
- break
273
- end
274
- break unless rules.validate_calls?
275
- params = first_param_hash(pins)
276
- pin.parameters.each_with_index do |par, idx|
277
- argchain = base.links.last.arguments[idx]
278
- if argchain.nil? && par.decl == :arg
279
- result.push Problem.new(location, "Not enough arguments to #{pin.path}")
280
- break
281
- end
282
- if argchain
283
- if par.decl != :arg
284
- result.concat kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, idx
285
- break
286
- else
287
- ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
288
- if ptype.nil?
289
- # @todo Some level (strong, I guess) should require the param here
290
- else
291
- argtype = argchain.infer(api_map, block_pin, locals)
292
- if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
293
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
294
- end
295
- end
296
- end
297
- elsif par.rest?
298
- next
299
- elsif par.decl == :kwarg
300
- result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
301
- break
302
- end
303
- end
304
- end
305
- base = base.base
306
- end
307
- result
308
- end
309
-
310
- def kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, first
311
- result = []
312
- kwargs = convert_hash(argchain.node)
313
- pin.parameters[first..-1].each_with_index do |par, cur|
314
- idx = first + cur
315
- argchain = kwargs[par.name.to_sym]
316
- if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
317
- result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
318
- else
319
- if argchain
320
- data = params[par.name]
321
- if data.nil?
322
- # @todo Some level (strong, I guess) should require the param here
323
- else
324
- ptype = data[:qualified]
325
- next if ptype.undefined?
326
- argtype = argchain.infer(api_map, block_pin, locals)
327
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
328
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
329
- end
330
- end
331
- elsif par.decl == :kwarg
332
- result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
333
- end
334
- end
335
- end
336
- result
337
- end
338
-
339
- def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
340
- result = []
341
- kwargs.each_pair do |pname, argchain|
342
- next unless params.key?(pname.to_s)
343
- ptype = params[pname.to_s][:qualified]
344
- argtype = argchain.infer(api_map, block_pin, locals)
345
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
346
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
347
- end
348
- end
349
- result
350
- end
351
-
352
- # @param [Pin::Method]
353
- # @return [Hash]
354
- def param_hash(pin)
355
- tags = pin.docstring.tags(:param)
356
- return {} if tags.empty?
357
- result = {}
358
- tags.each do |tag|
359
- next if tag.types.nil? || tag.types.empty?
360
- result[tag.name.to_s] = {
361
- tagged: tag.types.join(', '),
362
- qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
363
- }
364
- end
365
- result
366
- end
367
-
368
- # @param [Array<Pin::Method>]
369
- # @return [Hash]
370
- def first_param_hash(pins)
371
- pins.each do |pin|
372
- result = param_hash(pin)
373
- return result unless result.empty?
374
- end
375
- {}
376
- end
377
-
378
- # @param pin [Pin::Base]
379
- def internal? pin
380
- return false if pin.nil?
381
- pin.location && api_map.bundled?(pin.location.filename)
382
- end
383
-
384
- def internal_or_core? pin
385
- internal?(pin) || api_map.yard_map.core_pins.include?(pin) || api_map.yard_map.stdlib_pins.include?(pin)
386
- end
387
-
388
- # @param pin [Pin::Base]
389
- def external? pin
390
- !internal? pin
391
- end
392
-
393
- def declared_externally? pin
394
- return true if pin.assignment.nil?
395
- chain = Solargraph::Parser.chain(pin.assignment, filename)
396
- rng = Solargraph::Range.from_node(pin.assignment)
397
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
398
- location = Location.new(filename, Range.from_node(pin.assignment))
399
- locals = source_map.locals_at(location)
400
- type = chain.infer(api_map, block_pin, locals)
401
- if type.undefined? && !rules.ignore_all_undefined?
402
- base = chain
403
- missing = chain
404
- found = nil
405
- closest = ComplexType::UNDEFINED
406
- until base.links.first.undefined?
407
- found = base.define(api_map, block_pin, locals).first
408
- break if found
409
- missing = base
410
- base = base.base
411
- end
412
- closest = found.typify(api_map) if found
413
- if !found || closest.defined? || internal?(found)
414
- return false
415
- end
416
- end
417
- true
418
- end
419
-
420
- # @param pin [Pin::Method]
421
- def arity_problems_for(pin, arguments, location)
422
- ([pin] + pin.overloads).map do |p|
423
- result = pin_arity_problems_for(p, arguments, location)
424
- return [] if result.empty?
425
- result
426
- end.flatten.uniq(&:message)
427
- end
428
-
429
- # @param pin [Pin::Method]
430
- def pin_arity_problems_for(pin, arguments, location)
431
- return [] unless pin.explicit?
432
- return [] if pin.parameters.empty? && arguments.empty?
433
- if pin.parameters.empty?
434
- # Functions tagged param_tuple accepts two arguments (e.g., Hash#[]=)
435
- return [] if pin.docstring.tag(:param_tuple) && arguments.length == 2
436
- return [] if arguments.length == 1 && arguments.last.links.last.is_a?(Source::Chain::BlockVariable)
437
- return [Problem.new(location, "Too many arguments to #{pin.path}")]
438
- end
439
- unchecked = arguments.clone
440
- add_params = 0
441
- if unchecked.empty? && pin.parameters.any? { |param| param.decl == :kwarg }
442
- return [Problem.new(location, "Missing keyword arguments to #{pin.path}")]
443
- end
444
- settled_kwargs = 0
445
- unless unchecked.empty?
446
- if any_splatted_call?(unchecked.map(&:node))
447
- settled_kwargs = pin.parameters.count(&:keyword?)
448
- else
449
- kwargs = convert_hash(unchecked.last.node)
450
- if pin.parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
451
- if kwargs.empty?
452
- add_params += 1
453
- else
454
- unchecked.pop
455
- pin.parameters.each do |param|
456
- next unless param.keyword?
457
- if kwargs.key?(param.name.to_sym)
458
- kwargs.delete param.name.to_sym
459
- settled_kwargs += 1
460
- elsif param.decl == :kwarg
461
- return [] if arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash) && arguments.last.links.last.splatted?
462
- return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
463
- end
464
- end
465
- kwargs.clear if pin.parameters.any?(&:kwrestarg?)
466
- unless kwargs.empty?
467
- return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
468
- end
469
- end
470
- end
471
- end
472
- end
473
- req = required_param_count(pin)
474
- if req + add_params < unchecked.length
475
- return [] if pin.parameters.any?(&:rest?)
476
- opt = optional_param_count(pin)
477
- return [] if unchecked.length <= req + opt
478
- if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
479
- return []
480
- end
481
- if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
482
- return []
483
- end
484
- return [] if arguments.length - req == pin.parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length
485
- return [Problem.new(location, "Too many arguments to #{pin.path}")]
486
- elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash)))
487
- # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs.
488
- # See https://github.com/castwide/solargraph/issues/418
489
- unless arguments.empty? && pin.path == 'Kernel#raise'
490
- return [Problem.new(location, "Not enough arguments to #{pin.path}")]
491
- end
492
- end
493
- []
494
- end
495
-
496
- # @param pin [Pin::Method]
497
- def required_param_count(pin)
498
- pin.parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
499
- end
500
-
501
- # @param pin [Pin::Method]
502
- def optional_param_count(pin)
503
- count = 0
504
- pin.parameters.each do |param|
505
- next unless param.decl == :optarg
506
- count += 1
507
- end
508
- count
509
- end
510
-
511
- def abstract? pin
512
- pin.docstring.has_tag?(:abstract) ||
513
- (pin.closure && pin.closure.docstring.has_tag?(:abstract))
514
- end
515
-
516
- def fake_args_for(pin)
517
- args = []
518
- with_opts = false
519
- with_block = false
520
- pin.parameters.each do |pin|
521
- if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl)
522
- with_opts = true
523
- elsif pin.decl == :block
524
- with_block = true
525
- elsif pin.decl == :restarg
526
- args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true)
527
- else
528
- args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)])
529
- end
530
- end
531
- args.push Solargraph::Parser.chain_string('{}') if with_opts
532
- args.push Solargraph::Parser.chain_string('&') if with_block
533
- args
534
- end
535
-
536
- def without_ignored problems
537
- problems.reject do |problem|
538
- node = source_map.source.node_at(problem.location.range.start.line, problem.location.range.start.column)
539
- source_map.source.comments_for(node)&.include?('@sg-ignore')
540
- end
541
- end
542
- end
543
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ # A static analysis tool for validating data types.
5
+ #
6
+ class TypeChecker
7
+ autoload :Problem, 'solargraph/type_checker/problem'
8
+ autoload :ParamDef, 'solargraph/type_checker/param_def'
9
+ autoload :Rules, 'solargraph/type_checker/rules'
10
+ autoload :Checks, 'solargraph/type_checker/checks'
11
+
12
+ include Checks
13
+ include Parser::NodeMethods
14
+
15
+ # @return [String]
16
+ attr_reader :filename
17
+
18
+ # @return [Rules]
19
+ attr_reader :rules
20
+
21
+ # @return [ApiMap]
22
+ attr_reader :api_map
23
+
24
+ # @param filename [String]
25
+ # @param api_map [ApiMap, nil]
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
+ # @type [Array<Range>]
33
+ @marked_ranges = []
34
+ end
35
+
36
+ # @return [SourceMap]
37
+ def source_map
38
+ @source_map ||= api_map.source_map(filename)
39
+ end
40
+
41
+ # @return [Array<Problem>]
42
+ def problems
43
+ @problems ||= begin
44
+ without_ignored(
45
+ method_tag_problems
46
+ .concat variable_type_tag_problems
47
+ .concat const_problems
48
+ .concat call_problems
49
+ )
50
+ end
51
+ end
52
+
53
+ class << self
54
+ # @param filename [String]
55
+ # @param level [Symbol]
56
+ # @return [self]
57
+ def load filename, level = :normal
58
+ source = Solargraph::Source.load(filename)
59
+ api_map = Solargraph::ApiMap.new
60
+ api_map.map(source)
61
+ new(filename, api_map: api_map, level: level)
62
+ end
63
+
64
+ # @param code [String]
65
+ # @param filename [String, nil]
66
+ # @param level [Symbol]
67
+ # @return [self]
68
+ def load_string code, filename = nil, level = :normal
69
+ source = Solargraph::Source.load_string(code, filename)
70
+ api_map = Solargraph::ApiMap.new
71
+ api_map.map(source)
72
+ new(filename, api_map: api_map, level: level)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ # @return [Array<Problem>]
79
+ def method_tag_problems
80
+ result = []
81
+ # @param pin [Pin::Method]
82
+ source_map.pins_by_class(Pin::Method).each do |pin|
83
+ result.concat method_return_type_problems_for(pin)
84
+ result.concat method_param_type_problems_for(pin)
85
+ end
86
+ result
87
+ end
88
+
89
+ # @param pin [Pin::Method]
90
+ # @return [Array<Problem>]
91
+ def method_return_type_problems_for pin
92
+ return [] if pin.is_a?(Pin::MethodAlias)
93
+ result = []
94
+ declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, pin.full_context.tag)
95
+ if declared.undefined?
96
+ if pin.return_type.undefined? && rules.require_type_tags?
97
+ if pin.attribute?
98
+ inferred = pin.probe(api_map).self_to_type(pin.full_context)
99
+ result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin) unless inferred.defined?
100
+ else
101
+ result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin)
102
+ end
103
+ elsif pin.return_type.defined? && !resolved_constant?(pin)
104
+ result.push Problem.new(pin.location, "Unresolved return type #{pin.return_type} for #{pin.path}", pin: pin)
105
+ elsif rules.must_tag_or_infer? && pin.probe(api_map).undefined?
106
+ result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
107
+ end
108
+ elsif rules.validate_tags?
109
+ unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
110
+ inferred = pin.probe(api_map).self_to_type(pin.full_context)
111
+ if inferred.undefined?
112
+ unless rules.ignore_all_undefined? || external?(pin)
113
+ result.push Problem.new(pin.location, "#{pin.path} return type could not be inferred", pin: pin)
114
+ end
115
+ else
116
+ unless (rules.require_all_return_types_match_inferred? ? all_types_match?(api_map, inferred, declared) : any_types_match?(api_map, declared, inferred))
117
+ result.push Problem.new(pin.location, "Declared return type #{declared.rooted_tags} does not match inferred type #{inferred.rooted_tags} for #{pin.path}", pin: pin)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ result
123
+ end
124
+
125
+ # @todo This is not optimal. A better solution would probably be to mix
126
+ # namespace alias into types at the ApiMap level.
127
+ #
128
+ # @param pin [Pin::Base]
129
+ # @return [Boolean]
130
+ def resolved_constant? pin
131
+ return true if pin.typify(api_map).defined?
132
+ constant_pins = api_map.get_constants('', *pin.closure.gates)
133
+ .select { |p| p.name == pin.return_type.namespace }
134
+ return true if constant_pins.find { |p| p.typify(api_map).defined? }
135
+ # will need to probe when a constant name is assigned to a
136
+ # class/module (alias)
137
+ return true if constant_pins.find { |p| p.probe(api_map).defined? }
138
+ false
139
+ end
140
+
141
+ # @param pin [Pin::Base]
142
+ def virtual_pin? pin
143
+ pin.location && source_map.source.comment_at?(pin.location.range.ending)
144
+ end
145
+
146
+ # @param pin [Pin::Method]
147
+ # @return [Array<Problem>]
148
+ def method_param_type_problems_for pin
149
+ stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
150
+ params = first_param_hash(stack)
151
+ result = []
152
+ if rules.require_type_tags?
153
+ pin.signatures.each do |sig|
154
+ sig.parameters.each do |par|
155
+ break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
156
+ unless params[par.name]
157
+ if pin.attribute?
158
+ inferred = pin.probe(api_map).self_to_type(pin.full_context)
159
+ if inferred.undefined?
160
+ result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
161
+ end
162
+ else
163
+ result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ # @todo Should be able to probe type of name and data here
170
+ # @param name [String]
171
+ # @param data [Hash{Symbol => BasicObject}]
172
+ params.each_pair do |name, data|
173
+ # @type [ComplexType]
174
+ type = data[:qualified]
175
+ if type.undefined?
176
+ result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
177
+ end
178
+ end
179
+ result
180
+ end
181
+
182
+ # @return [Array<Pin::Base>]
183
+ def ignored_pins
184
+ @ignored_pins ||= []
185
+ end
186
+
187
+ # @return [Array<Problem>]
188
+ def variable_type_tag_problems
189
+ result = []
190
+ all_variables.each do |pin|
191
+ if pin.return_type.defined?
192
+ declared = pin.typify(api_map)
193
+ next if declared.duck_type?
194
+ if declared.defined?
195
+ if rules.validate_tags?
196
+ inferred = pin.probe(api_map)
197
+ if inferred.undefined?
198
+ next if rules.ignore_all_undefined?
199
+ if declared_externally?(pin)
200
+ ignored_pins.push pin
201
+ else
202
+ result.push Problem.new(pin.location, "Variable type could not be inferred for #{pin.name}", pin: pin)
203
+ end
204
+ else
205
+ unless any_types_match?(api_map, declared, inferred)
206
+ result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin)
207
+ end
208
+ end
209
+ elsif declared_externally?(pin)
210
+ ignored_pins.push pin
211
+ end
212
+ elsif !pin.is_a?(Pin::Parameter) && !resolved_constant?(pin)
213
+ result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
214
+ end
215
+ else
216
+ inferred = pin.probe(api_map)
217
+ if inferred.undefined? && declared_externally?(pin)
218
+ ignored_pins.push pin
219
+ end
220
+ end
221
+ end
222
+ result
223
+ end
224
+
225
+ # @return [Array<Pin::BaseVariable>]
226
+ def all_variables
227
+ source_map.pins_by_class(Pin::BaseVariable) + source_map.locals.select { |pin| pin.is_a?(Pin::LocalVariable) }
228
+ end
229
+
230
+ # @return [Array<Problem>]
231
+ def const_problems
232
+ return [] unless rules.validate_consts?
233
+ result = []
234
+ Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
235
+ rng = Solargraph::Range.from_node(const)
236
+ chain = Solargraph::Parser.chain(const, filename)
237
+ block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
238
+ location = Location.new(filename, rng)
239
+ locals = source_map.locals_at(location)
240
+ pins = chain.define(api_map, block_pin, locals)
241
+ if pins.empty?
242
+ result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
243
+ @marked_ranges.push location.range
244
+ end
245
+ end
246
+ result
247
+ end
248
+
249
+ # @return [Array<Problem>]
250
+ def call_problems
251
+ result = []
252
+ Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
253
+ rng = Solargraph::Range.from_node(call)
254
+ next if @marked_ranges.any? { |d| d.contain?(rng.start) }
255
+ chain = Solargraph::Parser.chain(call, filename)
256
+ block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
257
+ location = Location.new(filename, rng)
258
+ locals = source_map.locals_at(location)
259
+ type = chain.infer(api_map, block_pin, locals)
260
+ if type.undefined? && !rules.ignore_all_undefined?
261
+ base = chain
262
+ missing = chain
263
+ found = nil
264
+ closest = ComplexType::UNDEFINED
265
+ until base.links.first.undefined?
266
+ found = base.define(api_map, block_pin, locals).first
267
+ break if found
268
+ missing = base
269
+ base = base.base
270
+ end
271
+ closest = found.typify(api_map) if found
272
+ # @todo remove the internal_or_core? check at a higher-than-strict level
273
+ if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
274
+ unless closest.generic? || ignored_pins.include?(found)
275
+ result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
276
+ @marked_ranges.push rng
277
+ end
278
+ end
279
+ end
280
+ result.concat argument_problems_for(chain, api_map, block_pin, locals, location)
281
+ end
282
+ result
283
+ end
284
+
285
+ # @param chain [Solargraph::Source::Chain]
286
+ # @param api_map [Solargraph::ApiMap]
287
+ # @param block_pin [Solargraph::Pin::Base]
288
+ # @param locals [Array<Solargraph::Pin::Base>]
289
+ # @param location [Solargraph::Location]
290
+ # @return [Array<Problem>]
291
+ def argument_problems_for chain, api_map, block_pin, locals, location
292
+ result = []
293
+ base = chain
294
+ until base.links.length == 1 && base.undefined?
295
+ last_base_link = base.links.last
296
+ break unless last_base_link.is_a?(Solargraph::Source::Chain::Call)
297
+
298
+ arguments = last_base_link.arguments
299
+
300
+ pins = base.define(api_map, block_pin, locals)
301
+
302
+ first_pin = pins.first
303
+ if first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map)
304
+ # Do nothing, as we can't find the actual method implementation
305
+ elsif first_pin.is_a?(Pin::Method)
306
+ # @type [Pin::Method]
307
+ pin = first_pin
308
+ ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
309
+ arity_problems_for(pin, fake_args_for(block_pin), location)
310
+ elsif pin.path == 'Class#new'
311
+ fqns = if base.links.one?
312
+ block_pin.namespace
313
+ else
314
+ base.base.infer(api_map, block_pin, locals).namespace
315
+ end
316
+ init = api_map.get_method_stack(fqns, 'initialize').first
317
+ init ? arity_problems_for(init, arguments, location) : []
318
+ else
319
+ arity_problems_for(pin, arguments, location)
320
+ end
321
+ unless ap.empty?
322
+ result.concat ap
323
+ break
324
+ end
325
+ break if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper)
326
+
327
+ params = first_param_hash(pins)
328
+
329
+ all_errors = []
330
+ pin.signatures.sort { |sig| sig.parameters.length }.each do |sig|
331
+ errors = []
332
+ sig.parameters.each_with_index do |par, idx|
333
+ # @todo add logic mapping up restarg parameters with
334
+ # arguments (including restarg arguments). Use tuples
335
+ # when possible, and when not, ensure provably
336
+ # incorrect situations are detected.
337
+ break if par.decl == :restarg # bail out pending better arg processing
338
+ argchain = arguments[idx]
339
+ if argchain.nil?
340
+ if par.decl == :arg
341
+ final_arg = arguments.last
342
+ if final_arg && final_arg.node.type == :splat
343
+ argchain = final_arg
344
+ next # don't try to apply the type of the splat - unlikely to be specific enough
345
+ else
346
+ errors.push Problem.new(location, "Not enough arguments to #{pin.path}")
347
+ next
348
+ end
349
+ else
350
+ final_arg = arguments.last
351
+ argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type)
352
+ end
353
+ end
354
+ if argchain
355
+ if par.decl != :arg
356
+ errors.concat kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, params, idx
357
+ next
358
+ else
359
+ if argchain.node.type == :splat && argchain == arguments.last
360
+ final_arg = argchain
361
+ end
362
+ if (final_arg && final_arg.node.type == :splat)
363
+ # The final argument given has been seen and was a
364
+ # splat, which doesn't give us useful types or
365
+ # arities against positional parameters, so let's
366
+ # continue on in case there are any required
367
+ # kwargs we should warn about
368
+ next
369
+ end
370
+
371
+ if argchain.node.type == :splat && par != sig.parameters.last
372
+ # we have been given a splat and there are more
373
+ # arguments to come.
374
+
375
+ # @todo Improve this so that we can skip past the
376
+ # rest of the positional parameters here but still
377
+ # process the kwargs
378
+ break
379
+ end
380
+ ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
381
+ ptype = ptype.self_to_type(par.context)
382
+ if ptype.nil?
383
+ # @todo Some level (strong, I guess) should require the param here
384
+ else
385
+ argtype = argchain.infer(api_map, block_pin, locals)
386
+ if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
387
+ errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
388
+ next
389
+ end
390
+ end
391
+ end
392
+ elsif par.decl == :kwarg
393
+ errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
394
+ next
395
+ end
396
+ end
397
+ if errors.empty?
398
+ all_errors.clear
399
+ break
400
+ end
401
+ all_errors.concat errors
402
+ end
403
+ result.concat all_errors
404
+ end
405
+ base = base.base
406
+ end
407
+ result
408
+ end
409
+
410
+ # @param sig [Pin::Signature]
411
+ # @param argchain [Source::Chain]
412
+ # @param api_map [ApiMap]
413
+ # @param block_pin [Pin::Block]
414
+ # @param locals [Array<Pin::LocalVariable>]
415
+ # @param location [Location]
416
+ # @param pin [Pin::Method]
417
+ # @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}]
418
+ # @param idx [Integer]
419
+ #
420
+ # @return [Array<Problem>]
421
+ def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, params, idx
422
+ result = []
423
+ kwargs = convert_hash(argchain.node)
424
+ par = sig.parameters[idx]
425
+ argchain = kwargs[par.name.to_sym]
426
+ if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
427
+ result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
428
+ else
429
+ if argchain
430
+ data = params[par.name]
431
+ if data.nil?
432
+ # @todo Some level (strong, I guess) should require the param here
433
+ else
434
+ ptype = data[:qualified]
435
+ unless ptype.undefined?
436
+ argtype = argchain.infer(api_map, block_pin, locals)
437
+ if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
438
+ result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
439
+ end
440
+ end
441
+ end
442
+ elsif par.decl == :kwarg
443
+ result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
444
+ end
445
+ end
446
+ result
447
+ end
448
+
449
+ # @param api_map [ApiMap]
450
+ # @param block_pin [Pin::Block]
451
+ # @param locals [Array<Pin::LocalVariable>]
452
+ # @param location [Location]
453
+ # @param pin [Pin::Method]
454
+ # @param params [Hash{String => [nil, Hash]}]
455
+ # @param kwargs [Hash{Symbol => Source::Chain}]
456
+ # @return [Array<Problem>]
457
+ def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
458
+ result = []
459
+ kwargs.each_pair do |pname, argchain|
460
+ next unless params.key?(pname.to_s)
461
+ ptype = params[pname.to_s][:qualified]
462
+ argtype = argchain.infer(api_map, block_pin, locals)
463
+ if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
464
+ result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
465
+ end
466
+ end
467
+ result
468
+ end
469
+
470
+ # @param pin [Pin::Method]
471
+ # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
472
+ def param_hash(pin)
473
+ tags = pin.docstring.tags(:param)
474
+ return {} if tags.empty?
475
+ # @type [Hash{String => Hash{Symbol => String, ComplexType}}]
476
+ result = {}
477
+ tags.each do |tag|
478
+ next if tag.types.nil? || tag.types.empty?
479
+ result[tag.name.to_s] = {
480
+ tagged: tag.types.join(', '),
481
+ qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
482
+ }
483
+ end
484
+ result
485
+ end
486
+
487
+ # @param pins [Array<Pin::Method>]
488
+ # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
489
+ def first_param_hash(pins)
490
+ pins.each do |pin|
491
+ result = param_hash(pin)
492
+ return result unless result.empty?
493
+ end
494
+ {}
495
+ end
496
+
497
+ # @param pin [Pin::Base]
498
+ def internal? pin
499
+ return false if pin.nil?
500
+ pin.location && api_map.bundled?(pin.location.filename)
501
+ end
502
+
503
+ # True if the pin is either internal (part of the workspace) or from the core/stdlib
504
+ # @param pin [Pin::Base]
505
+ def internal_or_core? pin
506
+ # @todo RBS pins are not necessarily core/stdlib pins
507
+ internal?(pin) || pin.source == :rbs
508
+ end
509
+
510
+ # @param pin [Pin::Base]
511
+ def external? pin
512
+ !internal? pin
513
+ end
514
+
515
+ # @param pin [Pin::BaseVariable]
516
+ def declared_externally? pin
517
+ return true if pin.assignment.nil?
518
+ chain = Solargraph::Parser.chain(pin.assignment, filename)
519
+ rng = Solargraph::Range.from_node(pin.assignment)
520
+ block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
521
+ location = Location.new(filename, Range.from_node(pin.assignment))
522
+ locals = source_map.locals_at(location)
523
+ type = chain.infer(api_map, block_pin, locals)
524
+ if type.undefined? && !rules.ignore_all_undefined?
525
+ base = chain
526
+ missing = chain
527
+ found = nil
528
+ closest = ComplexType::UNDEFINED
529
+ until base.links.first.undefined?
530
+ found = base.define(api_map, block_pin, locals).first
531
+ break if found
532
+ missing = base
533
+ base = base.base
534
+ end
535
+ closest = found.typify(api_map) if found
536
+ if !found || closest.defined? || internal?(found)
537
+ return false
538
+ end
539
+ end
540
+ true
541
+ end
542
+
543
+ # @param pin [Pin::Method]
544
+ # @param arguments [Array<Source::Chain>]
545
+ # @param location [Location]
546
+ # @return [Array<Problem>]
547
+ def arity_problems_for pin, arguments, location
548
+ results = pin.signatures.map do |sig|
549
+ r = parameterized_arity_problems_for(pin, sig.parameters, arguments, location)
550
+ return [] if r.empty?
551
+ r
552
+ end
553
+ results.first
554
+ end
555
+
556
+ # @param pin [Pin::Method]
557
+ # @param parameters [Array<Pin::Parameter>]
558
+ # @param arguments [Array<Source::Chain>]
559
+ # @param location [Location]
560
+ # @return [Array<Problem>]
561
+ def parameterized_arity_problems_for(pin, parameters, arguments, location)
562
+ return [] unless pin.explicit?
563
+ return [] if parameters.empty? && arguments.empty?
564
+ return [] if pin.anon_splat?
565
+ unchecked = arguments.dup # creates copy of and unthaws array
566
+ add_params = 0
567
+ if unchecked.empty? && parameters.any? { |param| param.decl == :kwarg }
568
+ return [Problem.new(location, "Missing keyword arguments to #{pin.path}")]
569
+ end
570
+ settled_kwargs = 0
571
+ unless unchecked.empty?
572
+ if any_splatted_call?(unchecked.map(&:node))
573
+ settled_kwargs = parameters.count(&:keyword?)
574
+ else
575
+ kwargs = convert_hash(unchecked.last.node)
576
+ if parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
577
+ if kwargs.empty?
578
+ add_params += 1
579
+ else
580
+ unchecked.pop
581
+ parameters.each do |param|
582
+ next unless param.keyword?
583
+ if kwargs.key?(param.name.to_sym)
584
+ kwargs.delete param.name.to_sym
585
+ settled_kwargs += 1
586
+ elsif param.decl == :kwarg
587
+ return [] if arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash) && arguments.last.links.last.splatted?
588
+ return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
589
+ end
590
+ end
591
+ kwargs.clear if parameters.any?(&:kwrestarg?)
592
+ unless kwargs.empty?
593
+ return [Problem.new(location, "Unrecognized keyword argument #{kwargs.keys.first} to #{pin.path}")]
594
+ end
595
+ end
596
+ end
597
+ end
598
+ end
599
+ req = required_param_count(parameters)
600
+ if req + add_params < unchecked.length
601
+ return [] if parameters.any?(&:rest?)
602
+ opt = optional_param_count(parameters)
603
+ return [] if unchecked.length <= req + opt
604
+ if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
605
+ return []
606
+ end
607
+ return [] if arguments.length - req == parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length
608
+ return [Problem.new(location, "Too many arguments to #{pin.path}")]
609
+ elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash)))
610
+ # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs.
611
+ # See https://github.com/castwide/solargraph/issues/418
612
+ unless arguments.empty? && pin.path == 'Kernel#raise'
613
+ return [Problem.new(location, "Not enough arguments to #{pin.path}")]
614
+ end
615
+ end
616
+ []
617
+ end
618
+
619
+ # @param parameters [Enumerable<Pin::Parameter>]
620
+ # @todo need to use generic types in method to choose correct
621
+ # signature and generate Integer as return type
622
+ # @sg-ignore
623
+ # @return [Integer]
624
+ def required_param_count(parameters)
625
+ parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
626
+ end
627
+
628
+ # @param parameters [Enumerable<Pin::Parameter>]
629
+ # @param pin [Pin::Method]
630
+ # @return [Integer]
631
+ def optional_param_count(parameters)
632
+ parameters.select { |p| p.decl == :optarg }.length
633
+ end
634
+
635
+ # @param pin [Pin::Method]
636
+ def abstract? pin
637
+ pin.docstring.has_tag?('abstract') ||
638
+ (pin.closure && pin.closure.docstring.has_tag?('abstract'))
639
+ end
640
+
641
+ # @param pin [Pin::Method]
642
+ # @return [Array<Source::Chain>]
643
+ def fake_args_for(pin)
644
+ args = []
645
+ with_opts = false
646
+ with_block = false
647
+ pin.parameters.each do |pin|
648
+ if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl)
649
+ with_opts = true
650
+ elsif pin.decl == :block
651
+ with_block = true
652
+ elsif pin.decl == :restarg
653
+ args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)], nil, true)
654
+ else
655
+ args.push Solargraph::Source::Chain.new([Solargraph::Source::Chain::Variable.new(pin.name)])
656
+ end
657
+ end
658
+ args.push Solargraph::Parser.chain_string('{}') if with_opts
659
+ args.push Solargraph::Parser.chain_string('&') if with_block
660
+ args
661
+ end
662
+
663
+ # @param problems [Array<Problem>]
664
+ # @return [Array<Problem>]
665
+ def without_ignored problems
666
+ problems.reject do |problem|
667
+ node = source_map.source.node_at(problem.location.range.start.line, problem.location.range.start.column)
668
+ node && source_map.source.comments_for(node)&.include?('@sg-ignore')
669
+ end
670
+ end
671
+ end
672
+ end