syntax_tree 2.7.1 → 2.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e90bde4f4aa60ce6784fd2c03234853bc281b7012cdb496d3f2bd97a1d825a9
4
- data.tar.gz: 24c4a344934acc5fb0b8b7d8931f689f98185f00fd9b71726f5aeab2499dea98
3
+ metadata.gz: ad3c35843b6e3148499ac001d05a9c5d613d69debff5fdc411dff89a9adc5ca4
4
+ data.tar.gz: 570ae99edf8b17b5d205142872621b0551632d29ac4671ee14fed8415cf9cfe4
5
5
  SHA512:
6
- metadata.gz: 70d191270dbe2efd07300169410e4e0674b7abd8ec1ed4d95e87f7bb986b790195f8366f27b77f38eeeb94de1a08ce3467d64fbb126910922857366389ce66c0
7
- data.tar.gz: c3994813de2fa53eb132dce7e4d176e58a83071232bcdefe3ded77a3fdd35ed52639d99989878b4cf5ca3d13c0a5bc14cbdfb95555b9705e2dd08c88d2abf360
6
+ metadata.gz: 7ec0e9d8f5f4f828ba0d451d49bd7fa9f5576570f7427c91973503e795fe068e21a61dac4f1880be12dbd551855eb0b8d27f359a9a682b964f3183aaf687e1da
7
+ data.tar.gz: b8ae5c0132ebc445d060b1d5cef477253c724ea2c7a22a536d4ea78b3fb2d905c560e1f044f8843b92f79e0dbc1a434cda2a38e76b2732b426719150785a59af
data/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.8.0] - 2022-06-21
10
+
11
+ ### Added
12
+
13
+ - [#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.
14
+ - [#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.
15
+ - [#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.
16
+
17
+ ### Changed
18
+
19
+ - [#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`).
20
+ - [#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.
21
+
9
22
  ## [2.7.1] - 2022-05-25
10
23
 
11
24
  ### Added
@@ -259,7 +272,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
259
272
 
260
273
  - 🎉 Initial release! 🎉
261
274
 
262
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.7.1...HEAD
275
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.8.0...HEAD
276
+ [2.8.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.7.1...v2.8.0
263
277
  [2.7.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.7.0...v2.7.1
264
278
  [2.7.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.6.0...v2.7.0
265
279
  [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 (2.8.0)
5
5
  prettier_print
6
6
 
7
7
  GEM
@@ -9,25 +9,25 @@ GEM
9
9
  specs:
10
10
  ast (2.4.2)
11
11
  docile (1.4.0)
12
- minitest (5.15.0)
12
+ minitest (5.16.0)
13
13
  parallel (1.22.1)
14
14
  parser (3.1.2.0)
15
15
  ast (~> 2.4.1)
16
16
  prettier_print (0.1.0)
17
17
  rainbow (3.1.1)
18
18
  rake (13.0.6)
19
- regexp_parser (2.4.0)
19
+ regexp_parser (2.5.0)
20
20
  rexml (3.2.5)
21
- rubocop (1.29.1)
21
+ rubocop (1.30.1)
22
22
  parallel (~> 1.10)
23
23
  parser (>= 3.1.0.0)
24
24
  rainbow (>= 2.2.2, < 4.0)
25
25
  regexp_parser (>= 1.8, < 3.0)
26
26
  rexml (>= 3.2.5, < 4.0)
27
- rubocop-ast (>= 1.17.0, < 2.0)
27
+ rubocop-ast (>= 1.18.0, < 2.0)
28
28
  ruby-progressbar (~> 1.7)
29
29
  unicode-display_width (>= 1.4.0, < 3.0)
30
- rubocop-ast (1.17.0)
30
+ rubocop-ast (1.18.0)
31
31
  parser (>= 3.1.1.0)
32
32
  ruby-progressbar (1.11.0)
33
33
  simplecov (0.21.2)
@@ -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.
@@ -36,8 +36,9 @@ module SyntaxTree
36
36
  write(id: id, result: { capabilities: capabilities })
37
37
  in method: "initialized"
38
38
  # ignored
39
- in method: "shutdown"
39
+ in method: "shutdown" # tolerate missing ID to be a good citizen
40
40
  store.clear
41
+ write(id: request[:id], result: {})
41
42
  return
42
43
  in {
43
44
  method: "textDocument/didChange",
@@ -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 = "2.8.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: 2.8.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-06-21 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