solargraph 0.44.2 → 0.46.0

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 (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