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.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +25 -0
- data/EXAMPLES.md +76 -0
- data/Gemfile +3 -0
- data/LANGUAGE_SERVER.md +51 -0
- data/LICENSE +21 -0
- data/OVERVIEW.md +37 -0
- data/README.md +106 -0
- data/Rakefile +14 -0
- data/SERVER.md +95 -0
- data/bin/solargraph +0 -0
- data/bin/solargraph-runtime +5 -5
- data/lib/solargraph.rb +54 -54
- data/lib/solargraph/api_map.rb +659 -659
- data/lib/solargraph/api_map/cache.rb +49 -49
- data/lib/solargraph/api_map/source_to_yard.rb +67 -67
- data/lib/solargraph/api_map/store.rb +201 -201
- data/lib/solargraph/bundle.rb +24 -24
- data/lib/solargraph/complex_type.rb +150 -150
- data/lib/solargraph/complex_type/type_methods.rb +124 -124
- data/lib/solargraph/complex_type/unique_type.rb +44 -44
- data/lib/solargraph/core_fills.rb +37 -37
- data/lib/solargraph/diagnostics.rb +52 -52
- data/lib/solargraph/diagnostics/base.rb +20 -20
- data/lib/solargraph/diagnostics/require_not_found.rb +28 -28
- data/lib/solargraph/diagnostics/rubocop.rb +98 -98
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +46 -46
- data/lib/solargraph/diagnostics/type_not_defined.rb +108 -108
- data/lib/solargraph/diagnostics/update_errors.rb +38 -38
- data/lib/solargraph/language_server/completion_item_kinds.rb +33 -33
- data/lib/solargraph/language_server/error_codes.rb +18 -18
- data/lib/solargraph/language_server/host.rb +684 -681
- data/lib/solargraph/language_server/host/cataloger.rb +54 -79
- data/lib/solargraph/language_server/host/diagnoser.rb +80 -80
- data/lib/solargraph/language_server/host/dispatch.rb +112 -113
- data/lib/solargraph/language_server/host/sources.rb +138 -138
- data/lib/solargraph/language_server/message.rb +90 -90
- data/lib/solargraph/language_server/message/base.rb +83 -83
- data/lib/solargraph/language_server/message/completion_item/resolve.rb +40 -40
- data/lib/solargraph/language_server/message/exit_notification.rb +11 -11
- data/lib/solargraph/language_server/message/extended.rb +19 -19
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +86 -86
- data/lib/solargraph/language_server/message/extended/document.rb +18 -18
- data/lib/solargraph/language_server/message/extended/document_gems.rb +30 -30
- data/lib/solargraph/language_server/message/extended/environment.rb +20 -20
- data/lib/solargraph/language_server/message/extended/search.rb +18 -18
- data/lib/solargraph/language_server/message/initialize.rb +141 -141
- data/lib/solargraph/language_server/message/initialized.rb +23 -23
- data/lib/solargraph/language_server/message/shutdown.rb +11 -11
- data/lib/solargraph/language_server/message/text_document.rb +25 -25
- data/lib/solargraph/language_server/message/text_document/completion.rb +51 -51
- data/lib/solargraph/language_server/message/text_document/definition.rb +18 -18
- data/lib/solargraph/language_server/message/text_document/did_change.rb +13 -13
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +21 -21
- data/lib/solargraph/language_server/message/text_document/folding_range.rb +24 -24
- data/lib/solargraph/language_server/message/text_document/formatting.rb +50 -50
- data/lib/solargraph/language_server/message/text_document/hover.rb +31 -31
- data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +32 -32
- data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +9 -9
- data/lib/solargraph/language_server/message/text_document/references.rb +14 -14
- data/lib/solargraph/language_server/message/text_document/rename.rb +17 -17
- data/lib/solargraph/language_server/message/text_document/signature_help.rb +19 -19
- data/lib/solargraph/language_server/message/workspace.rb +12 -12
- data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -29
- data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +29 -27
- data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +21 -21
- data/lib/solargraph/language_server/request.rb +22 -22
- data/lib/solargraph/language_server/symbol_kinds.rb +34 -34
- data/lib/solargraph/language_server/transport.rb +11 -11
- data/lib/solargraph/language_server/transport/adapter.rb +60 -60
- data/lib/solargraph/language_server/transport/data_reader.rb +66 -66
- data/lib/solargraph/language_server/uri_helpers.rb +25 -25
- data/lib/solargraph/library.rb +421 -419
- data/lib/solargraph/live_map.rb +126 -126
- data/lib/solargraph/live_map/cache.rb +38 -38
- data/lib/solargraph/location.rb +31 -31
- data/lib/solargraph/logging.rb +25 -25
- data/lib/solargraph/page.rb +68 -68
- data/lib/solargraph/pin.rb +50 -50
- data/lib/solargraph/pin/attribute.rb +41 -41
- data/lib/solargraph/pin/base.rb +280 -280
- data/lib/solargraph/pin/base_method.rb +76 -76
- data/lib/solargraph/pin/base_variable.rb +72 -72
- data/lib/solargraph/pin/block.rb +32 -32
- data/lib/solargraph/pin/block_parameter.rb +103 -103
- data/lib/solargraph/pin/class_variable.rb +9 -9
- data/lib/solargraph/pin/constant.rb +30 -30
- data/lib/solargraph/pin/conversions.rb +79 -79
- data/lib/solargraph/pin/documenting.rb +41 -41
- data/lib/solargraph/pin/duck_method.rb +14 -14
- data/lib/solargraph/pin/global_variable.rb +9 -9
- data/lib/solargraph/pin/instance_variable.rb +9 -9
- data/lib/solargraph/pin/keyword.rb +17 -17
- data/lib/solargraph/pin/local_variable.rb +23 -23
- data/lib/solargraph/pin/localized.rb +22 -22
- data/lib/solargraph/pin/method.rb +126 -126
- data/lib/solargraph/pin/method_alias.rb +30 -30
- data/lib/solargraph/pin/method_parameter.rb +40 -40
- data/lib/solargraph/pin/namespace.rb +54 -54
- data/lib/solargraph/pin/plugin/method.rb +25 -25
- data/lib/solargraph/pin/proxy_type.rb +35 -35
- data/lib/solargraph/pin/reference.rb +22 -22
- data/lib/solargraph/pin/reference/extend.rb +11 -11
- data/lib/solargraph/pin/reference/include.rb +11 -11
- data/lib/solargraph/pin/reference/require.rb +15 -15
- data/lib/solargraph/pin/reference/superclass.rb +11 -11
- data/lib/solargraph/pin/symbol.rb +44 -44
- data/lib/solargraph/pin/yard_pin.rb +10 -10
- data/lib/solargraph/pin/yard_pin/constant.rb +14 -14
- data/lib/solargraph/pin/yard_pin/method.rb +35 -35
- data/lib/solargraph/pin/yard_pin/namespace.rb +19 -19
- data/lib/solargraph/pin/yard_pin/yard_mixin.rb +14 -14
- data/lib/solargraph/plugin.rb +8 -8
- data/lib/solargraph/plugin/base.rb +41 -41
- data/lib/solargraph/plugin/canceler.rb +11 -11
- data/lib/solargraph/plugin/process.rb +172 -172
- data/lib/solargraph/plugin/runtime.rb +134 -134
- data/lib/solargraph/position.rb +110 -110
- data/lib/solargraph/range.rb +83 -83
- data/lib/solargraph/server_methods.rb +14 -14
- data/lib/solargraph/shell.rb +102 -102
- data/lib/solargraph/source.rb +521 -521
- data/lib/solargraph/source/chain.rb +120 -120
- data/lib/solargraph/source/chain/call.rb +107 -107
- data/lib/solargraph/source/chain/class_variable.rb +11 -11
- data/lib/solargraph/source/chain/constant.rb +30 -30
- data/lib/solargraph/source/chain/global_variable.rb +11 -11
- data/lib/solargraph/source/chain/head.rb +33 -33
- data/lib/solargraph/source/chain/instance_variable.rb +11 -11
- data/lib/solargraph/source/chain/link.rb +33 -33
- data/lib/solargraph/source/chain/literal.rb +21 -21
- data/lib/solargraph/source/chain/variable.rb +11 -11
- data/lib/solargraph/source/change.rb +77 -77
- data/lib/solargraph/source/cursor.rb +157 -157
- data/lib/solargraph/source/node_chainer.rb +96 -96
- data/lib/solargraph/source/node_methods.rb +225 -225
- data/lib/solargraph/source/source_chainer.rb +183 -183
- data/lib/solargraph/source_map.rb +169 -169
- data/lib/solargraph/source_map/clip.rb +145 -145
- data/lib/solargraph/source_map/completion.rb +21 -21
- data/lib/solargraph/source_map/mapper.rb +149 -149
- data/lib/solargraph/source_map/node_processor.rb +78 -78
- data/lib/solargraph/source_map/node_processor/alias_node.rb +19 -19
- data/lib/solargraph/source_map/node_processor/args_node.rb +28 -28
- data/lib/solargraph/source_map/node_processor/base.rb +68 -68
- data/lib/solargraph/source_map/node_processor/begin_node.rb +11 -11
- data/lib/solargraph/source_map/node_processor/block_node.rb +14 -14
- data/lib/solargraph/source_map/node_processor/casgn_node.rb +14 -14
- data/lib/solargraph/source_map/node_processor/cvasgn_node.rb +14 -14
- data/lib/solargraph/source_map/node_processor/def_node.rb +54 -54
- data/lib/solargraph/source_map/node_processor/defs_node.rb +21 -21
- data/lib/solargraph/source_map/node_processor/gvasgn_node.rb +12 -12
- data/lib/solargraph/source_map/node_processor/ivasgn_node.rb +18 -18
- data/lib/solargraph/source_map/node_processor/lvasgn_node.rb +16 -16
- data/lib/solargraph/source_map/node_processor/namespace_node.rb +26 -26
- data/lib/solargraph/source_map/node_processor/orasgn_node.rb +12 -12
- data/lib/solargraph/source_map/node_processor/sclass_node.rb +11 -11
- data/lib/solargraph/source_map/node_processor/send_node.rb +162 -162
- data/lib/solargraph/source_map/node_processor/sym_node.rb +11 -11
- data/lib/solargraph/source_map/region.rb +58 -58
- data/lib/solargraph/version.rb +3 -3
- data/lib/solargraph/views/environment.erb +53 -53
- data/lib/solargraph/workspace.rb +183 -183
- data/lib/solargraph/workspace/config.rb +170 -170
- data/lib/solargraph/yard_map.rb +298 -298
- data/lib/solargraph/yard_map/cache.rb +17 -17
- data/lib/solargraph/yard_map/core_docs.rb +163 -163
- data/lib/solargraph/yard_map/core_gen.rb +76 -76
- data/lib/yard-coregen.rb +16 -16
- data/lib/yard-solargraph.rb +18 -18
- data/solargraph.gemspec +37 -0
- data/travis-bundler.rb +10 -0
- metadata +19 -6
data/lib/solargraph/range.rb
CHANGED
|
@@ -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
|
data/lib/solargraph/shell.rb
CHANGED
|
@@ -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
|
data/lib/solargraph/source.rb
CHANGED
|
@@ -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
|