solargraph 0.39.7

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 (252) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +28 -0
  5. data/.yardopts +2 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE +21 -0
  8. data/README.md +104 -0
  9. data/Rakefile +14 -0
  10. data/SPONSORS.md +9 -0
  11. data/bin/solargraph +5 -0
  12. data/lib/.rubocop.yml +21 -0
  13. data/lib/solargraph.rb +66 -0
  14. data/lib/solargraph/api_map.rb +745 -0
  15. data/lib/solargraph/api_map/bundler_methods.rb +27 -0
  16. data/lib/solargraph/api_map/cache.rb +66 -0
  17. data/lib/solargraph/api_map/source_to_yard.rb +81 -0
  18. data/lib/solargraph/api_map/store.rb +267 -0
  19. data/lib/solargraph/bundle.rb +26 -0
  20. data/lib/solargraph/complex_type.rb +213 -0
  21. data/lib/solargraph/complex_type/type_methods.rb +127 -0
  22. data/lib/solargraph/complex_type/unique_type.rb +75 -0
  23. data/lib/solargraph/convention.rb +38 -0
  24. data/lib/solargraph/convention/base.rb +25 -0
  25. data/lib/solargraph/convention/gemfile.rb +18 -0
  26. data/lib/solargraph/convention/gemspec.rb +25 -0
  27. data/lib/solargraph/convention/rspec.rb +24 -0
  28. data/lib/solargraph/converters/dd.rb +12 -0
  29. data/lib/solargraph/converters/dl.rb +12 -0
  30. data/lib/solargraph/converters/dt.rb +12 -0
  31. data/lib/solargraph/converters/misc.rb +1 -0
  32. data/lib/solargraph/core_fills.rb +159 -0
  33. data/lib/solargraph/diagnostics.rb +55 -0
  34. data/lib/solargraph/diagnostics/base.rb +29 -0
  35. data/lib/solargraph/diagnostics/require_not_found.rb +37 -0
  36. data/lib/solargraph/diagnostics/rubocop.rb +90 -0
  37. data/lib/solargraph/diagnostics/rubocop_helpers.rb +64 -0
  38. data/lib/solargraph/diagnostics/severities.rb +15 -0
  39. data/lib/solargraph/diagnostics/type_check.rb +54 -0
  40. data/lib/solargraph/diagnostics/update_errors.rb +41 -0
  41. data/lib/solargraph/documentor.rb +76 -0
  42. data/lib/solargraph/environ.rb +40 -0
  43. data/lib/solargraph/language_server.rb +19 -0
  44. data/lib/solargraph/language_server/completion_item_kinds.rb +35 -0
  45. data/lib/solargraph/language_server/error_codes.rb +20 -0
  46. data/lib/solargraph/language_server/host.rb +741 -0
  47. data/lib/solargraph/language_server/host/cataloger.rb +56 -0
  48. data/lib/solargraph/language_server/host/diagnoser.rb +81 -0
  49. data/lib/solargraph/language_server/host/dispatch.rb +112 -0
  50. data/lib/solargraph/language_server/host/sources.rb +156 -0
  51. data/lib/solargraph/language_server/message.rb +92 -0
  52. data/lib/solargraph/language_server/message/base.rb +85 -0
  53. data/lib/solargraph/language_server/message/cancel_request.rb +13 -0
  54. data/lib/solargraph/language_server/message/client.rb +11 -0
  55. data/lib/solargraph/language_server/message/client/register_capability.rb +15 -0
  56. data/lib/solargraph/language_server/message/completion_item.rb +11 -0
  57. data/lib/solargraph/language_server/message/completion_item/resolve.rb +57 -0
  58. data/lib/solargraph/language_server/message/exit_notification.rb +13 -0
  59. data/lib/solargraph/language_server/message/extended.rb +21 -0
  60. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +95 -0
  61. data/lib/solargraph/language_server/message/extended/document.rb +20 -0
  62. data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -0
  63. data/lib/solargraph/language_server/message/extended/download_core.rb +23 -0
  64. data/lib/solargraph/language_server/message/extended/environment.rb +25 -0
  65. data/lib/solargraph/language_server/message/extended/search.rb +20 -0
  66. data/lib/solargraph/language_server/message/initialize.rb +153 -0
  67. data/lib/solargraph/language_server/message/initialized.rb +26 -0
  68. data/lib/solargraph/language_server/message/method_not_found.rb +16 -0
  69. data/lib/solargraph/language_server/message/method_not_implemented.rb +14 -0
  70. data/lib/solargraph/language_server/message/shutdown.rb +13 -0
  71. data/lib/solargraph/language_server/message/text_document.rb +28 -0
  72. data/lib/solargraph/language_server/message/text_document/base.rb +19 -0
  73. data/lib/solargraph/language_server/message/text_document/code_action.rb +17 -0
  74. data/lib/solargraph/language_server/message/text_document/completion.rb +57 -0
  75. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -0
  76. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -0
  77. data/lib/solargraph/language_server/message/text_document/did_close.rb +15 -0
  78. data/lib/solargraph/language_server/message/text_document/did_open.rb +15 -0
  79. data/lib/solargraph/language_server/message/text_document/did_save.rb +17 -0
  80. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +23 -0
  81. data/lib/solargraph/language_server/message/text_document/folding_range.rb +26 -0
  82. data/lib/solargraph/language_server/message/text_document/formatting.rb +78 -0
  83. data/lib/solargraph/language_server/message/text_document/hover.rb +44 -0
  84. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +34 -0
  85. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +11 -0
  86. data/lib/solargraph/language_server/message/text_document/references.rb +16 -0
  87. data/lib/solargraph/language_server/message/text_document/rename.rb +19 -0
  88. data/lib/solargraph/language_server/message/text_document/signature_help.rb +29 -0
  89. data/lib/solargraph/language_server/message/workspace.rb +14 -0
  90. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -0
  91. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +33 -0
  92. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -0
  93. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -0
  94. data/lib/solargraph/language_server/message_types.rb +14 -0
  95. data/lib/solargraph/language_server/request.rb +24 -0
  96. data/lib/solargraph/language_server/symbol_kinds.rb +36 -0
  97. data/lib/solargraph/language_server/transport.rb +13 -0
  98. data/lib/solargraph/language_server/transport/adapter.rb +56 -0
  99. data/lib/solargraph/language_server/transport/data_reader.rb +72 -0
  100. data/lib/solargraph/language_server/uri_helpers.rb +49 -0
  101. data/lib/solargraph/library.rb +414 -0
  102. data/lib/solargraph/location.rb +37 -0
  103. data/lib/solargraph/logging.rb +27 -0
  104. data/lib/solargraph/page.rb +83 -0
  105. data/lib/solargraph/parser.rb +26 -0
  106. data/lib/solargraph/parser/comment_ripper.rb +52 -0
  107. data/lib/solargraph/parser/legacy.rb +12 -0
  108. data/lib/solargraph/parser/legacy/class_methods.rb +109 -0
  109. data/lib/solargraph/parser/legacy/flawed_builder.rb +16 -0
  110. data/lib/solargraph/parser/legacy/node_chainer.rb +118 -0
  111. data/lib/solargraph/parser/legacy/node_methods.rb +300 -0
  112. data/lib/solargraph/parser/legacy/node_processors.rb +54 -0
  113. data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +23 -0
  114. data/lib/solargraph/parser/legacy/node_processors/args_node.rb +35 -0
  115. data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +15 -0
  116. data/lib/solargraph/parser/legacy/node_processors/block_node.rb +22 -0
  117. data/lib/solargraph/parser/legacy/node_processors/casgn_node.rb +25 -0
  118. data/lib/solargraph/parser/legacy/node_processors/cvasgn_node.rb +23 -0
  119. data/lib/solargraph/parser/legacy/node_processors/def_node.rb +63 -0
  120. data/lib/solargraph/parser/legacy/node_processors/defs_node.rb +36 -0
  121. data/lib/solargraph/parser/legacy/node_processors/gvasgn_node.rb +23 -0
  122. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +38 -0
  123. data/lib/solargraph/parser/legacy/node_processors/lvasgn_node.rb +28 -0
  124. data/lib/solargraph/parser/legacy/node_processors/namespace_node.rb +39 -0
  125. data/lib/solargraph/parser/legacy/node_processors/orasgn_node.rb +16 -0
  126. data/lib/solargraph/parser/legacy/node_processors/resbody_node.rb +36 -0
  127. data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +21 -0
  128. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +234 -0
  129. data/lib/solargraph/parser/legacy/node_processors/sym_node.rb +18 -0
  130. data/lib/solargraph/parser/node_methods.rb +43 -0
  131. data/lib/solargraph/parser/node_processor.rb +43 -0
  132. data/lib/solargraph/parser/node_processor/base.rb +77 -0
  133. data/lib/solargraph/parser/region.rb +66 -0
  134. data/lib/solargraph/parser/rubyvm.rb +40 -0
  135. data/lib/solargraph/parser/rubyvm/class_methods.rb +150 -0
  136. data/lib/solargraph/parser/rubyvm/node_chainer.rb +135 -0
  137. data/lib/solargraph/parser/rubyvm/node_methods.rb +284 -0
  138. data/lib/solargraph/parser/rubyvm/node_processors.rb +61 -0
  139. data/lib/solargraph/parser/rubyvm/node_processors/alias_node.rb +23 -0
  140. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +62 -0
  141. data/lib/solargraph/parser/rubyvm/node_processors/begin_node.rb +15 -0
  142. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +22 -0
  143. data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +22 -0
  144. data/lib/solargraph/parser/rubyvm/node_processors/cvasgn_node.rb +23 -0
  145. data/lib/solargraph/parser/rubyvm/node_processors/def_node.rb +64 -0
  146. data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +57 -0
  147. data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +23 -0
  148. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +38 -0
  149. data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +39 -0
  150. data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +20 -0
  151. data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +27 -0
  152. data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +39 -0
  153. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +31 -0
  154. data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +15 -0
  155. data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +45 -0
  156. data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +21 -0
  157. data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +15 -0
  158. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +255 -0
  159. data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +18 -0
  160. data/lib/solargraph/parser/snippet.rb +13 -0
  161. data/lib/solargraph/pin.rb +39 -0
  162. data/lib/solargraph/pin/attribute.rb +49 -0
  163. data/lib/solargraph/pin/base.rb +296 -0
  164. data/lib/solargraph/pin/base_method.rb +141 -0
  165. data/lib/solargraph/pin/base_variable.rb +84 -0
  166. data/lib/solargraph/pin/block.rb +48 -0
  167. data/lib/solargraph/pin/class_variable.rb +8 -0
  168. data/lib/solargraph/pin/closure.rb +37 -0
  169. data/lib/solargraph/pin/common.rb +70 -0
  170. data/lib/solargraph/pin/constant.rb +43 -0
  171. data/lib/solargraph/pin/conversions.rb +97 -0
  172. data/lib/solargraph/pin/documenting.rb +110 -0
  173. data/lib/solargraph/pin/duck_method.rb +16 -0
  174. data/lib/solargraph/pin/global_variable.rb +8 -0
  175. data/lib/solargraph/pin/instance_variable.rb +30 -0
  176. data/lib/solargraph/pin/keyword.rb +15 -0
  177. data/lib/solargraph/pin/keyword_param.rb +8 -0
  178. data/lib/solargraph/pin/local_variable.rb +21 -0
  179. data/lib/solargraph/pin/localized.rb +43 -0
  180. data/lib/solargraph/pin/method.rb +111 -0
  181. data/lib/solargraph/pin/method_alias.rb +31 -0
  182. data/lib/solargraph/pin/namespace.rb +85 -0
  183. data/lib/solargraph/pin/parameter.rb +206 -0
  184. data/lib/solargraph/pin/proxy_type.rb +29 -0
  185. data/lib/solargraph/pin/reference.rb +14 -0
  186. data/lib/solargraph/pin/reference/extend.rb +10 -0
  187. data/lib/solargraph/pin/reference/include.rb +10 -0
  188. data/lib/solargraph/pin/reference/override.rb +29 -0
  189. data/lib/solargraph/pin/reference/prepend.rb +10 -0
  190. data/lib/solargraph/pin/reference/require.rb +14 -0
  191. data/lib/solargraph/pin/reference/superclass.rb +10 -0
  192. data/lib/solargraph/pin/singleton.rb +11 -0
  193. data/lib/solargraph/pin/symbol.rb +47 -0
  194. data/lib/solargraph/pin/yard_pin.rb +12 -0
  195. data/lib/solargraph/pin/yard_pin/constant.rb +25 -0
  196. data/lib/solargraph/pin/yard_pin/method.rb +65 -0
  197. data/lib/solargraph/pin/yard_pin/namespace.rb +27 -0
  198. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +26 -0
  199. data/lib/solargraph/position.rb +112 -0
  200. data/lib/solargraph/range.rb +95 -0
  201. data/lib/solargraph/server_methods.rb +16 -0
  202. data/lib/solargraph/shell.rb +221 -0
  203. data/lib/solargraph/source.rb +533 -0
  204. data/lib/solargraph/source/chain.rb +172 -0
  205. data/lib/solargraph/source/chain/block_variable.rb +13 -0
  206. data/lib/solargraph/source/chain/call.rb +203 -0
  207. data/lib/solargraph/source/chain/class_variable.rb +13 -0
  208. data/lib/solargraph/source/chain/constant.rb +75 -0
  209. data/lib/solargraph/source/chain/global_variable.rb +13 -0
  210. data/lib/solargraph/source/chain/head.rb +35 -0
  211. data/lib/solargraph/source/chain/instance_variable.rb +13 -0
  212. data/lib/solargraph/source/chain/link.rb +67 -0
  213. data/lib/solargraph/source/chain/literal.rb +23 -0
  214. data/lib/solargraph/source/chain/or.rb +23 -0
  215. data/lib/solargraph/source/chain/variable.rb +13 -0
  216. data/lib/solargraph/source/chain/z_super.rb +184 -0
  217. data/lib/solargraph/source/change.rb +79 -0
  218. data/lib/solargraph/source/cursor.rb +164 -0
  219. data/lib/solargraph/source/encoding_fixes.rb +23 -0
  220. data/lib/solargraph/source/source_chainer.rb +189 -0
  221. data/lib/solargraph/source/updater.rb +54 -0
  222. data/lib/solargraph/source_map.rb +170 -0
  223. data/lib/solargraph/source_map/clip.rb +190 -0
  224. data/lib/solargraph/source_map/completion.rb +23 -0
  225. data/lib/solargraph/source_map/mapper.rb +199 -0
  226. data/lib/solargraph/stdlib_fills.rb +32 -0
  227. data/lib/solargraph/type_checker.rb +498 -0
  228. data/lib/solargraph/type_checker/checks.rb +95 -0
  229. data/lib/solargraph/type_checker/param_def.rb +35 -0
  230. data/lib/solargraph/type_checker/problem.rb +32 -0
  231. data/lib/solargraph/type_checker/rules.rb +53 -0
  232. data/lib/solargraph/version.rb +5 -0
  233. data/lib/solargraph/views/_method.erb +62 -0
  234. data/lib/solargraph/views/_name_type_tag.erb +10 -0
  235. data/lib/solargraph/views/_namespace.erb +24 -0
  236. data/lib/solargraph/views/document.erb +23 -0
  237. data/lib/solargraph/views/environment.erb +58 -0
  238. data/lib/solargraph/views/layout.erb +44 -0
  239. data/lib/solargraph/views/search.erb +11 -0
  240. data/lib/solargraph/workspace.rb +209 -0
  241. data/lib/solargraph/workspace/config.rb +215 -0
  242. data/lib/solargraph/yard_map.rb +420 -0
  243. data/lib/solargraph/yard_map/cache.rb +19 -0
  244. data/lib/solargraph/yard_map/core_docs.rb +170 -0
  245. data/lib/solargraph/yard_map/core_gen.rb +76 -0
  246. data/lib/solargraph/yard_map/mapper.rb +71 -0
  247. data/lib/solargraph/yard_map/rdoc_to_yard.rb +136 -0
  248. data/lib/yard-solargraph.rb +30 -0
  249. data/solargraph.gemspec +41 -0
  250. data/travis-bundler.rb +11 -0
  251. data/yardoc/2.2.2.tar.gz +0 -0
  252. metadata +575 -0
