solargraph 0.32.1 → 0.32.2

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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +25 -0
  6. data/EXAMPLES.md +76 -0
  7. data/Gemfile +3 -0
  8. data/LANGUAGE_SERVER.md +51 -0
  9. data/LICENSE +21 -0
  10. data/OVERVIEW.md +37 -0
  11. data/README.md +106 -0
  12. data/Rakefile +14 -0
  13. data/SERVER.md +95 -0
  14. data/bin/solargraph +0 -0
  15. data/bin/solargraph-runtime +5 -5
  16. data/lib/solargraph.rb +54 -54
  17. data/lib/solargraph/api_map.rb +659 -659
  18. data/lib/solargraph/api_map/cache.rb +49 -49
  19. data/lib/solargraph/api_map/source_to_yard.rb +67 -67
  20. data/lib/solargraph/api_map/store.rb +201 -201
  21. data/lib/solargraph/bundle.rb +24 -24
  22. data/lib/solargraph/complex_type.rb +150 -150
  23. data/lib/solargraph/complex_type/type_methods.rb +124 -124
  24. data/lib/solargraph/complex_type/unique_type.rb +44 -44
  25. data/lib/solargraph/core_fills.rb +37 -37
  26. data/lib/solargraph/diagnostics.rb +52 -52
  27. data/lib/solargraph/diagnostics/base.rb +20 -20
  28. data/lib/solargraph/diagnostics/require_not_found.rb +28 -28
  29. data/lib/solargraph/diagnostics/rubocop.rb +98 -98
  30. data/lib/solargraph/diagnostics/rubocop_helpers.rb +46 -46
  31. data/lib/solargraph/diagnostics/type_not_defined.rb +108 -108
  32. data/lib/solargraph/diagnostics/update_errors.rb +38 -38
  33. data/lib/solargraph/language_server/completion_item_kinds.rb +33 -33
  34. data/lib/solargraph/language_server/error_codes.rb +18 -18
  35. data/lib/solargraph/language_server/host.rb +684 -681
  36. data/lib/solargraph/language_server/host/cataloger.rb +54 -79
  37. data/lib/solargraph/language_server/host/diagnoser.rb +80 -80
  38. data/lib/solargraph/language_server/host/dispatch.rb +112 -113
  39. data/lib/solargraph/language_server/host/sources.rb +138 -138
  40. data/lib/solargraph/language_server/message.rb +90 -90
  41. data/lib/solargraph/language_server/message/base.rb +83 -83
  42. data/lib/solargraph/language_server/message/completion_item/resolve.rb +40 -40
  43. data/lib/solargraph/language_server/message/exit_notification.rb +11 -11
  44. data/lib/solargraph/language_server/message/extended.rb +19 -19
  45. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +86 -86
  46. data/lib/solargraph/language_server/message/extended/document.rb +18 -18
  47. data/lib/solargraph/language_server/message/extended/document_gems.rb +30 -30
  48. data/lib/solargraph/language_server/message/extended/environment.rb +20 -20
  49. data/lib/solargraph/language_server/message/extended/search.rb +18 -18
  50. data/lib/solargraph/language_server/message/initialize.rb +141 -141
  51. data/lib/solargraph/language_server/message/initialized.rb +23 -23
  52. data/lib/solargraph/language_server/message/shutdown.rb +11 -11
  53. data/lib/solargraph/language_server/message/text_document.rb +25 -25
  54. data/lib/solargraph/language_server/message/text_document/completion.rb +51 -51
  55. data/lib/solargraph/language_server/message/text_document/definition.rb +18 -18
  56. data/lib/solargraph/language_server/message/text_document/did_change.rb +13 -13
  57. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +21 -21
  58. data/lib/solargraph/language_server/message/text_document/folding_range.rb +24 -24
  59. data/lib/solargraph/language_server/message/text_document/formatting.rb +50 -50
  60. data/lib/solargraph/language_server/message/text_document/hover.rb +31 -31
  61. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +32 -32
  62. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +9 -9
  63. data/lib/solargraph/language_server/message/text_document/references.rb +14 -14
  64. data/lib/solargraph/language_server/message/text_document/rename.rb +17 -17
  65. data/lib/solargraph/language_server/message/text_document/signature_help.rb +19 -19
  66. data/lib/solargraph/language_server/message/workspace.rb +12 -12
  67. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -29
  68. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +29 -27
  69. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
  70. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +21 -21
  71. data/lib/solargraph/language_server/request.rb +22 -22
  72. data/lib/solargraph/language_server/symbol_kinds.rb +34 -34
  73. data/lib/solargraph/language_server/transport.rb +11 -11
  74. data/lib/solargraph/language_server/transport/adapter.rb +60 -60
  75. data/lib/solargraph/language_server/transport/data_reader.rb +66 -66
  76. data/lib/solargraph/language_server/uri_helpers.rb +25 -25
  77. data/lib/solargraph/library.rb +421 -419
  78. data/lib/solargraph/live_map.rb +126 -126
  79. data/lib/solargraph/live_map/cache.rb +38 -38
  80. data/lib/solargraph/location.rb +31 -31
  81. data/lib/solargraph/logging.rb +25 -25
  82. data/lib/solargraph/page.rb +68 -68
  83. data/lib/solargraph/pin.rb +50 -50
  84. data/lib/solargraph/pin/attribute.rb +41 -41
  85. data/lib/solargraph/pin/base.rb +280 -280
  86. data/lib/solargraph/pin/base_method.rb +76 -76
  87. data/lib/solargraph/pin/base_variable.rb +72 -72
  88. data/lib/solargraph/pin/block.rb +32 -32
  89. data/lib/solargraph/pin/block_parameter.rb +103 -103
  90. data/lib/solargraph/pin/class_variable.rb +9 -9
  91. data/lib/solargraph/pin/constant.rb +30 -30
  92. data/lib/solargraph/pin/conversions.rb +79 -79
  93. data/lib/solargraph/pin/documenting.rb +41 -41
  94. data/lib/solargraph/pin/duck_method.rb +14 -14
  95. data/lib/solargraph/pin/global_variable.rb +9 -9
  96. data/lib/solargraph/pin/instance_variable.rb +9 -9
  97. data/lib/solargraph/pin/keyword.rb +17 -17
  98. data/lib/solargraph/pin/local_variable.rb +23 -23
  99. data/lib/solargraph/pin/localized.rb +22 -22
  100. data/lib/solargraph/pin/method.rb +126 -126
  101. data/lib/solargraph/pin/method_alias.rb +30 -30
  102. data/lib/solargraph/pin/method_parameter.rb +40 -40
  103. data/lib/solargraph/pin/namespace.rb +54 -54
  104. data/lib/solargraph/pin/plugin/method.rb +25 -25
  105. data/lib/solargraph/pin/proxy_type.rb +35 -35
  106. data/lib/solargraph/pin/reference.rb +22 -22
  107. data/lib/solargraph/pin/reference/extend.rb +11 -11
  108. data/lib/solargraph/pin/reference/include.rb +11 -11
  109. data/lib/solargraph/pin/reference/require.rb +15 -15
  110. data/lib/solargraph/pin/reference/superclass.rb +11 -11
  111. data/lib/solargraph/pin/symbol.rb +44 -44
  112. data/lib/solargraph/pin/yard_pin.rb +10 -10
  113. data/lib/solargraph/pin/yard_pin/constant.rb +14 -14
  114. data/lib/solargraph/pin/yard_pin/method.rb +35 -35
  115. data/lib/solargraph/pin/yard_pin/namespace.rb +19 -19
  116. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +14 -14
  117. data/lib/solargraph/plugin.rb +8 -8
  118. data/lib/solargraph/plugin/base.rb +41 -41
  119. data/lib/solargraph/plugin/canceler.rb +11 -11
  120. data/lib/solargraph/plugin/process.rb +172 -172
  121. data/lib/solargraph/plugin/runtime.rb +134 -134
  122. data/lib/solargraph/position.rb +110 -110
  123. data/lib/solargraph/range.rb +83 -83
  124. data/lib/solargraph/server_methods.rb +14 -14
  125. data/lib/solargraph/shell.rb +102 -102
  126. data/lib/solargraph/source.rb +521 -521
  127. data/lib/solargraph/source/chain.rb +120 -120
  128. data/lib/solargraph/source/chain/call.rb +107 -107
  129. data/lib/solargraph/source/chain/class_variable.rb +11 -11
  130. data/lib/solargraph/source/chain/constant.rb +30 -30
  131. data/lib/solargraph/source/chain/global_variable.rb +11 -11
  132. data/lib/solargraph/source/chain/head.rb +33 -33
  133. data/lib/solargraph/source/chain/instance_variable.rb +11 -11
  134. data/lib/solargraph/source/chain/link.rb +33 -33
  135. data/lib/solargraph/source/chain/literal.rb +21 -21
  136. data/lib/solargraph/source/chain/variable.rb +11 -11
  137. data/lib/solargraph/source/change.rb +77 -77
  138. data/lib/solargraph/source/cursor.rb +157 -157
  139. data/lib/solargraph/source/node_chainer.rb +96 -96
  140. data/lib/solargraph/source/node_methods.rb +225 -225
  141. data/lib/solargraph/source/source_chainer.rb +183 -183
  142. data/lib/solargraph/source_map.rb +169 -169
  143. data/lib/solargraph/source_map/clip.rb +145 -145
  144. data/lib/solargraph/source_map/completion.rb +21 -21
  145. data/lib/solargraph/source_map/mapper.rb +149 -149
  146. data/lib/solargraph/source_map/node_processor.rb +78 -78
  147. data/lib/solargraph/source_map/node_processor/alias_node.rb +19 -19
  148. data/lib/solargraph/source_map/node_processor/args_node.rb +28 -28
  149. data/lib/solargraph/source_map/node_processor/base.rb +68 -68
  150. data/lib/solargraph/source_map/node_processor/begin_node.rb +11 -11
  151. data/lib/solargraph/source_map/node_processor/block_node.rb +14 -14
  152. data/lib/solargraph/source_map/node_processor/casgn_node.rb +14 -14
  153. data/lib/solargraph/source_map/node_processor/cvasgn_node.rb +14 -14
  154. data/lib/solargraph/source_map/node_processor/def_node.rb +54 -54
  155. data/lib/solargraph/source_map/node_processor/defs_node.rb +21 -21
  156. data/lib/solargraph/source_map/node_processor/gvasgn_node.rb +12 -12
  157. data/lib/solargraph/source_map/node_processor/ivasgn_node.rb +18 -18
  158. data/lib/solargraph/source_map/node_processor/lvasgn_node.rb +16 -16
  159. data/lib/solargraph/source_map/node_processor/namespace_node.rb +26 -26
  160. data/lib/solargraph/source_map/node_processor/orasgn_node.rb +12 -12
  161. data/lib/solargraph/source_map/node_processor/sclass_node.rb +11 -11
  162. data/lib/solargraph/source_map/node_processor/send_node.rb +162 -162
  163. data/lib/solargraph/source_map/node_processor/sym_node.rb +11 -11
  164. data/lib/solargraph/source_map/region.rb +58 -58
  165. data/lib/solargraph/version.rb +3 -3
  166. data/lib/solargraph/views/environment.erb +53 -53
  167. data/lib/solargraph/workspace.rb +183 -183
  168. data/lib/solargraph/workspace/config.rb +170 -170
  169. data/lib/solargraph/yard_map.rb +298 -298
  170. data/lib/solargraph/yard_map/cache.rb +17 -17
  171. data/lib/solargraph/yard_map/core_docs.rb +163 -163
  172. data/lib/solargraph/yard_map/core_gen.rb +76 -76
  173. data/lib/yard-coregen.rb +16 -16
  174. data/lib/yard-solargraph.rb +18 -18
  175. data/solargraph.gemspec +37 -0
  176. data/travis-bundler.rb +10 -0
  177. metadata +19 -6
