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,546 +1,663 @@
1
- # frozen_string_literal: true
2
-
3
- require 'pathname'
4
-
5
- module Solargraph
6
- # A Library handles coordination between a Workspace and an ApiMap.
7
- #
8
- class Library
9
- include Logging
10
-
11
- # @return [Solargraph::Workspace]
12
- attr_reader :workspace
13
-
14
- # @return [String, nil]
15
- attr_reader :name
16
-
17
- # @return [Source, nil]
18
- attr_reader :current
19
-
20
- # @param workspace [Solargraph::Workspace]
21
- # @param name [String, nil]
22
- def initialize workspace = Solargraph::Workspace.new, name = nil
23
- @workspace = workspace
24
- @name = name
25
- @synchronized = false
26
- end
27
-
28
- def inspect
29
- # Let's not deal with insane data dumps in spec failures
30
- to_s
31
- end
32
-
33
- # True if the ApiMap is up to date with the library's workspace and open
34
- # files.
35
- #
36
- # @return [Boolean]
37
- def synchronized?
38
- @synchronized
39
- end
40
-
41
- # Attach a source to the library.
42
- #
43
- # The attached source does not need to be a part of the workspace. The
44
- # library will include it in the ApiMap while it's attached. Only one
45
- # source can be attached to the library at a time.
46
- #
47
- # @param source [Source, nil]
48
- # @return [void]
49
- def attach source
50
- mutex.synchronize do
51
- if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
52
- source_map_hash.delete @current.filename
53
- source_map_external_require_hash.delete @current.filename
54
- @external_requires = nil
55
- @synchronized = false
56
- end
57
- @current = source
58
- maybe_map @current
59
- catalog_inlock
60
- end
61
- end
62
-
63
- # True if the specified file is currently attached.
64
- #
65
- # @param filename [String]
66
- # @return [Boolean]
67
- def attached? filename
68
- !@current.nil? && @current.filename == filename
69
- end
70
- alias open? attached?
71
-
72
- # Detach the specified file if it is currently attached to the library.
73
- #
74
- # @param filename [String]
75
- # @return [Boolean] True if the specified file was detached
76
- def detach filename
77
- return false if @current.nil? || @current.filename != filename
78
- attach nil
79
- true
80
- end
81
-
82
- # True if the specified file is included in the workspace (but not
83
- # necessarily open).
84
- #
85
- # @param filename [String]
86
- # @return [Boolean]
87
- def contain? filename
88
- workspace.has_file?(filename)
89
- end
90
-
91
- # Create a source to be added to the workspace. The file is ignored if it is
92
- # neither open in the library nor included in the workspace.
93
- #
94
- # @param filename [String]
95
- # @param text [String] The contents of the file
96
- # @return [Boolean] True if the file was added to the workspace.
97
- def create filename, text
98
- result = false
99
- mutex.synchronize do
100
- next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
101
- @synchronized = false
102
- source = Solargraph::Source.load_string(text, filename)
103
- workspace.merge(source)
104
- result = true
105
- end
106
- result
107
- end
108
-
109
- # Create a file source from a file on disk. The file is ignored if it is
110
- # neither open in the library nor included in the workspace.
111
- #
112
- # @param filename [String]
113
- # @return [Boolean] True if the file was added to the workspace.
114
- def create_from_disk filename
115
- result = false
116
- mutex.synchronize do
117
- next if File.directory?(filename) || !File.exist?(filename)
118
- next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
119
- source = Solargraph::Source.load_string(File.read(filename), filename)
120
- workspace.merge(source)
121
- maybe_map source
122
- result = true
123
- end
124
- result
125
- end
126
-
127
- # Delete a file from the library. Deleting a file will make it unavailable
128
- # for checkout and optionally remove it from the workspace unless the
129
- # workspace configuration determines that it should still exist.
130
- #
131
- # @param filename [String]
132
- # @return [Boolean] True if the file was deleted
133
- def delete filename
134
- detach filename
135
- result = false
136
- mutex.synchronize do
137
- result = workspace.remove(filename)
138
- @synchronized = !result if synchronized?
139
- end
140
- result
141
- end
142
-
143
- # Close a file in the library. Closing a file will make it unavailable for
144
- # checkout although it may still exist in the workspace.
145
- #
146
- # @param filename [String]
147
- # @return [void]
148
- def close filename
149
- mutex.synchronize do
150
- @synchronized = false
151
- @current = nil if @current && @current.filename == filename
152
- catalog
153
- end
154
- end
155
-
156
- # Get completion suggestions at the specified file and location.
157
- #
158
- # @param filename [String] The file to analyze
159
- # @param line [Integer] The zero-based line number
160
- # @param column [Integer] The zero-based column number
161
- # @return [SourceMap::Completion]
162
- # @todo Take a Location instead of filename/line/column
163
- def completions_at filename, line, column
164
- position = Position.new(line, column)
165
- cursor = Source::Cursor.new(read(filename), position)
166
- api_map.clip(cursor).complete
167
- rescue FileNotFoundError => e
168
- handle_file_not_found filename, e
169
- end
170
-
171
- # Get definition suggestions for the expression at the specified file and
172
- # location.
173
- #
174
- # @param filename [String] The file to analyze
175
- # @param line [Integer] The zero-based line number
176
- # @param column [Integer] The zero-based column number
177
- # @return [Array<Solargraph::Pin::Base>]
178
- # @todo Take filename/position instead of filename/line/column
179
- def definitions_at filename, line, column
180
- position = Position.new(line, column)
181
- cursor = Source::Cursor.new(read(filename), position)
182
- if cursor.comment?
183
- source = read(filename)
184
- offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column))
185
- lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i)
186
- rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i)
187
- if lft && rgt
188
- tag = (lft[1] + rgt[1]).sub(/:+$/, '')
189
- clip = api_map.clip(cursor)
190
- clip.translate tag
191
- else
192
- []
193
- end
194
- else
195
- api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
196
- end
197
- rescue FileNotFoundError => e
198
- handle_file_not_found(filename, e)
199
- end
200
-
201
- # Get signature suggestions for the method at the specified file and
202
- # location.
203
- #
204
- # @param filename [String] The file to analyze
205
- # @param line [Integer] The zero-based line number
206
- # @param column [Integer] The zero-based column number
207
- # @return [Array<Solargraph::Pin::Base>]
208
- # @todo Take filename/position instead of filename/line/column
209
- def signatures_at filename, line, column
210
- position = Position.new(line, column)
211
- cursor = Source::Cursor.new(read(filename), position)
212
- api_map.clip(cursor).signify
213
- end
214
-
215
- # @param filename [String]
216
- # @param line [Integer]
217
- # @param column [Integer]
218
- # @param strip [Boolean] Strip special characters from variable names
219
- # @param only [Boolean] Search for references in the current file only
220
- # @return [Array<Solargraph::Range>]
221
- # @todo Take a Location instead of filename/line/column
222
- def references_from filename, line, column, strip: false, only: false
223
- cursor = api_map.cursor_at(filename, Position.new(line, column))
224
- clip = api_map.clip(cursor)
225
- pin = clip.define.first
226
- return [] unless pin
227
- result = []
228
- files = if only
229
- [api_map.source_map(filename)]
230
- else
231
- (workspace.sources + (@current ? [@current] : []))
232
- end
233
- files.uniq(&:filename).each do |source|
234
- found = source.references(pin.name)
235
- found.select! do |loc|
236
- referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first
237
- referenced && referenced.path == pin.path
238
- end
239
- # HACK: for language clients that exclude special characters from the start of variable names
240
- if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
241
- found.map! do |loc|
242
- Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
243
- end
244
- end
245
- result.concat(found.sort do |a, b|
246
- a.range.start.line <=> b.range.start.line
247
- end)
248
- end
249
- result.uniq
250
- end
251
-
252
- # Get the pins at the specified location or nil if the pin does not exist.
253
- #
254
- # @param location [Location]
255
- # @return [Array<Solargraph::Pin::Base>]
256
- def locate_pins location
257
- api_map.locate_pins(location).map { |pin| pin.realize(api_map) }
258
- end
259
-
260
- def locate_ref location
261
- map = source_map_hash[location.filename]
262
- return if map.nil?
263
- pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first
264
- return nil if pin.nil?
265
- workspace.require_paths.each do |path|
266
- full = Pathname.new(path).join("#{pin.name}.rb").to_s
267
- next unless source_map_hash.key?(full)
268
- return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
269
- end
270
- api_map.yard_map.require_reference(pin.name)
271
- rescue FileNotFoundError
272
- nil
273
- end
274
-
275
- # Get an array of pins that match a path.
276
- #
277
- # @param path [String]
278
- # @return [Array<Solargraph::Pin::Base>]
279
- def get_path_pins path
280
- api_map.get_path_suggestions(path)
281
- end
282
-
283
- # @param query [String]
284
- # @return [Array<YARD::CodeObjects::Base>]
285
- def document query
286
- api_map.document query
287
- end
288
-
289
- # @param query [String]
290
- # @return [Array<String>]
291
- def search query
292
- api_map.search query
293
- end
294
-
295
- # Get an array of all symbols in the workspace that match the query.
296
- #
297
- # @param query [String]
298
- # @return [Array<Pin::Base>]
299
- def query_symbols query
300
- api_map.query_symbols query
301
- end
302
-
303
- # Get an array of document symbols.
304
- #
305
- # Document symbols are composed of namespace, method, and constant pins.
306
- # The results of this query are appropriate for building the response to a
307
- # textDocument/documentSymbol message in the language server protocol.
308
- #
309
- # @param filename [String]
310
- # @return [Array<Solargraph::Pin::Base>]
311
- def document_symbols filename
312
- api_map.document_symbols(filename)
313
- end
314
-
315
- # @param path [String]
316
- # @return [Array<Solargraph::Pin::Base>]
317
- def path_pins path
318
- api_map.get_path_suggestions(path)
319
- end
320
-
321
- def source_maps
322
- source_map_hash.values
323
- end
324
-
325
- # Get the current text of a file in the library.
326
- #
327
- # @param filename [String]
328
- # @return [String]
329
- def read_text filename
330
- source = read(filename)
331
- source.code
332
- end
333
-
334
- # Get diagnostics about a file.
335
- #
336
- # @param filename [String]
337
- # @return [Array<Hash>]
338
- def diagnose filename
339
- # @todo Only open files get diagnosed. Determine whether anything or
340
- # everything in the workspace should get diagnosed, or if there should
341
- # be an option to do so.
342
- #
343
- return [] unless open?(filename)
344
- result = []
345
- source = read(filename)
346
- catalog
347
- repargs = {}
348
- workspace.config.reporters.each do |line|
349
- if line == 'all!'
350
- Diagnostics.reporters.each do |reporter|
351
- repargs[reporter] ||= []
352
- end
353
- else
354
- args = line.split(':').map(&:strip)
355
- name = args.shift
356
- reporter = Diagnostics.reporter(name)
357
- raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil?
358
- repargs[reporter] ||= []
359
- repargs[reporter].concat args
360
- end
361
- end
362
- repargs.each_pair do |reporter, args|
363
- result.concat reporter.new(*args.uniq).diagnose(source, api_map)
364
- end
365
- result
366
- end
367
-
368
- # Update the ApiMap from the library's workspace and open files.
369
- #
370
- # @return [void]
371
- def catalog
372
- mutex.synchronize do
373
- catalog_inlock
374
- end
375
- end
376
-
377
- private def catalog_inlock
378
- return if synchronized?
379
- logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
380
- api_map.catalog bench
381
- @synchronized = true
382
- logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" if logger.info?
383
- end
384
-
385
- def bench
386
- Bench.new(
387
- source_maps: source_map_hash.values,
388
- workspace: workspace,
389
- external_requires: external_requires
390
- )
391
- end
392
-
393
- # Get an array of foldable ranges for the specified file.
394
- #
395
- # @deprecated The library should not need to handle folding ranges. The
396
- # source itself has all the information it needs.
397
- #
398
- # @param filename [String]
399
- # @return [Array<Range>]
400
- def folding_ranges filename
401
- read(filename).folding_ranges
402
- end
403
-
404
- # Create a library from a directory.
405
- #
406
- # @param directory [String] The path to be used for the workspace
407
- # @param name [String, nil]
408
- # @return [Solargraph::Library]
409
- def self.load directory = '', name = nil
410
- Solargraph::Library.new(Solargraph::Workspace.new(directory), name)
411
- end
412
-
413
- # Try to merge a source into the library's workspace. If the workspace is
414
- # not configured to include the source, it gets ignored.
415
- #
416
- # @param source [Source]
417
- # @return [Boolean] True if the source was merged into the workspace.
418
- def merge source
419
- Logging.logger.debug "Merging source: #{source.filename}"
420
- result = false
421
- mutex.synchronize do
422
- result = workspace.merge(source)
423
- maybe_map source
424
- end
425
- # catalog
426
- result
427
- end
428
-
429
- def source_map_hash
430
- @source_map_hash ||= {}
431
- end
432
-
433
- def mapped?
434
- (workspace.filenames - source_map_hash.keys).empty?
435
- end
436
-
437
- def next_map
438
- return false if mapped?
439
- mutex.synchronize do
440
- @synchronized = false
441
- src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
442
- if src
443
- Logging.logger.debug "Mapping #{src.filename}"
444
- source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
445
- find_external_requires(source_map_hash[src.filename])
446
- source_map_hash[src.filename]
447
- else
448
- false
449
- end
450
- end
451
- end
452
-
453
- def map!
454
- workspace.sources.each do |src|
455
- source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
456
- find_external_requires(source_map_hash[src.filename])
457
- end
458
- self
459
- end
460
-
461
- def pins
462
- @pins ||= []
463
- end
464
-
465
- def external_requires
466
- @external_requires ||= source_map_external_require_hash.values.flatten.to_set
467
- end
468
-
469
- private
470
-
471
- def source_map_external_require_hash
472
- @source_map_external_require_hash ||= {}
473
- end
474
-
475
- # @param source_map [SourceMap]
476
- def find_external_requires source_map
477
- new_set = source_map.requires.map(&:name).to_set
478
- # return if new_set == source_map_external_require_hash[source_map.filename]
479
- source_map_external_require_hash[source_map.filename] = new_set.reject do |path|
480
- workspace.require_paths.any? do |base|
481
- full = Pathname.new(base).join("#{path}.rb").to_s
482
- workspace.filenames.include?(full)
483
- end
484
- end
485
- @external_requires = nil
486
- end
487
-
488
- # @return [Mutex]
489
- def mutex
490
- @mutex ||= Mutex.new
491
- end
492
-
493
- # @return [ApiMap]
494
- def api_map
495
- @api_map ||= Solargraph::ApiMap.new
496
- end
497
-
498
- # Get the source for an open file or create a new source if the file
499
- # exists on disk. Sources created from disk are not added to the open
500
- # workspace files, i.e., the version on disk remains the authoritative
501
- # version.
502
- #
503
- # @raise [FileNotFoundError] if the file does not exist
504
- # @param filename [String]
505
- # @return [Solargraph::Source]
506
- def read filename
507
- return @current if @current && @current.filename == filename
508
- raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
509
- workspace.source(filename)
510
- end
511
-
512
- def handle_file_not_found filename, error
513
- if workspace.source(filename)
514
- Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap"
515
- nil
516
- else
517
- raise error
518
- end
519
- end
520
-
521
- def maybe_map source
522
- return unless source
523
- return unless @current == source || workspace.has_file?(source.filename)
524
- if source_map_hash.key?(source.filename)
525
- return if source_map_hash[source.filename].code == source.code &&
526
- source_map_hash[source.filename].source.synchronized? &&
527
- source.synchronized?
528
- if source.synchronized?
529
- new_map = Solargraph::SourceMap.map(source)
530
- unless source_map_hash[source.filename].try_merge!(new_map)
531
- source_map_hash[source.filename] = new_map
532
- find_external_requires(source_map_hash[source.filename])
533
- @synchronized = false
534
- end
535
- else
536
- # @todo Smelly instance variable access
537
- source_map_hash[source.filename].instance_variable_set(:@source, source)
538
- end
539
- else
540
- source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
541
- find_external_requires(source_map_hash[source.filename])
542
- @synchronized = false
543
- end
544
- end
545
- end
546
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'observer'
5
+
6
+ module Solargraph
7
+ # A Library handles coordination between a Workspace and an ApiMap.
8
+ #
9
+ class Library
10
+ include Logging
11
+ include Observable
12
+
13
+ # @return [Solargraph::Workspace]
14
+ attr_reader :workspace
15
+
16
+ # @return [String, nil]
17
+ attr_reader :name
18
+
19
+ # @return [Source, nil]
20
+ attr_reader :current
21
+
22
+ # @return [LanguageServer::Progress, nil]
23
+ attr_reader :cache_progress
24
+
25
+ # @param workspace [Solargraph::Workspace]
26
+ # @param name [String, nil]
27
+ def initialize workspace = Solargraph::Workspace.new, name = nil
28
+ @workspace = workspace
29
+ @name = name
30
+ # @type [Integer, nil]
31
+ @total = nil
32
+ # @type [Source, nil]
33
+ @current = nil
34
+ @sync_count = 0
35
+ end
36
+
37
+ def inspect
38
+ # Let's not deal with insane data dumps in spec failures
39
+ to_s
40
+ end
41
+
42
+ # True if the ApiMap is up to date with the library's workspace and open
43
+ # files.
44
+ #
45
+ # @return [Boolean]
46
+ def synchronized?
47
+ @sync_count < 2
48
+ end
49
+
50
+ # Attach a source to the library.
51
+ #
52
+ # The attached source does not need to be a part of the workspace. The
53
+ # library will include it in the ApiMap while it's attached. Only one
54
+ # source can be attached to the library at a time.
55
+ #
56
+ # @param source [Source, nil]
57
+ # @return [void]
58
+ def attach source
59
+ if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
60
+ source_map_hash.delete @current.filename
61
+ source_map_external_require_hash.delete @current.filename
62
+ @external_requires = nil
63
+ end
64
+ changed = source && @current != source
65
+ @current = source
66
+ maybe_map @current
67
+ catalog if changed
68
+ end
69
+
70
+ # True if the specified file is currently attached.
71
+ #
72
+ # @param filename [String]
73
+ # @return [Boolean]
74
+ def attached? filename
75
+ !@current.nil? && @current.filename == filename
76
+ end
77
+ alias open? attached?
78
+
79
+ # Detach the specified file if it is currently attached to the library.
80
+ #
81
+ # @param filename [String]
82
+ # @return [Boolean] True if the specified file was detached
83
+ def detach filename
84
+ return false if @current.nil? || @current.filename != filename
85
+ attach nil
86
+ true
87
+ end
88
+
89
+ # True if the specified file is included in the workspace (but not
90
+ # necessarily open).
91
+ #
92
+ # @param filename [String]
93
+ # @return [Boolean]
94
+ def contain? filename
95
+ workspace.has_file?(filename)
96
+ end
97
+
98
+ # Create a source to be added to the workspace. The file is ignored if it is
99
+ # neither open in the library nor included in the workspace.
100
+ #
101
+ # @param filename [String]
102
+ # @param text [String] The contents of the file
103
+ # @return [Boolean] True if the file was added to the workspace.
104
+ def create filename, text
105
+ return false unless contain?(filename) || open?(filename)
106
+ source = Solargraph::Source.load_string(text, filename)
107
+ workspace.merge(source)
108
+ true
109
+ end
110
+
111
+ # Create file sources from files on disk. A file is ignored if it is
112
+ # neither open in the library nor included in the workspace.
113
+ #
114
+ # @param filenames [Array<String>]
115
+ # @return [Boolean] True if at least one file was added to the workspace.
116
+ def create_from_disk *filenames
117
+ sources = filenames
118
+ .reject { |filename| File.directory?(filename) || !File.exist?(filename) }
119
+ .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) }
120
+ result = workspace.merge(*sources)
121
+ sources.each { |source| maybe_map source }
122
+ result
123
+ end
124
+
125
+ # Delete files from the library. Deleting a file will make it unavailable
126
+ # for checkout and optionally remove it from the workspace unless the
127
+ # workspace configuration determines that it should still exist.
128
+ #
129
+ # @param filenames [Array<String>]
130
+ # @return [Boolean] True if any file was deleted
131
+ def delete *filenames
132
+ result = false
133
+ filenames.each do |filename|
134
+ detach filename
135
+ source_map_hash.delete(filename)
136
+ result ||= workspace.remove(filename)
137
+ end
138
+ result
139
+ end
140
+
141
+ # Close a file in the library. Closing a file will make it unavailable for
142
+ # checkout although it may still exist in the workspace.
143
+ #
144
+ # @param filename [String]
145
+ # @return [void]
146
+ def close filename
147
+ return unless @current&.filename == filename
148
+
149
+ @current = nil
150
+ catalog unless workspace.has_file?(filename)
151
+ end
152
+
153
+ # Get completion suggestions at the specified file and location.
154
+ #
155
+ # @param filename [String] The file to analyze
156
+ # @param line [Integer] The zero-based line number
157
+ # @param column [Integer] The zero-based column number
158
+ # @return [SourceMap::Completion, nil]
159
+ # @todo Take a Location instead of filename/line/column
160
+ def completions_at filename, line, column
161
+ sync_catalog
162
+ position = Position.new(line, column)
163
+ cursor = Source::Cursor.new(read(filename), position)
164
+ mutex.synchronize { api_map.clip(cursor).complete }
165
+ rescue FileNotFoundError => e
166
+ handle_file_not_found filename, e
167
+ end
168
+
169
+ # Get definition suggestions for the expression at the specified file and
170
+ # location.
171
+ #
172
+ # @param filename [String] The file to analyze
173
+ # @param line [Integer] The zero-based line number
174
+ # @param column [Integer] The zero-based column number
175
+ # @return [Array<Solargraph::Pin::Base>, nil]
176
+ # @todo Take filename/position instead of filename/line/column
177
+ def definitions_at filename, line, column
178
+ sync_catalog
179
+ position = Position.new(line, column)
180
+ cursor = Source::Cursor.new(read(filename), position)
181
+ if cursor.comment?
182
+ source = read(filename)
183
+ offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column))
184
+ lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i)
185
+ rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i)
186
+ if lft && rgt
187
+ tag = (lft[1] + rgt[1]).sub(/:+$/, '')
188
+ clip = mutex.synchronize { api_map.clip(cursor) }
189
+ clip.translate tag
190
+ else
191
+ []
192
+ end
193
+ else
194
+ mutex.synchronize do
195
+ clip = api_map.clip(cursor)
196
+ clip.define.map { |pin| pin.realize(api_map) }
197
+ end
198
+ end
199
+ rescue FileNotFoundError => e
200
+ handle_file_not_found(filename, e)
201
+ end
202
+
203
+ # Get type definition suggestions for the expression at the specified file and
204
+ # location.
205
+ #
206
+ # @param filename [String] The file to analyze
207
+ # @param line [Integer] The zero-based line number
208
+ # @param column [Integer] The zero-based column number
209
+ # @return [Array<Solargraph::Pin::Base>, nil]
210
+ # @todo Take filename/position instead of filename/line/column
211
+ def type_definitions_at filename, line, column
212
+ sync_catalog
213
+ position = Position.new(line, column)
214
+ cursor = Source::Cursor.new(read(filename), position)
215
+ mutex.synchronize { api_map.clip(cursor).types }
216
+ rescue FileNotFoundError => e
217
+ handle_file_not_found filename, e
218
+ end
219
+
220
+ # Get signature suggestions for the method at the specified file and
221
+ # location.
222
+ #
223
+ # @param filename [String] The file to analyze
224
+ # @param line [Integer] The zero-based line number
225
+ # @param column [Integer] The zero-based column number
226
+ # @return [Array<Solargraph::Pin::Base>]
227
+ # @todo Take filename/position instead of filename/line/column
228
+ def signatures_at filename, line, column
229
+ sync_catalog
230
+ position = Position.new(line, column)
231
+ cursor = Source::Cursor.new(read(filename), position)
232
+ mutex.synchronize { api_map.clip(cursor).signify }
233
+ end
234
+
235
+ # @param filename [String]
236
+ # @param line [Integer]
237
+ # @param column [Integer]
238
+ # @param strip [Boolean] Strip special characters from variable names
239
+ # @param only [Boolean] Search for references in the current file only
240
+ # @return [Array<Solargraph::Location>]
241
+ # @todo Take a Location instead of filename/line/column
242
+ def references_from filename, line, column, strip: false, only: false
243
+ sync_catalog
244
+ cursor = Source::Cursor.new(read(filename), [line, column])
245
+ clip = mutex.synchronize { api_map.clip(cursor) }
246
+ pin = clip.define.first
247
+ return [] unless pin
248
+ result = []
249
+ files = if only
250
+ [api_map.source_map(filename)]
251
+ else
252
+ (workspace.sources + (@current ? [@current] : []))
253
+ end
254
+ files.uniq(&:filename).each do |source|
255
+ found = source.references(pin.name)
256
+ found.select! do |loc|
257
+ referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first
258
+ referenced&.path == pin.path
259
+ end
260
+ if pin.path == 'Class#new'
261
+ caller = cursor.chain.base.infer(api_map, clip.send(:block), clip.locals).first
262
+ if caller.defined?
263
+ found.select! do |loc|
264
+ clip = api_map.clip_at(loc.filename, loc.range.start)
265
+ other = clip.send(:cursor).chain.base.infer(api_map, clip.send(:block), clip.locals).first
266
+ caller == other
267
+ end
268
+ else
269
+ found.clear
270
+ end
271
+ end
272
+ # HACK: for language clients that exclude special characters from the start of variable names
273
+ if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
274
+ found.map! do |loc|
275
+ Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
276
+ end
277
+ end
278
+ result.concat(found.sort do |a, b|
279
+ a.range.start.line <=> b.range.start.line
280
+ end)
281
+ end
282
+ result.uniq
283
+ end
284
+
285
+ # Get the pins at the specified location or nil if the pin does not exist.
286
+ #
287
+ # @param location [Location]
288
+ # @return [Array<Solargraph::Pin::Base>]
289
+ def locate_pins location
290
+ sync_catalog
291
+ mutex.synchronize { api_map.locate_pins(location).map { |pin| pin.realize(api_map) } }
292
+ end
293
+
294
+ # Match a require reference to a file.
295
+ #
296
+ # @param location [Location]
297
+ # @return [Location, nil]
298
+ def locate_ref location
299
+ map = source_map_hash[location.filename]
300
+ return if map.nil?
301
+ pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first
302
+ return nil if pin.nil?
303
+ # @param full [String]
304
+ return_if_match = proc do |full|
305
+ if source_map_hash.key?(full)
306
+ return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
307
+ end
308
+ end
309
+ workspace.require_paths.each do |path|
310
+ full = File.join path, pin.name
311
+ return_if_match.(full)
312
+ return_if_match.(full << ".rb")
313
+ end
314
+ nil
315
+ rescue FileNotFoundError
316
+ nil
317
+ end
318
+
319
+ # Get an array of pins that match a path.
320
+ #
321
+ # @param path [String]
322
+ # @return [Enumerable<Solargraph::Pin::Base>]
323
+ def get_path_pins path
324
+ sync_catalog
325
+ mutex.synchronize { api_map.get_path_suggestions(path) }
326
+ end
327
+
328
+ # @param query [String]
329
+ # @return [Enumerable<YARD::CodeObjects::Base>]
330
+ def document query
331
+ sync_catalog
332
+ mutex.synchronize { api_map.document query }
333
+ end
334
+
335
+ # @param query [String]
336
+ # @return [Array<String>]
337
+ def search query
338
+ sync_catalog
339
+ mutex.synchronize { api_map.search query }
340
+ end
341
+
342
+ # Get an array of all symbols in the workspace that match the query.
343
+ #
344
+ # @param query [String]
345
+ # @return [Array<Pin::Base>]
346
+ def query_symbols query
347
+ sync_catalog
348
+ mutex.synchronize { api_map.query_symbols query }
349
+ end
350
+
351
+ # Get an array of document symbols.
352
+ #
353
+ # Document symbols are composed of namespace, method, and constant pins.
354
+ # The results of this query are appropriate for building the response to a
355
+ # textDocument/documentSymbol message in the language server protocol.
356
+ #
357
+ # @param filename [String]
358
+ # @return [Array<Solargraph::Pin::Base>]
359
+ def document_symbols filename
360
+ sync_catalog
361
+ mutex.synchronize { api_map.document_symbols(filename) }
362
+ end
363
+
364
+ # @param path [String]
365
+ # @return [Enumerable<Solargraph::Pin::Base>]
366
+ def path_pins path
367
+ sync_catalog
368
+ mutex.synchronize { api_map.get_path_suggestions(path) }
369
+ end
370
+
371
+ # @return [Array<SourceMap>]
372
+ def source_maps
373
+ source_map_hash.values
374
+ end
375
+
376
+ # Get the current text of a file in the library.
377
+ #
378
+ # @param filename [String]
379
+ # @return [String]
380
+ def read_text filename
381
+ source = read(filename)
382
+ source.code
383
+ end
384
+
385
+ # Get diagnostics about a file.
386
+ #
387
+ # @param filename [String]
388
+ # @return [Array<Hash>]
389
+ def diagnose filename
390
+ # @todo Only open files get diagnosed. Determine whether anything or
391
+ # everything in the workspace should get diagnosed, or if there should
392
+ # be an option to do so.
393
+ #
394
+ sync_catalog
395
+ return [] unless open?(filename)
396
+ result = []
397
+ source = read(filename)
398
+
399
+ # @type [Hash{Class<Solargraph::Diagnostics::Base> => Array<String>}]
400
+ repargs = {}
401
+ workspace.config.reporters.each do |line|
402
+ if line == 'all!'
403
+ Diagnostics.reporters.each do |reporter|
404
+ repargs[reporter] ||= []
405
+ end
406
+ else
407
+ args = line.split(':').map(&:strip)
408
+ name = args.shift
409
+ reporter = Diagnostics.reporter(name)
410
+ raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil?
411
+ repargs[reporter] ||= []
412
+ repargs[reporter].concat args
413
+ end
414
+ end
415
+ repargs.each_pair do |reporter, args|
416
+ result.concat reporter.new(*args.uniq).diagnose(source, api_map)
417
+ end
418
+ result
419
+ end
420
+
421
+ # Update the ApiMap from the library's workspace and open files.
422
+ #
423
+ # @return [void]
424
+ def catalog
425
+ @sync_count += 1
426
+ end
427
+
428
+ # @return [Bench]
429
+ def bench
430
+ Bench.new(
431
+ source_maps: source_map_hash.values,
432
+ workspace: workspace,
433
+ external_requires: external_requires,
434
+ live_map: @current ? source_map_hash[@current.filename] : nil
435
+ )
436
+ end
437
+
438
+ # Get an array of foldable ranges for the specified file.
439
+ #
440
+ # @deprecated The library should not need to handle folding ranges. The
441
+ # source itself has all the information it needs.
442
+ #
443
+ # @param filename [String]
444
+ # @return [Array<Range>]
445
+ def folding_ranges filename
446
+ read(filename).folding_ranges
447
+ end
448
+
449
+ # Create a library from a directory.
450
+ #
451
+ # @param directory [String] The path to be used for the workspace
452
+ # @param name [String, nil]
453
+ # @return [Solargraph::Library]
454
+ def self.load directory = '', name = nil
455
+ Solargraph::Library.new(Solargraph::Workspace.new(directory), name)
456
+ end
457
+
458
+ # Try to merge a source into the library's workspace. If the workspace is
459
+ # not configured to include the source, it gets ignored.
460
+ #
461
+ # @param source [Source]
462
+ # @return [Boolean] True if the source was merged into the workspace.
463
+ def merge source
464
+ result = workspace.merge(source)
465
+ maybe_map source
466
+ result
467
+ end
468
+
469
+ # @return [Hash{String => SourceMap}]
470
+ def source_map_hash
471
+ @source_map_hash ||= {}
472
+ end
473
+
474
+ def mapped?
475
+ (workspace.filenames - source_map_hash.keys).empty?
476
+ end
477
+
478
+ # @return [SourceMap, Boolean]
479
+ def next_map
480
+ return false if mapped?
481
+ src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
482
+ if src
483
+ Logging.logger.debug "Mapping #{src.filename}"
484
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
485
+ source_map_hash[src.filename]
486
+ else
487
+ false
488
+ end
489
+ end
490
+
491
+ # @return [self]
492
+ def map!
493
+ workspace.sources.each do |src|
494
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
495
+ find_external_requires source_map_hash[src.filename]
496
+ end
497
+ self
498
+ end
499
+
500
+ # @return [Array<Solargraph::Pin::Base>]
501
+ def pins
502
+ @pins ||= []
503
+ end
504
+
505
+ # @return [Set<String>]
506
+ def external_requires
507
+ @external_requires ||= source_map_external_require_hash.values.flatten.to_set
508
+ end
509
+
510
+ private
511
+
512
+ # @return [Hash{String => Set<String>}]
513
+ def source_map_external_require_hash
514
+ @source_map_external_require_hash ||= {}
515
+ end
516
+
517
+ # @param source_map [SourceMap]
518
+ # @return [void]
519
+ def find_external_requires source_map
520
+ new_set = source_map.requires.map(&:name).to_set
521
+ # return if new_set == source_map_external_require_hash[source_map.filename]
522
+ _filenames = nil
523
+ filenames = ->{ _filenames ||= workspace.filenames.to_set }
524
+ source_map_external_require_hash[source_map.filename] = new_set.reject do |path|
525
+ workspace.require_paths.any? do |base|
526
+ full = File.join(base, path)
527
+ filenames[].include?(full) or filenames[].include?(full << ".rb")
528
+ end
529
+ end
530
+ @external_requires = nil
531
+ end
532
+
533
+ # @return [Mutex]
534
+ def mutex
535
+ @mutex ||= Mutex.new
536
+ end
537
+
538
+ # @return [ApiMap]
539
+ def api_map
540
+ @api_map ||= Solargraph::ApiMap.new
541
+ end
542
+
543
+ # Get the source for an open file or create a new source if the file
544
+ # exists on disk. Sources created from disk are not added to the open
545
+ # workspace files, i.e., the version on disk remains the authoritative
546
+ # version.
547
+ #
548
+ # @raise [FileNotFoundError] if the file does not exist
549
+ # @param filename [String]
550
+ # @return [Solargraph::Source]
551
+ def read filename
552
+ return @current if @current && @current.filename == filename
553
+ raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
554
+ workspace.source(filename)
555
+ end
556
+
557
+ # @param filename [String]
558
+ # @param error [FileNotFoundError]
559
+ # @return [nil]
560
+ def handle_file_not_found filename, error
561
+ if workspace.source(filename)
562
+ Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap"
563
+ nil
564
+ else
565
+ raise error
566
+ end
567
+ end
568
+
569
+ # @param source [Source, nil]
570
+ # @return [void]
571
+ def maybe_map source
572
+ return unless source
573
+ return unless @current == source || workspace.has_file?(source.filename)
574
+ if source_map_hash.key?(source.filename)
575
+ new_map = Solargraph::SourceMap.map(source)
576
+ source_map_hash[source.filename] = new_map
577
+ else
578
+ source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
579
+ end
580
+ end
581
+
582
+ # @return [Set<Gem::Specification>]
583
+ def cache_errors
584
+ @cache_errors ||= Set.new
585
+ end
586
+
587
+ # @return [void]
588
+ def cache_next_gemspec
589
+ return if @cache_progress
590
+ spec = api_map.uncached_gemspecs.find { |spec| !cache_errors.include?(spec) }
591
+ return end_cache_progress unless spec
592
+
593
+ pending = api_map.uncached_gemspecs.length - cache_errors.length - 1
594
+ logger.info "Caching #{spec.name} #{spec.version}"
595
+ Thread.new do
596
+ cache_pid = Process.spawn(workspace.command_path, 'cache', spec.name, spec.version.to_s)
597
+ report_cache_progress spec.name, pending
598
+ Process.wait(cache_pid)
599
+ logger.info "Cached #{spec.name} #{spec.version}"
600
+ rescue Errno::EINVAL => _e
601
+ logger.info "Cached #{spec.name} #{spec.version} with EINVAL"
602
+ rescue StandardError => e
603
+ cache_errors.add spec
604
+ Solargraph.logger.warn "Error caching gemspec #{spec.name} #{spec.version}: [#{e.class}] #{e.message}"
605
+ ensure
606
+ end_cache_progress
607
+ catalog
608
+ sync_catalog
609
+ end
610
+ end
611
+
612
+ # @param gem_name [String]
613
+ # @param pending [Integer]
614
+ # @return [void]
615
+ def report_cache_progress gem_name, pending
616
+ @total ||= pending
617
+ @total = pending if pending > @total
618
+ finished = @total - pending
619
+ pct = if @total.zero?
620
+ 0
621
+ else
622
+ ((finished.to_f / @total.to_f) * 100).to_i
623
+ end
624
+ message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}"
625
+ # "
626
+ if @cache_progress
627
+ @cache_progress.report(message, pct)
628
+ else
629
+ @cache_progress = LanguageServer::Progress.new('Caching gem')
630
+ # If we don't send both a begin and a report, the progress notification
631
+ # might get stuck in the status bar forever
632
+ @cache_progress.begin(message, pct)
633
+ changed
634
+ notify_observers @cache_progress
635
+ @cache_progress.report(message, pct)
636
+ end
637
+ changed
638
+ notify_observers @cache_progress
639
+ end
640
+
641
+ # @return [void]
642
+ def end_cache_progress
643
+ changed if @cache_progress&.finish('done')
644
+ notify_observers @cache_progress
645
+ @cache_progress = nil
646
+ @total = nil
647
+ end
648
+
649
+ def sync_catalog
650
+ return if @sync_count == 0
651
+
652
+ mutex.synchronize do
653
+ logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
654
+ api_map.catalog bench
655
+ source_map_hash.values.each { |map| find_external_requires(map) }
656
+ logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
657
+ logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
658
+ cache_next_gemspec
659
+ @sync_count = 0
660
+ end
661
+ end
662
+ end
663
+ end