@@ -0,0 +1,11 @@
1
+ <h1>Search Results for <kbd><%= query %></kbd></h1>
2
+
3
+ <% unless results.empty? %>
4
+ <ul class="doc-list">
5
+ <% results.each do |result| %>
6
+ <li>
7
+ <a href="solargraph:/document?query=<%= CGI.escape(result) %>"><%= result %></a>
8
+ </li>
9
+ <% end %>
10
+ </ul>
11
+ <% end %>
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ # A workspace consists of the files in a project's directory and the
5
+ # project's configuration. It provides a Source for each file to be used
6
+ # in an associated Library or ApiMap.
7
+ #
8
+ class Workspace
9
+ autoload :Config, 'solargraph/workspace/config'
10
+
11
+ # @return [String]
12
+ attr_reader :directory
13
+
14
+ # The require paths associated with the workspace.
15
+ #
16
+ # @return [Array<String>]
17
+ attr_reader :require_paths
18
+
19
+ # @return [Array<String>]
20
+ attr_reader :gemnames
21
+
22
+ # @param directory [String]
23
+ # @param config [Config, nil]
24
+ def initialize directory = '', config = nil
25
+ @directory = directory
26
+ @config = config
27
+ load_sources
28
+ @gemnames = []
29
+ @require_paths = generate_require_paths
30
+ require_plugins
31
+ end
32
+
33
+ # @return [Solargraph::Workspace::Config]
34
+ def config
35
+ @config ||= Solargraph::Workspace::Config.new(directory)
36
+ end
37
+
38
+ # Merge the source. A merge will update the existing source for the file
39
+ # or add it to the sources if the workspace is configured to include it.
40
+ # The source is ignored if the configuration excludes it.
41
+ #
42
+ # @param source [Solargraph::Source]
43
+ # @return [Boolean] True if the source was added to the workspace
44
+ def merge source
45
+ unless directory == '*' || source_hash.key?(source.filename)
46
+ # Reload the config to determine if a new source should be included
47
+ @config = Solargraph::Workspace::Config.new(directory)
48
+ return false unless config.calculated.include?(source.filename)
49
+ end
50
+ source_hash[source.filename] = source
51
+ true
52
+ end
53
+
54
+ # Determine whether a file would be merged into the workspace.
55
+ #
56
+ # @param filename [String]
57
+ # @return [Boolean]
58
+ def would_merge? filename
59
+ return true if directory == '*' || source_hash.include?(filename)
60
+ @config = Solargraph::Workspace::Config.new(directory)
61
+ config.calculated.include?(filename)
62
+ end
63
+
64
+ # Remove a source from the workspace. The source will not be removed if
65
+ # its file exists and the workspace is configured to include it.
66
+ #
67
+ # @param filename [String]
68
+ # @return [Boolean] True if the source was removed from the workspace
69
+ def remove filename
70
+ return false unless source_hash.key?(filename)
71
+ source_hash.delete filename
72
+ true
73
+ end
74
+
75
+ # @return [Array<String>]
76
+ def filenames
77
+ source_hash.keys
78
+ end
79
+
80
+ # @return [Array<Solargraph::Source>]
81
+ def sources
82
+ source_hash.values
83
+ end
84
+
85
+ # @param filename [String]
86
+ # @return [Boolean]
87
+ def has_file? filename
88
+ source_hash.key?(filename)
89
+ end
90
+
91
+ # Get a source by its filename.
92
+ #
93
+ # @param filename [String]
94
+ # @return [Solargraph::Source]
95
+ def source filename
96
+ source_hash[filename]
97
+ end
98
+
99
+ # True if the path resolves to a file in the workspace's require paths.
100
+ #
101
+ # @param path [String]
102
+ # @return [Boolean]
103
+ def would_require? path
104
+ require_paths.each do |rp|
105
+ return true if File.exist?(File.join(rp, "#{path}.rb"))
106
+ end
107
+ false
108
+ end
109
+
110
+ # True if the workspace contains at least one gemspec file.
111
+ #
112
+ # @return [Boolean]
113
+ def gemspec?
114
+ !gemspecs.empty?
115
+ end
116
+
117
+ # Get an array of all gemspec files in the workspace.
118
+ #
119
+ # @return [Array<String>]
120
+ def gemspecs
121
+ return [] if directory.empty? || directory == '*'
122
+ @gemspecs ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs|
123
+ config.allow? gs
124
+ end
125
+ end
126
+
127
+ # Synchronize the workspace from the provided updater.
128
+ #
129
+ # @param updater [Source::Updater]
130
+ # @return [void]
131
+ def synchronize! updater
132
+ source_hash[updater.filename] = source_hash[updater.filename].synchronize(updater)
133
+ end
134
+
135
+ private
136
+
137
+ # @return [Hash{String => Solargraph::Source}]
138
+ def source_hash
139
+ @source_hash ||= {}
140
+ end
141
+
142
+ # @return [void]
143
+ def load_sources
144
+ source_hash.clear
145
+ unless directory.empty? || directory == '*'
146
+ size = config.calculated.length
147
+ raise WorkspaceTooLargeError, "The workspace is too large to index (#{size} files, #{config.max_files} max)" if config.max_files > 0 and size > config.max_files
148
+ config.calculated.each do |filename|
149
+ begin
150
+ source_hash[filename] = Solargraph::Source.load(filename)
151
+ rescue Errno::ENOENT => e
152
+ Solargraph.logger.warn("Error loading #{filename}: [#{e.class}] #{e.message}")
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ # Generate require paths from gemspecs if they exist or assume the default
159
+ # lib directory.
160
+ #
161
+ # @return [Array<String>]
162
+ def generate_require_paths
163
+ return configured_require_paths unless gemspec?
164
+ result = []
165
+ gemspecs.each do |file|
166
+ base = File.dirname(file)
167
+ # HACK: Evaluating gemspec files violates the goal of not running
168
+ # workspace code, but this is how Gem::Specification.load does it
169
+ # anyway.
170
+ Dir.chdir base do
171
+ begin
172
+ # @type [Gem::Specification]
173
+ spec = eval(File.read(file), TOPLEVEL_BINDING, file)
174
+ next unless Gem::Specification === spec
175
+ @gemnames.push spec.name
176
+ result.concat(spec.require_paths.map { |path| File.join(base, path) })
177
+ rescue RuntimeError, ScriptError, Errno::ENOENT => e
178
+ # Don't die if we have an error during eval-ing a gem spec.
179
+ # Concat the default lib directory instead.
180
+ Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}"
181
+ result.push File.join(base, 'lib')
182
+ end
183
+ end
184
+ end
185
+ result.concat config.require_paths
186
+ result.push File.join(directory, 'lib') if result.empty?
187
+ result
188
+ end
189
+
190
+ # Get additional require paths defined in the configuration.
191
+ #
192
+ # @return [Array<String>]
193
+ def configured_require_paths
194
+ return ['lib'] if directory.empty?
195
+ return [File.join(directory, 'lib')] if config.require_paths.empty?
196
+ config.require_paths.map{|p| File.join(directory, p)}
197
+ end
198
+
199
+ def require_plugins
200
+ config.plugins.each do |plugin|
201
+ begin
202
+ require plugin
203
+ rescue LoadError
204
+ Solargraph.logger.warn "Failed to load plugin '#{plugin}'"
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Solargraph
6
+ class Workspace
7
+ # Configuration data for a workspace.
8
+ #
9
+ class Config
10
+ # The maximum number of files that can be added to a workspace.
11
+ # The workspace's .solargraph.yml can override this value.
12
+ MAX_FILES = 5000
13
+
14
+ # @return [String]
15
+ attr_reader :directory
16
+
17
+ # @return [Hash]
18
+ attr_reader :raw_data
19
+
20
+ # @param directory [String]
21
+ def initialize directory = ''
22
+ @directory = directory
23
+ @raw_data = config_data
24
+ included
25
+ excluded
26
+ end
27
+
28
+ # An array of files included in the workspace (before calculating excluded files).
29
+ #
30
+ # @return [Array<String>]
31
+ def included
32
+ return [] if directory.empty? || directory == '*'
33
+ @included ||= process_globs(@raw_data['include'])
34
+ end
35
+
36
+ # An array of files excluded from the workspace.
37
+ #
38
+ # @return [Array<String>]
39
+ def excluded
40
+ return [] if directory.empty? || directory == '*'
41
+ @excluded ||= process_exclusions(@raw_data['exclude'])
42
+ end
43
+
44
+ def allow? filename
45
+ filename.start_with?(directory) &&
46
+ !excluded.include?(filename) &&
47
+ excluded_directories.none? { |d| filename.start_with?(d) }
48
+ end
49
+
50
+ # The calculated array of (included - excluded) files in the workspace.
51
+ #
52
+ # @return [Array<String>]
53
+ def calculated
54
+ Solargraph.logger.info "Indexing workspace files in #{directory}" unless @calculated || directory.empty? || directory == '*'
55
+ @calculated ||= included - excluded
56
+ end
57
+
58
+ # An array of domains configured for the workspace.
59
+ # A domain is a namespace that the ApiMap should include in the global
60
+ # namespace. It's typically used to identify available DSLs.
61
+ #
62
+ # @return [Array<String>]
63
+ def domains
64
+ raw_data['domains']
65
+ end
66
+
67
+ # An array of required paths to add to the workspace.
68
+ #
69
+ # @return [Array<String>]
70
+ def required
71
+ raw_data['require']
72
+ end
73
+
74
+ # An array of load paths for required paths.
75
+ #
76
+ # @return [Array<String>]
77
+ def require_paths
78
+ raw_data['require_paths'] || []
79
+ end
80
+
81
+ # An array of reporters to use for diagnostics.
82
+ #
83
+ # @return [Array<String>]
84
+ def reporters
85
+ raw_data['reporters']
86
+ end
87
+
88
+ # An array of plugins to require.
89
+ #
90
+ # @return [Array<String>]
91
+ def plugins
92
+ raw_data['plugins']
93
+ end
94
+
95
+ # The maximum number of files to parse from the workspace.
96
+ #
97
+ # @return [Integer]
98
+ def max_files
99
+ raw_data['max_files']
100
+ end
101
+
102
+ private
103
+
104
+ # @return [String]
105
+ def global_config_path
106
+ ENV['SOLARGRAPH_GLOBAL_CONFIG'] ||
107
+ File.join(Dir.home, '.config', 'solargraph', 'config.yml')
108
+ end
109
+
110
+ # @return [String]
111
+ def workspace_config_path
112
+ return '' if @directory.empty?
113
+ File.join(@directory, '.solargraph.yml')
114
+ end
115
+
116
+ # @return [Hash]
117
+ def config_data
118
+ workspace_config = read_config(workspace_config_path)
119
+ global_config = read_config(global_config_path)
120
+
121
+ defaults = default_config
122
+ defaults.merge({'exclude' => []}) unless workspace_config.nil?
123
+
124
+ defaults
125
+ .merge(global_config || {})
126
+ .merge(workspace_config || {})
127
+ end
128
+
129
+ # Read a .solargraph yaml config
130
+ #
131
+ # @param directory [String]
132
+ # @return [Hash, nil]
133
+ def read_config config_path = ''
134
+ return nil if config_path.empty?
135
+ return nil unless File.file?(config_path)
136
+ YAML.safe_load(File.read(config_path))
137
+ end
138
+
139
+ # @return [Hash]
140
+ def default_config
141
+ {
142
+ 'include' => ['**/*.rb'],
143
+ 'exclude' => ['spec/**/*', 'test/**/*', 'vendor/**/*', '.bundle/**/*'],
144
+ 'require' => [],
145
+ 'domains' => [],
146
+ 'reporters' => %w[rubocop require_not_found],
147
+ 'require_paths' => [],
148
+ 'plugins' => [],
149
+ 'max_files' => MAX_FILES
150
+ }
151
+ end
152
+
153
+ # Get an array of files from the provided globs.
154
+ #
155
+ # @param globs [Array<String>]
156
+ # @return [Array<String>]
157
+ def process_globs globs
158
+ result = []
159
+ globs.each do |glob|
160
+ result.concat Dir[File.join directory, glob].map{ |f| f.gsub(/\\/, '/') }
161
+ end
162
+ result
163
+ end
164
+
165
+ # Modify the included files based on excluded directories and get an
166
+ # array of additional files to exclude.
167
+ #
168
+ # @param globs [Array<String>]
169
+ # @return [Array<String>]
170
+ def process_exclusions globs
171
+ remainder = globs.select do |glob|
172
+ if glob_is_directory?(glob)
173
+ exdir = File.join(directory, glob_to_directory(glob))
174
+ included.delete_if { |file| file.start_with?(exdir) }
175
+ false
176
+ else
177
+ true
178
+ end
179
+ end
180
+ process_globs remainder
181
+ end
182
+
183
+ # True if the glob translates to a whole directory.
184
+ #
185
+ # @example
186
+ # glob_is_directory?('path/to/dir') # => true
187
+ # glob_is_directory?('path/to/dir/**/*) # => true
188
+ # glob_is_directory?('path/to/file.txt') # => false
189
+ # glob_is_directory?('path/to/*.txt') # => false
190
+ #
191
+ # @param glob [String]
192
+ # @return [Boolean]
193
+ def glob_is_directory? glob
194
+ File.directory?(glob) || File.directory?(glob_to_directory(glob))
195
+ end
196
+
197
+ # Translate a glob to a base directory if applicable
198
+ #
199
+ # @example
200
+ # glob_to_directory('path/to/dir/**/*') # => 'path/to/dir'
201
+ #
202
+ # @param glob [String]
203
+ # @return [String]
204
+ def glob_to_directory glob
205
+ glob.gsub(/(\/\*|\/\*\*\/\*\*?)$/, '')
206
+ end
207
+
208
+ def excluded_directories
209
+ @raw_data['exclude']
210
+ .select { |g| glob_is_directory?(g) }
211
+ .map { |g| File.join(directory, glob_to_directory(g)) }
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,420 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yard'
4
+ require 'yard-solargraph'
5
+ require 'rubygems/package'
6
+
7
+ module Solargraph
8
+ # The YardMap provides access to YARD documentation for the Ruby core, the
9
+ # stdlib, and gems.
10
+ #
11
+ class YardMap
12
+ autoload :Cache, 'solargraph/yard_map/cache'
13
+ autoload :CoreDocs, 'solargraph/yard_map/core_docs'
14
+ autoload :CoreGen, 'solargraph/yard_map/core_gen'
15
+ autoload :Mapper, 'solargraph/yard_map/mapper'
16
+ autoload :RdocToYard, 'solargraph/yard_map/rdoc_to_yard'
17
+
18
+ CoreDocs.require_minimum
19
+
20
+ def stdlib_paths
21
+ @@stdlib_paths ||= begin
22
+ result = {}
23
+ YARD::Registry.load! CoreDocs.yardoc_stdlib_file
24
+ YARD::Registry.all.each do |co|
25
+ next if co.file.nil?
26
+ path = co.file.sub(/^(ext|lib)\//, '').sub(/\.(rb|c)$/, '')
27
+ base = path.split('/').first
28
+ result[base] ||= []
29
+ result[base].push co
30
+ end
31
+ result
32
+ end
33
+ end
34
+
35
+ # @return [Array<String>]
36
+ attr_reader :required
37
+
38
+ # @return [Boolean]
39
+ attr_writer :with_dependencies
40
+
41
+ # A hash of gem names and the version numbers to include in the map.
42
+ #
43
+ # @return [Hash{String => String}]
44
+ attr_reader :gemset
45
+
46
+ # @param required [Array<String>]
47
+ # @param gemset [Hash{String => String}]
48
+ # @param with_dependencies [Boolean]
49
+ def initialize(required: [], gemset: {}, with_dependencies: true)
50
+ # HACK: YardMap needs its own copy of this array
51
+ @required = required.clone
52
+ @with_dependencies = with_dependencies
53
+ @gem_paths = {}
54
+ @stdlib_namespaces = []
55
+ @gemset = gemset
56
+ @source_gems = []
57
+ process_requires
58
+ yardocs.uniq!
59
+ end
60
+
61
+ # @return [Array<Solargraph::Pin::Base>]
62
+ def pins
63
+ @pins ||= []
64
+ end
65
+
66
+ def with_dependencies?
67
+ @with_dependencies ||= true unless @with_dependencies == false
68
+ @with_dependencies
69
+ end
70
+
71
+ # @param new_requires [Array<String>]
72
+ # @param new_gemset [Hash{String => String}]
73
+ # @return [Boolean]
74
+ def change new_requires, new_gemset, source_gems = []
75
+ if new_requires.uniq.sort == required.uniq.sort && new_gemset == gemset && @source_gems.uniq.sort == source_gems.uniq.sort
76
+ false
77
+ else
78
+ required.clear
79
+ required.concat new_requires
80
+ @gemset = new_gemset
81
+ @source_gems = source_gems
82
+ process_requires
83
+ true
84
+ end
85
+ end
86
+
87
+ # @return [Array<String>]
88
+ def yardocs
89
+ @yardocs ||= []
90
+ end
91
+
92
+ # @return [Array<String>]
93
+ def unresolved_requires
94
+ @unresolved_requires ||= []
95
+ end
96
+
97
+ # @param y [String]
98
+ # @return [YARD::Registry]
99
+ def load_yardoc y
100
+ if y.is_a?(Array)
101
+ YARD::Registry.load y, true
102
+ else
103
+ YARD::Registry.load! y
104
+ end
105
+ rescue StandardError => e
106
+ Solargraph::Logging.logger.warn "Error loading yardoc '#{y}' #{e.class} #{e.message}"
107
+ yardocs.delete y
108
+ nil
109
+ end
110
+
111
+ # @return [Array<Solargraph::Pin::Base>]
112
+ def core_pins
113
+ # Using a class variable to reduce loads
114
+ @@core_pins ||= load_core_pins
115
+ end
116
+
117
+ # @param path [String]
118
+ # @return [Pin::Base]
119
+ def path_pin path
120
+ pins.select{ |p| p.path == path }.first
121
+ end
122
+
123
+ # Get the location of a file referenced by a require path.
124
+ #
125
+ # @param path [String]
126
+ # @return [Location]
127
+ def require_reference path
128
+ # @type [Gem::Specification]
129
+ spec = spec_for_require(path)
130
+ spec.full_require_paths.each do |rp|
131
+ file = File.join(rp, "#{path}.rb")
132
+ next unless File.file?(file)
133
+ return Solargraph::Location.new(file, Solargraph::Range.from_to(0, 0, 0, 0))
134
+ end
135
+ nil
136
+ rescue Gem::LoadError
137
+ nil
138
+ end
139
+
140
+ private
141
+
142
+ # @return [YardMap::Cache]
143
+ def cache
144
+ @cache ||= YardMap::Cache.new
145
+ end
146
+
147
+ # @param ns [YARD::CodeObjects::NamespaceObject]
148
+ # @return [Array<YARD::CodeObjects::Base>]
149
+ def recurse_namespace_object ns
150
+ result = []
151
+ ns.children.each do |c|
152
+ result.push c
153
+ result.concat recurse_namespace_object(c) if c.respond_to?(:children)
154
+ end
155
+ result
156
+ end
157
+
158
+ # @return [void]
159
+ def process_requires
160
+ pins.clear
161
+ unresolved_requires.clear
162
+ # stdnames = {}
163
+ done = []
164
+ from_std = []
165
+ required.each do |r|
166
+ next if r.nil? || r.empty? || done.include?(r)
167
+ done.push r
168
+ cached = cache.get_path_pins(r)
169
+ unless cached.nil?
170
+ pins.concat cached
171
+ next
172
+ end
173
+ result = []
174
+ begin
175
+ spec = spec_for_require(r)
176
+ if @source_gems.include?(spec.name)
177
+ next
178
+ end
179
+ next if @gem_paths.key?(spec.name)
180
+ yd = yardoc_file_for_spec(spec)
181
+ # YARD detects gems for certain libraries that do not have a yardoc
182
+ # but exist in the stdlib. `fileutils` is an example. Treat those
183
+ # cases as errors and check the stdlib yardoc.
184
+ raise Gem::LoadError if yd.nil?
185
+ @gem_paths[spec.name] = spec.full_gem_path
186
+ unless yardocs.include?(yd)
187
+ yardocs.unshift yd
188
+ result.concat process_yardoc yd, spec
189
+ result.concat add_gem_dependencies(spec) if with_dependencies?
190
+ stdlib_fill r, result
191
+ end
192
+ rescue Gem::LoadError => e
193
+ base = r.split('/').first
194
+ next if from_std.include?(base)
195
+ from_std.push base
196
+ stdtmp = load_stdlib_pins(base)
197
+ if stdtmp.empty?
198
+ unresolved_requires.push r
199
+ else
200
+ stdlib_fill base, stdtmp
201
+ result.concat stdtmp
202
+ end
203
+ end
204
+ result.delete_if(&:nil?)
205
+ unless result.empty?
206
+ cache.set_path_pins r, result
207
+ pins.concat result
208
+ end
209
+ end
210
+ pins.concat core_pins
211
+ end
212
+
213
+ # @param spec [Gem::Specification]
214
+ # @return [void]
215
+ def add_gem_dependencies spec
216
+ result = []
217
+ (spec.dependencies - spec.development_dependencies).each do |dep|
218
+ begin
219
+ next if @source_gems.include?(dep.name) || @gem_paths.key?(dep.name)
220
+ depspec = Gem::Specification.find_by_name(dep.name)
221
+ next if depspec.nil?
222
+ @gem_paths[depspec.name] = depspec.full_gem_path
223
+ gy = yardoc_file_for_spec(depspec)
224
+ if gy.nil?
225
+ unresolved_requires.push dep.name
226
+ else
227
+ next if yardocs.include?(gy)
228
+ yardocs.unshift gy
229
+ result.concat process_yardoc gy, depspec
230
+ result.concat add_gem_dependencies(depspec)
231
+ end
232
+ rescue Gem::LoadError
233
+ # This error probably indicates a bug in an installed gem
234
+ Solargraph::Logging.logger.warn "Failed to resolve #{dep.name} gem dependency for #{spec.name}"
235
+ end
236
+ end
237
+ result
238
+ end
239
+
240
+ # @param y [String, nil]
241
+ # @param spec [Gem::Specification, nil]
242
+ # @return [Array<Pin::Base>]
243
+ def process_yardoc y, spec = nil
244
+ return [] if y.nil?
245
+ size = Dir.glob(File.join(y, '**', '*'))
246
+ .map{ |f| File.size(f) }
247
+ .inject(:+)
248
+ if spec
249
+ ser = File.join(CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}.ser")
250
+ if File.file?(ser)
251
+ Solargraph.logger.info "Loading #{spec.name} #{spec.version} from cache"
252
+ file = File.open(ser, 'rb')
253
+ dump = file.read
254
+ file.close
255
+ begin
256
+ return Marshal.load(dump)
257
+ rescue StandardError => e
258
+ Solargraph.logger.warn "Error loading pin cache: [#{e.class}] #{e.message}"
259
+ File.unlink ser
260
+ end
261
+ end
262
+ end
263
+ if !size.nil? && size > 20_000_000
264
+ Solargraph::Logging.logger.warn "Yardoc at #{y} is too large to process (#{size} bytes)"
265
+ return []
266
+ end
267
+ load_yardoc y
268
+ Solargraph.logger.info "Loading #{spec.name} #{spec.version} from yardoc"
269
+ result = Mapper.new(YARD::Registry.all, spec).map
270
+ if spec
271
+ ser = File.join(CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}.ser")
272
+ file = File.open(ser, 'wb')
273
+ file.write Marshal.dump(result)
274
+ file.close
275
+ end
276
+ result
277
+ end
278
+
279
+ # @param spec [Gem::Specification]
280
+ # @return [String]
281
+ def yardoc_file_for_spec spec
282
+ cache_dir = File.join(Solargraph::YardMap::CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}", 'yardoc')
283
+ if File.exist?(cache_dir)
284
+ Solargraph.logger.info "Using cached documentation for #{spec.name} at #{cache_dir}"
285
+ cache_dir
286
+ else
287
+ YARD::Registry.yardoc_file_for_gem(spec.name, "= #{spec.version}")
288
+ end
289
+ end
290
+
291
+ # @param path [String]
292
+ # @return [Gem::Specification]
293
+ def spec_for_require path
294
+ spec = Gem::Specification.find_by_path(path) || Gem::Specification.find_by_name(path.split('/').first)
295
+ # Avoid loading the spec again if it's going to be skipped anyway
296
+ return spec if @source_gems.include?(spec.name)
297
+ # Avoid loading the spec again if it's already the correct version
298
+ if @gemset[spec.name] && @gemset[spec.name] != spec.version
299
+ begin
300
+ return Gem::Specification.find_by_name(spec.name, "= #{@gemset[spec.name]}")
301
+ rescue Gem::LoadError
302
+ Solargraph.logger.warn "Unable to load #{spec.name} #{@gemset[spec.name]} specified by workspace, using #{spec.version} instead"
303
+ end
304
+ end
305
+ spec
306
+ end
307
+
308
+ # @param path [String]
309
+ # @param pins [Array<Pin::Base>]
310
+ # @return [void]
311
+ def stdlib_fill path, pins
312
+ StdlibFills.get(path).each do |ovr|
313
+ pin = pins.select { |p| p.path == ovr.name }.first
314
+ next if pin.nil?
315
+ (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag|
316
+ pin.docstring.delete_tags tag.to_sym
317
+ end
318
+ ovr.tags.each do |tag|
319
+ pin.docstring.add_tag(tag)
320
+ end
321
+ end
322
+ end
323
+
324
+ def load_core_pins
325
+ yd = CoreDocs.yardoc_file
326
+ ser = File.join(File.dirname(yd), 'core.ser')
327
+ result = if File.file?(ser)
328
+ file = File.open(ser, 'rb')
329
+ dump = file.read
330
+ file.close
331
+ begin
332
+ Marshal.load(dump)
333
+ rescue StandardError => e
334
+ Solargraph.logger.warn "Error loading core pin cache: [#{e.class}] #{e.message}"
335
+ File.unlink ser
336
+ read_core_and_save_cache(yd, ser)
337
+ end
338
+ else
339
+ read_core_and_save_cache(yd, ser)
340
+ end
341
+ # HACK: Add Errno exception classes
342
+ errno = result.select{ |pin| pin.path == 'Errno' }.first
343
+ Errno.constants.each do |const|
344
+ result.push Solargraph::Pin::Namespace.new(type: :class, name: const.to_s, closure: errno)
345
+ result.push Solargraph::Pin::Reference::Superclass.new(closure: result.last, name: 'SystemCallError')
346
+ end
347
+ CoreFills::OVERRIDES.each do |ovr|
348
+ pin = result.select { |p| p.path == ovr.name }.first
349
+ next if pin.nil?
350
+ (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag|
351
+ pin.docstring.delete_tags tag.to_sym
352
+ end
353
+ ovr.tags.each do |tag|
354
+ pin.docstring.add_tag(tag)
355
+ end
356
+ end
357
+ result
358
+ end
359
+
360
+ def read_core_and_save_cache yd, ser
361
+ result = []
362
+ load_yardoc yd
363
+ result.concat Mapper.new(YARD::Registry.all).map
364
+ # HACK: Assume core methods with a single `args` parameter accept restarg
365
+ result.select { |pin| pin.is_a?(Solargraph::Pin::BaseMethod )}.each do |pin|
366
+ if pin.parameters.length == 1 && pin.parameters.first.name == 'args' && pin.parameters.first.decl == :arg
367
+ # @todo Smelly instance variable access
368
+ pin.parameters.first.instance_variable_set(:@decl, :restarg)
369
+ end
370
+ end
371
+ # HACK: Set missing parameters on `==` methods, e.g., `Symbol#==`
372
+ result.select { |pin| pin.name == '==' && pin.parameters.empty? }.each do |pin|
373
+ pin.parameters.push Pin::Parameter.new(decl: :arg, name: 'obj2')
374
+ end
375
+ dump = Marshal.dump(result)
376
+ file = File.open(ser, 'wb')
377
+ file.write dump
378
+ file.close
379
+ result
380
+ end
381
+
382
+ def load_stdlib_pins base
383
+ ser = File.join(File.dirname(CoreDocs.yardoc_stdlib_file), "#{base}.ser")
384
+ if File.file?(ser)
385
+ Solargraph.logger.info "Loading #{base} stdlib from cache"
386
+ file = File.open(ser, 'rb')
387
+ dump = file.read
388
+ file.close
389
+ begin
390
+ Marshal.load(dump)
391
+ rescue StandardError => e
392
+ Solargraph.logger.warn "Error loading #{base} stdlib pin cache: [#{e.class}] #{e.message}"
393
+ File.unlink ser
394
+ read_stdlib_and_save_cache(base, ser)
395
+ end
396
+ else
397
+ read_stdlib_and_save_cache(base, ser)
398
+ end
399
+ end
400
+
401
+ def read_stdlib_and_save_cache base, ser
402
+ result = []
403
+ if stdlib_paths[base]
404
+ Solargraph.logger.info "Loading #{base} stdlib from yardoc"
405
+ result.concat Mapper.new(stdlib_paths[base]).map
406
+ unless result.empty?
407
+ dump = Marshal.dump(result)
408
+ file = File.open(ser, 'wb')
409
+ file.write dump
410
+ file.close
411
+ end
412
+ end
413
+ result
414
+ end
415
+ end
416
+ end
417
+
418
+ Solargraph::YardMap::CoreDocs.require_minimum
419
+ # Change YARD log IO to avoid sending unexpected messages to STDOUT
420
+ YARD::Logger.instance.io = File.new(File::NULL, 'w')