solargraph 0.58.1 → 0.58.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 +1 -0
- data/CHANGELOG.md +7 -1
- data/lib/solargraph/api_map/cache.rb +110 -110
- data/lib/solargraph/api_map/constants.rb +279 -279
- data/lib/solargraph/api_map/index.rb +193 -193
- data/lib/solargraph/api_map/source_to_yard.rb +97 -97
- data/lib/solargraph/api_map/store.rb +384 -384
- data/lib/solargraph/api_map.rb +945 -945
- data/lib/solargraph/complex_type/type_methods.rb +228 -228
- data/lib/solargraph/complex_type/unique_type.rb +482 -482
- data/lib/solargraph/complex_type.rb +444 -444
- data/lib/solargraph/convention/data_definition/data_definition_node.rb +91 -91
- data/lib/solargraph/convention/data_definition.rb +105 -105
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +61 -61
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +102 -102
- data/lib/solargraph/convention/struct_definition.rb +164 -164
- data/lib/solargraph/diagnostics/require_not_found.rb +53 -53
- data/lib/solargraph/diagnostics/rubocop.rb +118 -118
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +68 -68
- data/lib/solargraph/diagnostics/type_check.rb +55 -55
- data/lib/solargraph/doc_map.rb +439 -439
- data/lib/solargraph/equality.rb +34 -34
- data/lib/solargraph/gem_pins.rb +98 -98
- data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
- data/lib/solargraph/language_server/host/dispatch.rb +130 -130
- data/lib/solargraph/language_server/host/message_worker.rb +112 -112
- data/lib/solargraph/language_server/host/sources.rb +99 -99
- data/lib/solargraph/language_server/host.rb +878 -878
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +114 -114
- data/lib/solargraph/language_server/message/extended/document.rb +23 -23
- data/lib/solargraph/language_server/message/text_document/completion.rb +56 -56
- data/lib/solargraph/language_server/message/text_document/definition.rb +40 -40
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +26 -26
- data/lib/solargraph/language_server/message/text_document/formatting.rb +148 -148
- data/lib/solargraph/language_server/message/text_document/hover.rb +58 -58
- data/lib/solargraph/language_server/message/text_document/signature_help.rb +24 -24
- data/lib/solargraph/language_server/message/text_document/type_definition.rb +25 -25
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -23
- data/lib/solargraph/library.rb +683 -683
- data/lib/solargraph/location.rb +82 -82
- data/lib/solargraph/logging.rb +37 -37
- data/lib/solargraph/parser/comment_ripper.rb +69 -69
- data/lib/solargraph/parser/flow_sensitive_typing.rb +255 -255
- data/lib/solargraph/parser/node_processor/base.rb +92 -92
- data/lib/solargraph/parser/node_processor.rb +62 -62
- data/lib/solargraph/parser/parser_gem/class_methods.rb +149 -149
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +166 -166
- data/lib/solargraph/parser/parser_gem/node_methods.rb +486 -486
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
- data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +59 -59
- data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +15 -15
- data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
- data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +53 -53
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +23 -23
- data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +40 -40
- data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +29 -29
- data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +59 -59
- data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
- data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
- data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +38 -38
- data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +52 -52
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +291 -291
- data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -29
- data/lib/solargraph/parser/parser_gem/node_processors.rb +70 -70
- data/lib/solargraph/parser/region.rb +69 -69
- data/lib/solargraph/parser/snippet.rb +17 -17
- data/lib/solargraph/pin/base.rb +729 -729
- data/lib/solargraph/pin/base_variable.rb +126 -126
- data/lib/solargraph/pin/block.rb +104 -104
- data/lib/solargraph/pin/breakable.rb +9 -9
- data/lib/solargraph/pin/callable.rb +231 -231
- data/lib/solargraph/pin/closure.rb +72 -72
- data/lib/solargraph/pin/common.rb +79 -79
- data/lib/solargraph/pin/conversions.rb +123 -123
- data/lib/solargraph/pin/delegated_method.rb +120 -120
- data/lib/solargraph/pin/documenting.rb +114 -114
- data/lib/solargraph/pin/instance_variable.rb +34 -34
- data/lib/solargraph/pin/keyword.rb +20 -20
- data/lib/solargraph/pin/local_variable.rb +75 -75
- data/lib/solargraph/pin/method.rb +672 -672
- data/lib/solargraph/pin/method_alias.rb +34 -34
- data/lib/solargraph/pin/namespace.rb +115 -115
- data/lib/solargraph/pin/parameter.rb +275 -275
- data/lib/solargraph/pin/proxy_type.rb +39 -39
- data/lib/solargraph/pin/reference/override.rb +47 -47
- data/lib/solargraph/pin/reference/superclass.rb +15 -15
- data/lib/solargraph/pin/reference.rb +39 -39
- data/lib/solargraph/pin/search.rb +61 -61
- data/lib/solargraph/pin/signature.rb +61 -61
- data/lib/solargraph/pin/symbol.rb +53 -53
- data/lib/solargraph/pin/until.rb +18 -18
- data/lib/solargraph/pin/while.rb +18 -18
- data/lib/solargraph/pin.rb +44 -44
- data/lib/solargraph/pin_cache.rb +245 -245
- data/lib/solargraph/position.rb +132 -119
- data/lib/solargraph/range.rb +112 -112
- data/lib/solargraph/rbs_map/conversions.rb +823 -823
- data/lib/solargraph/rbs_map/core_map.rb +58 -58
- data/lib/solargraph/rbs_map/stdlib_map.rb +43 -43
- data/lib/solargraph/rbs_map.rb +163 -163
- data/lib/solargraph/shell.rb +352 -352
- data/lib/solargraph/source/chain/call.rb +337 -337
- data/lib/solargraph/source/chain/constant.rb +26 -26
- data/lib/solargraph/source/chain/hash.rb +34 -34
- data/lib/solargraph/source/chain/if.rb +28 -28
- data/lib/solargraph/source/chain/instance_variable.rb +13 -13
- data/lib/solargraph/source/chain/literal.rb +48 -48
- data/lib/solargraph/source/chain/or.rb +23 -23
- data/lib/solargraph/source/chain.rb +291 -291
- data/lib/solargraph/source/change.rb +82 -82
- data/lib/solargraph/source/cursor.rb +166 -166
- data/lib/solargraph/source/source_chainer.rb +194 -194
- data/lib/solargraph/source/updater.rb +55 -55
- data/lib/solargraph/source.rb +498 -498
- data/lib/solargraph/source_map/clip.rb +226 -226
- data/lib/solargraph/source_map/data.rb +34 -34
- data/lib/solargraph/source_map/mapper.rb +259 -259
- data/lib/solargraph/source_map.rb +212 -212
- data/lib/solargraph/type_checker/checks.rb +124 -124
- data/lib/solargraph/type_checker/param_def.rb +37 -37
- data/lib/solargraph/type_checker/problem.rb +32 -32
- data/lib/solargraph/type_checker/rules.rb +84 -84
- data/lib/solargraph/type_checker.rb +814 -814
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace/config.rb +255 -255
- data/lib/solargraph/workspace/require_paths.rb +97 -97
- data/lib/solargraph/workspace.rb +220 -220
- data/lib/solargraph/yard_map/helpers.rb +44 -44
- data/lib/solargraph/yard_map/mapper/to_method.rb +130 -130
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +31 -31
- data/lib/solargraph/yard_map/mapper.rb +79 -79
- data/lib/solargraph/yard_map/to_method.rb +89 -89
- data/lib/solargraph/yardoc.rb +87 -87
- data/lib/solargraph.rb +105 -105
- data/rbs_collection.yaml +1 -1
- metadata +12 -12
- /data/{sig → rbs}/shims/ast/0/node.rbs +0 -0
- /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
- /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/thor.rbs +0 -0
data/lib/solargraph/source.rb
CHANGED
|
@@ -1,498 +1,498 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'yard'
|
|
4
|
-
|
|
5
|
-
module Solargraph
|
|
6
|
-
# A Ruby file that has been parsed into an AST.
|
|
7
|
-
#
|
|
8
|
-
class Source
|
|
9
|
-
autoload :Updater, 'solargraph/source/updater'
|
|
10
|
-
autoload :Change, 'solargraph/source/change'
|
|
11
|
-
autoload :EncodingFixes, 'solargraph/source/encoding_fixes'
|
|
12
|
-
autoload :Cursor, 'solargraph/source/cursor'
|
|
13
|
-
autoload :Chain, 'solargraph/source/chain'
|
|
14
|
-
autoload :SourceChainer, 'solargraph/source/source_chainer'
|
|
15
|
-
|
|
16
|
-
include EncodingFixes
|
|
17
|
-
|
|
18
|
-
# @return [String, nil]
|
|
19
|
-
attr_reader :filename
|
|
20
|
-
|
|
21
|
-
# @return [String]
|
|
22
|
-
def code
|
|
23
|
-
finalize
|
|
24
|
-
@code
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# @return [Parser::AST::Node, nil]
|
|
28
|
-
def node
|
|
29
|
-
finalize
|
|
30
|
-
@node
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# @return [Hash{Integer => Solargraph::Parser::Snippet}]
|
|
34
|
-
def comments
|
|
35
|
-
finalize
|
|
36
|
-
@comments
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @todo Deprecate?
|
|
40
|
-
# @return [Integer]
|
|
41
|
-
attr_reader :version
|
|
42
|
-
|
|
43
|
-
# @param code [String]
|
|
44
|
-
# @param filename [String, nil]
|
|
45
|
-
# @param version [Integer]
|
|
46
|
-
def initialize code, filename = nil, version = 0
|
|
47
|
-
@code = normalize(code)
|
|
48
|
-
@repaired = code
|
|
49
|
-
@filename = filename
|
|
50
|
-
@version = version
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# @param range [Solargraph::Range]
|
|
54
|
-
# @return [String]
|
|
55
|
-
def at range
|
|
56
|
-
from_to range.start.line, range.start.character, range.ending.line, range.ending.character
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# @param l1 [Integer]
|
|
60
|
-
# @param c1 [Integer]
|
|
61
|
-
# @param l2 [Integer]
|
|
62
|
-
# @param c2 [Integer]
|
|
63
|
-
# @return [String]
|
|
64
|
-
def from_to l1, c1, l2, c2
|
|
65
|
-
b = Solargraph::Position.line_char_to_offset(code, l1, c1)
|
|
66
|
-
e = Solargraph::Position.line_char_to_offset(code, l2, c2)
|
|
67
|
-
code[b..e-1]
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Get the nearest node that contains the specified index.
|
|
71
|
-
#
|
|
72
|
-
# @param line [Integer]
|
|
73
|
-
# @param column [Integer]
|
|
74
|
-
# @return [AST::Node]
|
|
75
|
-
def node_at(line, column)
|
|
76
|
-
tree_at(line, column).first
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Get an array of nodes containing the specified index, starting with the
|
|
80
|
-
# nearest node and ending with the root.
|
|
81
|
-
#
|
|
82
|
-
# @param line [Integer]
|
|
83
|
-
# @param column [Integer]
|
|
84
|
-
# @return [Array<AST::Node>]
|
|
85
|
-
def tree_at(line, column)
|
|
86
|
-
position = Position.new(line, column)
|
|
87
|
-
stack = []
|
|
88
|
-
inner_tree_at node, position, stack
|
|
89
|
-
stack
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Synchronize the Source with an update. This method applies changes to the
|
|
93
|
-
# code, parses the new code's AST, and returns the resulting Source object.
|
|
94
|
-
#
|
|
95
|
-
# @param updater [Source::Updater]
|
|
96
|
-
# @return [Source]
|
|
97
|
-
def synchronize updater
|
|
98
|
-
raise 'Invalid synchronization' unless updater.filename == filename
|
|
99
|
-
real_code = updater.write(@code)
|
|
100
|
-
if real_code == @code
|
|
101
|
-
@version = updater.version
|
|
102
|
-
return self
|
|
103
|
-
end
|
|
104
|
-
Source.new(@code, filename, updater.version).tap do |src|
|
|
105
|
-
src.repaired = @repaired
|
|
106
|
-
src.error_ranges.concat error_ranges
|
|
107
|
-
src.changes.concat(changes + updater.changes)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# @param position [Position, Array(Integer, Integer)]
|
|
112
|
-
# @return [Source::Cursor]
|
|
113
|
-
def cursor_at position
|
|
114
|
-
finalize
|
|
115
|
-
Cursor.new(self, position)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# @return [Boolean]
|
|
119
|
-
def parsed?
|
|
120
|
-
finalize
|
|
121
|
-
@parsed
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def repaired?
|
|
125
|
-
code != @repaired
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# @param position [Position]
|
|
129
|
-
# @return [Boolean]
|
|
130
|
-
def string_at? position
|
|
131
|
-
return false if Position.to_offset(code, position) >= code.length
|
|
132
|
-
string_nodes.each do |node|
|
|
133
|
-
range = Range.from_node(node)
|
|
134
|
-
next if range.ending.line < position.line
|
|
135
|
-
break if range.ending.line > position.line
|
|
136
|
-
return true if node.type == :str && range.include?(position) && range.start != position
|
|
137
|
-
return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position
|
|
138
|
-
if node.type == :dstr
|
|
139
|
-
inner = node_at(position.line, position.column)
|
|
140
|
-
next if inner.nil?
|
|
141
|
-
inner_range = Range.from_node(inner)
|
|
142
|
-
next unless range.include?(inner_range.ending)
|
|
143
|
-
return true if inner.type == :str
|
|
144
|
-
inner_code = at(Solargraph::Range.new(inner_range.start, position))
|
|
145
|
-
return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') ||
|
|
146
|
-
(inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}'))
|
|
147
|
-
end
|
|
148
|
-
break if range.ending.line > position.line
|
|
149
|
-
end
|
|
150
|
-
false
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
# @return [::Array<Range>]
|
|
154
|
-
def string_ranges
|
|
155
|
-
@string_ranges ||= Parser.string_ranges(node)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# @param position [Position]
|
|
159
|
-
# @return [Boolean]
|
|
160
|
-
def comment_at? position
|
|
161
|
-
comment_ranges.each do |range|
|
|
162
|
-
return true if range.include?(position) ||
|
|
163
|
-
(range.ending.line == position.line && range.ending.column < position.column)
|
|
164
|
-
break if range.ending.line > position.line
|
|
165
|
-
end
|
|
166
|
-
false
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# @param name [String]
|
|
170
|
-
# @return [Array<Location>]
|
|
171
|
-
def references name
|
|
172
|
-
Parser.references self, name
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# @return [Array<Range>]
|
|
176
|
-
def error_ranges
|
|
177
|
-
@error_ranges ||= []
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# @param node [Parser::AST::Node]
|
|
181
|
-
# @return [String]
|
|
182
|
-
def code_for(node)
|
|
183
|
-
rng = Range.from_node(node)
|
|
184
|
-
b = Position.line_char_to_offset(code, rng.start.line, rng.start.column)
|
|
185
|
-
e = Position.line_char_to_offset(code, rng.ending.line, rng.ending.column)
|
|
186
|
-
frag = code[b..e-1].to_s
|
|
187
|
-
frag.strip.gsub(/,$/, '')
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# @param node [Parser::AST::Node]
|
|
191
|
-
# @return [String, nil]
|
|
192
|
-
def comments_for node
|
|
193
|
-
rng = Range.from_node(node)
|
|
194
|
-
stringified_comments[rng.start.line] ||= begin
|
|
195
|
-
buff = associated_comments[rng.start.line]
|
|
196
|
-
buff ? stringify_comment_array(buff) : nil
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# A location representing the file in its entirety.
|
|
201
|
-
#
|
|
202
|
-
# @return [Location]
|
|
203
|
-
def location
|
|
204
|
-
st = Position.new(0, 0)
|
|
205
|
-
en = Position.from_offset(code, code.length)
|
|
206
|
-
range = Range.new(st, en)
|
|
207
|
-
Location.new(filename, range)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
FOLDING_NODE_TYPES = %i[
|
|
211
|
-
class sclass module def defs if str dstr array while unless kwbegin hash block
|
|
212
|
-
].freeze
|
|
213
|
-
|
|
214
|
-
# Get an array of ranges that can be folded, e.g., the range of a class
|
|
215
|
-
# definition or an if condition.
|
|
216
|
-
#
|
|
217
|
-
# See FOLDING_NODE_TYPES for the list of node types that can be folded.
|
|
218
|
-
#
|
|
219
|
-
# @return [Array<Range>]
|
|
220
|
-
def folding_ranges
|
|
221
|
-
@folding_ranges ||= begin
|
|
222
|
-
result = []
|
|
223
|
-
inner_folding_ranges node, result
|
|
224
|
-
result.concat foldable_comment_block_ranges
|
|
225
|
-
result
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def synchronized?
|
|
230
|
-
true
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# Get a hash of comments grouped by the line numbers of the associated code.
|
|
234
|
-
#
|
|
235
|
-
# @return [Hash{Integer => String}]
|
|
236
|
-
def associated_comments
|
|
237
|
-
@associated_comments ||= begin
|
|
238
|
-
# @type [Hash{Integer => String}]
|
|
239
|
-
result = {}
|
|
240
|
-
buffer = String.new('')
|
|
241
|
-
# @type [Integer, nil]
|
|
242
|
-
last = nil
|
|
243
|
-
comments.each_pair do |num, snip|
|
|
244
|
-
if !last || num == last + 1
|
|
245
|
-
buffer.concat "#{snip.text}\n"
|
|
246
|
-
else
|
|
247
|
-
result[first_not_empty_from(last + 1)] = buffer.clone
|
|
248
|
-
buffer.replace "#{snip.text}\n"
|
|
249
|
-
end
|
|
250
|
-
last = num
|
|
251
|
-
end
|
|
252
|
-
result[first_not_empty_from(last + 1)] = buffer unless buffer.empty? || last.nil?
|
|
253
|
-
result
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
private
|
|
258
|
-
|
|
259
|
-
# @param line [Integer]
|
|
260
|
-
# @return [Integer]
|
|
261
|
-
def first_not_empty_from line
|
|
262
|
-
cursor = line
|
|
263
|
-
cursor += 1 while cursor < code_lines.length && code_lines[cursor].strip.empty?
|
|
264
|
-
cursor = line if cursor > code_lines.length - 1
|
|
265
|
-
cursor
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# @param top [Parser::AST::Node]
|
|
269
|
-
# @param result [Array<Range>]
|
|
270
|
-
# @param parent [Symbol, nil]
|
|
271
|
-
# @return [void]
|
|
272
|
-
def inner_folding_ranges top, result = [], parent = nil
|
|
273
|
-
return unless Parser.is_ast_node?(top)
|
|
274
|
-
if FOLDING_NODE_TYPES.include?(top.type)
|
|
275
|
-
range = Range.from_node(top)
|
|
276
|
-
if result.empty? || range.start.line > result.last.start.line
|
|
277
|
-
result.push range unless range.ending.line - range.start.line < 2
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
top.children.each do |child|
|
|
281
|
-
inner_folding_ranges(child, result, top.type)
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
# Get a string representation of an array of comments.
|
|
286
|
-
#
|
|
287
|
-
# @param comments [String]
|
|
288
|
-
# @return [String]
|
|
289
|
-
def stringify_comment_array comments
|
|
290
|
-
ctxt = String.new('')
|
|
291
|
-
started = false
|
|
292
|
-
skip = nil
|
|
293
|
-
comments.lines.each { |l|
|
|
294
|
-
# Trim the comment and minimum leading whitespace
|
|
295
|
-
p = l.force_encoding('UTF-8').encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '')
|
|
296
|
-
if p.strip.empty?
|
|
297
|
-
next unless started
|
|
298
|
-
ctxt.concat p
|
|
299
|
-
else
|
|
300
|
-
here = p.index(/[^ \t]/)
|
|
301
|
-
skip = here if skip.nil? || here < skip
|
|
302
|
-
ctxt.concat p[skip..-1]
|
|
303
|
-
end
|
|
304
|
-
started = true
|
|
305
|
-
}
|
|
306
|
-
ctxt
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# A hash of line numbers and their associated comments.
|
|
310
|
-
#
|
|
311
|
-
# @return [Hash{Integer => Array<String>, nil}]
|
|
312
|
-
def stringified_comments
|
|
313
|
-
@stringified_comments ||= {}
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
# @return [Array<Parser::AST::Node>]
|
|
317
|
-
def string_nodes
|
|
318
|
-
@string_nodes ||= string_nodes_in(node)
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
# @return [Array<Solargraph::Range>]
|
|
322
|
-
def comment_ranges
|
|
323
|
-
@comment_ranges ||= comments.values.map(&:range)
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# Get an array of foldable comment block ranges. Blocks are excluded if
|
|
327
|
-
# they are less than 3 lines long.
|
|
328
|
-
#
|
|
329
|
-
# @return [Array<Range>]
|
|
330
|
-
def foldable_comment_block_ranges
|
|
331
|
-
return [] unless synchronized?
|
|
332
|
-
result = []
|
|
333
|
-
grouped = []
|
|
334
|
-
comments.keys.each do |l|
|
|
335
|
-
if grouped.empty? || l == grouped.last + 1
|
|
336
|
-
grouped.push l
|
|
337
|
-
else
|
|
338
|
-
result.push Range.from_to(grouped.first, 0, grouped.last, 0) unless grouped.length < 3
|
|
339
|
-
grouped = [l]
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
result.push Range.from_to(grouped.first, 0, grouped.last, 0) unless grouped.length < 3
|
|
343
|
-
result
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
# @param n [Parser::AST::Node, nil]
|
|
347
|
-
# @return [Array<Parser::AST::Node>]
|
|
348
|
-
def string_nodes_in n
|
|
349
|
-
result = []
|
|
350
|
-
if Parser.is_ast_node?(n)
|
|
351
|
-
if n.type == :str || n.type == :dstr || n.type == :STR || n.type == :DSTR
|
|
352
|
-
result.push n
|
|
353
|
-
else
|
|
354
|
-
n.children.each{ |c| result.concat string_nodes_in(c) }
|
|
355
|
-
end
|
|
356
|
-
end
|
|
357
|
-
result
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
# @param node [Parser::AST::Node, nil]
|
|
361
|
-
# @param position [Position]
|
|
362
|
-
# @param stack [Array<Parser::AST::Node>]
|
|
363
|
-
# @return [void]
|
|
364
|
-
def inner_tree_at node, position, stack
|
|
365
|
-
return if node.nil?
|
|
366
|
-
here = Range.from_node(node)
|
|
367
|
-
if here.contain?(position)
|
|
368
|
-
stack.unshift node
|
|
369
|
-
node.children.each do |c|
|
|
370
|
-
next unless Parser.is_ast_node?(c)
|
|
371
|
-
next if c.loc.expression.nil?
|
|
372
|
-
inner_tree_at(c, position, stack)
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
protected
|
|
378
|
-
|
|
379
|
-
# @return [Array<Change>]
|
|
380
|
-
def changes
|
|
381
|
-
@changes ||= []
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
# @return [String]
|
|
385
|
-
attr_writer :filename
|
|
386
|
-
|
|
387
|
-
# @return [Integer]
|
|
388
|
-
attr_writer :version
|
|
389
|
-
|
|
390
|
-
# @return [void]
|
|
391
|
-
def finalize
|
|
392
|
-
return if @finalized && changes.empty?
|
|
393
|
-
|
|
394
|
-
changes.each do |change|
|
|
395
|
-
@code = change.write(@code)
|
|
396
|
-
end
|
|
397
|
-
@finalized = true
|
|
398
|
-
begin
|
|
399
|
-
@node, @comments = Solargraph::Parser.parse_with_comments(@code, filename)
|
|
400
|
-
@parsed = true
|
|
401
|
-
@repaired = @code
|
|
402
|
-
rescue Parser::SyntaxError, EncodingError => e
|
|
403
|
-
@node = nil
|
|
404
|
-
@comments = {}
|
|
405
|
-
@parsed = false
|
|
406
|
-
ensure
|
|
407
|
-
@code.freeze
|
|
408
|
-
end
|
|
409
|
-
if !@parsed && !changes.empty?
|
|
410
|
-
changes.each do |change|
|
|
411
|
-
@repaired = change.repair(@repaired)
|
|
412
|
-
end
|
|
413
|
-
error_ranges.concat(changes.map(&:range))
|
|
414
|
-
begin
|
|
415
|
-
@node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename)
|
|
416
|
-
@parsed = true
|
|
417
|
-
rescue Parser::SyntaxError, EncodingError => e
|
|
418
|
-
@node = nil
|
|
419
|
-
@comments = {}
|
|
420
|
-
@parsed = false
|
|
421
|
-
end
|
|
422
|
-
elsif @parsed
|
|
423
|
-
error_ranges.clear
|
|
424
|
-
end
|
|
425
|
-
changes.clear
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
# @param val [String]
|
|
429
|
-
# @return [String]
|
|
430
|
-
def code=(val)
|
|
431
|
-
@code_lines = nil
|
|
432
|
-
@finalized = false
|
|
433
|
-
@code = val
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
# @return [Parser::AST::Node, nil]
|
|
437
|
-
attr_writer :node
|
|
438
|
-
|
|
439
|
-
# @return [Array<Range>]
|
|
440
|
-
attr_writer :error_ranges
|
|
441
|
-
|
|
442
|
-
# @return [String]
|
|
443
|
-
attr_writer :repaired
|
|
444
|
-
|
|
445
|
-
# @return [String]
|
|
446
|
-
def repaired
|
|
447
|
-
finalize
|
|
448
|
-
@repaired
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
# @return [Boolean]
|
|
452
|
-
attr_writer :parsed
|
|
453
|
-
|
|
454
|
-
# @return [Hash{Integer => String}
|
|
455
|
-
attr_writer :comments
|
|
456
|
-
|
|
457
|
-
# @return [Boolean]
|
|
458
|
-
attr_writer :synchronized
|
|
459
|
-
|
|
460
|
-
private
|
|
461
|
-
|
|
462
|
-
# @return [Array<String>]
|
|
463
|
-
def code_lines
|
|
464
|
-
@code_lines ||= code.lines
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
class << self
|
|
468
|
-
# @param filename [String]
|
|
469
|
-
# @return [Solargraph::Source]
|
|
470
|
-
def load filename
|
|
471
|
-
file = File.open(filename)
|
|
472
|
-
code = file.read
|
|
473
|
-
file.close
|
|
474
|
-
Source.load_string(code, filename)
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
# @param code [String]
|
|
478
|
-
# @param filename [String, nil]
|
|
479
|
-
# @param version [Integer]
|
|
480
|
-
# @return [Solargraph::Source]
|
|
481
|
-
def load_string code, filename = nil, version = 0
|
|
482
|
-
Source.new code, filename, version
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
# @param comments [String]
|
|
486
|
-
# @return [YARD::DocstringParser]
|
|
487
|
-
def parse_docstring comments
|
|
488
|
-
# HACK: Pass a dummy code object to the parser for plugins that
|
|
489
|
-
# expect it not to be nil
|
|
490
|
-
YARD::Docstring.parser.parse(comments, YARD::CodeObjects::Base.new(:root, 'stub'))
|
|
491
|
-
rescue StandardError => e
|
|
492
|
-
Solargraph.logger.info "YARD failed to parse docstring: [#{e.class}] #{e.message}"
|
|
493
|
-
Solargraph.logger.debug "Unparsed comment: #{comments}"
|
|
494
|
-
YARD::Docstring.parser
|
|
495
|
-
end
|
|
496
|
-
end
|
|
497
|
-
end
|
|
498
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yard'
|
|
4
|
+
|
|
5
|
+
module Solargraph
|
|
6
|
+
# A Ruby file that has been parsed into an AST.
|
|
7
|
+
#
|
|
8
|
+
class Source
|
|
9
|
+
autoload :Updater, 'solargraph/source/updater'
|
|
10
|
+
autoload :Change, 'solargraph/source/change'
|
|
11
|
+
autoload :EncodingFixes, 'solargraph/source/encoding_fixes'
|
|
12
|
+
autoload :Cursor, 'solargraph/source/cursor'
|
|
13
|
+
autoload :Chain, 'solargraph/source/chain'
|
|
14
|
+
autoload :SourceChainer, 'solargraph/source/source_chainer'
|
|
15
|
+
|
|
16
|
+
include EncodingFixes
|
|
17
|
+
|
|
18
|
+
# @return [String, nil]
|
|
19
|
+
attr_reader :filename
|
|
20
|
+
|
|
21
|
+
# @return [String]
|
|
22
|
+
def code
|
|
23
|
+
finalize
|
|
24
|
+
@code
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Parser::AST::Node, nil]
|
|
28
|
+
def node
|
|
29
|
+
finalize
|
|
30
|
+
@node
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash{Integer => Solargraph::Parser::Snippet}]
|
|
34
|
+
def comments
|
|
35
|
+
finalize
|
|
36
|
+
@comments
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @todo Deprecate?
|
|
40
|
+
# @return [Integer]
|
|
41
|
+
attr_reader :version
|
|
42
|
+
|
|
43
|
+
# @param code [String]
|
|
44
|
+
# @param filename [String, nil]
|
|
45
|
+
# @param version [Integer]
|
|
46
|
+
def initialize code, filename = nil, version = 0
|
|
47
|
+
@code = normalize(code)
|
|
48
|
+
@repaired = code
|
|
49
|
+
@filename = filename
|
|
50
|
+
@version = version
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param range [Solargraph::Range]
|
|
54
|
+
# @return [String]
|
|
55
|
+
def at range
|
|
56
|
+
from_to range.start.line, range.start.character, range.ending.line, range.ending.character
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param l1 [Integer]
|
|
60
|
+
# @param c1 [Integer]
|
|
61
|
+
# @param l2 [Integer]
|
|
62
|
+
# @param c2 [Integer]
|
|
63
|
+
# @return [String]
|
|
64
|
+
def from_to l1, c1, l2, c2
|
|
65
|
+
b = Solargraph::Position.line_char_to_offset(code, l1, c1)
|
|
66
|
+
e = Solargraph::Position.line_char_to_offset(code, l2, c2)
|
|
67
|
+
code[b..e-1]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get the nearest node that contains the specified index.
|
|
71
|
+
#
|
|
72
|
+
# @param line [Integer]
|
|
73
|
+
# @param column [Integer]
|
|
74
|
+
# @return [AST::Node]
|
|
75
|
+
def node_at(line, column)
|
|
76
|
+
tree_at(line, column).first
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get an array of nodes containing the specified index, starting with the
|
|
80
|
+
# nearest node and ending with the root.
|
|
81
|
+
#
|
|
82
|
+
# @param line [Integer]
|
|
83
|
+
# @param column [Integer]
|
|
84
|
+
# @return [Array<AST::Node>]
|
|
85
|
+
def tree_at(line, column)
|
|
86
|
+
position = Position.new(line, column)
|
|
87
|
+
stack = []
|
|
88
|
+
inner_tree_at node, position, stack
|
|
89
|
+
stack
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Synchronize the Source with an update. This method applies changes to the
|
|
93
|
+
# code, parses the new code's AST, and returns the resulting Source object.
|
|
94
|
+
#
|
|
95
|
+
# @param updater [Source::Updater]
|
|
96
|
+
# @return [Source]
|
|
97
|
+
def synchronize updater
|
|
98
|
+
raise 'Invalid synchronization' unless updater.filename == filename
|
|
99
|
+
real_code = updater.write(@code)
|
|
100
|
+
if real_code == @code
|
|
101
|
+
@version = updater.version
|
|
102
|
+
return self
|
|
103
|
+
end
|
|
104
|
+
Source.new(@code, filename, updater.version).tap do |src|
|
|
105
|
+
src.repaired = @repaired
|
|
106
|
+
src.error_ranges.concat error_ranges
|
|
107
|
+
src.changes.concat(changes + updater.changes)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @param position [Position, Array(Integer, Integer)]
|
|
112
|
+
# @return [Source::Cursor]
|
|
113
|
+
def cursor_at position
|
|
114
|
+
finalize
|
|
115
|
+
Cursor.new(self, position)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @return [Boolean]
|
|
119
|
+
def parsed?
|
|
120
|
+
finalize
|
|
121
|
+
@parsed
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def repaired?
|
|
125
|
+
code != @repaired
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @param position [Position]
|
|
129
|
+
# @return [Boolean]
|
|
130
|
+
def string_at? position
|
|
131
|
+
return false if Position.to_offset(code, position) >= code.length
|
|
132
|
+
string_nodes.each do |node|
|
|
133
|
+
range = Range.from_node(node)
|
|
134
|
+
next if range.ending.line < position.line
|
|
135
|
+
break if range.ending.line > position.line
|
|
136
|
+
return true if node.type == :str && range.include?(position) && range.start != position
|
|
137
|
+
return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position
|
|
138
|
+
if node.type == :dstr
|
|
139
|
+
inner = node_at(position.line, position.column)
|
|
140
|
+
next if inner.nil?
|
|
141
|
+
inner_range = Range.from_node(inner)
|
|
142
|
+
next unless range.include?(inner_range.ending)
|
|
143
|
+
return true if inner.type == :str
|
|
144
|
+
inner_code = at(Solargraph::Range.new(inner_range.start, position))
|
|
145
|
+
return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') ||
|
|
146
|
+
(inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}'))
|
|
147
|
+
end
|
|
148
|
+
break if range.ending.line > position.line
|
|
149
|
+
end
|
|
150
|
+
false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @return [::Array<Range>]
|
|
154
|
+
def string_ranges
|
|
155
|
+
@string_ranges ||= Parser.string_ranges(node)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @param position [Position]
|
|
159
|
+
# @return [Boolean]
|
|
160
|
+
def comment_at? position
|
|
161
|
+
comment_ranges.each do |range|
|
|
162
|
+
return true if range.include?(position) ||
|
|
163
|
+
(range.ending.line == position.line && range.ending.column < position.column)
|
|
164
|
+
break if range.ending.line > position.line
|
|
165
|
+
end
|
|
166
|
+
false
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @param name [String]
|
|
170
|
+
# @return [Array<Location>]
|
|
171
|
+
def references name
|
|
172
|
+
Parser.references self, name
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @return [Array<Range>]
|
|
176
|
+
def error_ranges
|
|
177
|
+
@error_ranges ||= []
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @param node [Parser::AST::Node]
|
|
181
|
+
# @return [String]
|
|
182
|
+
def code_for(node)
|
|
183
|
+
rng = Range.from_node(node)
|
|
184
|
+
b = Position.line_char_to_offset(code, rng.start.line, rng.start.column)
|
|
185
|
+
e = Position.line_char_to_offset(code, rng.ending.line, rng.ending.column)
|
|
186
|
+
frag = code[b..e-1].to_s
|
|
187
|
+
frag.strip.gsub(/,$/, '')
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# @param node [Parser::AST::Node]
|
|
191
|
+
# @return [String, nil]
|
|
192
|
+
def comments_for node
|
|
193
|
+
rng = Range.from_node(node)
|
|
194
|
+
stringified_comments[rng.start.line] ||= begin
|
|
195
|
+
buff = associated_comments[rng.start.line]
|
|
196
|
+
buff ? stringify_comment_array(buff) : nil
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# A location representing the file in its entirety.
|
|
201
|
+
#
|
|
202
|
+
# @return [Location]
|
|
203
|
+
def location
|
|
204
|
+
st = Position.new(0, 0)
|
|
205
|
+
en = Position.from_offset(code, code.length)
|
|
206
|
+
range = Range.new(st, en)
|
|
207
|
+
Location.new(filename, range)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
FOLDING_NODE_TYPES = %i[
|
|
211
|
+
class sclass module def defs if str dstr array while unless kwbegin hash block
|
|
212
|
+
].freeze
|
|
213
|
+
|
|
214
|
+
# Get an array of ranges that can be folded, e.g., the range of a class
|
|
215
|
+
# definition or an if condition.
|
|
216
|
+
#
|
|
217
|
+
# See FOLDING_NODE_TYPES for the list of node types that can be folded.
|
|
218
|
+
#
|
|
219
|
+
# @return [Array<Range>]
|
|
220
|
+
def folding_ranges
|
|
221
|
+
@folding_ranges ||= begin
|
|
222
|
+
result = []
|
|
223
|
+
inner_folding_ranges node, result
|
|
224
|
+
result.concat foldable_comment_block_ranges
|
|
225
|
+
result
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def synchronized?
|
|
230
|
+
true
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Get a hash of comments grouped by the line numbers of the associated code.
|
|
234
|
+
#
|
|
235
|
+
# @return [Hash{Integer => String}]
|
|
236
|
+
def associated_comments
|
|
237
|
+
@associated_comments ||= begin
|
|
238
|
+
# @type [Hash{Integer => String}]
|
|
239
|
+
result = {}
|
|
240
|
+
buffer = String.new('')
|
|
241
|
+
# @type [Integer, nil]
|
|
242
|
+
last = nil
|
|
243
|
+
comments.each_pair do |num, snip|
|
|
244
|
+
if !last || num == last + 1
|
|
245
|
+
buffer.concat "#{snip.text}\n"
|
|
246
|
+
else
|
|
247
|
+
result[first_not_empty_from(last + 1)] = buffer.clone
|
|
248
|
+
buffer.replace "#{snip.text}\n"
|
|
249
|
+
end
|
|
250
|
+
last = num
|
|
251
|
+
end
|
|
252
|
+
result[first_not_empty_from(last + 1)] = buffer unless buffer.empty? || last.nil?
|
|
253
|
+
result
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
private
|
|
258
|
+
|
|
259
|
+
# @param line [Integer]
|
|
260
|
+
# @return [Integer]
|
|
261
|
+
def first_not_empty_from line
|
|
262
|
+
cursor = line
|
|
263
|
+
cursor += 1 while cursor < code_lines.length && code_lines[cursor].strip.empty?
|
|
264
|
+
cursor = line if cursor > code_lines.length - 1
|
|
265
|
+
cursor
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @param top [Parser::AST::Node]
|
|
269
|
+
# @param result [Array<Range>]
|
|
270
|
+
# @param parent [Symbol, nil]
|
|
271
|
+
# @return [void]
|
|
272
|
+
def inner_folding_ranges top, result = [], parent = nil
|
|
273
|
+
return unless Parser.is_ast_node?(top)
|
|
274
|
+
if FOLDING_NODE_TYPES.include?(top.type)
|
|
275
|
+
range = Range.from_node(top)
|
|
276
|
+
if result.empty? || range.start.line > result.last.start.line
|
|
277
|
+
result.push range unless range.ending.line - range.start.line < 2
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
top.children.each do |child|
|
|
281
|
+
inner_folding_ranges(child, result, top.type)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Get a string representation of an array of comments.
|
|
286
|
+
#
|
|
287
|
+
# @param comments [String]
|
|
288
|
+
# @return [String]
|
|
289
|
+
def stringify_comment_array comments
|
|
290
|
+
ctxt = String.new('')
|
|
291
|
+
started = false
|
|
292
|
+
skip = nil
|
|
293
|
+
comments.lines.each { |l|
|
|
294
|
+
# Trim the comment and minimum leading whitespace
|
|
295
|
+
p = l.force_encoding('UTF-8').encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '')
|
|
296
|
+
if p.strip.empty?
|
|
297
|
+
next unless started
|
|
298
|
+
ctxt.concat p
|
|
299
|
+
else
|
|
300
|
+
here = p.index(/[^ \t]/)
|
|
301
|
+
skip = here if skip.nil? || here < skip
|
|
302
|
+
ctxt.concat p[skip..-1]
|
|
303
|
+
end
|
|
304
|
+
started = true
|
|
305
|
+
}
|
|
306
|
+
ctxt
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# A hash of line numbers and their associated comments.
|
|
310
|
+
#
|
|
311
|
+
# @return [Hash{Integer => Array<String>, nil}]
|
|
312
|
+
def stringified_comments
|
|
313
|
+
@stringified_comments ||= {}
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# @return [Array<Parser::AST::Node>]
|
|
317
|
+
def string_nodes
|
|
318
|
+
@string_nodes ||= string_nodes_in(node)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# @return [Array<Solargraph::Range>]
|
|
322
|
+
def comment_ranges
|
|
323
|
+
@comment_ranges ||= comments.values.map(&:range)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Get an array of foldable comment block ranges. Blocks are excluded if
|
|
327
|
+
# they are less than 3 lines long.
|
|
328
|
+
#
|
|
329
|
+
# @return [Array<Range>]
|
|
330
|
+
def foldable_comment_block_ranges
|
|
331
|
+
return [] unless synchronized?
|
|
332
|
+
result = []
|
|
333
|
+
grouped = []
|
|
334
|
+
comments.keys.each do |l|
|
|
335
|
+
if grouped.empty? || l == grouped.last + 1
|
|
336
|
+
grouped.push l
|
|
337
|
+
else
|
|
338
|
+
result.push Range.from_to(grouped.first, 0, grouped.last, 0) unless grouped.length < 3
|
|
339
|
+
grouped = [l]
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
result.push Range.from_to(grouped.first, 0, grouped.last, 0) unless grouped.length < 3
|
|
343
|
+
result
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# @param n [Parser::AST::Node, nil]
|
|
347
|
+
# @return [Array<Parser::AST::Node>]
|
|
348
|
+
def string_nodes_in n
|
|
349
|
+
result = []
|
|
350
|
+
if Parser.is_ast_node?(n)
|
|
351
|
+
if n.type == :str || n.type == :dstr || n.type == :STR || n.type == :DSTR
|
|
352
|
+
result.push n
|
|
353
|
+
else
|
|
354
|
+
n.children.each{ |c| result.concat string_nodes_in(c) }
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
result
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# @param node [Parser::AST::Node, nil]
|
|
361
|
+
# @param position [Position]
|
|
362
|
+
# @param stack [Array<Parser::AST::Node>]
|
|
363
|
+
# @return [void]
|
|
364
|
+
def inner_tree_at node, position, stack
|
|
365
|
+
return if node.nil?
|
|
366
|
+
here = Range.from_node(node)
|
|
367
|
+
if here.contain?(position)
|
|
368
|
+
stack.unshift node
|
|
369
|
+
node.children.each do |c|
|
|
370
|
+
next unless Parser.is_ast_node?(c)
|
|
371
|
+
next if c.loc.expression.nil?
|
|
372
|
+
inner_tree_at(c, position, stack)
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
protected
|
|
378
|
+
|
|
379
|
+
# @return [Array<Change>]
|
|
380
|
+
def changes
|
|
381
|
+
@changes ||= []
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# @return [String]
|
|
385
|
+
attr_writer :filename
|
|
386
|
+
|
|
387
|
+
# @return [Integer]
|
|
388
|
+
attr_writer :version
|
|
389
|
+
|
|
390
|
+
# @return [void]
|
|
391
|
+
def finalize
|
|
392
|
+
return if @finalized && changes.empty?
|
|
393
|
+
|
|
394
|
+
changes.each do |change|
|
|
395
|
+
@code = change.write(@code)
|
|
396
|
+
end
|
|
397
|
+
@finalized = true
|
|
398
|
+
begin
|
|
399
|
+
@node, @comments = Solargraph::Parser.parse_with_comments(@code, filename)
|
|
400
|
+
@parsed = true
|
|
401
|
+
@repaired = @code
|
|
402
|
+
rescue Parser::SyntaxError, EncodingError => e
|
|
403
|
+
@node = nil
|
|
404
|
+
@comments = {}
|
|
405
|
+
@parsed = false
|
|
406
|
+
ensure
|
|
407
|
+
@code.freeze
|
|
408
|
+
end
|
|
409
|
+
if !@parsed && !changes.empty?
|
|
410
|
+
changes.each do |change|
|
|
411
|
+
@repaired = change.repair(@repaired)
|
|
412
|
+
end
|
|
413
|
+
error_ranges.concat(changes.map(&:range))
|
|
414
|
+
begin
|
|
415
|
+
@node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename)
|
|
416
|
+
@parsed = true
|
|
417
|
+
rescue Parser::SyntaxError, EncodingError => e
|
|
418
|
+
@node = nil
|
|
419
|
+
@comments = {}
|
|
420
|
+
@parsed = false
|
|
421
|
+
end
|
|
422
|
+
elsif @parsed
|
|
423
|
+
error_ranges.clear
|
|
424
|
+
end
|
|
425
|
+
changes.clear
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# @param val [String]
|
|
429
|
+
# @return [String]
|
|
430
|
+
def code=(val)
|
|
431
|
+
@code_lines = nil
|
|
432
|
+
@finalized = false
|
|
433
|
+
@code = val
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# @return [Parser::AST::Node, nil]
|
|
437
|
+
attr_writer :node
|
|
438
|
+
|
|
439
|
+
# @return [Array<Range>]
|
|
440
|
+
attr_writer :error_ranges
|
|
441
|
+
|
|
442
|
+
# @return [String]
|
|
443
|
+
attr_writer :repaired
|
|
444
|
+
|
|
445
|
+
# @return [String]
|
|
446
|
+
def repaired
|
|
447
|
+
finalize
|
|
448
|
+
@repaired
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# @return [Boolean]
|
|
452
|
+
attr_writer :parsed
|
|
453
|
+
|
|
454
|
+
# @return [Hash{Integer => String}
|
|
455
|
+
attr_writer :comments
|
|
456
|
+
|
|
457
|
+
# @return [Boolean]
|
|
458
|
+
attr_writer :synchronized
|
|
459
|
+
|
|
460
|
+
private
|
|
461
|
+
|
|
462
|
+
# @return [Array<String>]
|
|
463
|
+
def code_lines
|
|
464
|
+
@code_lines ||= code.lines
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
class << self
|
|
468
|
+
# @param filename [String]
|
|
469
|
+
# @return [Solargraph::Source]
|
|
470
|
+
def load filename
|
|
471
|
+
file = File.open(filename)
|
|
472
|
+
code = file.read
|
|
473
|
+
file.close
|
|
474
|
+
Source.load_string(code, filename)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# @param code [String]
|
|
478
|
+
# @param filename [String, nil]
|
|
479
|
+
# @param version [Integer]
|
|
480
|
+
# @return [Solargraph::Source]
|
|
481
|
+
def load_string code, filename = nil, version = 0
|
|
482
|
+
Source.new code, filename, version
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# @param comments [String]
|
|
486
|
+
# @return [YARD::DocstringParser]
|
|
487
|
+
def parse_docstring comments
|
|
488
|
+
# HACK: Pass a dummy code object to the parser for plugins that
|
|
489
|
+
# expect it not to be nil
|
|
490
|
+
YARD::Docstring.parser.parse(comments, YARD::CodeObjects::Base.new(:root, 'stub'))
|
|
491
|
+
rescue StandardError => e
|
|
492
|
+
Solargraph.logger.info "YARD failed to parse docstring: [#{e.class}] #{e.message}"
|
|
493
|
+
Solargraph.logger.debug "Unparsed comment: #{comments}"
|
|
494
|
+
YARD::Docstring.parser
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
end
|