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
|
@@ -1,225 +1,225 @@
|
|
|
1
|
-
module Solargraph
|
|
2
|
-
class Source
|
|
3
|
-
module NodeMethods
|
|
4
|
-
module_function
|
|
5
|
-
|
|
6
|
-
# @param node [Parser::AST::Node]
|
|
7
|
-
# @return [String]
|
|
8
|
-
def unpack_name(node)
|
|
9
|
-
pack_name(node).join("::")
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# @param node [Parser::AST::Node]
|
|
13
|
-
# @return [Array<String>]
|
|
14
|
-
def pack_name(node)
|
|
15
|
-
parts = []
|
|
16
|
-
if node.kind_of?(AST::Node)
|
|
17
|
-
node.children.each { |n|
|
|
18
|
-
if n.kind_of?(AST::Node)
|
|
19
|
-
if n.type == :cbase
|
|
20
|
-
parts = [''] + pack_name(n)
|
|
21
|
-
else
|
|
22
|
-
parts += pack_name(n)
|
|
23
|
-
end
|
|
24
|
-
else
|
|
25
|
-
parts.push n unless n.nil?
|
|
26
|
-
end
|
|
27
|
-
}
|
|
28
|
-
end
|
|
29
|
-
parts
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# @param node [Parser::AST::Node]
|
|
33
|
-
# @return [String]
|
|
34
|
-
def const_from node
|
|
35
|
-
if node.kind_of?(AST::Node) and node.type == :const
|
|
36
|
-
result = ''
|
|
37
|
-
unless node.children[0].nil?
|
|
38
|
-
result = const_from(node.children[0])
|
|
39
|
-
end
|
|
40
|
-
if result == ''
|
|
41
|
-
result = node.children[1].to_s
|
|
42
|
-
else
|
|
43
|
-
result = result + '::' + node.children[1].to_s
|
|
44
|
-
end
|
|
45
|
-
result
|
|
46
|
-
else
|
|
47
|
-
nil
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# @param node [Parser::AST::Node]
|
|
52
|
-
# @return [String]
|
|
53
|
-
def infer_literal_node_type node
|
|
54
|
-
return nil unless node.kind_of?(AST::Node)
|
|
55
|
-
if node.type == :str or node.type == :dstr
|
|
56
|
-
return 'String'
|
|
57
|
-
elsif node.type == :array
|
|
58
|
-
return 'Array'
|
|
59
|
-
elsif node.type == :hash
|
|
60
|
-
return 'Hash'
|
|
61
|
-
elsif node.type == :int
|
|
62
|
-
return 'Integer'
|
|
63
|
-
elsif node.type == :float
|
|
64
|
-
return 'Float'
|
|
65
|
-
elsif node.type == :sym
|
|
66
|
-
return 'Symbol'
|
|
67
|
-
elsif node.type == :regexp
|
|
68
|
-
return 'Regexp'
|
|
69
|
-
# @todo Maybe ignore nils
|
|
70
|
-
# elsif node.type == :nil
|
|
71
|
-
# return 'NilClass'
|
|
72
|
-
end
|
|
73
|
-
nil
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Get a call signature from a node.
|
|
77
|
-
# The result should be a string in the form of a method path, e.g.,
|
|
78
|
-
# String.new or variable.method.
|
|
79
|
-
#
|
|
80
|
-
# @param node [Parser::AST::Node]
|
|
81
|
-
# @return [String]
|
|
82
|
-
def resolve_node_signature node
|
|
83
|
-
result = drill_signature node, ''
|
|
84
|
-
return nil if result.empty?
|
|
85
|
-
result
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# @param node [Parser::AST::Node]
|
|
89
|
-
# @return [Position]
|
|
90
|
-
def get_node_start_position(node)
|
|
91
|
-
Position.new(node.loc.line, node.loc.column)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# @param node [Parser::AST::Node]
|
|
95
|
-
# @return [Position]
|
|
96
|
-
def get_node_end_position(node)
|
|
97
|
-
Position.new(node.loc.last_line, node.loc.last_column)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def drill_signature node, signature
|
|
101
|
-
return signature unless node.kind_of?(AST::Node)
|
|
102
|
-
if node.type == :const or node.type == :cbase
|
|
103
|
-
unless node.children[0].nil?
|
|
104
|
-
signature += drill_signature(node.children[0], signature)
|
|
105
|
-
end
|
|
106
|
-
signature += '::' unless signature.empty?
|
|
107
|
-
signature += node.children[1].to_s
|
|
108
|
-
elsif node.type == :lvar or node.type == :ivar or node.type == :cvar
|
|
109
|
-
signature += '.' unless signature.empty?
|
|
110
|
-
signature += node.children[0].to_s
|
|
111
|
-
elsif node.type == :send
|
|
112
|
-
unless node.children[0].nil?
|
|
113
|
-
signature += drill_signature(node.children[0], signature)
|
|
114
|
-
end
|
|
115
|
-
signature += '.' unless signature.empty?
|
|
116
|
-
signature += node.children[1].to_s
|
|
117
|
-
end
|
|
118
|
-
signature
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Find all the nodes within the provided node that potentially return a
|
|
122
|
-
# value.
|
|
123
|
-
#
|
|
124
|
-
# The node parameter typically represents a method's logic, e.g., the
|
|
125
|
-
# second child (after the :args node) of a :def node. A simple one-line
|
|
126
|
-
# method would typically return itself, while a node with conditions
|
|
127
|
-
# would return the resulting node from each conditional branch. Nodes
|
|
128
|
-
# that follow a :return node are assumed to be unreachable. Implicit nil
|
|
129
|
-
# values are ignored.
|
|
130
|
-
#
|
|
131
|
-
# @todo Maybe this method should include implicit nil values in results.
|
|
132
|
-
# For example, a bare `return` would return a :nil node instead of an
|
|
133
|
-
# empty array.
|
|
134
|
-
#
|
|
135
|
-
# @param node [AST::Node]
|
|
136
|
-
# @return [Array<AST::Node>]
|
|
137
|
-
def returns_from node
|
|
138
|
-
DeepInference.get_return_nodes(node)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
module DeepInference
|
|
142
|
-
class << self
|
|
143
|
-
CONDITIONAL = [:if, :unless]
|
|
144
|
-
REDUCEABLE = [:begin, :kwbegin]
|
|
145
|
-
SKIPPABLE = [:def, :defs, :class, :sclass, :module]
|
|
146
|
-
|
|
147
|
-
# @param node [Parser::AST::Node]
|
|
148
|
-
# @return [Array<Parser::AST::Node>]
|
|
149
|
-
def get_return_nodes node
|
|
150
|
-
return [] unless node.is_a?(Parser::AST::Node)
|
|
151
|
-
result = []
|
|
152
|
-
if REDUCEABLE.include?(node.type)
|
|
153
|
-
result.concat get_return_nodes_from_children(node)
|
|
154
|
-
elsif CONDITIONAL.include?(node.type)
|
|
155
|
-
result.concat reduce_to_value_nodes(node.children[1..-1])
|
|
156
|
-
elsif node.type == :and || node.type == :or
|
|
157
|
-
result.concat reduce_to_value_nodes(node.children)
|
|
158
|
-
elsif node.type == :return
|
|
159
|
-
result.concat reduce_to_value_nodes([node.children[0]])
|
|
160
|
-
else
|
|
161
|
-
result.push node
|
|
162
|
-
end
|
|
163
|
-
result
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
private
|
|
167
|
-
|
|
168
|
-
def get_return_nodes_from_children parent
|
|
169
|
-
result = []
|
|
170
|
-
nodes = parent.children.select{|n| n.is_a?(AST::Node)}
|
|
171
|
-
nodes[0..-2].each do |node|
|
|
172
|
-
next if SKIPPABLE.include?(node.type)
|
|
173
|
-
if node.type == :return
|
|
174
|
-
result.concat reduce_to_value_nodes([node.children[0]])
|
|
175
|
-
# Return the result here because the rest of the code is
|
|
176
|
-
# unreachable
|
|
177
|
-
return result
|
|
178
|
-
else
|
|
179
|
-
result.concat get_return_nodes_only(node)
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
result.concat reduce_to_value_nodes([nodes.last]) unless nodes.last.nil?
|
|
183
|
-
result
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def get_return_nodes_only parent
|
|
187
|
-
result = []
|
|
188
|
-
nodes = parent.children.select{|n| n.is_a?(AST::Node)}
|
|
189
|
-
nodes.each do |node|
|
|
190
|
-
next if SKIPPABLE.include?(node.type)
|
|
191
|
-
if node.type == :return
|
|
192
|
-
result.concat reduce_to_value_nodes([node.children[0]])
|
|
193
|
-
# Return the result here because the rest of the code is
|
|
194
|
-
# unreachable
|
|
195
|
-
return result
|
|
196
|
-
else
|
|
197
|
-
result.concat get_return_nodes_only(node)
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
result
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def reduce_to_value_nodes nodes
|
|
204
|
-
result = []
|
|
205
|
-
nodes.each do |node|
|
|
206
|
-
next unless node.is_a?(Parser::AST::Node)
|
|
207
|
-
if REDUCEABLE.include?(node.type)
|
|
208
|
-
result.concat get_return_nodes_from_children(node)
|
|
209
|
-
elsif CONDITIONAL.include?(node.type)
|
|
210
|
-
result.concat reduce_to_value_nodes(node.children[1..-1])
|
|
211
|
-
elsif node.type == :return
|
|
212
|
-
result.concat get_return_nodes(node.children[0])
|
|
213
|
-
elsif node.type == :and || node.type == :or
|
|
214
|
-
result.concat reduce_to_value_nodes(node.children)
|
|
215
|
-
else
|
|
216
|
-
result.push node
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
result
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
1
|
+
module Solargraph
|
|
2
|
+
class Source
|
|
3
|
+
module NodeMethods
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
# @param node [Parser::AST::Node]
|
|
7
|
+
# @return [String]
|
|
8
|
+
def unpack_name(node)
|
|
9
|
+
pack_name(node).join("::")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @param node [Parser::AST::Node]
|
|
13
|
+
# @return [Array<String>]
|
|
14
|
+
def pack_name(node)
|
|
15
|
+
parts = []
|
|
16
|
+
if node.kind_of?(AST::Node)
|
|
17
|
+
node.children.each { |n|
|
|
18
|
+
if n.kind_of?(AST::Node)
|
|
19
|
+
if n.type == :cbase
|
|
20
|
+
parts = [''] + pack_name(n)
|
|
21
|
+
else
|
|
22
|
+
parts += pack_name(n)
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
parts.push n unless n.nil?
|
|
26
|
+
end
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
parts
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param node [Parser::AST::Node]
|
|
33
|
+
# @return [String]
|
|
34
|
+
def const_from node
|
|
35
|
+
if node.kind_of?(AST::Node) and node.type == :const
|
|
36
|
+
result = ''
|
|
37
|
+
unless node.children[0].nil?
|
|
38
|
+
result = const_from(node.children[0])
|
|
39
|
+
end
|
|
40
|
+
if result == ''
|
|
41
|
+
result = node.children[1].to_s
|
|
42
|
+
else
|
|
43
|
+
result = result + '::' + node.children[1].to_s
|
|
44
|
+
end
|
|
45
|
+
result
|
|
46
|
+
else
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param node [Parser::AST::Node]
|
|
52
|
+
# @return [String]
|
|
53
|
+
def infer_literal_node_type node
|
|
54
|
+
return nil unless node.kind_of?(AST::Node)
|
|
55
|
+
if node.type == :str or node.type == :dstr
|
|
56
|
+
return 'String'
|
|
57
|
+
elsif node.type == :array
|
|
58
|
+
return 'Array'
|
|
59
|
+
elsif node.type == :hash
|
|
60
|
+
return 'Hash'
|
|
61
|
+
elsif node.type == :int
|
|
62
|
+
return 'Integer'
|
|
63
|
+
elsif node.type == :float
|
|
64
|
+
return 'Float'
|
|
65
|
+
elsif node.type == :sym
|
|
66
|
+
return 'Symbol'
|
|
67
|
+
elsif node.type == :regexp
|
|
68
|
+
return 'Regexp'
|
|
69
|
+
# @todo Maybe ignore nils
|
|
70
|
+
# elsif node.type == :nil
|
|
71
|
+
# return 'NilClass'
|
|
72
|
+
end
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get a call signature from a node.
|
|
77
|
+
# The result should be a string in the form of a method path, e.g.,
|
|
78
|
+
# String.new or variable.method.
|
|
79
|
+
#
|
|
80
|
+
# @param node [Parser::AST::Node]
|
|
81
|
+
# @return [String]
|
|
82
|
+
def resolve_node_signature node
|
|
83
|
+
result = drill_signature node, ''
|
|
84
|
+
return nil if result.empty?
|
|
85
|
+
result
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @param node [Parser::AST::Node]
|
|
89
|
+
# @return [Position]
|
|
90
|
+
def get_node_start_position(node)
|
|
91
|
+
Position.new(node.loc.line, node.loc.column)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param node [Parser::AST::Node]
|
|
95
|
+
# @return [Position]
|
|
96
|
+
def get_node_end_position(node)
|
|
97
|
+
Position.new(node.loc.last_line, node.loc.last_column)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def drill_signature node, signature
|
|
101
|
+
return signature unless node.kind_of?(AST::Node)
|
|
102
|
+
if node.type == :const or node.type == :cbase
|
|
103
|
+
unless node.children[0].nil?
|
|
104
|
+
signature += drill_signature(node.children[0], signature)
|
|
105
|
+
end
|
|
106
|
+
signature += '::' unless signature.empty?
|
|
107
|
+
signature += node.children[1].to_s
|
|
108
|
+
elsif node.type == :lvar or node.type == :ivar or node.type == :cvar
|
|
109
|
+
signature += '.' unless signature.empty?
|
|
110
|
+
signature += node.children[0].to_s
|
|
111
|
+
elsif node.type == :send
|
|
112
|
+
unless node.children[0].nil?
|
|
113
|
+
signature += drill_signature(node.children[0], signature)
|
|
114
|
+
end
|
|
115
|
+
signature += '.' unless signature.empty?
|
|
116
|
+
signature += node.children[1].to_s
|
|
117
|
+
end
|
|
118
|
+
signature
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Find all the nodes within the provided node that potentially return a
|
|
122
|
+
# value.
|
|
123
|
+
#
|
|
124
|
+
# The node parameter typically represents a method's logic, e.g., the
|
|
125
|
+
# second child (after the :args node) of a :def node. A simple one-line
|
|
126
|
+
# method would typically return itself, while a node with conditions
|
|
127
|
+
# would return the resulting node from each conditional branch. Nodes
|
|
128
|
+
# that follow a :return node are assumed to be unreachable. Implicit nil
|
|
129
|
+
# values are ignored.
|
|
130
|
+
#
|
|
131
|
+
# @todo Maybe this method should include implicit nil values in results.
|
|
132
|
+
# For example, a bare `return` would return a :nil node instead of an
|
|
133
|
+
# empty array.
|
|
134
|
+
#
|
|
135
|
+
# @param node [AST::Node]
|
|
136
|
+
# @return [Array<AST::Node>]
|
|
137
|
+
def returns_from node
|
|
138
|
+
DeepInference.get_return_nodes(node)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
module DeepInference
|
|
142
|
+
class << self
|
|
143
|
+
CONDITIONAL = [:if, :unless]
|
|
144
|
+
REDUCEABLE = [:begin, :kwbegin]
|
|
145
|
+
SKIPPABLE = [:def, :defs, :class, :sclass, :module]
|
|
146
|
+
|
|
147
|
+
# @param node [Parser::AST::Node]
|
|
148
|
+
# @return [Array<Parser::AST::Node>]
|
|
149
|
+
def get_return_nodes node
|
|
150
|
+
return [] unless node.is_a?(Parser::AST::Node)
|
|
151
|
+
result = []
|
|
152
|
+
if REDUCEABLE.include?(node.type)
|
|
153
|
+
result.concat get_return_nodes_from_children(node)
|
|
154
|
+
elsif CONDITIONAL.include?(node.type)
|
|
155
|
+
result.concat reduce_to_value_nodes(node.children[1..-1])
|
|
156
|
+
elsif node.type == :and || node.type == :or
|
|
157
|
+
result.concat reduce_to_value_nodes(node.children)
|
|
158
|
+
elsif node.type == :return
|
|
159
|
+
result.concat reduce_to_value_nodes([node.children[0]])
|
|
160
|
+
else
|
|
161
|
+
result.push node
|
|
162
|
+
end
|
|
163
|
+
result
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def get_return_nodes_from_children parent
|
|
169
|
+
result = []
|
|
170
|
+
nodes = parent.children.select{|n| n.is_a?(AST::Node)}
|
|
171
|
+
nodes[0..-2].each do |node|
|
|
172
|
+
next if SKIPPABLE.include?(node.type)
|
|
173
|
+
if node.type == :return
|
|
174
|
+
result.concat reduce_to_value_nodes([node.children[0]])
|
|
175
|
+
# Return the result here because the rest of the code is
|
|
176
|
+
# unreachable
|
|
177
|
+
return result
|
|
178
|
+
else
|
|
179
|
+
result.concat get_return_nodes_only(node)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
result.concat reduce_to_value_nodes([nodes.last]) unless nodes.last.nil?
|
|
183
|
+
result
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def get_return_nodes_only parent
|
|
187
|
+
result = []
|
|
188
|
+
nodes = parent.children.select{|n| n.is_a?(AST::Node)}
|
|
189
|
+
nodes.each do |node|
|
|
190
|
+
next if SKIPPABLE.include?(node.type)
|
|
191
|
+
if node.type == :return
|
|
192
|
+
result.concat reduce_to_value_nodes([node.children[0]])
|
|
193
|
+
# Return the result here because the rest of the code is
|
|
194
|
+
# unreachable
|
|
195
|
+
return result
|
|
196
|
+
else
|
|
197
|
+
result.concat get_return_nodes_only(node)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
result
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def reduce_to_value_nodes nodes
|
|
204
|
+
result = []
|
|
205
|
+
nodes.each do |node|
|
|
206
|
+
next unless node.is_a?(Parser::AST::Node)
|
|
207
|
+
if REDUCEABLE.include?(node.type)
|
|
208
|
+
result.concat get_return_nodes_from_children(node)
|
|
209
|
+
elsif CONDITIONAL.include?(node.type)
|
|
210
|
+
result.concat reduce_to_value_nodes(node.children[1..-1])
|
|
211
|
+
elsif node.type == :return
|
|
212
|
+
result.concat get_return_nodes(node.children[0])
|
|
213
|
+
elsif node.type == :and || node.type == :or
|
|
214
|
+
result.concat reduce_to_value_nodes(node.children)
|
|
215
|
+
else
|
|
216
|
+
result.push node
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
result
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
module Solargraph
|
|
2
|
-
class Source
|
|
3
|
-
# Information about a location in a source, including the location's word
|
|
4
|
-
# and signature, literal values at the base of signatures, and whether the
|
|
5
|
-
# location is inside a string or comment. ApiMaps use Fragments to provide
|
|
6
|
-
# results for completion and definition queries.
|
|
7
|
-
#
|
|
8
|
-
class SourceChainer
|
|
9
|
-
include Source::NodeMethods
|
|
10
|
-
|
|
11
|
-
private_class_method :new
|
|
12
|
-
|
|
13
|
-
class << self
|
|
14
|
-
# @param source [Source]
|
|
15
|
-
# @param position [Position]
|
|
16
|
-
# @return [Source::Chain]
|
|
17
|
-
def chain source, position
|
|
18
|
-
# raise "Not a source" unless source.is_a?(Source)
|
|
19
|
-
new(source, position).chain
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# @param source [Source]
|
|
24
|
-
# @param position [Position]
|
|
25
|
-
def initialize source, position
|
|
26
|
-
@source = source
|
|
27
|
-
@position = position
|
|
28
|
-
@calculated_literal = false
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# @return [Source::Chain]
|
|
32
|
-
def chain
|
|
33
|
-
return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
|
|
34
|
-
begin
|
|
35
|
-
return Chain.new([]) if phrase.end_with?('..')
|
|
36
|
-
if !source.repaired? && source.parsed? && source.synchronized?
|
|
37
|
-
node = source.node_at(position.line, position.column)
|
|
38
|
-
else
|
|
39
|
-
node = nil
|
|
40
|
-
node = source.node_at(fixed_position.line, fixed_position.column) unless source.error_ranges.any?{|r| r.nil? || r.include?(fixed_position)}
|
|
41
|
-
# Exception for positions that chain literal nodes in unsynchronized sources
|
|
42
|
-
node = nil unless source.synchronized? || !infer_literal_node_type(node).nil?
|
|
43
|
-
node = Source.parse(fixed_phrase) if node.nil?
|
|
44
|
-
end
|
|
45
|
-
rescue Parser::SyntaxError
|
|
46
|
-
return Chain.new([Chain::UNDEFINED_CALL])
|
|
47
|
-
end
|
|
48
|
-
return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':'))
|
|
49
|
-
chain = NodeChainer.chain(node, source.filename)
|
|
50
|
-
if source.repaired? || !source.parsed? || !source.synchronized?
|
|
51
|
-
if end_of_phrase.strip == '.'
|
|
52
|
-
chain.links.push Chain::UNDEFINED_CALL
|
|
53
|
-
elsif end_of_phrase.strip == '::'
|
|
54
|
-
chain.links.push Chain::UNDEFINED_CONSTANT
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
chain
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
private
|
|
61
|
-
|
|
62
|
-
# @return [Position]
|
|
63
|
-
attr_reader :position
|
|
64
|
-
|
|
65
|
-
# @return [Solargraph::Source]
|
|
66
|
-
attr_reader :source
|
|
67
|
-
|
|
68
|
-
# @return [String]
|
|
69
|
-
def phrase
|
|
70
|
-
@phrase ||= source.code[signature_data[0]..offset-1]
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# @return [String]
|
|
74
|
-
def fixed_phrase
|
|
75
|
-
@fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)]
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# @return [Position]
|
|
79
|
-
def fixed_position
|
|
80
|
-
@fixed_position ||= Position.from_offset(source.code, offset - end_of_phrase.length)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# @return [String]
|
|
84
|
-
def end_of_phrase
|
|
85
|
-
@end_of_phrase ||= begin
|
|
86
|
-
match = phrase.match(/[\s]*(\.{1}|::)[\s]*$/)
|
|
87
|
-
if match
|
|
88
|
-
match[0]
|
|
89
|
-
else
|
|
90
|
-
''
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# True if the current offset is inside a string.
|
|
96
|
-
#
|
|
97
|
-
# @return [Boolean]
|
|
98
|
-
def string?
|
|
99
|
-
# @string ||= (node.type == :str or node.type == :dstr)
|
|
100
|
-
@string ||= @source.string_at?(position)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# @return [Integer]
|
|
104
|
-
def offset
|
|
105
|
-
@offset ||= get_offset(position.line, position.column)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# @param line [Integer]
|
|
109
|
-
# @param column [Integer]
|
|
110
|
-
# @return [Integer]
|
|
111
|
-
def get_offset line, column
|
|
112
|
-
Position.line_char_to_offset(@source.code, line, column)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def signature_data
|
|
116
|
-
@signature_data ||= get_signature_data_at(offset)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def get_signature_data_at index
|
|
120
|
-
brackets = 0
|
|
121
|
-
squares = 0
|
|
122
|
-
parens = 0
|
|
123
|
-
signature = ''
|
|
124
|
-
index -=1
|
|
125
|
-
in_whitespace = false
|
|
126
|
-
while index >= 0
|
|
127
|
-
pos = Position.from_offset(@source.code, index)
|
|
128
|
-
break if index > 0 and @source.comment_at?(pos)
|
|
129
|
-
unless !in_whitespace and string?
|
|
130
|
-
break if brackets > 0 or parens > 0 or squares > 0
|
|
131
|
-
char = @source.code[index, 1]
|
|
132
|
-
break if char.nil? # @todo Is this the right way to handle this?
|
|
133
|
-
if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
|
|
134
|
-
in_whitespace = true
|
|
135
|
-
else
|
|
136
|
-
if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
|
|
137
|
-
unless char == '.' or @source.code[index+1..-1].strip.start_with?('.')
|
|
138
|
-
old = @source.code[index+1..-1]
|
|
139
|
-
nxt = @source.code[index+1..-1].lstrip
|
|
140
|
-
index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length)
|
|
141
|
-
break
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
if char == ')'
|
|
145
|
-
parens -=1
|
|
146
|
-
elsif char == ']'
|
|
147
|
-
squares -=1
|
|
148
|
-
elsif char == '}'
|
|
149
|
-
brackets -= 1
|
|
150
|
-
elsif char == '('
|
|
151
|
-
parens += 1
|
|
152
|
-
elsif char == '{'
|
|
153
|
-
brackets += 1
|
|
154
|
-
elsif char == '['
|
|
155
|
-
squares += 1
|
|
156
|
-
signature = ".[]#{signature}" if parens.zero? and brackets.zero? and squares.zero? and @source.code[index-2] != '%'
|
|
157
|
-
end
|
|
158
|
-
if brackets.zero? and parens.zero? and squares.zero?
|
|
159
|
-
break if ['"', "'", ',', ';', '%'].include?(char)
|
|
160
|
-
break if !signature.empty? && ['!', '?'].include?(char)
|
|
161
|
-
signature = char + signature if char.match(/[a-z0-9:\._@\$\?\!]/i) and @source.code[index - 1] != '%'
|
|
162
|
-
break if char == '$'
|
|
163
|
-
if char == '@'
|
|
164
|
-
index -= 1
|
|
165
|
-
if @source.code[index, 1] == '@'
|
|
166
|
-
index -= 1
|
|
167
|
-
signature = "@#{signature}"
|
|
168
|
-
end
|
|
169
|
-
break
|
|
170
|
-
end
|
|
171
|
-
elsif parens == 1 || brackets == 1 || squares == 1
|
|
172
|
-
break
|
|
173
|
-
end
|
|
174
|
-
in_whitespace = false
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
index -= 1
|
|
178
|
-
end
|
|
179
|
-
[index + 1, signature]
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
end
|
|
1
|
+
module Solargraph
|
|
2
|
+
class Source
|
|
3
|
+
# Information about a location in a source, including the location's word
|
|
4
|
+
# and signature, literal values at the base of signatures, and whether the
|
|
5
|
+
# location is inside a string or comment. ApiMaps use Fragments to provide
|
|
6
|
+
# results for completion and definition queries.
|
|
7
|
+
#
|
|
8
|
+
class SourceChainer
|
|
9
|
+
include Source::NodeMethods
|
|
10
|
+
|
|
11
|
+
private_class_method :new
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# @param source [Source]
|
|
15
|
+
# @param position [Position]
|
|
16
|
+
# @return [Source::Chain]
|
|
17
|
+
def chain source, position
|
|
18
|
+
# raise "Not a source" unless source.is_a?(Source)
|
|
19
|
+
new(source, position).chain
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param source [Source]
|
|
24
|
+
# @param position [Position]
|
|
25
|
+
def initialize source, position
|
|
26
|
+
@source = source
|
|
27
|
+
@position = position
|
|
28
|
+
@calculated_literal = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Source::Chain]
|
|
32
|
+
def chain
|
|
33
|
+
return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
|
|
34
|
+
begin
|
|
35
|
+
return Chain.new([]) if phrase.end_with?('..')
|
|
36
|
+
if !source.repaired? && source.parsed? && source.synchronized?
|
|
37
|
+
node = source.node_at(position.line, position.column)
|
|
38
|
+
else
|
|
39
|
+
node = nil
|
|
40
|
+
node = source.node_at(fixed_position.line, fixed_position.column) unless source.error_ranges.any?{|r| r.nil? || r.include?(fixed_position)}
|
|
41
|
+
# Exception for positions that chain literal nodes in unsynchronized sources
|
|
42
|
+
node = nil unless source.synchronized? || !infer_literal_node_type(node).nil?
|
|
43
|
+
node = Source.parse(fixed_phrase) if node.nil?
|
|
44
|
+
end
|
|
45
|
+
rescue Parser::SyntaxError
|
|
46
|
+
return Chain.new([Chain::UNDEFINED_CALL])
|
|
47
|
+
end
|
|
48
|
+
return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':'))
|
|
49
|
+
chain = NodeChainer.chain(node, source.filename)
|
|
50
|
+
if source.repaired? || !source.parsed? || !source.synchronized?
|
|
51
|
+
if end_of_phrase.strip == '.'
|
|
52
|
+
chain.links.push Chain::UNDEFINED_CALL
|
|
53
|
+
elsif end_of_phrase.strip == '::'
|
|
54
|
+
chain.links.push Chain::UNDEFINED_CONSTANT
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
chain
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# @return [Position]
|
|
63
|
+
attr_reader :position
|
|
64
|
+
|
|
65
|
+
# @return [Solargraph::Source]
|
|
66
|
+
attr_reader :source
|
|
67
|
+
|
|
68
|
+
# @return [String]
|
|
69
|
+
def phrase
|
|
70
|
+
@phrase ||= source.code[signature_data[0]..offset-1]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [String]
|
|
74
|
+
def fixed_phrase
|
|
75
|
+
@fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [Position]
|
|
79
|
+
def fixed_position
|
|
80
|
+
@fixed_position ||= Position.from_offset(source.code, offset - end_of_phrase.length)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [String]
|
|
84
|
+
def end_of_phrase
|
|
85
|
+
@end_of_phrase ||= begin
|
|
86
|
+
match = phrase.match(/[\s]*(\.{1}|::)[\s]*$/)
|
|
87
|
+
if match
|
|
88
|
+
match[0]
|
|
89
|
+
else
|
|
90
|
+
''
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# True if the current offset is inside a string.
|
|
96
|
+
#
|
|
97
|
+
# @return [Boolean]
|
|
98
|
+
def string?
|
|
99
|
+
# @string ||= (node.type == :str or node.type == :dstr)
|
|
100
|
+
@string ||= @source.string_at?(position)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [Integer]
|
|
104
|
+
def offset
|
|
105
|
+
@offset ||= get_offset(position.line, position.column)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @param line [Integer]
|
|
109
|
+
# @param column [Integer]
|
|
110
|
+
# @return [Integer]
|
|
111
|
+
def get_offset line, column
|
|
112
|
+
Position.line_char_to_offset(@source.code, line, column)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def signature_data
|
|
116
|
+
@signature_data ||= get_signature_data_at(offset)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def get_signature_data_at index
|
|
120
|
+
brackets = 0
|
|
121
|
+
squares = 0
|
|
122
|
+
parens = 0
|
|
123
|
+
signature = ''
|
|
124
|
+
index -=1
|
|
125
|
+
in_whitespace = false
|
|
126
|
+
while index >= 0
|
|
127
|
+
pos = Position.from_offset(@source.code, index)
|
|
128
|
+
break if index > 0 and @source.comment_at?(pos)
|
|
129
|
+
unless !in_whitespace and string?
|
|
130
|
+
break if brackets > 0 or parens > 0 or squares > 0
|
|
131
|
+
char = @source.code[index, 1]
|
|
132
|
+
break if char.nil? # @todo Is this the right way to handle this?
|
|
133
|
+
if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
|
|
134
|
+
in_whitespace = true
|
|
135
|
+
else
|
|
136
|
+
if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
|
|
137
|
+
unless char == '.' or @source.code[index+1..-1].strip.start_with?('.')
|
|
138
|
+
old = @source.code[index+1..-1]
|
|
139
|
+
nxt = @source.code[index+1..-1].lstrip
|
|
140
|
+
index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length)
|
|
141
|
+
break
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
if char == ')'
|
|
145
|
+
parens -=1
|
|
146
|
+
elsif char == ']'
|
|
147
|
+
squares -=1
|
|
148
|
+
elsif char == '}'
|
|
149
|
+
brackets -= 1
|
|
150
|
+
elsif char == '('
|
|
151
|
+
parens += 1
|
|
152
|
+
elsif char == '{'
|
|
153
|
+
brackets += 1
|
|
154
|
+
elsif char == '['
|
|
155
|
+
squares += 1
|
|
156
|
+
signature = ".[]#{signature}" if parens.zero? and brackets.zero? and squares.zero? and @source.code[index-2] != '%'
|
|
157
|
+
end
|
|
158
|
+
if brackets.zero? and parens.zero? and squares.zero?
|
|
159
|
+
break if ['"', "'", ',', ';', '%'].include?(char)
|
|
160
|
+
break if !signature.empty? && ['!', '?'].include?(char)
|
|
161
|
+
signature = char + signature if char.match(/[a-z0-9:\._@\$\?\!]/i) and @source.code[index - 1] != '%'
|
|
162
|
+
break if char == '$'
|
|
163
|
+
if char == '@'
|
|
164
|
+
index -= 1
|
|
165
|
+
if @source.code[index, 1] == '@'
|
|
166
|
+
index -= 1
|
|
167
|
+
signature = "@#{signature}"
|
|
168
|
+
end
|
|
169
|
+
break
|
|
170
|
+
end
|
|
171
|
+
elsif parens == 1 || brackets == 1 || squares == 1
|
|
172
|
+
break
|
|
173
|
+
end
|
|
174
|
+
in_whitespace = false
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
index -= 1
|
|
178
|
+
end
|
|
179
|
+
[index + 1, signature]
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|