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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e90bde4f4aa60ce6784fd2c03234853bc281b7012cdb496d3f2bd97a1d825a9
4
- data.tar.gz: 24c4a344934acc5fb0b8b7d8931f689f98185f00fd9b71726f5aeab2499dea98
3
+ metadata.gz: 28783cde4387bbc7d38d9de500564804cd885733abe59e017be73ce04981a854
4
+ data.tar.gz: 6a54750102b8978df75e7f8a7d611e596067bb69863f91d5a8daa9aec8509c31
5
5
  SHA512:
6
- metadata.gz: 70d191270dbe2efd07300169410e4e0674b7abd8ec1ed4d95e87f7bb986b790195f8366f27b77f38eeeb94de1a08ce3467d64fbb126910922857366389ce66c0
7
- data.tar.gz: c3994813de2fa53eb132dce7e4d176e58a83071232bcdefe3ded77a3fdd35ed52639d99989878b4cf5ca3d13c0a5bc14cbdfb95555b9705e2dd08c88d2abf360
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.7.1...HEAD
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 (2.7.1)
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
- minitest (5.15.0)
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.4.0)
20
+ regexp_parser (2.5.0)
20
21
  rexml (3.2.5)
21
- rubocop (1.29.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.17.0, < 2.0)
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.17.0)
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.1.0)
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/inlayHints](#textdocumentinlayhints)
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/inlayHints
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. However, to ease mental overhead, our language server includes small parentheses to make this explicit, as in:
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.correct_error(VisitMethodError, self)
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
@@ -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(handler, filepath, source)
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(handler, _filepath, source)
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(handler, filepath, source)
63
- raise UnformattedError if source != handler.format(source)
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(handler, filepath, source)
85
- warning = "[#{Color.yellow("warn")}] #{filepath}"
86
- formatted = handler.format(source)
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(handler, _filepath, source)
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(handler, _filepath, source)
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(handler, _filepath, source)
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(handler, _filepath, source)
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(handler, filepath, source)
140
- print filepath
176
+ def run(item)
177
+ filepath = item.filepath
141
178
  start = Time.now
142
179
 
143
- formatted = handler.format(source)
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 "\r#{color} #{delta}ms"
187
+ puts "#{color} #{delta}ms"
150
188
  rescue StandardError
151
- puts "\r#{filepath}"
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
- # If there are any plugins specified on the command line, then load them
248
- # by requiring them here. We do this by transforming something like
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
- # Track whether or not there are any errors from any of the files that
262
- # we take action on so that we can properly clean up and exit.
263
- errored = false
264
-
265
- each_file(arguments) do |handler, filepath, source|
266
- action.run(handler, filepath, source)
267
- rescue Parser::ParseError => error
268
- warn("Error: #{error.message}")
269
- highlight_error(error, source)
270
- errored = true
271
- rescue Check::UnformattedError, Debug::NonIdempotentFormatError
272
- errored = true
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 each_file(arguments)
291
- if $stdin.tty? || arguments.any?
292
- arguments.each do |pattern|
293
- Dir
294
- .glob(pattern)
295
- .each do |filepath|
296
- next unless File.file?(filepath)
297
-
298
- handler = HANDLERS[File.extname(filepath)]
299
- source = handler.read(filepath)
300
- yield handler, filepath, source
301
- end
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
- else
304
- yield HANDLERS[".rb"], :stdin, $stdin.read
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
- text(source[node.location.start_char...node.location.end_char])
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. It is loosely
6
- # designed around the LSP spec, but existed before the spec was finalized so
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
- attr_reader :stack, :before, :after
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
- @before = Hash.new { |hash, key| hash[key] = +"" }
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
- after[node.location.start_char + "rescue".length] << " StandardError"
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
- before[location.start_char] << "₍"
133
- after[location.end_char] << "₎"
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
- hash[uri] = File.binread(CGI.unescape(URI.parse(uri).path))
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
- method: "textDocument/formatting",
58
- id:,
59
- params: { textDocument: { uri: } }
60
- }
61
- write(id: id, result: [format(store[uri])])
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
- inlay_hints = InlayHints.find(SyntaxTree.parse(source))
112
- serialize = ->(position, text) { { position: position, text: text } }
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)
@@ -4813,7 +4813,7 @@ module SyntaxTree
4813
4813
  # [HeredocBeg] the opening of the heredoc
4814
4814
  attr_reader :beginning
4815
4815
 
4816
- # [String] the ending of the heredoc
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.text(ending)
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
  #
@@ -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: value.chomp,
1655
+ ending: heredoc_end,
1646
1656
  dedent: heredoc.dedent,
1647
1657
  parts: heredoc.parts,
1648
1658
  location:
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "2.7.1"
4
+ VERSION = "3.0.0"
5
5
  end
@@ -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
@@ -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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "etc"
3
4
  require "json"
4
5
  require "pp"
5
6
  require "prettier_print"
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: 2.7.1
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-25 00:00:00.000000000 Z
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.4.0.dev
163
+ rubygems_version: 3.3.3
164
164
  signing_key:
165
165
  specification_version: 4
166
166
  summary: A parser based on ripper