solargraph 0.45.0 → 0.46.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +41 -41
  3. data/.gitignore +9 -9
  4. data/.rspec +2 -2
  5. data/.travis.yml +19 -19
  6. data/CHANGELOG.md +1115 -1109
  7. data/Gemfile +0 -0
  8. data/LICENSE +0 -0
  9. data/README.md +128 -128
  10. data/Rakefile +0 -0
  11. data/SPONSORS.md +18 -17
  12. data/bin/solargraph +0 -0
  13. data/lib/solargraph/api_map/bundler_methods.rb +22 -22
  14. data/lib/solargraph/api_map/cache.rb +70 -70
  15. data/lib/solargraph/api_map/source_to_yard.rb +81 -81
  16. data/lib/solargraph/api_map/store.rb +256 -256
  17. data/lib/solargraph/api_map.rb +686 -681
  18. data/lib/solargraph/bench.rb +27 -27
  19. data/lib/solargraph/compat.rb +37 -37
  20. data/lib/solargraph/complex_type/type_methods.rb +130 -130
  21. data/lib/solargraph/complex_type/unique_type.rb +75 -75
  22. data/lib/solargraph/complex_type.rb +221 -221
  23. data/lib/solargraph/convention/base.rb +33 -33
  24. data/lib/solargraph/convention/gemfile.rb +15 -15
  25. data/lib/solargraph/convention/gemspec.rb +22 -22
  26. data/lib/solargraph/convention/rspec.rb +30 -30
  27. data/lib/solargraph/convention.rb +47 -47
  28. data/lib/solargraph/converters/dd.rb +12 -12
  29. data/lib/solargraph/converters/dl.rb +12 -12
  30. data/lib/solargraph/converters/dt.rb +12 -12
  31. data/lib/solargraph/converters/misc.rb +1 -1
  32. data/lib/solargraph/diagnostics/base.rb +29 -29
  33. data/lib/solargraph/diagnostics/require_not_found.rb +53 -37
  34. data/lib/solargraph/diagnostics/rubocop.rb +98 -98
  35. data/lib/solargraph/diagnostics/rubocop_helpers.rb +63 -63
  36. data/lib/solargraph/diagnostics/severities.rb +15 -15
  37. data/lib/solargraph/diagnostics/type_check.rb +54 -54
  38. data/lib/solargraph/diagnostics/update_errors.rb +41 -41
  39. data/lib/solargraph/diagnostics.rb +55 -55
  40. data/lib/solargraph/documentor.rb +76 -76
  41. data/lib/solargraph/environ.rb +45 -45
  42. data/lib/solargraph/language_server/completion_item_kinds.rb +35 -35
  43. data/lib/solargraph/language_server/error_codes.rb +20 -20
  44. data/lib/solargraph/language_server/host/cataloger.rb +56 -56
  45. data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
  46. data/lib/solargraph/language_server/host/dispatch.rb +111 -111
  47. data/lib/solargraph/language_server/host/message_worker.rb +59 -59
  48. data/lib/solargraph/language_server/host/sources.rb +156 -156
  49. data/lib/solargraph/language_server/host.rb +865 -865
  50. data/lib/solargraph/language_server/message/base.rb +89 -89
  51. data/lib/solargraph/language_server/message/cancel_request.rb +13 -13
  52. data/lib/solargraph/language_server/message/client/register_capability.rb +15 -15
  53. data/lib/solargraph/language_server/message/client.rb +11 -11
  54. data/lib/solargraph/language_server/message/completion_item/resolve.rb +58 -58
  55. data/lib/solargraph/language_server/message/completion_item.rb +11 -11
  56. data/lib/solargraph/language_server/message/exit_notification.rb +13 -13
  57. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +100 -100
  58. data/lib/solargraph/language_server/message/extended/document.rb +20 -20
  59. data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -32
  60. data/lib/solargraph/language_server/message/extended/download_core.rb +23 -23
  61. data/lib/solargraph/language_server/message/extended/environment.rb +25 -25
  62. data/lib/solargraph/language_server/message/extended/search.rb +20 -20
  63. data/lib/solargraph/language_server/message/extended.rb +21 -21
  64. data/lib/solargraph/language_server/message/initialize.rb +162 -162
  65. data/lib/solargraph/language_server/message/initialized.rb +27 -27
  66. data/lib/solargraph/language_server/message/method_not_found.rb +16 -16
  67. data/lib/solargraph/language_server/message/method_not_implemented.rb +14 -14
  68. data/lib/solargraph/language_server/message/shutdown.rb +13 -13
  69. data/lib/solargraph/language_server/message/text_document/base.rb +19 -19
  70. data/lib/solargraph/language_server/message/text_document/code_action.rb +17 -17
  71. data/lib/solargraph/language_server/message/text_document/completion.rb +59 -59
  72. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -38
  73. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -15
  74. data/lib/solargraph/language_server/message/text_document/did_close.rb +15 -15
  75. data/lib/solargraph/language_server/message/text_document/did_open.rb +15 -15
  76. data/lib/solargraph/language_server/message/text_document/did_save.rb +17 -17
  77. data/lib/solargraph/language_server/message/text_document/document_highlight.rb +16 -16
  78. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +23 -23
  79. data/lib/solargraph/language_server/message/text_document/folding_range.rb +26 -26
  80. data/lib/solargraph/language_server/message/text_document/formatting.rb +126 -126
  81. data/lib/solargraph/language_server/message/text_document/hover.rb +54 -44
  82. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +34 -34
  83. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +11 -11
  84. data/lib/solargraph/language_server/message/text_document/references.rb +16 -16
  85. data/lib/solargraph/language_server/message/text_document/rename.rb +19 -19
  86. data/lib/solargraph/language_server/message/text_document/signature_help.rb +29 -29
  87. data/lib/solargraph/language_server/message/text_document.rb +28 -28
  88. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +30 -30
  89. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +33 -33
  90. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
  91. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -23
  92. data/lib/solargraph/language_server/message/workspace.rb +14 -14
  93. data/lib/solargraph/language_server/message.rb +93 -93
  94. data/lib/solargraph/language_server/message_types.rb +14 -14
  95. data/lib/solargraph/language_server/request.rb +24 -24
  96. data/lib/solargraph/language_server/symbol_kinds.rb +36 -36
  97. data/lib/solargraph/language_server/transport/adapter.rb +53 -53
  98. data/lib/solargraph/language_server/transport/data_reader.rb +72 -72
  99. data/lib/solargraph/language_server/transport.rb +13 -13
  100. data/lib/solargraph/language_server/uri_helpers.rb +49 -49
  101. data/lib/solargraph/language_server.rb +19 -19
  102. data/lib/solargraph/library.rb +546 -546
  103. data/lib/solargraph/location.rb +37 -37
  104. data/lib/solargraph/logging.rb +27 -27
  105. data/lib/solargraph/page.rb +83 -83
  106. data/lib/solargraph/parser/comment_ripper.rb +52 -52
  107. data/lib/solargraph/parser/legacy/class_methods.rb +135 -135
  108. data/lib/solargraph/parser/legacy/flawed_builder.rb +16 -16
  109. data/lib/solargraph/parser/legacy/node_chainer.rb +148 -148
  110. data/lib/solargraph/parser/legacy/node_methods.rb +325 -325
  111. data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +23 -23
  112. data/lib/solargraph/parser/legacy/node_processors/args_node.rb +35 -35
  113. data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +15 -15
  114. data/lib/solargraph/parser/legacy/node_processors/block_node.rb +42 -42
  115. data/lib/solargraph/parser/legacy/node_processors/casgn_node.rb +25 -25
  116. data/lib/solargraph/parser/legacy/node_processors/cvasgn_node.rb +23 -23
  117. data/lib/solargraph/parser/legacy/node_processors/def_node.rb +63 -63
  118. data/lib/solargraph/parser/legacy/node_processors/defs_node.rb +36 -36
  119. data/lib/solargraph/parser/legacy/node_processors/gvasgn_node.rb +23 -23
  120. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +38 -38
  121. data/lib/solargraph/parser/legacy/node_processors/lvasgn_node.rb +28 -28
  122. data/lib/solargraph/parser/legacy/node_processors/namespace_node.rb +39 -39
  123. data/lib/solargraph/parser/legacy/node_processors/orasgn_node.rb +16 -16
  124. data/lib/solargraph/parser/legacy/node_processors/resbody_node.rb +36 -36
  125. data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +21 -21
  126. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +257 -257
  127. data/lib/solargraph/parser/legacy/node_processors/sym_node.rb +18 -18
  128. data/lib/solargraph/parser/legacy/node_processors.rb +54 -54
  129. data/lib/solargraph/parser/legacy.rb +12 -12
  130. data/lib/solargraph/parser/node_methods.rb +43 -43
  131. data/lib/solargraph/parser/node_processor/base.rb +77 -77
  132. data/lib/solargraph/parser/node_processor.rb +43 -43
  133. data/lib/solargraph/parser/region.rb +66 -66
  134. data/lib/solargraph/parser/rubyvm/class_methods.rb +144 -144
  135. data/lib/solargraph/parser/rubyvm/node_chainer.rb +160 -160
  136. data/lib/solargraph/parser/rubyvm/node_methods.rb +315 -315
  137. data/lib/solargraph/parser/rubyvm/node_processors/alias_node.rb +23 -23
  138. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +85 -85
  139. data/lib/solargraph/parser/rubyvm/node_processors/begin_node.rb +15 -15
  140. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +42 -42
  141. data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +22 -22
  142. data/lib/solargraph/parser/rubyvm/node_processors/cvasgn_node.rb +23 -23
  143. data/lib/solargraph/parser/rubyvm/node_processors/def_node.rb +63 -64
  144. data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +57 -57
  145. data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +23 -23
  146. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +38 -38
  147. data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +39 -39
  148. data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +20 -20
  149. data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +27 -27
  150. data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +39 -39
  151. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +26 -26
  152. data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +15 -15
  153. data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +45 -45
  154. data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +21 -21
  155. data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +15 -15
  156. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +277 -277
  157. data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +18 -18
  158. data/lib/solargraph/parser/rubyvm/node_processors.rb +63 -63
  159. data/lib/solargraph/parser/rubyvm.rb +40 -40
  160. data/lib/solargraph/parser/snippet.rb +13 -13
  161. data/lib/solargraph/parser.rb +26 -26
  162. data/lib/solargraph/pin/base.rb +296 -296
  163. data/lib/solargraph/pin/base_variable.rb +84 -84
  164. data/lib/solargraph/pin/block.rb +72 -72
  165. data/lib/solargraph/pin/class_variable.rb +8 -8
  166. data/lib/solargraph/pin/closure.rb +37 -37
  167. data/lib/solargraph/pin/common.rb +70 -70
  168. data/lib/solargraph/pin/constant.rb +43 -43
  169. data/lib/solargraph/pin/conversions.rb +96 -96
  170. data/lib/solargraph/pin/documenting.rb +105 -105
  171. data/lib/solargraph/pin/duck_method.rb +16 -16
  172. data/lib/solargraph/pin/global_variable.rb +8 -8
  173. data/lib/solargraph/pin/instance_variable.rb +30 -30
  174. data/lib/solargraph/pin/keyword.rb +15 -15
  175. data/lib/solargraph/pin/keyword_param.rb +8 -8
  176. data/lib/solargraph/pin/local_variable.rb +55 -55
  177. data/lib/solargraph/pin/method.rb +245 -245
  178. data/lib/solargraph/pin/method_alias.rb +31 -31
  179. data/lib/solargraph/pin/namespace.rb +91 -91
  180. data/lib/solargraph/pin/parameter.rb +201 -203
  181. data/lib/solargraph/pin/proxy_type.rb +29 -29
  182. data/lib/solargraph/pin/reference/extend.rb +10 -10
  183. data/lib/solargraph/pin/reference/include.rb +10 -10
  184. data/lib/solargraph/pin/reference/override.rb +29 -29
  185. data/lib/solargraph/pin/reference/prepend.rb +10 -10
  186. data/lib/solargraph/pin/reference/require.rb +14 -14
  187. data/lib/solargraph/pin/reference/superclass.rb +10 -10
  188. data/lib/solargraph/pin/reference.rb +14 -14
  189. data/lib/solargraph/pin/search.rb +56 -0
  190. data/lib/solargraph/pin/singleton.rb +11 -11
  191. data/lib/solargraph/pin/symbol.rb +47 -47
  192. data/lib/solargraph/pin.rb +37 -36
  193. data/lib/solargraph/position.rb +100 -100
  194. data/lib/solargraph/range.rb +95 -95
  195. data/lib/solargraph/server_methods.rb +16 -16
  196. data/lib/solargraph/shell.rb +226 -226
  197. data/lib/solargraph/source/chain/block_variable.rb +13 -13
  198. data/lib/solargraph/source/chain/call.rb +204 -204
  199. data/lib/solargraph/source/chain/class_variable.rb +13 -13
  200. data/lib/solargraph/source/chain/constant.rb +75 -75
  201. data/lib/solargraph/source/chain/global_variable.rb +13 -13
  202. data/lib/solargraph/source/chain/hash.rb +28 -28
  203. data/lib/solargraph/source/chain/head.rb +19 -19
  204. data/lib/solargraph/source/chain/instance_variable.rb +13 -13
  205. data/lib/solargraph/source/chain/link.rb +71 -71
  206. data/lib/solargraph/source/chain/literal.rb +23 -23
  207. data/lib/solargraph/source/chain/or.rb +23 -23
  208. data/lib/solargraph/source/chain/q_call.rb +11 -11
  209. data/lib/solargraph/source/chain/variable.rb +13 -13
  210. data/lib/solargraph/source/chain/z_super.rb +30 -30
  211. data/lib/solargraph/source/chain.rb +164 -164
  212. data/lib/solargraph/source/change.rb +79 -79
  213. data/lib/solargraph/source/cursor.rb +164 -164
  214. data/lib/solargraph/source/source_chainer.rb +191 -191
  215. data/lib/solargraph/source/updater.rb +54 -54
  216. data/lib/solargraph/source.rb +522 -522
  217. data/lib/solargraph/source_map/clip.rb +224 -224
  218. data/lib/solargraph/source_map/completion.rb +23 -23
  219. data/lib/solargraph/source_map/mapper.rb +212 -212
  220. data/lib/solargraph/source_map.rb +180 -189
  221. data/lib/solargraph/type_checker/checks.rb +99 -99
  222. data/lib/solargraph/type_checker/param_def.rb +35 -35
  223. data/lib/solargraph/type_checker/problem.rb +32 -32
  224. data/lib/solargraph/type_checker/rules.rb +57 -57
  225. data/lib/solargraph/type_checker.rb +543 -529
  226. data/lib/solargraph/version.rb +5 -5
  227. data/lib/solargraph/views/environment.erb +58 -58
  228. data/lib/solargraph/workspace/config.rb +231 -231
  229. data/lib/solargraph/workspace.rb +215 -214
  230. data/lib/solargraph/yard_map/cache.rb +19 -19
  231. data/lib/solargraph/yard_map/core_docs.rb +170 -170
  232. data/lib/solargraph/yard_map/core_fills.rb +208 -208
  233. data/lib/solargraph/yard_map/core_gen.rb +76 -76
  234. data/lib/solargraph/yard_map/helpers.rb +16 -16
  235. data/lib/solargraph/yard_map/mapper/to_constant.rb +25 -25
  236. data/lib/solargraph/yard_map/mapper/to_method.rb +78 -78
  237. data/lib/solargraph/yard_map/mapper/to_namespace.rb +27 -27
  238. data/lib/solargraph/yard_map/mapper.rb +77 -77
  239. data/lib/solargraph/yard_map/rdoc_to_yard.rb +140 -140
  240. data/lib/solargraph/yard_map/stdlib_fills.rb +43 -43
  241. data/lib/solargraph/yard_map/to_method.rb +79 -79
  242. data/lib/solargraph/yard_map.rb +460 -445
  243. data/lib/solargraph.rb +69 -69
  244. data/lib/yard-solargraph.rb +33 -33
  245. data/solargraph.gemspec +0 -0
  246. metadata +13 -12
@@ -1,546 +1,546 @@
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
+
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