@@ -1,83 +1,83 @@
1
- module Solargraph
2
- # A pair of positions that compose a section of text.
3
- #
4
- class Range
5
- # @return [Position]
6
- attr_reader :start
7
-
8
- # @return [Position]
9
- attr_reader :ending
10
-
11
- # @param start [Position]
12
- # @param ending [Position]
13
- def initialize start, ending
14
- @start = start
15
- @ending = ending
16
- end
17
-
18
- # Get a hash of the range. This representation is suitable for use in
19
- # the language server protocol.
20
- #
21
- # @return [Hash<Symbol, Position>]
22
- def to_hash
23
- {
24
- start: start.to_hash,
25
- end: ending.to_hash
26
- }
27
- end
28
-
29
- # True if the specified position is inside the range.
30
- #
31
- # @param position [Position, Array(Integer, Integer)]
32
- # @return [Boolean]
33
- def contain? position
34
- position = Position.normalize(position)
35
- return false if position.line < start.line || position.line > ending.line
36
- return false if position.line == start.line && position.character < start.character
37
- return false if position.line == ending.line && position.character > ending.character
38
- true
39
- end
40
-
41
- # True if the range contains the specified position and the position does not precede it.
42
- #
43
- # @param position [Position, Array(Integer, Integer)]
44
- # @return [Boolean]
45
- def include? position
46
- position = Position.normalize(position)
47
- contain?(position) && !(position.line == start.line && position.character == start.character)
48
- end
49
-
50
- # Create a range from a pair of lines and characters.
51
- #
52
- # @param l1 [Integer] Starting line
53
- # @param c1 [Integer] Starting character
54
- # @param l2 [Integer] Ending line
55
- # @param c2 [Integer] Ending character
56
- # @return [Range]
57
- def self.from_to l1, c1, l2, c2
58
- Range.new(Position.new(l1, c1), Position.new(l2, c2))
59
- end
60
-
61
- # Get a range from a node.
62
- #
63
- # @param node [Parser::AST::Node]
64
- # @return [Range]
65
- def self.from_node node
66
- from_expr(node.loc.expression)
67
- end
68
-
69
- # Get a range from a Parser range, usually found in
70
- # Parser::AST::Node#location#expression.
71
- #
72
- # @param expr [Parser::Source::Range]
73
- # @return [Range]
74
- def self.from_expr expr
75
- from_to(expr.line, expr.column, expr.last_line, expr.last_column)
76
- end
77
-
78
- def == other
79
- return false unless other.is_a?(Range)
80
- start == other.start && ending == other.ending
81
- end
82
- end
83
- end
1
+ module Solargraph
2
+ # A pair of positions that compose a section of text.
3
+ #
4
+ class Range
5
+ # @return [Position]
6
+ attr_reader :start
7
+
8
+ # @return [Position]
9
+ attr_reader :ending
10
+
11
+ # @param start [Position]
12
+ # @param ending [Position]
13
+ def initialize start, ending
14
+ @start = start
15
+ @ending = ending
16
+ end
17
+
18
+ # Get a hash of the range. This representation is suitable for use in
19
+ # the language server protocol.
20
+ #
21
+ # @return [Hash<Symbol, Position>]
22
+ def to_hash
23
+ {
24
+ start: start.to_hash,
25
+ end: ending.to_hash
26
+ }
27
+ end
28
+
29
+ # True if the specified position is inside the range.
30
+ #
31
+ # @param position [Position, Array(Integer, Integer)]
32
+ # @return [Boolean]
33
+ def contain? position
34
+ position = Position.normalize(position)
35
+ return false if position.line < start.line || position.line > ending.line
36
+ return false if position.line == start.line && position.character < start.character
37
+ return false if position.line == ending.line && position.character > ending.character
38
+ true
39
+ end
40
+
41
+ # True if the range contains the specified position and the position does not precede it.
42
+ #
43
+ # @param position [Position, Array(Integer, Integer)]
44
+ # @return [Boolean]
45
+ def include? position
46
+ position = Position.normalize(position)
47
+ contain?(position) && !(position.line == start.line && position.character == start.character)
48
+ end
49
+
50
+ # Create a range from a pair of lines and characters.
51
+ #
52
+ # @param l1 [Integer] Starting line
53
+ # @param c1 [Integer] Starting character
54
+ # @param l2 [Integer] Ending line
55
+ # @param c2 [Integer] Ending character
56
+ # @return [Range]
57
+ def self.from_to l1, c1, l2, c2
58
+ Range.new(Position.new(l1, c1), Position.new(l2, c2))
59
+ end
60
+
61
+ # Get a range from a node.
62
+ #
63
+ # @param node [Parser::AST::Node]
64
+ # @return [Range]
65
+ def self.from_node node
66
+ from_expr(node.loc.expression)
67
+ end
68
+
69
+ # Get a range from a Parser range, usually found in
70
+ # Parser::AST::Node#location#expression.
71
+ #
72
+ # @param expr [Parser::Source::Range]
73
+ # @return [Range]
74
+ def self.from_expr expr
75
+ from_to(expr.line, expr.column, expr.last_line, expr.last_column)
76
+ end
77
+
78
+ def == other
79
+ return false unless other.is_a?(Range)
80
+ start == other.start && ending == other.ending
81
+ end
82
+ end
83
+ end
@@ -1,14 +1,14 @@
1
- require 'socket'
2
-
3
- module Solargraph
4
- module ServerMethods
5
- # @return [Integer]
6
- def available_port
7
- socket = Socket.new(:INET, :STREAM, 0)
8
- socket.bind(Addrinfo.tcp("127.0.0.1", 0))
9
- port = socket.local_address.ip_port
10
- socket.close
11
- port
12
- end
13
- end
14
- end
1
+ require 'socket'
2
+
3
+ module Solargraph
4
+ module ServerMethods
5
+ # @return [Integer]
6
+ def available_port
7
+ socket = Socket.new(:INET, :STREAM, 0)
8
+ socket.bind(Addrinfo.tcp("127.0.0.1", 0))
9
+ port = socket.local_address.ip_port
10
+ socket.close
11
+ port
12
+ end
13
+ end
14
+ end
@@ -1,102 +1,102 @@
1
- require 'thor'
2
- require 'json'
3
- require 'fileutils'
4
- require 'rubygems/package'
5
- require 'zlib'
6
- require 'backport'
7
-
8
- module Solargraph
9
- class Shell < Thor
10
- include Solargraph::ServerMethods
11
-
12
- map %w[--version -v] => :version
13
-
14
- desc "--version, -v", "Print the version"
15
- def version
16
- puts Solargraph::VERSION
17
- end
18
-
19
- desc 'socket', 'Run a Solargraph socket server'
20
- option :host, type: :string, aliases: :h, desc: 'The server host', default: '127.0.0.1'
21
- option :port, type: :numeric, aliases: :p, desc: 'The server port', default: 7658
22
- def socket
23
- port = options[:port]
24
- port = available_port if port.zero?
25
- Backport.run do
26
- Signal.trap("INT") do
27
- Backport.stop
28
- end
29
- Signal.trap("TERM") do
30
- Backport.stop
31
- end
32
- Backport.prepare_tcp_server host: options[:host], port: port, adapter: Solargraph::LanguageServer::Transport::Adapter
33
- STDERR.puts "Solargraph is listening PORT=#{port} PID=#{Process.pid}"
34
- end
35
- end
36
-
37
- desc 'stdio', 'Run a Solargraph stdio server'
38
- def stdio
39
- Backport.run do
40
- Signal.trap("INT") do
41
- Backport.stop
42
- end
43
- Signal.trap("TERM") do
44
- Backport.stop
45
- end
46
- Backport.prepare_stdio_server adapter: Solargraph::LanguageServer::Transport::Adapter
47
- STDERR.puts "Solargraph is listening on stdio PID=#{Process.pid}"
48
- end
49
- end
50
-
51
- desc 'config [DIRECTORY]', 'Create or overwrite a default configuration file'
52
- option :extensions, type: :boolean, aliases: :e, desc: 'Add installed extensions', default: true
53
- def config(directory = '.')
54
- matches = []
55
- if options[:extensions]
56
- Gem::Specification.each do |g|
57
- if g.name.match(/^solargraph\-[A-Za-z0-9_\-]*?\-ext/)
58
- require g.name
59
- matches.push g.name
60
- end
61
- end
62
- end
63
- conf = Solargraph::Workspace::Config.new.raw_data
64
- unless matches.empty?
65
- matches.each do |m|
66
- conf['extensions'].push m
67
- end
68
- end
69
- File.open(File.join(directory, '.solargraph.yml'), 'w') do |file|
70
- file.puts conf.to_yaml
71
- end
72
- STDOUT.puts "Configuration file initialized."
73
- end
74
-
75
- desc 'download-core [VERSION]', 'Download core documentation'
76
- def download_core version = nil
77
- ver = version || Solargraph::YardMap::CoreDocs.best_download
78
- puts "Downloading docs for #{ver}..."
79
- Solargraph::YardMap::CoreDocs.download ver
80
- end
81
-
82
- desc 'list-cores', 'List the local documentation versions'
83
- def list_cores
84
- puts Solargraph::YardMap::CoreDocs.versions.join("\n")
85
- end
86
-
87
- desc 'available-cores', 'List available documentation versions'
88
- def available_cores
89
- puts Solargraph::YardMap::CoreDocs.available.join("\n")
90
- end
91
-
92
- desc 'clear-cores', 'Clear the cached core documentation'
93
- def clear_cores
94
- Solargraph::YardMap::CoreDocs.clear
95
- end
96
-
97
- desc 'reporters', 'Get a list of diagnostics reporters'
98
- def reporters
99
- puts Solargraph::Diagnostics.reporters
100
- end
101
- end
102
- end
1
+ require 'thor'
2
+ require 'json'
3
+ require 'fileutils'
4
+ require 'rubygems/package'
5
+ require 'zlib'
6
+ require 'backport'
7
+
8
+ module Solargraph
9
+ class Shell < Thor
10
+ include Solargraph::ServerMethods
11
+
12
+ map %w[--version -v] => :version
13
+
14
+ desc "--version, -v", "Print the version"
15
+ def version
16
+ puts Solargraph::VERSION
17
+ end
18
+
19
+ desc 'socket', 'Run a Solargraph socket server'
20
+ option :host, type: :string, aliases: :h, desc: 'The server host', default: '127.0.0.1'
21
+ option :port, type: :numeric, aliases: :p, desc: 'The server port', default: 7658
22
+ def socket
23
+ port = options[:port]
24
+ port = available_port if port.zero?
25
+ Backport.run do
26
+ Signal.trap("INT") do
27
+ Backport.stop
28
+ end
29
+ Signal.trap("TERM") do
30
+ Backport.stop
31
+ end
32
+ Backport.prepare_tcp_server host: options[:host], port: port, adapter: Solargraph::LanguageServer::Transport::Adapter
33
+ STDERR.puts "Solargraph is listening PORT=#{port} PID=#{Process.pid}"
34
+ end
35
+ end
36
+
37
+ desc 'stdio', 'Run a Solargraph stdio server'
38
+ def stdio
39
+ Backport.run do
40
+ Signal.trap("INT") do
41
+ Backport.stop
42
+ end
43
+ Signal.trap("TERM") do
44
+ Backport.stop
45
+ end
46
+ Backport.prepare_stdio_server adapter: Solargraph::LanguageServer::Transport::Adapter
47
+ STDERR.puts "Solargraph is listening on stdio PID=#{Process.pid}"
48
+ end
49
+ end
50
+
51
+ desc 'config [DIRECTORY]', 'Create or overwrite a default configuration file'
52
+ option :extensions, type: :boolean, aliases: :e, desc: 'Add installed extensions', default: true
53
+ def config(directory = '.')
54
+ matches = []
55
+ if options[:extensions]
56
+ Gem::Specification.each do |g|
57
+ if g.name.match(/^solargraph\-[A-Za-z0-9_\-]*?\-ext/)
58
+ require g.name
59
+ matches.push g.name
60
+ end
61
+ end
62
+ end
63
+ conf = Solargraph::Workspace::Config.new.raw_data
64
+ unless matches.empty?
65
+ matches.each do |m|
66
+ conf['extensions'].push m
67
+ end
68
+ end
69
+ File.open(File.join(directory, '.solargraph.yml'), 'w') do |file|
70
+ file.puts conf.to_yaml
71
+ end
72
+ STDOUT.puts "Configuration file initialized."
73
+ end
74
+
75
+ desc 'download-core [VERSION]', 'Download core documentation'
76
+ def download_core version = nil
77
+ ver = version || Solargraph::YardMap::CoreDocs.best_download
78
+ puts "Downloading docs for #{ver}..."
79
+ Solargraph::YardMap::CoreDocs.download ver
80
+ end
81
+
82
+ desc 'list-cores', 'List the local documentation versions'
83
+ def list_cores
84
+ puts Solargraph::YardMap::CoreDocs.versions.join("\n")
85
+ end
86
+
87
+ desc 'available-cores', 'List available documentation versions'
88
+ def available_cores
89
+ puts Solargraph::YardMap::CoreDocs.available.join("\n")
90
+ end
91
+
92
+ desc 'clear-cores', 'Clear the cached core documentation'
93
+ def clear_cores
94
+ Solargraph::YardMap::CoreDocs.clear
95
+ end
96
+
97
+ desc 'reporters', 'Get a list of diagnostics reporters'
98
+ def reporters
99
+ puts Solargraph::Diagnostics.reporters
100
+ end
101
+ end
102
+ end
@@ -1,521 +1,521 @@
1
- require 'parser/current'
2
- require 'yard'
3
-
4
- module Solargraph
5
- # A Ruby file that has been parsed into an AST.
6
- #
7
- class Source
8
- autoload :FlawedBuilder, 'solargraph/source/flawed_builder'
9
- autoload :Updater, 'solargraph/source/updater'
10
- autoload :Change, 'solargraph/source/change'
11
- autoload :Mapper, 'solargraph/source/mapper'
12
- autoload :NodeMethods, 'solargraph/source/node_methods'
13
- autoload :EncodingFixes, 'solargraph/source/encoding_fixes'
14
- autoload :Cursor, 'solargraph/source/cursor'
15
- autoload :Chain, 'solargraph/source/chain'
16
- autoload :SourceChainer, 'solargraph/source/source_chainer'
17
- autoload :NodeChainer, 'solargraph/source/node_chainer'
18
-
19
- include EncodingFixes
20
- include NodeMethods
21
-
22
- # @return [String]
23
- attr_reader :filename
24
-
25
- # @return [String]
26
- attr_reader :code
27
-
28
- # @return [Parser::AST::Node]
29
- attr_reader :node
30
-
31
- # @return [Array<Parser::Source::Comment>]
32
- attr_reader :comments
33
-
34
- # @todo Deprecate?
35
- # @return [Integer]
36
- attr_reader :version
37
-
38
- # @param code [String]
39
- # @param filename [String]
40
- # @param version [Integer]
41
- def initialize code, filename = nil, version = 0
42
- @code = normalize(code)
43
- @repaired = code
44
- @filename = filename
45
- @version = version
46
- @domains = []
47
- begin
48
- @node, @comments = Source.parse_with_comments(@code, filename)
49
- @parsed = true
50
- rescue Parser::SyntaxError, EncodingError => e
51
- # @todo 100% whitespace results in a nil node, so there's no reason to parse it.
52
- # We still need to determine whether the resulting node should be nil or a dummy
53
- # node with a location that encompasses the range.
54
- # @node, @comments = Source.parse_with_comments(@code.gsub(/[^\s]/, ' '), filename)
55
- @node = nil
56
- @comments = []
57
- @parsed = false
58
- rescue Exception => e
59
- STDERR.puts e.message
60
- STDERR.puts e.backtrace
61
- raise "Error parsing #{filename || '(source)'}: [#{e.class}] #{e.message}"
62
- ensure
63
- @code.freeze
64
- end
65
- end
66
-
67
- # @param range [Solargraph::Range]
68
- # @return [String]
69
- def at range
70
- from_to range.start.line, range.start.character, range.ending.line, range.ending.character
71
- end
72
-
73
- # @param l1 [Integer]
74
- # @param c1 [Integer]
75
- # @param l2 [Integer]
76
- # @param c2 [Integer]
77
- # @return [String]
78
- def from_to l1, c1, l2, c2
79
- b = Solargraph::Position.line_char_to_offset(@code, l1, c1)
80
- e = Solargraph::Position.line_char_to_offset(@code, l2, c2)
81
- @code[b..e-1]
82
- end
83
-
84
- # Get the nearest node that contains the specified index.
85
- #
86
- # @param line [Integer]
87
- # @param column [Integer]
88
- # @return [AST::Node]
89
- def node_at(line, column)
90
- tree_at(line, column).first
91
- end
92
-
93
- # Get an array of nodes containing the specified index, starting with the
94
- # nearest node and ending with the root.
95
- #
96
- # @param line [Integer]
97
- # @param column [Integer]
98
- # @return [Array<AST::Node>]
99
- def tree_at(line, column)
100
- # offset = Position.line_char_to_offset(@code, line, column)
101
- position = Position.new(line, column)
102
- stack = []
103
- inner_tree_at @node, position, stack
104
- stack
105
- end
106
-
107
- # Start synchronizing the source. This method updates the code without
108
- # parsing a new AST. The resulting Source object will be marked not
109
- # synchronized (#synchronized? == false).
110
- #
111
- # @param updater [Source::Updater]
112
- # @return [Source]
113
- def start_synchronize updater
114
- raise 'Invalid synchronization' unless updater.filename == filename
115
- real_code = updater.write(@code)
116
- src = Source.allocate
117
- src.filename = filename
118
- src.code = real_code
119
- src.version = updater.version
120
- src.parsed = parsed?
121
- src.repaired = updater.repair(@repaired)
122
- src.synchronized = false
123
- src.node = @node
124
- src.comments = @comments
125
- src.error_ranges = error_ranges
126
- src.last_updater = updater
127
- return src.finish_synchronize unless real_code.lines.length == @code.lines.length
128
- src
129
- end
130
-
131
- # Finish synchronizing a source that was updated via #start_synchronize.
132
- # This method returns self if the source is already synchronized. Otherwise
133
- # it parses the AST and returns a new synchronized Source.
134
- #
135
- # @return [Source]
136
- def finish_synchronize
137
- return self if synchronized?
138
- synced = Source.new(@code, filename)
139
- if synced.parsed?
140
- synced.version = version
141
- return synced
142
- end
143
- synced = Source.new(@repaired, filename)
144
- synced.error_ranges.concat (error_ranges + last_updater.changes.map(&:range))
145
- synced.code = @code
146
- synced.synchronized = true
147
- synced.version = version
148
- synced
149
- end
150
-
151
- # Synchronize the Source with an update. This method applies changes to the
152
- # code, parses the new code's AST, and returns the resulting Source object.
153
- #
154
- # @param updater [Source::Updater]
155
- # @return [Source]
156
- def synchronize updater
157
- raise 'Invalid synchronization' unless updater.filename == filename
158
- real_code = updater.write(@code)
159
- if real_code == @code
160
- @version = updater.version
161
- return self
162
- end
163
- synced = Source.new(real_code, filename)
164
- if synced.parsed?
165
- synced.version = updater.version
166
- return synced
167
- end
168
- incr_code = updater.repair(@repaired)
169
- synced = Source.new(incr_code, filename)
170
- synced.error_ranges.concat (error_ranges + updater.changes.map(&:range))
171
- synced.code = real_code
172
- synced.version = updater.version
173
- synced
174
- end
175
-
176
- # @param position [Position]
177
- # @return [Source::Cursor]
178
- def cursor_at position
179
- Cursor.new(self, position)
180
- end
181
-
182
- # @return [Boolean]
183
- def parsed?
184
- @parsed
185
- end
186
-
187
- def repaired?
188
- @is_repaired ||= (@code != @repaired)
189
- end
190
-
191
- # @param position [Position]
192
- # @return [Boolean]
193
- def string_at? position
194
- string_ranges.each do |range|
195
- return true if range.include?(position)
196
- break if range.ending.line > position.line
197
- end
198
- false
199
- end
200
-
201
- # @param position [Position]
202
- # @return [Boolean]
203
- def comment_at? position
204
- comment_ranges.each do |range|
205
- return true if range.include?(position) ||
206
- (range.ending.line == position.line && range.ending.column < position.column)
207
- break if range.ending.line > position.line
208
- end
209
- false
210
- end
211
-
212
- # @param name [String]
213
- # @return [Array<Location>]
214
- def references name
215
- inner_node_references(name, node).map do |n|
216
- offset = Position.to_offset(code, get_node_start_position(n))
217
- soff = code.index(name, offset)
218
- eoff = soff + name.length
219
- Location.new(
220
- filename,
221
- Range.new(
222
- Position.from_offset(code, soff),
223
- Position.from_offset(code, eoff)
224
- )
225
- )
226
- end
227
- end
228
-
229
- # @return [Array<Range>]
230
- def error_ranges
231
- @error_ranges ||= []
232
- end
233
-
234
- # @param node [Parser::AST::Node]
235
- # @return [String]
236
- def code_for(node)
237
- b = Position.line_char_to_offset(@code, node.location.line, node.location.column)
238
- e = Position.line_char_to_offset(@code, node.location.last_line, node.location.last_column)
239
- frag = code[b..e-1].to_s
240
- frag.strip.gsub(/,$/, '')
241
- end
242
-
243
- # @param node [Parser::AST::Node]
244
- # @return [String]
245
- def comments_for node
246
- stringified_comments[node.loc.line] ||= begin
247
- arr = associated_comments[node.loc.line]
248
- arr ? stringify_comment_array(arr) : nil
249
- end
250
- end
251
-
252
- # A location representing the file in its entirety.
253
- #
254
- # @return [Location]
255
- def location
256
- st = Position.new(0, 0)
257
- en = Position.from_offset(code, code.length)
258
- range = Range.new(st, en)
259
- Location.new(filename, range)
260
- end
261
-
262
- FOLDING_NODE_TYPES = %i[
263
- class sclass module def defs if str dstr array while unless kwbegin hash block
264
- ].freeze
265
-
266
- # Get an array of ranges that can be folded, e.g., the range of a class
267
- # definition or an if condition.
268
- #
269
- # See FOLDING_NODE_TYPES for the list of node types that can be folded.
270
- #
271
- # @return [Array<Range>]
272
- def folding_ranges
273
- @folding_ranges ||= begin
274
- result = []
275
- inner_folding_ranges node, result
276
- result.concat foldable_comment_block_ranges
277
- result
278
- end
279
- end
280
-
281
- def synchronized?
282
- @synchronized = true if @synchronized.nil?
283
- @synchronized
284
- end
285
-
286
- # Get a hash of comments grouped by the line numbers of the associated code.
287
- #
288
- # @return [Hash{Integer => Array<Parser::Source::Comment>}]
289
- def associated_comments
290
- @associated_comments ||= begin
291
- result = {}
292
- Parser::Source::Comment.associate_locations(node, comments).each_pair do |loc, all|
293
- block = all #.select{ |l| l.document? || code.lines[l.loc.line].strip.start_with?('#')}
294
- next if block.empty?
295
- result[loc.line] ||= []
296
- result[loc.line].concat block
297
- end
298
- result
299
- end
300
- end
301
-
302
- private
303
-
304
- # @param top [Parser::AST::Node]
305
- # @param result [Array<Range>]
306
- # @return [void]
307
- def inner_folding_ranges top, result = []
308
- return unless top.is_a?(Parser::AST::Node)
309
- if FOLDING_NODE_TYPES.include?(top.type)
310
- range = Range.from_node(top)
311
- if result.empty? || range.start.line > result.last.start.line
312
- result.push range unless range.ending.line - range.start.line < 2
313
- end
314
- end
315
- top.children.each do |child|
316
- inner_folding_ranges(child, result)
317
- end
318
- end
319
-
320
- # Get a string representation of an array of comments.
321
- #
322
- # @param comments [Array<Parser::Source::Comment>]
323
- # @return [String]
324
- def stringify_comment_array comments
325
- ctxt = ''
326
- num = nil
327
- started = false
328
- last_line = nil
329
- comments.each { |l|
330
- # Trim the comment and minimum leading whitespace
331
- p = l.text.gsub(/^#+/, '')
332
- if num.nil? and !p.strip.empty?
333
- num = p.index(/[^ ]/)
334
- started = true
335
- elsif started and !p.strip.empty?
336
- cur = p.index(/[^ ]/)
337
- num = cur if cur < num
338
- end
339
- # Include blank lines between comments
340
- ctxt += ("\n" * (l.loc.first_line - last_line - 1)) unless last_line.nil? || l.loc.first_line - last_line <= 0
341
- ctxt += "#{p[num..-1]}\n" if started
342
- last_line = l.loc.last_line if last_line.nil? || l.loc.last_line > last_line
343
- }
344
- ctxt
345
- end
346
-
347
- # A hash of line numbers and their associated comments.
348
- #
349
- # @return [Hash{Integer => Array<String>}]
350
- def stringified_comments
351
- @stringified_comments ||= {}
352
- end
353
-
354
- # @return [Array<Range>]
355
- def string_ranges
356
- @string_ranges ||= string_ranges_in(@node)
357
- end
358
-
359
- # @return [Array<Range>]
360
- def comment_ranges
361
- @comment_ranges ||= @comments.map do |cmnt|
362
- Range.from_expr(cmnt.loc.expression)
363
- end
364
- end
365
-
366
- # Get an array of foldable comment block ranges. Blocks are excluded if
367
- # they are less than 3 lines long.
368
- #
369
- # @return [Array<Range>]
370
- def foldable_comment_block_ranges
371
- return [] unless synchronized?
372
- result = []
373
- grouped = []
374
- # @param cmnt [Parser::Source::Comment]
375
- @comments.each do |cmnt|
376
- if cmnt.document?
377
- result.push Range.from_expr(cmnt.loc.expression)
378
- elsif code.lines[cmnt.loc.expression.line].strip.start_with?('#')
379
- if grouped.empty? || cmnt.loc.expression.line == grouped.last.loc.expression.line + 1
380
- grouped.push cmnt
381
- else
382
- result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
383
- grouped = [cmnt]
384
- end
385
- else
386
- unless grouped.length < 3
387
- result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0)
388
- end
389
- grouped.clear
390
- end
391
- end
392
- result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
393
- result
394
- end
395
-
396
- def string_ranges_in n
397
- result = []
398
- if n.is_a?(Parser::AST::Node)
399
- if n.type == :str
400
- result.push Range.from_node(n)
401
- else
402
- n.children.each{ |c| result.concat string_ranges_in(c) }
403
- end
404
- end
405
- result
406
- end
407
-
408
- def inner_tree_at node, position, stack
409
- return if node.nil?
410
- here = Range.from_to(node.loc.expression.line, node.loc.expression.column, node.loc.expression.last_line, node.loc.expression.last_column)
411
- if here.contain?(position)
412
- stack.unshift node
413
- node.children.each do |c|
414
- next unless c.is_a?(AST::Node)
415
- next if c.loc.expression.nil?
416
- inner_tree_at(c, position, stack)
417
- end
418
- end
419
- end
420
-
421
- # @param name [String]
422
- # @param top [AST::Node]
423
- # @return [Array<AST::Node>]
424
- def inner_node_references name, top
425
- result = []
426
- if top.is_a?(AST::Node)
427
- result.push top if top.children.any? { |c| c.to_s == name }
428
- top.children.each { |c| result.concat inner_node_references(name, c) }
429
- end
430
- result
431
- end
432
-
433
- protected
434
-
435
- # @return [String]
436
- attr_writer :filename
437
-
438
- # @return [Integer]
439
- attr_writer :version
440
-
441
- # @return [String]
442
- attr_writer :code
443
-
444
- # @return [Parser::AST::Node]
445
- attr_writer :node
446
-
447
- # @return [Array<Range>]
448
- attr_writer :error_ranges
449
-
450
- # @return [String]
451
- attr_accessor :repaired
452
-
453
- # @return [Boolean]
454
- attr_writer :parsed
455
-
456
- # @return [Array<Parser::Source::Comment>]
457
- attr_writer :comments
458
-
459
- # @return [Boolean]
460
- attr_writer :synchronized
461
-
462
- # @return [Source::Updater]
463
- attr_accessor :last_updater
464
-
465
- class << self
466
- # @param filename [String]
467
- # @return [Solargraph::Source]
468
- def load filename
469
- file = File.open(filename)
470
- code = file.read
471
- file.close
472
- Source.load_string(code, filename)
473
- end
474
-
475
- # @param code [String]
476
- # @param filename [String]
477
- # @param version [Integer]
478
- # @return [Solargraph::Source]
479
- def load_string code, filename = nil, version = 0
480
- Source.new code, filename, version
481
- end
482
-
483
- # @param code [String]
484
- # @param filename [String]
485
- # @return [Array(Parser::AST::Node, Array<Parser::Source::Comment>)]
486
- def parse_with_comments code, filename = nil
487
- buffer = Parser::Source::Buffer.new(filename, 0)
488
- buffer.source = code
489
- parser.parse_with_comments(buffer)
490
- end
491
-
492
- # @param code [String]
493
- # @param filename [String, nil]
494
- # @param line [Integer]
495
- # @return [Parser::AST::Node]
496
- def parse code, filename = nil, line = 0
497
- buffer = Parser::Source::Buffer.new(filename, line)
498
- buffer.source = code
499
- parser.parse(buffer)
500
- end
501
-
502
- # @return [Parser::Base]
503
- def parser
504
- # @todo Consider setting an instance variable. We might not need to
505
- # recreate the parser every time we use it.
506
- parser = Parser::CurrentRuby.new(FlawedBuilder.new)
507
- parser.diagnostics.all_errors_are_fatal = true
508
- parser.diagnostics.ignore_warnings = true
509
- parser
510
- end
511
-
512
- # @param comments [String]
513
- # @return [YARD::DocstringParser]
514
- def parse_docstring comments
515
- # HACK: Pass a dummy code object to the parser for plugins that
516
- # expect it not to be nil
517
- YARD::Docstring.parser.parse(comments, YARD::CodeObjects::Base.new(:root, 'stub'))
518
- end
519
- end
520
- end
521
- end
1
+ require 'parser/current'
2
+ require 'yard'
3
+
4
+ module Solargraph
5
+ # A Ruby file that has been parsed into an AST.
6
+ #
7
+ class Source
8
+ autoload :FlawedBuilder, 'solargraph/source/flawed_builder'
9
+ autoload :Updater, 'solargraph/source/updater'
10
+ autoload :Change, 'solargraph/source/change'
11
+ autoload :Mapper, 'solargraph/source/mapper'
12
+ autoload :NodeMethods, 'solargraph/source/node_methods'
13
+ autoload :EncodingFixes, 'solargraph/source/encoding_fixes'
14
+ autoload :Cursor, 'solargraph/source/cursor'
15
+ autoload :Chain, 'solargraph/source/chain'
16
+ autoload :SourceChainer, 'solargraph/source/source_chainer'
17
+ autoload :NodeChainer, 'solargraph/source/node_chainer'
18
+
19
+ include EncodingFixes
20
+ include NodeMethods
21
+
22
+ # @return [String]
23
+ attr_reader :filename
24
+
25
+ # @return [String]
26
+ attr_reader :code
27
+
28
+ # @return [Parser::AST::Node]
29
+ attr_reader :node
30
+
31
+ # @return [Array<Parser::Source::Comment>]
32
+ attr_reader :comments
33
+
34
+ # @todo Deprecate?
35
+ # @return [Integer]
36
+ attr_reader :version
37
+
38
+ # @param code [String]
39
+ # @param filename [String]
40
+ # @param version [Integer]
41
+ def initialize code, filename = nil, version = 0
42
+ @code = normalize(code)
43
+ @repaired = code
44
+ @filename = filename
45
+ @version = version
46
+ @domains = []
47
+ begin
48
+ @node, @comments = Source.parse_with_comments(@code, filename)
49
+ @parsed = true
50
+ rescue Parser::SyntaxError, EncodingError => e
51
+ # @todo 100% whitespace results in a nil node, so there's no reason to parse it.
52
+ # We still need to determine whether the resulting node should be nil or a dummy
53
+ # node with a location that encompasses the range.
54
+ # @node, @comments = Source.parse_with_comments(@code.gsub(/[^\s]/, ' '), filename)
55
+ @node = nil
56
+ @comments = []
57
+ @parsed = false
58
+ rescue Exception => e
59
+ STDERR.puts e.message
60
+ STDERR.puts e.backtrace
61
+ raise "Error parsing #{filename || '(source)'}: [#{e.class}] #{e.message}"
62
+ ensure
63
+ @code.freeze
64
+ end
65
+ end
66
+
67
+ # @param range [Solargraph::Range]
68
+ # @return [String]
69
+ def at range
70
+ from_to range.start.line, range.start.character, range.ending.line, range.ending.character
71
+ end
72
+
73
+ # @param l1 [Integer]
74
+ # @param c1 [Integer]
75
+ # @param l2 [Integer]
76
+ # @param c2 [Integer]
77
+ # @return [String]
78
+ def from_to l1, c1, l2, c2
79
+ b = Solargraph::Position.line_char_to_offset(@code, l1, c1)
80
+ e = Solargraph::Position.line_char_to_offset(@code, l2, c2)
81
+ @code[b..e-1]
82
+ end
83
+
84
+ # Get the nearest node that contains the specified index.
85
+ #
86
+ # @param line [Integer]
87
+ # @param column [Integer]
88
+ # @return [AST::Node]
89
+ def node_at(line, column)
90
+ tree_at(line, column).first
91
+ end
92
+
93
+ # Get an array of nodes containing the specified index, starting with the
94
+ # nearest node and ending with the root.
95
+ #
96
+ # @param line [Integer]
97
+ # @param column [Integer]
98
+ # @return [Array<AST::Node>]
99
+ def tree_at(line, column)
100
+ # offset = Position.line_char_to_offset(@code, line, column)
101
+ position = Position.new(line, column)
102
+ stack = []
103
+ inner_tree_at @node, position, stack
104
+ stack
105
+ end
106
+
107
+ # Start synchronizing the source. This method updates the code without
108
+ # parsing a new AST. The resulting Source object will be marked not
109
+ # synchronized (#synchronized? == false).
110
+ #
111
+ # @param updater [Source::Updater]
112
+ # @return [Source]
113
+ def start_synchronize updater
114
+ raise 'Invalid synchronization' unless updater.filename == filename
115
+ real_code = updater.write(@code)
116
+ src = Source.allocate
117
+ src.filename = filename
118
+ src.code = real_code
119
+ src.version = updater.version
120
+ src.parsed = parsed?
121
+ src.repaired = updater.repair(@repaired)
122
+ src.synchronized = false
123
+ src.node = @node
124
+ src.comments = @comments
125
+ src.error_ranges = error_ranges
126
+ src.last_updater = updater
127
+ return src.finish_synchronize unless real_code.lines.length == @code.lines.length
128
+ src
129
+ end
130
+
131
+ # Finish synchronizing a source that was updated via #start_synchronize.
132
+ # This method returns self if the source is already synchronized. Otherwise
133
+ # it parses the AST and returns a new synchronized Source.
134
+ #
135
+ # @return [Source]
136
+ def finish_synchronize
137
+ return self if synchronized?
138
+ synced = Source.new(@code, filename)
139
+ if synced.parsed?
140
+ synced.version = version
141
+ return synced
142
+ end
143
+ synced = Source.new(@repaired, filename)
144
+ synced.error_ranges.concat (error_ranges + last_updater.changes.map(&:range))
145
+ synced.code = @code
146
+ synced.synchronized = true
147
+ synced.version = version
148
+ synced
149
+ end
150
+
151
+ # Synchronize the Source with an update. This method applies changes to the
152
+ # code, parses the new code's AST, and returns the resulting Source object.
153
+ #
154
+ # @param updater [Source::Updater]
155
+ # @return [Source]
156
+ def synchronize updater
157
+ raise 'Invalid synchronization' unless updater.filename == filename
158
+ real_code = updater.write(@code)
159
+ if real_code == @code
160
+ @version = updater.version
161
+ return self
162
+ end
163
+ synced = Source.new(real_code, filename)
164
+ if synced.parsed?
165
+ synced.version = updater.version
166
+ return synced
167
+ end
168
+ incr_code = updater.repair(@repaired)
169
+ synced = Source.new(incr_code, filename)
170
+ synced.error_ranges.concat (error_ranges + updater.changes.map(&:range))
171
+ synced.code = real_code
172
+ synced.version = updater.version
173
+ synced
174
+ end
175
+
176
+ # @param position [Position]
177
+ # @return [Source::Cursor]
178
+ def cursor_at position
179
+ Cursor.new(self, position)
180
+ end
181
+
182
+ # @return [Boolean]
183
+ def parsed?
184
+ @parsed
185
+ end
186
+
187
+ def repaired?
188
+ @is_repaired ||= (@code != @repaired)
189
+ end
190
+
191
+ # @param position [Position]
192
+ # @return [Boolean]
193
+ def string_at? position
194
+ string_ranges.each do |range|
195
+ return true if range.include?(position)
196
+ break if range.ending.line > position.line
197
+ end
198
+ false
199
+ end
200
+
201
+ # @param position [Position]
202
+ # @return [Boolean]
203
+ def comment_at? position
204
+ comment_ranges.each do |range|
205
+ return true if range.include?(position) ||
206
+ (range.ending.line == position.line && range.ending.column < position.column)
207
+ break if range.ending.line > position.line
208
+ end
209
+ false
210
+ end
211
+
212
+ # @param name [String]
213
+ # @return [Array<Location>]
214
+ def references name
215
+ inner_node_references(name, node).map do |n|
216
+ offset = Position.to_offset(code, get_node_start_position(n))
217
+ soff = code.index(name, offset)
218
+ eoff = soff + name.length
219
+ Location.new(
220
+ filename,
221
+ Range.new(
222
+ Position.from_offset(code, soff),
223
+ Position.from_offset(code, eoff)
224
+ )
225
+ )
226
+ end
227
+ end
228
+
229
+ # @return [Array<Range>]
230
+ def error_ranges
231
+ @error_ranges ||= []
232
+ end
233
+
234
+ # @param node [Parser::AST::Node]
235
+ # @return [String]
236
+ def code_for(node)
237
+ b = Position.line_char_to_offset(@code, node.location.line, node.location.column)
238
+ e = Position.line_char_to_offset(@code, node.location.last_line, node.location.last_column)
239
+ frag = code[b..e-1].to_s
240
+ frag.strip.gsub(/,$/, '')
241
+ end
242
+
243
+ # @param node [Parser::AST::Node]
244
+ # @return [String]
245
+ def comments_for node
246
+ stringified_comments[node.loc.line] ||= begin
247
+ arr = associated_comments[node.loc.line]
248
+ arr ? stringify_comment_array(arr) : nil
249
+ end
250
+ end
251
+
252
+ # A location representing the file in its entirety.
253
+ #
254
+ # @return [Location]
255
+ def location
256
+ st = Position.new(0, 0)
257
+ en = Position.from_offset(code, code.length)
258
+ range = Range.new(st, en)
259
+ Location.new(filename, range)
260
+ end
261
+
262
+ FOLDING_NODE_TYPES = %i[
263
+ class sclass module def defs if str dstr array while unless kwbegin hash block
264
+ ].freeze
265
+
266
+ # Get an array of ranges that can be folded, e.g., the range of a class
267
+ # definition or an if condition.
268
+ #
269
+ # See FOLDING_NODE_TYPES for the list of node types that can be folded.
270
+ #
271
+ # @return [Array<Range>]
272
+ def folding_ranges
273
+ @folding_ranges ||= begin
274
+ result = []
275
+ inner_folding_ranges node, result
276
+ result.concat foldable_comment_block_ranges
277
+ result
278
+ end
279
+ end
280
+
281
+ def synchronized?
282
+ @synchronized = true if @synchronized.nil?
283
+ @synchronized
284
+ end
285
+
286
+ # Get a hash of comments grouped by the line numbers of the associated code.
287
+ #
288
+ # @return [Hash{Integer => Array<Parser::Source::Comment>}]
289
+ def associated_comments
290
+ @associated_comments ||= begin
291
+ result = {}
292
+ Parser::Source::Comment.associate_locations(node, comments).each_pair do |loc, all|
293
+ block = all #.select{ |l| l.document? || code.lines[l.loc.line].strip.start_with?('#')}
294
+ next if block.empty?
295
+ result[loc.line] ||= []
296
+ result[loc.line].concat block
297
+ end
298
+ result
299
+ end
300
+ end
301
+
302
+ private
303
+
304
+ # @param top [Parser::AST::Node]
305
+ # @param result [Array<Range>]
306
+ # @return [void]
307
+ def inner_folding_ranges top, result = []
308
+ return unless top.is_a?(Parser::AST::Node)
309
+ if FOLDING_NODE_TYPES.include?(top.type)
310
+ range = Range.from_node(top)
311
+ if result.empty? || range.start.line > result.last.start.line
312
+ result.push range unless range.ending.line - range.start.line < 2
313
+ end
314
+ end
315
+ top.children.each do |child|
316
+ inner_folding_ranges(child, result)
317
+ end
318
+ end
319
+
320
+ # Get a string representation of an array of comments.
321
+ #
322
+ # @param comments [Array<Parser::Source::Comment>]
323
+ # @return [String]
324
+ def stringify_comment_array comments
325
+ ctxt = ''
326
+ num = nil
327
+ started = false
328
+ last_line = nil
329
+ comments.each { |l|
330
+ # Trim the comment and minimum leading whitespace
331
+ p = l.text.gsub(/^#+/, '')
332
+ if num.nil? and !p.strip.empty?
333
+ num = p.index(/[^ ]/)
334
+ started = true
335
+ elsif started and !p.strip.empty?
336
+ cur = p.index(/[^ ]/)
337
+ num = cur if cur < num
338
+ end
339
+ # Include blank lines between comments
340
+ ctxt += ("\n" * (l.loc.first_line - last_line - 1)) unless last_line.nil? || l.loc.first_line - last_line <= 0
341
+ ctxt += "#{p[num..-1]}\n" if started
342
+ last_line = l.loc.last_line if last_line.nil? || l.loc.last_line > last_line
343
+ }
344
+ ctxt
345
+ end
346
+
347
+ # A hash of line numbers and their associated comments.
348
+ #
349
+ # @return [Hash{Integer => Array<String>}]
350
+ def stringified_comments
351
+ @stringified_comments ||= {}
352
+ end
353
+
354
+ # @return [Array<Range>]
355
+ def string_ranges
356
+ @string_ranges ||= string_ranges_in(@node)
357
+ end
358
+
359
+ # @return [Array<Range>]
360
+ def comment_ranges
361
+ @comment_ranges ||= @comments.map do |cmnt|
362
+ Range.from_expr(cmnt.loc.expression)
363
+ end
364
+ end
365
+
366
+ # Get an array of foldable comment block ranges. Blocks are excluded if
367
+ # they are less than 3 lines long.
368
+ #
369
+ # @return [Array<Range>]
370
+ def foldable_comment_block_ranges
371
+ return [] unless synchronized?
372
+ result = []
373
+ grouped = []
374
+ # @param cmnt [Parser::Source::Comment]
375
+ @comments.each do |cmnt|
376
+ if cmnt.document?
377
+ result.push Range.from_expr(cmnt.loc.expression)
378
+ elsif code.lines[cmnt.loc.expression.line].strip.start_with?('#')
379
+ if grouped.empty? || cmnt.loc.expression.line == grouped.last.loc.expression.line + 1
380
+ grouped.push cmnt
381
+ else
382
+ result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
383
+ grouped = [cmnt]
384
+ end
385
+ else
386
+ unless grouped.length < 3
387
+ result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0)
388
+ end
389
+ grouped.clear
390
+ end
391
+ end
392
+ result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
393
+ result
394
+ end
395
+
396
+ def string_ranges_in n
397
+ result = []
398
+ if n.is_a?(Parser::AST::Node)
399
+ if n.type == :str
400
+ result.push Range.from_node(n)
401
+ else
402
+ n.children.each{ |c| result.concat string_ranges_in(c) }
403
+ end
404
+ end
405
+ result
406
+ end
407
+
408
+ def inner_tree_at node, position, stack
409
+ return if node.nil?
410
+ here = Range.from_to(node.loc.expression.line, node.loc.expression.column, node.loc.expression.last_line, node.loc.expression.last_column)
411
+ if here.contain?(position)
412
+ stack.unshift node
413
+ node.children.each do |c|
414
+ next unless c.is_a?(AST::Node)
415
+ next if c.loc.expression.nil?
416
+ inner_tree_at(c, position, stack)
417
+ end
418
+ end
419
+ end
420
+
421
+ # @param name [String]
422
+ # @param top [AST::Node]
423
+ # @return [Array<AST::Node>]
424
+ def inner_node_references name, top
425
+ result = []
426
+ if top.is_a?(AST::Node)
427
+ result.push top if top.children.any? { |c| c.to_s == name }
428
+ top.children.each { |c| result.concat inner_node_references(name, c) }
429
+ end
430
+ result
431
+ end
432
+
433
+ protected
434
+
435
+ # @return [String]
436
+ attr_writer :filename
437
+
438
+ # @return [Integer]
439
+ attr_writer :version
440
+
441
+ # @return [String]
442
+ attr_writer :code
443
+
444
+ # @return [Parser::AST::Node]
445
+ attr_writer :node
446
+
447
+ # @return [Array<Range>]
448
+ attr_writer :error_ranges
449
+
450
+ # @return [String]
451
+ attr_accessor :repaired
452
+
453
+ # @return [Boolean]
454
+ attr_writer :parsed
455
+
456
+ # @return [Array<Parser::Source::Comment>]
457
+ attr_writer :comments
458
+
459
+ # @return [Boolean]
460
+ attr_writer :synchronized
461
+
462
+ # @return [Source::Updater]
463
+ attr_accessor :last_updater
464
+
465
+ class << self
466
+ # @param filename [String]
467
+ # @return [Solargraph::Source]
468
+ def load filename
469
+ file = File.open(filename)
470
+ code = file.read
471
+ file.close
472
+ Source.load_string(code, filename)
473
+ end
474
+
475
+ # @param code [String]
476
+ # @param filename [String]
477
+ # @param version [Integer]
478
+ # @return [Solargraph::Source]
479
+ def load_string code, filename = nil, version = 0
480
+ Source.new code, filename, version
481
+ end
482
+
483
+ # @param code [String]
484
+ # @param filename [String]
485
+ # @return [Array(Parser::AST::Node, Array<Parser::Source::Comment>)]
486
+ def parse_with_comments code, filename = nil
487
+ buffer = Parser::Source::Buffer.new(filename, 0)
488
+ buffer.source = code
489
+ parser.parse_with_comments(buffer)
490
+ end
491
+
492
+ # @param code [String]
493
+ # @param filename [String, nil]
494
+ # @param line [Integer]
495
+ # @return [Parser::AST::Node]
496
+ def parse code, filename = nil, line = 0
497
+ buffer = Parser::Source::Buffer.new(filename, line)
498
+ buffer.source = code
499
+ parser.parse(buffer)
500
+ end
501
+
502
+ # @return [Parser::Base]
503
+ def parser
504
+ # @todo Consider setting an instance variable. We might not need to
505
+ # recreate the parser every time we use it.
506
+ parser = Parser::CurrentRuby.new(FlawedBuilder.new)
507
+ parser.diagnostics.all_errors_are_fatal = true
508
+ parser.diagnostics.ignore_warnings = true
509
+ parser
510
+ end
511
+
512
+ # @param comments [String]
513
+ # @return [YARD::DocstringParser]
514
+ def parse_docstring comments
515
+ # HACK: Pass a dummy code object to the parser for plugins that
516
+ # expect it not to be nil
517
+ YARD::Docstring.parser.parse(comments, YARD::CodeObjects::Base.new(:root, 'stub'))
518
+ end
519
+ end
520
+ end
521
+ end