syntax_tree 2.7.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -1
- data/Gemfile.lock +9 -7
- data/README.md +3 -3
- data/lib/syntax_tree/basic_visitor.rb +5 -1
- data/lib/syntax_tree/cli.rb +133 -67
- data/lib/syntax_tree/formatter.rb +3 -1
- data/lib/syntax_tree/language_server/inlay_hints.rb +42 -18
- data/lib/syntax_tree/language_server.rb +26 -39
- data/lib/syntax_tree/node.rb +42 -3
- data/lib/syntax_tree/parser.rb +11 -1
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/field_visitor.rb +4 -0
- data/lib/syntax_tree/visitor.rb +3 -0
- data/lib/syntax_tree.rb +1 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28783cde4387bbc7d38d9de500564804cd885733abe59e017be73ce04981a854
|
4
|
+
data.tar.gz: 6a54750102b8978df75e7f8a7d611e596067bb69863f91d5a8daa9aec8509c31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2f61f01bfa53604686798c96a53d8c98abd30145038db3b7f73f92c5c6b282447f3fe355382f6b3325b3ee98ce60ddcc5c102234b78e9c8d1fe9cbf006cb38d
|
7
|
+
data.tar.gz: e5a63e7fdc4f7e70e354a5861e42b8c0dffbbd2ffde38754c0a1fca8a6033be2deecd52ea3ec5fb7a2aa05e0d07ab93d3b4d69762ca3c911f36a6e898ef2ffaf
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,35 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [3.0.0] - 2022-07-04
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
|
13
|
+
- [#102](https://github.com/ruby-syntax-tree/syntax_tree/issues/102) - Handle requests to the language server for files that do not yet exist on disk.
|
14
|
+
|
15
|
+
### Removed
|
16
|
+
|
17
|
+
- [#108](https://github.com/ruby-syntax-tree/syntax_tree/pull/108) - Remove old inlay hints code.
|
18
|
+
|
19
|
+
## [2.9.0] - 2022-07-04
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
- [#106](https://github.com/ruby-syntax-tree/syntax_tree/pull/106) - Add inlay hint support to match the LSP specification.
|
24
|
+
|
25
|
+
## [2.8.0] - 2022-06-21
|
26
|
+
|
27
|
+
### Added
|
28
|
+
|
29
|
+
- [#95](https://github.com/ruby-syntax-tree/syntax_tree/pull/95) - The `HeredocEnd` node has been added which effectively results in the ability to determine the location of the ending of a heredoc from source.
|
30
|
+
- [#99](https://github.com/ruby-syntax-tree/syntax_tree/pull/99) - The LSP now allows you to pass the same configuration options as the other CLI commands which allows formatting to be modified in the VSCode extension.
|
31
|
+
- [#100](https://github.com/ruby-syntax-tree/syntax_tree/pull/100) - The LSP now explicitly responds to the shutdown request so that VSCode never deadlocks.
|
32
|
+
|
33
|
+
### Changed
|
34
|
+
|
35
|
+
- [#96](https://github.com/ruby-syntax-tree/syntax_tree/pull/96) - The CLI now runs in parallel by default. There is a worker created for each processor on the running machine (as determined by `Etc.nprocessors`).
|
36
|
+
- [#97](https://github.com/ruby-syntax-tree/syntax_tree/pull/97) - Syntax Tree now handles the case where `DidYouMean` is not available for whatever reason, as well as handles the newer `detailed_message` API for errors.
|
37
|
+
|
9
38
|
## [2.7.1] - 2022-05-25
|
10
39
|
|
11
40
|
### Added
|
@@ -259,7 +288,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
259
288
|
|
260
289
|
- 🎉 Initial release! 🎉
|
261
290
|
|
262
|
-
[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.
|
291
|
+
[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.9.0...HEAD
|
292
|
+
[2.9.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.8.0...v2.9.0
|
293
|
+
[2.8.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.7.1...v2.8.0
|
263
294
|
[2.7.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.7.0...v2.7.1
|
264
295
|
[2.7.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.6.0...v2.7.0
|
265
296
|
[2.6.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.5.0...v2.6.0
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
syntax_tree (
|
4
|
+
syntax_tree (3.0.0)
|
5
5
|
prettier_print
|
6
6
|
|
7
7
|
GEM
|
@@ -9,25 +9,27 @@ GEM
|
|
9
9
|
specs:
|
10
10
|
ast (2.4.2)
|
11
11
|
docile (1.4.0)
|
12
|
-
|
12
|
+
json (2.6.2)
|
13
|
+
minitest (5.16.2)
|
13
14
|
parallel (1.22.1)
|
14
15
|
parser (3.1.2.0)
|
15
16
|
ast (~> 2.4.1)
|
16
17
|
prettier_print (0.1.0)
|
17
18
|
rainbow (3.1.1)
|
18
19
|
rake (13.0.6)
|
19
|
-
regexp_parser (2.
|
20
|
+
regexp_parser (2.5.0)
|
20
21
|
rexml (3.2.5)
|
21
|
-
rubocop (1.
|
22
|
+
rubocop (1.31.1)
|
23
|
+
json (~> 2.3)
|
22
24
|
parallel (~> 1.10)
|
23
25
|
parser (>= 3.1.0.0)
|
24
26
|
rainbow (>= 2.2.2, < 4.0)
|
25
27
|
regexp_parser (>= 1.8, < 3.0)
|
26
28
|
rexml (>= 3.2.5, < 4.0)
|
27
|
-
rubocop-ast (>= 1.
|
29
|
+
rubocop-ast (>= 1.18.0, < 2.0)
|
28
30
|
ruby-progressbar (~> 1.7)
|
29
31
|
unicode-display_width (>= 1.4.0, < 3.0)
|
30
|
-
rubocop-ast (1.
|
32
|
+
rubocop-ast (1.18.0)
|
31
33
|
parser (>= 3.1.1.0)
|
32
34
|
ruby-progressbar (1.11.0)
|
33
35
|
simplecov (0.21.2)
|
@@ -36,7 +38,7 @@ GEM
|
|
36
38
|
simplecov_json_formatter (~> 0.1)
|
37
39
|
simplecov-html (0.12.3)
|
38
40
|
simplecov_json_formatter (0.1.4)
|
39
|
-
unicode-display_width (2.
|
41
|
+
unicode-display_width (2.2.0)
|
40
42
|
|
41
43
|
PLATFORMS
|
42
44
|
arm64-darwin-21
|
data/README.md
CHANGED
@@ -35,7 +35,7 @@ It is built with only standard library dependencies. It additionally ships with
|
|
35
35
|
- [BasicVisitor](#basicvisitor)
|
36
36
|
- [Language server](#language-server)
|
37
37
|
- [textDocument/formatting](#textdocumentformatting)
|
38
|
-
- [textDocument/
|
38
|
+
- [textDocument/inlayHint](#textdocumentinlayhint)
|
39
39
|
- [syntaxTree/visualizing](#syntaxtreevisualizing)
|
40
40
|
- [Plugins](#plugins)
|
41
41
|
- [Configuration](#configuration)
|
@@ -402,7 +402,7 @@ By default, the language server is relatively minimal, mostly meant to provide a
|
|
402
402
|
|
403
403
|
As mentioned above, the language server responds to formatting requests with the formatted document. It typically responds on the order of tens of milliseconds, so it should be fast enough for any IDE.
|
404
404
|
|
405
|
-
### textDocument/
|
405
|
+
### textDocument/inlayHint
|
406
406
|
|
407
407
|
The language server also responds to the relatively new inlay hints request. This request allows the language server to define additional information that should exist in the source code as helpful hints to the developer. In our case we use it to display things like implicit parentheses. For example, if you had the following code:
|
408
408
|
|
@@ -410,7 +410,7 @@ The language server also responds to the relatively new inlay hints request. Thi
|
|
410
410
|
1 + 2 * 3
|
411
411
|
```
|
412
412
|
|
413
|
-
Implicity, the `2 * 3` is going to be executed first because the `*` operator has higher precedence than the `+` operator.
|
413
|
+
Implicity, the `2 * 3` is going to be executed first because the `*` operator has higher precedence than the `+` operator. To ease mental overhead, our language server includes small parentheses to make this explicit, as in:
|
414
414
|
|
415
415
|
```ruby
|
416
416
|
1 + ₍2 * 3₎
|
@@ -33,7 +33,11 @@ module SyntaxTree
|
|
33
33
|
).correct(visit_method)
|
34
34
|
end
|
35
35
|
|
36
|
-
DidYouMean
|
36
|
+
# In some setups with Ruby you can turn off DidYouMean, so we're going to
|
37
|
+
# respect that setting here.
|
38
|
+
if defined?(DidYouMean) && DidYouMean.method_defined?(:correct_error)
|
39
|
+
DidYouMean.correct_error(VisitMethodError, self)
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
class << self
|
data/lib/syntax_tree/cli.rb
CHANGED
@@ -34,9 +34,41 @@ module SyntaxTree
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
# An item of work that corresponds to a file to be processed.
|
38
|
+
class FileItem
|
39
|
+
attr_reader :filepath
|
40
|
+
|
41
|
+
def initialize(filepath)
|
42
|
+
@filepath = filepath
|
43
|
+
end
|
44
|
+
|
45
|
+
def handler
|
46
|
+
HANDLERS[File.extname(filepath)]
|
47
|
+
end
|
48
|
+
|
49
|
+
def source
|
50
|
+
handler.read(filepath)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# An item of work that corresponds to the stdin content.
|
55
|
+
class STDINItem
|
56
|
+
def handler
|
57
|
+
HANDLERS[".rb"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def filepath
|
61
|
+
:stdin
|
62
|
+
end
|
63
|
+
|
64
|
+
def source
|
65
|
+
$stdin.read
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
37
69
|
# The parent action class for the CLI that implements the basics.
|
38
70
|
class Action
|
39
|
-
def run(
|
71
|
+
def run(item)
|
40
72
|
end
|
41
73
|
|
42
74
|
def success
|
@@ -48,8 +80,8 @@ module SyntaxTree
|
|
48
80
|
|
49
81
|
# An action of the CLI that prints out the AST for the given source.
|
50
82
|
class AST < Action
|
51
|
-
def run(
|
52
|
-
pp handler.parse(source)
|
83
|
+
def run(item)
|
84
|
+
pp item.handler.parse(item.source)
|
53
85
|
end
|
54
86
|
end
|
55
87
|
|
@@ -59,10 +91,11 @@ module SyntaxTree
|
|
59
91
|
class UnformattedError < StandardError
|
60
92
|
end
|
61
93
|
|
62
|
-
def run(
|
63
|
-
|
94
|
+
def run(item)
|
95
|
+
source = item.source
|
96
|
+
raise UnformattedError if source != item.handler.format(source)
|
64
97
|
rescue StandardError
|
65
|
-
warn("[#{Color.yellow("warn")}] #{filepath}")
|
98
|
+
warn("[#{Color.yellow("warn")}] #{item.filepath}")
|
66
99
|
raise
|
67
100
|
end
|
68
101
|
|
@@ -81,9 +114,11 @@ module SyntaxTree
|
|
81
114
|
class NonIdempotentFormatError < StandardError
|
82
115
|
end
|
83
116
|
|
84
|
-
def run(
|
85
|
-
|
86
|
-
|
117
|
+
def run(item)
|
118
|
+
handler = item.handler
|
119
|
+
|
120
|
+
warning = "[#{Color.yellow("warn")}] #{item.filepath}"
|
121
|
+
formatted = handler.format(item.source)
|
87
122
|
|
88
123
|
raise NonIdempotentFormatError if formatted != handler.format(formatted)
|
89
124
|
rescue StandardError
|
@@ -102,25 +137,27 @@ module SyntaxTree
|
|
102
137
|
|
103
138
|
# An action of the CLI that prints out the doc tree IR for the given source.
|
104
139
|
class Doc < Action
|
105
|
-
def run(
|
140
|
+
def run(item)
|
141
|
+
source = item.source
|
142
|
+
|
106
143
|
formatter = Formatter.new(source, [])
|
107
|
-
handler.parse(source).format(formatter)
|
144
|
+
item.handler.parse(source).format(formatter)
|
108
145
|
pp formatter.groups.first
|
109
146
|
end
|
110
147
|
end
|
111
148
|
|
112
149
|
# An action of the CLI that formats the input source and prints it out.
|
113
150
|
class Format < Action
|
114
|
-
def run(
|
115
|
-
puts handler.format(source)
|
151
|
+
def run(item)
|
152
|
+
puts item.handler.format(item.source)
|
116
153
|
end
|
117
154
|
end
|
118
155
|
|
119
156
|
# An action of the CLI that converts the source into its equivalent JSON
|
120
157
|
# representation.
|
121
158
|
class Json < Action
|
122
|
-
def run(
|
123
|
-
object = Visitor::JSONVisitor.new.visit(handler.parse(source))
|
159
|
+
def run(item)
|
160
|
+
object = Visitor::JSONVisitor.new.visit(item.handler.parse(item.source))
|
124
161
|
puts JSON.pretty_generate(object)
|
125
162
|
end
|
126
163
|
end
|
@@ -128,27 +165,28 @@ module SyntaxTree
|
|
128
165
|
# An action of the CLI that outputs a pattern-matching Ruby expression that
|
129
166
|
# would match the input given.
|
130
167
|
class Match < Action
|
131
|
-
def run(
|
132
|
-
puts handler.parse(source).construct_keys
|
168
|
+
def run(item)
|
169
|
+
puts item.handler.parse(item.source).construct_keys
|
133
170
|
end
|
134
171
|
end
|
135
172
|
|
136
173
|
# An action of the CLI that formats the input source and writes the
|
137
174
|
# formatted output back to the file.
|
138
175
|
class Write < Action
|
139
|
-
def run(
|
140
|
-
|
176
|
+
def run(item)
|
177
|
+
filepath = item.filepath
|
141
178
|
start = Time.now
|
142
179
|
|
143
|
-
|
180
|
+
source = item.source
|
181
|
+
formatted = item.handler.format(source)
|
144
182
|
File.write(filepath, formatted) if filepath != :stdin
|
145
183
|
|
146
184
|
color = source == formatted ? Color.gray(filepath) : filepath
|
147
185
|
delta = ((Time.now - start) * 1000).round
|
148
186
|
|
149
|
-
puts "
|
187
|
+
puts "#{color} #{delta}ms"
|
150
188
|
rescue StandardError
|
151
|
-
puts
|
189
|
+
puts filepath
|
152
190
|
raise
|
153
191
|
end
|
154
192
|
end
|
@@ -180,7 +218,7 @@ module SyntaxTree
|
|
180
218
|
#{Color.bold("stree help")}
|
181
219
|
Display this help message
|
182
220
|
|
183
|
-
#{Color.bold("stree lsp")}
|
221
|
+
#{Color.bold("stree lsp [OPTIONS]")}
|
184
222
|
Run syntax tree in language server mode
|
185
223
|
|
186
224
|
#{Color.bold("stree version")}
|
@@ -201,6 +239,20 @@ module SyntaxTree
|
|
201
239
|
def run(argv)
|
202
240
|
name, *arguments = argv
|
203
241
|
|
242
|
+
# If there are any plugins specified on the command line, then load them
|
243
|
+
# by requiring them here. We do this by transforming something like
|
244
|
+
#
|
245
|
+
# stree format --plugins=haml template.haml
|
246
|
+
#
|
247
|
+
# into
|
248
|
+
#
|
249
|
+
# require "syntax_tree/haml"
|
250
|
+
#
|
251
|
+
if arguments.first&.start_with?("--plugins=")
|
252
|
+
plugins = arguments.shift[/^--plugins=(.*)$/, 1]
|
253
|
+
plugins.split(",").each { |plugin| require "syntax_tree/#{plugin}" }
|
254
|
+
end
|
255
|
+
|
204
256
|
case name
|
205
257
|
when "help"
|
206
258
|
puts HELP
|
@@ -244,38 +296,41 @@ module SyntaxTree
|
|
244
296
|
return 1
|
245
297
|
end
|
246
298
|
|
247
|
-
#
|
248
|
-
|
249
|
-
#
|
250
|
-
# stree format --plugins=haml template.haml
|
251
|
-
#
|
252
|
-
# into
|
253
|
-
#
|
254
|
-
# require "syntax_tree/haml"
|
255
|
-
#
|
256
|
-
if arguments.first&.start_with?("--plugins=")
|
257
|
-
plugins = arguments.shift[/^--plugins=(.*)$/, 1]
|
258
|
-
plugins.split(",").each { |plugin| require "syntax_tree/#{plugin}" }
|
259
|
-
end
|
299
|
+
# We're going to build up a queue of items to process.
|
300
|
+
queue = Queue.new
|
260
301
|
|
261
|
-
#
|
262
|
-
#
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
rescue StandardError => error
|
274
|
-
warn(error.message)
|
275
|
-
warn(error.backtrace)
|
276
|
-
errored = true
|
302
|
+
# If we're reading from stdin, then we'll just add the stdin object to
|
303
|
+
# the queue. Otherwise, we'll add each of the filepaths to the queue.
|
304
|
+
if $stdin.tty? || arguments.any?
|
305
|
+
arguments.each do |pattern|
|
306
|
+
Dir
|
307
|
+
.glob(pattern)
|
308
|
+
.each do |filepath|
|
309
|
+
queue << FileItem.new(filepath) if File.file?(filepath)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
else
|
313
|
+
queue << STDINItem.new
|
277
314
|
end
|
278
315
|
|
316
|
+
# At the end, we're going to return whether or not this worker ever
|
317
|
+
# encountered an error.
|
318
|
+
errored =
|
319
|
+
with_workers(queue) do |item|
|
320
|
+
action.run(item)
|
321
|
+
false
|
322
|
+
rescue Parser::ParseError => error
|
323
|
+
warn("Error: #{error.message}")
|
324
|
+
highlight_error(error, item.source)
|
325
|
+
true
|
326
|
+
rescue Check::UnformattedError, Debug::NonIdempotentFormatError
|
327
|
+
true
|
328
|
+
rescue StandardError => error
|
329
|
+
warn(error.message)
|
330
|
+
warn(error.backtrace)
|
331
|
+
true
|
332
|
+
end
|
333
|
+
|
279
334
|
if errored
|
280
335
|
action.failure
|
281
336
|
1
|
@@ -287,22 +342,33 @@ module SyntaxTree
|
|
287
342
|
|
288
343
|
private
|
289
344
|
|
290
|
-
def
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
345
|
+
def with_workers(queue)
|
346
|
+
# If the queue is just 1 item, then we're not going to bother going
|
347
|
+
# through the whole ceremony of parallelizing the work.
|
348
|
+
return yield queue.shift if queue.size == 1
|
349
|
+
|
350
|
+
workers =
|
351
|
+
Etc.nprocessors.times.map do
|
352
|
+
Thread.new do
|
353
|
+
# Propagate errors in the worker threads up to the parent thread.
|
354
|
+
Thread.current.abort_on_exception = true
|
355
|
+
|
356
|
+
# Track whether or not there are any errors from any of the files
|
357
|
+
# that we take action on so that we can properly clean up and
|
358
|
+
# exit.
|
359
|
+
errored = false
|
360
|
+
|
361
|
+
# While there is still work left to do, shift off the queue and
|
362
|
+
# process the item.
|
363
|
+
(errored ||= yield queue.shift) until queue.empty?
|
364
|
+
|
365
|
+
# At the end, we're going to return whether or not this worker
|
366
|
+
# ever encountered an error.
|
367
|
+
errored
|
368
|
+
end
|
302
369
|
end
|
303
|
-
|
304
|
-
|
305
|
-
end
|
370
|
+
|
371
|
+
workers.inject(false) { |accum, thread| accum || thread.value }
|
306
372
|
end
|
307
373
|
|
308
374
|
# Highlights a snippet from a source and parse error.
|
@@ -68,7 +68,9 @@ module SyntaxTree
|
|
68
68
|
# going to just print out the node as it was seen in the source.
|
69
69
|
doc =
|
70
70
|
if leading.last&.ignore?
|
71
|
-
|
71
|
+
range = source[node.location.start_char...node.location.end_char]
|
72
|
+
separator = -> { breakable(indent: false, force: true) }
|
73
|
+
seplist(range.split(/\r?\n/, -1), separator) { |line| text(line) }
|
72
74
|
else
|
73
75
|
node.format(self)
|
74
76
|
end
|
@@ -2,20 +2,37 @@
|
|
2
2
|
|
3
3
|
module SyntaxTree
|
4
4
|
class LanguageServer
|
5
|
-
# This class provides inlay hints for the language server.
|
6
|
-
#
|
7
|
-
# is a little different for now.
|
8
|
-
#
|
9
|
-
# For more information, see the spec here:
|
5
|
+
# This class provides inlay hints for the language server. For more
|
6
|
+
# information, see the spec here:
|
10
7
|
# https://github.com/microsoft/language-server-protocol/issues/956.
|
11
|
-
#
|
12
8
|
class InlayHints < Visitor
|
13
|
-
|
9
|
+
# This represents a hint that is going to be displayed in the editor.
|
10
|
+
class Hint
|
11
|
+
attr_reader :line, :character, :label
|
12
|
+
|
13
|
+
def initialize(line:, character:, label:)
|
14
|
+
@line = line
|
15
|
+
@character = character
|
16
|
+
@label = label
|
17
|
+
end
|
18
|
+
|
19
|
+
# This is the shape that the LSP expects.
|
20
|
+
def to_json(*opts)
|
21
|
+
{
|
22
|
+
position: {
|
23
|
+
line: line,
|
24
|
+
character: character
|
25
|
+
},
|
26
|
+
label: label
|
27
|
+
}.to_json(*opts)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :stack, :hints
|
14
32
|
|
15
33
|
def initialize
|
16
34
|
@stack = []
|
17
|
-
@
|
18
|
-
@after = Hash.new { |hash, key| hash[key] = +"" }
|
35
|
+
@hints = []
|
19
36
|
end
|
20
37
|
|
21
38
|
def visit(node)
|
@@ -97,7 +114,11 @@ module SyntaxTree
|
|
97
114
|
#
|
98
115
|
def visit_rescue(node)
|
99
116
|
if node.exception.nil?
|
100
|
-
|
117
|
+
hints << Hint.new(
|
118
|
+
line: node.location.start_line - 1,
|
119
|
+
character: node.location.start_column + "rescue".length,
|
120
|
+
label: " StandardError"
|
121
|
+
)
|
101
122
|
end
|
102
123
|
|
103
124
|
super
|
@@ -120,17 +141,20 @@ module SyntaxTree
|
|
120
141
|
super
|
121
142
|
end
|
122
143
|
|
123
|
-
def self.find(program)
|
124
|
-
visitor = new
|
125
|
-
visitor.visit(program)
|
126
|
-
visitor
|
127
|
-
end
|
128
|
-
|
129
144
|
private
|
130
145
|
|
131
146
|
def parentheses(location)
|
132
|
-
|
133
|
-
|
147
|
+
hints << Hint.new(
|
148
|
+
line: location.start_line - 1,
|
149
|
+
character: location.start_column,
|
150
|
+
label: "₍"
|
151
|
+
)
|
152
|
+
|
153
|
+
hints << Hint.new(
|
154
|
+
line: location.end_line - 1,
|
155
|
+
character: location.end_column,
|
156
|
+
label: "₎"
|
157
|
+
)
|
134
158
|
end
|
135
159
|
end
|
136
160
|
end
|
@@ -20,70 +20,60 @@ module SyntaxTree
|
|
20
20
|
@output = output.binmode
|
21
21
|
end
|
22
22
|
|
23
|
+
# rubocop:disable Layout/LineLength
|
23
24
|
def run
|
24
25
|
store =
|
25
26
|
Hash.new do |hash, uri|
|
26
|
-
|
27
|
+
filepath = CGI.unescape(URI.parse(uri).path)
|
28
|
+
File.exist?(filepath) ? (hash[uri] = File.read(filepath)) : nil
|
27
29
|
end
|
28
30
|
|
29
31
|
while (headers = input.gets("\r\n\r\n"))
|
30
32
|
source = input.read(headers[/Content-Length: (\d+)/i, 1].to_i)
|
31
33
|
request = JSON.parse(source, symbolize_names: true)
|
32
34
|
|
35
|
+
# stree-ignore
|
33
36
|
case request
|
34
37
|
in { method: "initialize", id: }
|
35
38
|
store.clear
|
36
39
|
write(id: id, result: { capabilities: capabilities })
|
37
|
-
in method: "initialized"
|
40
|
+
in { method: "initialized" }
|
38
41
|
# ignored
|
39
|
-
in method: "shutdown"
|
42
|
+
in { method: "shutdown" } # tolerate missing ID to be a good citizen
|
40
43
|
store.clear
|
44
|
+
write(id: request[:id], result: {})
|
41
45
|
return
|
42
|
-
in {
|
43
|
-
method: "textDocument/didChange",
|
44
|
-
params: { textDocument: { uri: }, contentChanges: [{ text: }, *] }
|
45
|
-
}
|
46
|
+
in { method: "textDocument/didChange", params: { textDocument: { uri: }, contentChanges: [{ text: }, *] } }
|
46
47
|
store[uri] = text
|
47
|
-
in {
|
48
|
-
method: "textDocument/didOpen",
|
49
|
-
params: { textDocument: { uri:, text: } }
|
50
|
-
}
|
48
|
+
in { method: "textDocument/didOpen", params: { textDocument: { uri:, text: } } }
|
51
49
|
store[uri] = text
|
52
|
-
in {
|
53
|
-
method: "textDocument/didClose", params: { textDocument: { uri: } }
|
54
|
-
}
|
50
|
+
in { method: "textDocument/didClose", params: { textDocument: { uri: } } }
|
55
51
|
store.delete(uri)
|
56
|
-
in {
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
write(id: id, result:
|
62
|
-
in {
|
63
|
-
method: "textDocument/inlayHints",
|
64
|
-
id:,
|
65
|
-
params: { textDocument: { uri: } }
|
66
|
-
}
|
67
|
-
write(id: id, result: inlay_hints(store[uri]))
|
68
|
-
in {
|
69
|
-
method: "syntaxTree/visualizing",
|
70
|
-
id:,
|
71
|
-
params: { textDocument: { uri: } }
|
72
|
-
}
|
52
|
+
in { method: "textDocument/formatting", id:, params: { textDocument: { uri: } } }
|
53
|
+
contents = store[uri]
|
54
|
+
write(id: id, result: contents ? [format(store[uri])] : nil)
|
55
|
+
in { method: "textDocument/inlayHint", id:, params: { textDocument: { uri: } } }
|
56
|
+
contents = store[uri]
|
57
|
+
write(id: id, result: contents ? inlay_hints(store[uri]) : nil)
|
58
|
+
in { method: "syntaxTree/visualizing", id:, params: { textDocument: { uri: } } }
|
73
59
|
write(id: id, result: PP.pp(SyntaxTree.parse(store[uri]), +""))
|
74
|
-
in method: %r{\$/.+}
|
60
|
+
in { method: %r{\$/.+} }
|
75
61
|
# ignored
|
76
62
|
else
|
77
63
|
raise ArgumentError, "Unhandled: #{request}"
|
78
64
|
end
|
79
65
|
end
|
80
66
|
end
|
67
|
+
# rubocop:enable Layout/LineLength
|
81
68
|
|
82
69
|
private
|
83
70
|
|
84
71
|
def capabilities
|
85
72
|
{
|
86
73
|
documentFormattingProvider: true,
|
74
|
+
inlayHintProvider: {
|
75
|
+
resolveProvider: false
|
76
|
+
},
|
87
77
|
textDocumentSync: {
|
88
78
|
change: 1,
|
89
79
|
openClose: true
|
@@ -108,16 +98,13 @@ module SyntaxTree
|
|
108
98
|
end
|
109
99
|
|
110
100
|
def inlay_hints(source)
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
{
|
115
|
-
before: inlay_hints.before.map(&serialize),
|
116
|
-
after: inlay_hints.after.map(&serialize)
|
117
|
-
}
|
101
|
+
visitor = InlayHints.new
|
102
|
+
SyntaxTree.parse(source).accept(visitor)
|
103
|
+
visitor.hints
|
118
104
|
rescue Parser::ParseError
|
119
105
|
# If there is a parse error, then we're not going to return any inlay
|
120
106
|
# hints for this source.
|
107
|
+
[]
|
121
108
|
end
|
122
109
|
|
123
110
|
def write(value)
|
data/lib/syntax_tree/node.rb
CHANGED
@@ -4813,7 +4813,7 @@ module SyntaxTree
|
|
4813
4813
|
# [HeredocBeg] the opening of the heredoc
|
4814
4814
|
attr_reader :beginning
|
4815
4815
|
|
4816
|
-
# [
|
4816
|
+
# [HeredocEnd] the ending of the heredoc
|
4817
4817
|
attr_reader :ending
|
4818
4818
|
|
4819
4819
|
# [Integer] how far to dedent the heredoc
|
@@ -4847,7 +4847,7 @@ module SyntaxTree
|
|
4847
4847
|
end
|
4848
4848
|
|
4849
4849
|
def child_nodes
|
4850
|
-
[beginning, *parts]
|
4850
|
+
[beginning, *parts, ending]
|
4851
4851
|
end
|
4852
4852
|
|
4853
4853
|
alias deconstruct child_nodes
|
@@ -4883,7 +4883,7 @@ module SyntaxTree
|
|
4883
4883
|
end
|
4884
4884
|
end
|
4885
4885
|
|
4886
|
-
q.
|
4886
|
+
q.format(ending)
|
4887
4887
|
end
|
4888
4888
|
end
|
4889
4889
|
end
|
@@ -4929,6 +4929,45 @@ module SyntaxTree
|
|
4929
4929
|
end
|
4930
4930
|
end
|
4931
4931
|
|
4932
|
+
# HeredocEnd represents the closing declaration of a heredoc.
|
4933
|
+
#
|
4934
|
+
# <<~DOC
|
4935
|
+
# contents
|
4936
|
+
# DOC
|
4937
|
+
#
|
4938
|
+
# In the example above the HeredocEnd node represents the closing DOC.
|
4939
|
+
class HeredocEnd < Node
|
4940
|
+
# [String] the closing declaration of the heredoc
|
4941
|
+
attr_reader :value
|
4942
|
+
|
4943
|
+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
4944
|
+
attr_reader :comments
|
4945
|
+
|
4946
|
+
def initialize(value:, location:, comments: [])
|
4947
|
+
@value = value
|
4948
|
+
@location = location
|
4949
|
+
@comments = comments
|
4950
|
+
end
|
4951
|
+
|
4952
|
+
def accept(visitor)
|
4953
|
+
visitor.visit_heredoc_end(self)
|
4954
|
+
end
|
4955
|
+
|
4956
|
+
def child_nodes
|
4957
|
+
[]
|
4958
|
+
end
|
4959
|
+
|
4960
|
+
alias deconstruct child_nodes
|
4961
|
+
|
4962
|
+
def deconstruct_keys(_keys)
|
4963
|
+
{ value: value, location: location, comments: comments }
|
4964
|
+
end
|
4965
|
+
|
4966
|
+
def format(q)
|
4967
|
+
q.text(value)
|
4968
|
+
end
|
4969
|
+
end
|
4970
|
+
|
4932
4971
|
# HshPtn represents matching against a hash pattern using the Ruby 2.7+
|
4933
4972
|
# pattern matching syntax.
|
4934
4973
|
#
|
data/lib/syntax_tree/parser.rb
CHANGED
@@ -1640,9 +1640,19 @@ module SyntaxTree
|
|
1640
1640
|
def on_heredoc_end(value)
|
1641
1641
|
heredoc = @heredocs[-1]
|
1642
1642
|
|
1643
|
+
location =
|
1644
|
+
Location.token(
|
1645
|
+
line: lineno,
|
1646
|
+
char: char_pos,
|
1647
|
+
column: current_column,
|
1648
|
+
size: value.size + 1
|
1649
|
+
)
|
1650
|
+
|
1651
|
+
heredoc_end = HeredocEnd.new(value: value.chomp, location: location)
|
1652
|
+
|
1643
1653
|
@heredocs[-1] = Heredoc.new(
|
1644
1654
|
beginning: heredoc.beginning,
|
1645
|
-
ending:
|
1655
|
+
ending: heredoc_end,
|
1646
1656
|
dedent: heredoc.dedent,
|
1647
1657
|
parts: heredoc.parts,
|
1648
1658
|
location:
|
data/lib/syntax_tree/version.rb
CHANGED
@@ -497,6 +497,10 @@ module SyntaxTree
|
|
497
497
|
visit_token(node, "heredoc_beg")
|
498
498
|
end
|
499
499
|
|
500
|
+
def visit_heredoc_end(node)
|
501
|
+
visit_token(node, "heredoc_end")
|
502
|
+
end
|
503
|
+
|
500
504
|
def visit_hshptn(node)
|
501
505
|
node(node, "hshptn") do
|
502
506
|
field("constant", node.constant) if node.constant
|
data/lib/syntax_tree/visitor.rb
CHANGED
@@ -194,6 +194,9 @@ module SyntaxTree
|
|
194
194
|
# Visit a HeredocBeg node.
|
195
195
|
alias visit_heredoc_beg visit_child_nodes
|
196
196
|
|
197
|
+
# Visit a HeredocEnd node.
|
198
|
+
alias visit_heredoc_end visit_child_nodes
|
199
|
+
|
197
200
|
# Visit a HshPtn node.
|
198
201
|
alias visit_hshptn visit_child_nodes
|
199
202
|
|
data/lib/syntax_tree.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syntax_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Newton
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-05
|
11
|
+
date: 2022-07-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prettier_print
|
@@ -160,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
160
|
- !ruby/object:Gem::Version
|
161
161
|
version: '0'
|
162
162
|
requirements: []
|
163
|
-
rubygems_version: 3.
|
163
|
+
rubygems_version: 3.3.3
|
164
164
|
signing_key:
|
165
165
|
specification_version: 4
|
166
166
|
summary: A parser based on ripper
|