solargraph 0.58.0 → 0.59.0

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