syntax_tree 2.7.1 → 2.8.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: 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