solargraph 0.32.1 → 0.32.2

Sign up to get free protection for your applications and to get access to all the features.
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