syntax_tree 2.5.0 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +39 -1
- data/Gemfile.lock +1 -1
- data/README.md +63 -2
- data/Rakefile +5 -20
- data/lib/syntax_tree/basic_visitor.rb +74 -0
- data/lib/syntax_tree/formatter.rb +29 -4
- data/lib/syntax_tree/language_server/inlay_hints.rb +12 -1
- data/lib/syntax_tree/language_server.rb +2 -8
- data/lib/syntax_tree/node.rb +181 -59
- data/lib/syntax_tree/parser.rb +117 -13
- data/lib/syntax_tree/plugin/single_quotes.rb +1 -2
- data/lib/syntax_tree/plugin/trailing_comma.rb +3 -0
- data/lib/syntax_tree/rake/check_task.rb +66 -0
- data/lib/syntax_tree/rake/write_task.rb +66 -0
- data/lib/syntax_tree/rake_tasks.rb +4 -0
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/field_visitor.rb +9 -3
- data/lib/syntax_tree/visitor.rb +4 -66
- data/lib/syntax_tree.rb +2 -0
- metadata +8 -4
- data/lib/syntax_tree/formatter/single_quotes.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e90bde4f4aa60ce6784fd2c03234853bc281b7012cdb496d3f2bd97a1d825a9
|
4
|
+
data.tar.gz: 24c4a344934acc5fb0b8b7d8931f689f98185f00fd9b71726f5aeab2499dea98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70d191270dbe2efd07300169410e4e0674b7abd8ec1ed4d95e87f7bb986b790195f8366f27b77f38eeeb94de1a08ce3467d64fbb126910922857366389ce66c0
|
7
|
+
data.tar.gz: c3994813de2fa53eb132dce7e4d176e58a83071232bcdefe3ded77a3fdd35ed52639d99989878b4cf5ca3d13c0a5bc14cbdfb95555b9705e2dd08c88d2abf360
|
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,41 @@ 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.7.1] - 2022-05-25
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- [#92](https://github.com/ruby-syntax-tree/syntax_tree/pull/92) - (Internal) Drastically increase test coverage, including many more tests for the language server and the CLI.
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
- [#87](https://github.com/ruby-syntax-tree/syntax_tree/pull/87) - Don't convert quotes on strings if it would result in more escapes.
|
18
|
+
- [#91](https://github.com/ruby-syntax-tree/syntax_tree/pull/91) - Always use `[]` with array patterns. There are just too many edge cases where you have to use them anyway. This simplifies the look and makes it more consistent.
|
19
|
+
- [#92](https://github.com/ruby-syntax-tree/syntax_tree/pull/92) - Remodel the currently shipped plugins such that they're modifying an options hash instead of overriding methods. This should make it easier for other plugins to reference the already loaded plugins, e.g., the RBS plugin referencing the quotes.
|
20
|
+
- [#92](https://github.com/ruby-syntax-tree/syntax_tree/pull/92) - Fix up the language server inlay hints to continue walking the tree once a pattern is found. This should increase useability.
|
21
|
+
|
22
|
+
## [2.7.0] - 2022-05-19
|
23
|
+
|
24
|
+
### Added
|
25
|
+
|
26
|
+
- [#88](https://github.com/ruby-syntax-tree/syntax_tree/pull/88) - Provide a `SyntaxTree::BasicVisitor` that has no visit methods implemented.
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
|
30
|
+
- [#90](https://github.com/ruby-syntax-tree/syntax_tree/pull/90) - Provide better formatting for `SyntaxTree::AryPtn` when its nested inside a `SyntaxTree::RAssign`.
|
31
|
+
|
32
|
+
## [2.6.0] - 2022-05-16
|
33
|
+
|
34
|
+
### Added
|
35
|
+
|
36
|
+
- [#74](https://github.com/ruby-syntax-tree/syntax_tree/pull/74) - Add Rake test to run check and format commands.
|
37
|
+
- [#83](https://github.com/ruby-syntax-tree/syntax_tree/pull/83) - Add a trailing commas plugin.
|
38
|
+
- [#84](https://github.com/ruby-syntax-tree/syntax_tree/pull/84) - Handle lambda block-local variables.
|
39
|
+
|
40
|
+
### Changed
|
41
|
+
|
42
|
+
- [#85](https://github.com/ruby-syntax-tree/syntax_tree/pull/85) - Better handle trailing operators on command calls.
|
43
|
+
|
9
44
|
## [2.5.0] - 2022-05-13
|
10
45
|
|
11
46
|
### Added
|
@@ -224,7 +259,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
224
259
|
|
225
260
|
- 🎉 Initial release! 🎉
|
226
261
|
|
227
|
-
[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.
|
262
|
+
[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.7.1...HEAD
|
263
|
+
[2.7.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.7.0...v2.7.1
|
264
|
+
[2.7.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.6.0...v2.7.0
|
265
|
+
[2.6.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.5.0...v2.6.0
|
228
266
|
[2.5.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.4.1...v2.5.0
|
229
267
|
[2.4.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.4.0...v2.4.1
|
230
268
|
[2.4.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.3.1...v2.4.0
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -32,12 +32,16 @@ It is built with only standard library dependencies. It additionally ships with
|
|
32
32
|
- [construct_keys](#construct_keys)
|
33
33
|
- [Visitor](#visitor)
|
34
34
|
- [visit_method](#visit_method)
|
35
|
+
- [BasicVisitor](#basicvisitor)
|
35
36
|
- [Language server](#language-server)
|
36
37
|
- [textDocument/formatting](#textdocumentformatting)
|
37
38
|
- [textDocument/inlayHints](#textdocumentinlayhints)
|
38
39
|
- [syntaxTree/visualizing](#syntaxtreevisualizing)
|
39
40
|
- [Plugins](#plugins)
|
41
|
+
- [Configuration](#configuration)
|
42
|
+
- [Languages](#languages)
|
40
43
|
- [Integration](#integration)
|
44
|
+
- [Rake](#rake)
|
41
45
|
- [RuboCop](#rubocop)
|
42
46
|
- [VSCode](#vscode)
|
43
47
|
- [Contributing](#contributing)
|
@@ -223,7 +227,7 @@ This function takes an input string containing Ruby code and returns the syntax
|
|
223
227
|
|
224
228
|
### SyntaxTree.format(source)
|
225
229
|
|
226
|
-
This function takes an input string containing Ruby code, parses it into its underlying syntax tree, and formats it back out to a string.
|
230
|
+
This function takes an input string containing Ruby code, parses it into its underlying syntax tree, and formats it back out to a string. You can optionally pass a second argument to this method as well that is the maximum width to print. It defaults to `80`.
|
227
231
|
|
228
232
|
## Nodes
|
229
233
|
|
@@ -370,6 +374,20 @@ Did you mean? visit_binary
|
|
370
374
|
from bin/console:8:in `<main>'
|
371
375
|
```
|
372
376
|
|
377
|
+
### BasicVisitor
|
378
|
+
|
379
|
+
When you're defining your own visitor, by default it will walk down the tree even if you don't define `visit_*` methods. This is to ensure you can define a subset of the necessary methods in order to only interact with the nodes you're interested in. If you'd like to change this default to instead raise an error if you visit a node you haven't explicitly handled, you can instead inherit from `BasicVisitor`.
|
380
|
+
|
381
|
+
```ruby
|
382
|
+
class MyVisitor < SyntaxTree::BasicVisitor
|
383
|
+
def visit_int(node)
|
384
|
+
# ...
|
385
|
+
end
|
386
|
+
end
|
387
|
+
```
|
388
|
+
|
389
|
+
The visitor defined above will error out unless it's only visiting a `SyntaxTree::Int` node. This is useful in a couple of ways, e.g., if you're trying to define a visitor to handle the whole tree but it's currently a work-in-progress.
|
390
|
+
|
373
391
|
## Language server
|
374
392
|
|
375
393
|
Syntax Tree additionally ships with a language server conforming to the [language server protocol](https://microsoft.github.io/language-server-protocol/). It can be invoked through the CLI by running:
|
@@ -408,9 +426,12 @@ You can register additional configuration and additional languages that can flow
|
|
408
426
|
|
409
427
|
### Configuration
|
410
428
|
|
411
|
-
To register additional configuration, define a file somewhere in your load path named `syntax_tree/my_plugin
|
429
|
+
To register additional configuration, define a file somewhere in your load path named `syntax_tree/my_plugin`. Then when invoking the CLI, you will pass `--plugins=my_plugin`. To require multiple, separate them by a comma. In this way, you can modify Syntax Tree however you would like. Some plugins ship with Syntax Tree itself. They are:
|
412
430
|
|
413
431
|
* `plugin/single_quotes` - This will change all of your string literals to use single quotes instead of the default double quotes.
|
432
|
+
* `plugin/trailing_comma` - This will put trailing commas into multiline array literals, hash literals, and method calls that can support trailing commas.
|
433
|
+
|
434
|
+
If you're using Syntax Tree as a library, you should require those files directly.
|
414
435
|
|
415
436
|
### Languages
|
416
437
|
|
@@ -436,6 +457,46 @@ Below are listed all of the "official" language plugins hosted under the same Gi
|
|
436
457
|
|
437
458
|
Syntax Tree's goal is to seemlessly integrate into your workflow. To this end, it provides a couple of additional tools beyond the CLI and the Ruby library.
|
438
459
|
|
460
|
+
### Rake
|
461
|
+
|
462
|
+
Syntax Tree ships with the ability to define [rake](https://github.com/ruby/rake) tasks that will trigger runs of the CLI. To define them in your application, add the following configuration to your `Rakefile`:
|
463
|
+
|
464
|
+
```ruby
|
465
|
+
require "syntax_tree/rake_tasks"
|
466
|
+
SyntaxTree::Rake::CheckTask.new
|
467
|
+
SyntaxTree::Rake::WriteTask.new
|
468
|
+
```
|
469
|
+
|
470
|
+
These calls will define `rake stree:check` and `rake stree:write` (equivalent to calling `stree check` and `stree write` with the CLI respectively). You can configure them by either passing arguments to the `new` method or by using a block.
|
471
|
+
|
472
|
+
#### `name`
|
473
|
+
|
474
|
+
If you'd like to change the default name of the rake task, you can pass that as the first argument, as in:
|
475
|
+
|
476
|
+
```ruby
|
477
|
+
SyntaxTree::Rake::WriteTask.new(:format)
|
478
|
+
```
|
479
|
+
|
480
|
+
#### `source_files`
|
481
|
+
|
482
|
+
If you wanted to configure Syntax Tree to check or write different files than the default (`lib/**/*.rb`), you can set the `source_files` field, as in:
|
483
|
+
|
484
|
+
```ruby
|
485
|
+
SyntaxTree::Rake::WriteTask.new do |t|
|
486
|
+
t.source_files = FileList[%w[Gemfile Rakefile lib/**/*.rb test/**/*.rb]]
|
487
|
+
end
|
488
|
+
```
|
489
|
+
|
490
|
+
#### `plugins`
|
491
|
+
|
492
|
+
If you're running Syntax Tree with plugins (either your own or the pre-built ones), you can pass that to the `plugins` field, as in:
|
493
|
+
|
494
|
+
```ruby
|
495
|
+
SyntaxTree::Rake::WriteTask.new do |t|
|
496
|
+
t.plugins = ["plugin/single_quotes"]
|
497
|
+
end
|
498
|
+
```
|
499
|
+
|
439
500
|
### RuboCop
|
440
501
|
|
441
502
|
RuboCop and Syntax Tree serve different purposes, but there is overlap with some of RuboCop's functionality. Syntax Tree provides a RuboCop configuration file to disable rules that are redundant with Syntax Tree. To use this configuration file, add the following snippet to the top of your project's `.rubocop.yml`:
|
data/Rakefile
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
require "rake/testtask"
|
5
|
+
require "syntax_tree/rake_tasks"
|
5
6
|
|
6
7
|
Rake::TestTask.new(:test) do |t|
|
7
8
|
t.libs << "test"
|
@@ -11,24 +12,8 @@ end
|
|
11
12
|
|
12
13
|
task default: :test
|
13
14
|
|
14
|
-
|
15
|
-
Gemfile
|
16
|
-
Rakefile
|
17
|
-
syntax_tree.gemspec
|
18
|
-
lib/**/*.rb
|
19
|
-
test/*.rb
|
20
|
-
].freeze
|
15
|
+
SOURCE_FILES =
|
16
|
+
FileList[%w[Gemfile Rakefile syntax_tree.gemspec lib/**/*.rb test/*.rb]]
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
require "syntax_tree"
|
25
|
-
require "syntax_tree/cli"
|
26
|
-
end
|
27
|
-
|
28
|
-
task check: :syntax_tree do
|
29
|
-
exit SyntaxTree::CLI.run(["check"] + FILEPATHS)
|
30
|
-
end
|
31
|
-
|
32
|
-
task format: :syntax_tree do
|
33
|
-
exit SyntaxTree::CLI.run(["write"] + FILEPATHS)
|
34
|
-
end
|
18
|
+
SyntaxTree::Rake::CheckTask.new { |t| t.source_files = SOURCE_FILES }
|
19
|
+
SyntaxTree::Rake::WriteTask.new { |t| t.source_files = SOURCE_FILES }
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# BasicVisitor is the parent class of the Visitor class that provides the
|
5
|
+
# ability to walk down the tree. It does not define any handlers, so you
|
6
|
+
# should extend this class if you want your visitor to raise an error if you
|
7
|
+
# attempt to visit a node that you don't handle.
|
8
|
+
class BasicVisitor
|
9
|
+
# This is raised when you use the Visitor.visit_method method and it fails.
|
10
|
+
# It is correctable to through DidYouMean.
|
11
|
+
class VisitMethodError < StandardError
|
12
|
+
attr_reader :visit_method
|
13
|
+
|
14
|
+
def initialize(visit_method)
|
15
|
+
@visit_method = visit_method
|
16
|
+
super("Invalid visit method: #{visit_method}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# This class is used by DidYouMean to offer corrections to invalid visit
|
21
|
+
# method names.
|
22
|
+
class VisitMethodChecker
|
23
|
+
attr_reader :visit_method
|
24
|
+
|
25
|
+
def initialize(error)
|
26
|
+
@visit_method = error.visit_method
|
27
|
+
end
|
28
|
+
|
29
|
+
def corrections
|
30
|
+
@corrections ||=
|
31
|
+
DidYouMean::SpellChecker.new(
|
32
|
+
dictionary: Visitor.visit_methods
|
33
|
+
).correct(visit_method)
|
34
|
+
end
|
35
|
+
|
36
|
+
DidYouMean.correct_error(VisitMethodError, self)
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
# This method is here to help folks write visitors.
|
41
|
+
#
|
42
|
+
# It's not always easy to ensure you're writing the correct method name in
|
43
|
+
# the visitor since it's perfectly valid to define methods that don't
|
44
|
+
# override these parent methods.
|
45
|
+
#
|
46
|
+
# If you use this method, you can ensure you're writing the correct method
|
47
|
+
# name. It will raise an error if the visit method you're defining isn't
|
48
|
+
# actually a method on the parent visitor.
|
49
|
+
def visit_method(method_name)
|
50
|
+
return if visit_methods.include?(method_name)
|
51
|
+
|
52
|
+
raise VisitMethodError, method_name
|
53
|
+
end
|
54
|
+
|
55
|
+
# This is the list of all of the valid visit methods.
|
56
|
+
def visit_methods
|
57
|
+
@visit_methods ||=
|
58
|
+
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def visit(node)
|
63
|
+
node&.accept(self)
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_all(nodes)
|
67
|
+
nodes.map { |node| visit(node) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_child_nodes(node)
|
71
|
+
visit_all(node.child_nodes)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -4,17 +4,42 @@ module SyntaxTree
|
|
4
4
|
# A slightly enhanced PP that knows how to format recursively including
|
5
5
|
# comments.
|
6
6
|
class Formatter < PrettierPrint
|
7
|
+
# We want to minimize as much as possible the number of options that are
|
8
|
+
# available in syntax tree. For the most part, if users want non-default
|
9
|
+
# formatting, they should override the format methods on the specific nodes
|
10
|
+
# themselves. However, because of some history with prettier and the fact
|
11
|
+
# that folks have become entrenched in their ways, we decided to provide a
|
12
|
+
# small amount of configurability.
|
13
|
+
#
|
14
|
+
# Note that we're keeping this in a global-ish hash instead of just
|
15
|
+
# overriding methods on classes so that other plugins can reference this if
|
16
|
+
# necessary. For example, the RBS plugin references the quote style.
|
17
|
+
OPTIONS = { quote: "\"", trailing_comma: false }
|
18
|
+
|
7
19
|
COMMENT_PRIORITY = 1
|
8
20
|
HEREDOC_PRIORITY = 2
|
9
21
|
|
10
|
-
attr_reader :source, :stack
|
22
|
+
attr_reader :source, :stack
|
23
|
+
|
24
|
+
# These options are overridden in plugins to we need to make sure they are
|
25
|
+
# available here.
|
26
|
+
attr_reader :quote, :trailing_comma
|
27
|
+
alias trailing_comma? trailing_comma
|
11
28
|
|
12
|
-
def initialize(
|
13
|
-
|
29
|
+
def initialize(
|
30
|
+
source,
|
31
|
+
*args,
|
32
|
+
quote: OPTIONS[:quote],
|
33
|
+
trailing_comma: OPTIONS[:trailing_comma]
|
34
|
+
)
|
35
|
+
super(*args)
|
14
36
|
|
15
37
|
@source = source
|
16
38
|
@stack = []
|
17
|
-
|
39
|
+
|
40
|
+
# Memoizing these values per formatter to make access faster.
|
41
|
+
@quote = quote
|
42
|
+
@trailing_comma = trailing_comma
|
18
43
|
end
|
19
44
|
|
20
45
|
def self.format(source, node)
|
@@ -38,6 +38,7 @@ module SyntaxTree
|
|
38
38
|
#
|
39
39
|
def visit_assign(node)
|
40
40
|
parentheses(node.location) if stack[-2].is_a?(Params)
|
41
|
+
super
|
41
42
|
end
|
42
43
|
|
43
44
|
# Adds parentheses around binary expressions to make it clear which
|
@@ -57,6 +58,8 @@ module SyntaxTree
|
|
57
58
|
parentheses(node.location)
|
58
59
|
else
|
59
60
|
end
|
61
|
+
|
62
|
+
super
|
60
63
|
end
|
61
64
|
|
62
65
|
# Adds parentheses around ternary operators contained within certain
|
@@ -70,9 +73,13 @@ module SyntaxTree
|
|
70
73
|
# a ? b : ₍c ? d : e₎
|
71
74
|
#
|
72
75
|
def visit_if_op(node)
|
73
|
-
|
76
|
+
case stack[-2]
|
77
|
+
in Assign | Binary | IfOp | OpAssign
|
74
78
|
parentheses(node.location)
|
79
|
+
else
|
75
80
|
end
|
81
|
+
|
82
|
+
super
|
76
83
|
end
|
77
84
|
|
78
85
|
# Adds the implicitly rescued StandardError into a bare rescue clause. For
|
@@ -92,6 +99,8 @@ module SyntaxTree
|
|
92
99
|
if node.exception.nil?
|
93
100
|
after[node.location.start_char + "rescue".length] << " StandardError"
|
94
101
|
end
|
102
|
+
|
103
|
+
super
|
95
104
|
end
|
96
105
|
|
97
106
|
# Adds parentheses around unary statements using the - operator that are
|
@@ -107,6 +116,8 @@ module SyntaxTree
|
|
107
116
|
if stack[-2].is_a?(Binary) && (node.operator == "-")
|
108
117
|
parentheses(node.location)
|
109
118
|
end
|
119
|
+
|
120
|
+
super
|
110
121
|
end
|
111
122
|
|
112
123
|
def self.find(program)
|
@@ -70,13 +70,11 @@ module SyntaxTree
|
|
70
70
|
id:,
|
71
71
|
params: { textDocument: { uri: } }
|
72
72
|
}
|
73
|
-
|
74
|
-
PP.pp(SyntaxTree.parse(store[uri]), output)
|
75
|
-
write(id: id, result: output.join)
|
73
|
+
write(id: id, result: PP.pp(SyntaxTree.parse(store[uri]), +""))
|
76
74
|
in method: %r{\$/.+}
|
77
75
|
# ignored
|
78
76
|
else
|
79
|
-
raise "Unhandled: #{request}"
|
77
|
+
raise ArgumentError, "Unhandled: #{request}"
|
80
78
|
end
|
81
79
|
end
|
82
80
|
end
|
@@ -109,10 +107,6 @@ module SyntaxTree
|
|
109
107
|
}
|
110
108
|
end
|
111
109
|
|
112
|
-
def log(message)
|
113
|
-
write(method: "window/logMessage", params: { type: 4, message: message })
|
114
|
-
end
|
115
|
-
|
116
110
|
def inlay_hints(source)
|
117
111
|
inlay_hints = InlayHints.find(SyntaxTree.parse(source))
|
118
112
|
serialize = ->(position, text) { { position: position, text: text } }
|
data/lib/syntax_tree/node.rb
CHANGED
@@ -597,10 +597,30 @@ module SyntaxTree
|
|
597
597
|
q.indent do
|
598
598
|
q.breakable("")
|
599
599
|
q.format(arguments)
|
600
|
+
q.if_break { q.text(",") } if q.trailing_comma? && trailing_comma?
|
600
601
|
end
|
601
602
|
q.breakable("")
|
602
603
|
end
|
603
604
|
end
|
605
|
+
|
606
|
+
private
|
607
|
+
|
608
|
+
def trailing_comma?
|
609
|
+
case arguments
|
610
|
+
in Args[parts: [*, ArgBlock]]
|
611
|
+
# If the last argument is a block, then we can't put a trailing comma
|
612
|
+
# after it without resulting in a syntax error.
|
613
|
+
false
|
614
|
+
in Args[parts: [Command | CommandCall]]
|
615
|
+
# If the only argument is a command or command call, then a trailing
|
616
|
+
# comma would be parsed as part of that expression instead of on this
|
617
|
+
# one, so we don't want to add a trailing comma.
|
618
|
+
false
|
619
|
+
else
|
620
|
+
# Otherwise, we should be okay to add a trailing comma.
|
621
|
+
true
|
622
|
+
end
|
623
|
+
end
|
604
624
|
end
|
605
625
|
|
606
626
|
# Args represents a list of arguments being passed to a method call or array
|
@@ -859,6 +879,7 @@ module SyntaxTree
|
|
859
879
|
end
|
860
880
|
|
861
881
|
q.seplist(contents.parts, separator) { |part| q.format(part) }
|
882
|
+
q.if_break { q.text(",") } if q.trailing_comma?
|
862
883
|
end
|
863
884
|
q.breakable("")
|
864
885
|
end
|
@@ -954,6 +975,7 @@ module SyntaxTree
|
|
954
975
|
q.indent do
|
955
976
|
q.breakable("")
|
956
977
|
q.format(contents)
|
978
|
+
q.if_break { q.text(",") } if q.trailing_comma?
|
957
979
|
end
|
958
980
|
end
|
959
981
|
|
@@ -1101,30 +1123,20 @@ module SyntaxTree
|
|
1101
1123
|
end
|
1102
1124
|
|
1103
1125
|
def format(q)
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1126
|
+
q.group do
|
1127
|
+
q.format(constant) if constant
|
1128
|
+
q.text("[")
|
1129
|
+
q.indent do
|
1130
|
+
q.breakable("")
|
1131
|
+
|
1132
|
+
parts = [*requireds]
|
1133
|
+
parts << RestFormatter.new(rest) if rest
|
1134
|
+
parts += posts
|
1107
1135
|
|
1108
|
-
if constant
|
1109
|
-
q.group do
|
1110
|
-
q.format(constant)
|
1111
|
-
q.text("[")
|
1112
1136
|
q.seplist(parts) { |part| q.format(part) }
|
1113
|
-
q.text("]")
|
1114
1137
|
end
|
1115
|
-
|
1116
|
-
return
|
1117
|
-
end
|
1118
|
-
|
1119
|
-
parent = q.parent
|
1120
|
-
if parts.length == 1 || PATTERNS.include?(parent.class)
|
1121
|
-
q.text("[")
|
1122
|
-
q.seplist(parts) { |part| q.format(part) }
|
1138
|
+
q.breakable("")
|
1123
1139
|
q.text("]")
|
1124
|
-
elsif parts.empty?
|
1125
|
-
q.text("[]")
|
1126
|
-
else
|
1127
|
-
q.group { q.seplist(parts) { |part| q.format(part) } }
|
1128
1140
|
end
|
1129
1141
|
end
|
1130
1142
|
end
|
@@ -2117,11 +2129,13 @@ module SyntaxTree
|
|
2117
2129
|
#
|
2118
2130
|
# break
|
2119
2131
|
#
|
2120
|
-
in [
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2132
|
+
in [
|
2133
|
+
Paren[
|
2134
|
+
contents: {
|
2135
|
+
body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array]
|
2136
|
+
}
|
2137
|
+
]
|
2138
|
+
]
|
2125
2139
|
# Here we have a single argument that is a set of parentheses wrapping
|
2126
2140
|
# an array literal that has at least 2 elements. We're going to print
|
2127
2141
|
# the contents of the array directly. This would be like if we had:
|
@@ -2755,10 +2769,17 @@ module SyntaxTree
|
|
2755
2769
|
q.format(value)
|
2756
2770
|
q.text(" ")
|
2757
2771
|
q.format(operator)
|
2758
|
-
|
2759
|
-
|
2760
|
-
|
2761
|
-
|
2772
|
+
|
2773
|
+
case pattern
|
2774
|
+
in AryPtn | FndPtn | HshPtn
|
2775
|
+
q.text(" ")
|
2776
|
+
q.format(pattern)
|
2777
|
+
else
|
2778
|
+
q.group do
|
2779
|
+
q.indent do
|
2780
|
+
q.breakable
|
2781
|
+
q.format(pattern)
|
2782
|
+
end
|
2762
2783
|
end
|
2763
2784
|
end
|
2764
2785
|
end
|
@@ -3030,8 +3051,20 @@ module SyntaxTree
|
|
3030
3051
|
doc =
|
3031
3052
|
q.nest(0) do
|
3032
3053
|
q.format(receiver)
|
3033
|
-
|
3034
|
-
|
3054
|
+
|
3055
|
+
# If there are leading comments on the message then we know we have
|
3056
|
+
# a newline in the source that is forcing these things apart. In
|
3057
|
+
# this case we will have to use a trailing operator.
|
3058
|
+
if message.comments.any?(&:leading?)
|
3059
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3060
|
+
q.indent do
|
3061
|
+
q.breakable("")
|
3062
|
+
q.format(message)
|
3063
|
+
end
|
3064
|
+
else
|
3065
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3066
|
+
q.format(message)
|
3067
|
+
end
|
3035
3068
|
end
|
3036
3069
|
|
3037
3070
|
case arguments
|
@@ -3830,9 +3863,9 @@ module SyntaxTree
|
|
3830
3863
|
# whichever quote the user chose. (If they chose single quotes, then double
|
3831
3864
|
# quoting would activate the escape sequence, and if they chose double
|
3832
3865
|
# quotes, then single quotes would deactivate it.)
|
3833
|
-
def self.locked?(node)
|
3866
|
+
def self.locked?(node, quote)
|
3834
3867
|
node.parts.any? do |part|
|
3835
|
-
!part.is_a?(TStringContent) || part.value.match?(/\\|#[@${]/)
|
3868
|
+
!part.is_a?(TStringContent) || part.value.match?(/\\|#[@${]|#{quote}/)
|
3836
3869
|
end
|
3837
3870
|
end
|
3838
3871
|
|
@@ -3947,12 +3980,12 @@ module SyntaxTree
|
|
3947
3980
|
|
3948
3981
|
if matched
|
3949
3982
|
[quote, matching]
|
3950
|
-
elsif Quotes.locked?(self)
|
3983
|
+
elsif Quotes.locked?(self, q.quote)
|
3951
3984
|
["#{":" unless hash_key}'", "'"]
|
3952
3985
|
else
|
3953
3986
|
["#{":" unless hash_key}#{q.quote}", q.quote]
|
3954
3987
|
end
|
3955
|
-
elsif Quotes.locked?(self)
|
3988
|
+
elsif Quotes.locked?(self, q.quote)
|
3956
3989
|
if quote.start_with?(":")
|
3957
3990
|
[hash_key ? quote[1..] : quote, quote[1..]]
|
3958
3991
|
else
|
@@ -4539,16 +4572,26 @@ module SyntaxTree
|
|
4539
4572
|
|
4540
4573
|
def format(q)
|
4541
4574
|
q.format(constant) if constant
|
4542
|
-
q.group(0, "[", "]") do
|
4543
|
-
q.text("*")
|
4544
|
-
q.format(left)
|
4545
|
-
q.comma_breakable
|
4546
4575
|
|
4547
|
-
|
4548
|
-
q.
|
4576
|
+
q.group do
|
4577
|
+
q.text("[")
|
4549
4578
|
|
4550
|
-
q.
|
4551
|
-
|
4579
|
+
q.indent do
|
4580
|
+
q.breakable("")
|
4581
|
+
|
4582
|
+
q.text("*")
|
4583
|
+
q.format(left)
|
4584
|
+
q.comma_breakable
|
4585
|
+
|
4586
|
+
q.seplist(values) { |value| q.format(value) }
|
4587
|
+
q.comma_breakable
|
4588
|
+
|
4589
|
+
q.text("*")
|
4590
|
+
q.format(right)
|
4591
|
+
end
|
4592
|
+
|
4593
|
+
q.breakable("")
|
4594
|
+
q.text("]")
|
4552
4595
|
end
|
4553
4596
|
end
|
4554
4597
|
end
|
@@ -4751,6 +4794,7 @@ module SyntaxTree
|
|
4751
4794
|
q.indent do
|
4752
4795
|
q.breakable
|
4753
4796
|
q.seplist(assocs) { |assoc| q.format(assoc) }
|
4797
|
+
q.if_break { q.text(",") } if q.trailing_comma?
|
4754
4798
|
end
|
4755
4799
|
q.breakable
|
4756
4800
|
end
|
@@ -5430,12 +5474,14 @@ module SyntaxTree
|
|
5430
5474
|
q.format(predicate)
|
5431
5475
|
q.text(" ?")
|
5432
5476
|
|
5433
|
-
q.
|
5434
|
-
|
5435
|
-
|
5477
|
+
q.indent do
|
5478
|
+
q.breakable
|
5479
|
+
q.format(truthy)
|
5480
|
+
q.text(" :")
|
5436
5481
|
|
5437
|
-
|
5438
|
-
|
5482
|
+
q.breakable
|
5483
|
+
q.format(falsy)
|
5484
|
+
end
|
5439
5485
|
end
|
5440
5486
|
end
|
5441
5487
|
|
@@ -5877,7 +5923,7 @@ module SyntaxTree
|
|
5877
5923
|
# ->(value) { value * 2 }
|
5878
5924
|
#
|
5879
5925
|
class Lambda < Node
|
5880
|
-
# [
|
5926
|
+
# [LambdaVar | Paren] the parameter declaration for this lambda
|
5881
5927
|
attr_reader :params
|
5882
5928
|
|
5883
5929
|
# [BodyStmt | Statements] the expressions to be executed in this lambda
|
@@ -5932,24 +5978,100 @@ module SyntaxTree
|
|
5932
5978
|
node.is_a?(Command) || node.is_a?(CommandCall)
|
5933
5979
|
end
|
5934
5980
|
|
5935
|
-
|
5936
|
-
|
5981
|
+
if force_parens
|
5982
|
+
q.text("{")
|
5983
|
+
|
5984
|
+
unless statements.empty?
|
5985
|
+
q.indent do
|
5986
|
+
q.breakable
|
5987
|
+
q.format(statements)
|
5988
|
+
end
|
5989
|
+
q.breakable
|
5990
|
+
end
|
5991
|
+
|
5992
|
+
q.text("}")
|
5993
|
+
else
|
5994
|
+
q.text("do")
|
5995
|
+
|
5996
|
+
unless statements.empty?
|
5997
|
+
q.indent do
|
5998
|
+
q.breakable
|
5999
|
+
q.format(statements)
|
6000
|
+
end
|
6001
|
+
end
|
6002
|
+
|
5937
6003
|
q.breakable
|
5938
|
-
q.
|
6004
|
+
q.text("end")
|
5939
6005
|
end
|
5940
|
-
|
5941
|
-
q.breakable
|
5942
|
-
q.text(force_parens ? "}" : "end")
|
5943
6006
|
end
|
5944
6007
|
.if_flat do
|
5945
|
-
q.text("{
|
5946
|
-
|
5947
|
-
|
6008
|
+
q.text("{")
|
6009
|
+
|
6010
|
+
unless statements.empty?
|
6011
|
+
q.text(" ")
|
6012
|
+
q.format(statements)
|
6013
|
+
q.text(" ")
|
6014
|
+
end
|
6015
|
+
|
6016
|
+
q.text("}")
|
5948
6017
|
end
|
5949
6018
|
end
|
5950
6019
|
end
|
5951
6020
|
end
|
5952
6021
|
|
6022
|
+
# LambdaVar represents the parameters being declared for a lambda. Effectively
|
6023
|
+
# this node is everything contained within the parentheses. This includes all
|
6024
|
+
# of the various parameter types, as well as block-local variable
|
6025
|
+
# declarations.
|
6026
|
+
#
|
6027
|
+
# -> (positional, optional = value, keyword:, █ local) do
|
6028
|
+
# end
|
6029
|
+
#
|
6030
|
+
class LambdaVar < Node
|
6031
|
+
# [Params] the parameters being declared with the block
|
6032
|
+
attr_reader :params
|
6033
|
+
|
6034
|
+
# [Array[ Ident ]] the list of block-local variable declarations
|
6035
|
+
attr_reader :locals
|
6036
|
+
|
6037
|
+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
6038
|
+
attr_reader :comments
|
6039
|
+
|
6040
|
+
def initialize(params:, locals:, location:, comments: [])
|
6041
|
+
@params = params
|
6042
|
+
@locals = locals
|
6043
|
+
@location = location
|
6044
|
+
@comments = comments
|
6045
|
+
end
|
6046
|
+
|
6047
|
+
def accept(visitor)
|
6048
|
+
visitor.visit_lambda_var(self)
|
6049
|
+
end
|
6050
|
+
|
6051
|
+
def child_nodes
|
6052
|
+
[params, *locals]
|
6053
|
+
end
|
6054
|
+
|
6055
|
+
alias deconstruct child_nodes
|
6056
|
+
|
6057
|
+
def deconstruct_keys(_keys)
|
6058
|
+
{ params: params, locals: locals, location: location, comments: comments }
|
6059
|
+
end
|
6060
|
+
|
6061
|
+
def empty?
|
6062
|
+
params.empty? && locals.empty?
|
6063
|
+
end
|
6064
|
+
|
6065
|
+
def format(q)
|
6066
|
+
q.format(params)
|
6067
|
+
|
6068
|
+
if locals.any?
|
6069
|
+
q.text("; ")
|
6070
|
+
q.seplist(locals, -> { q.text(", ") }) { |local| q.format(local) }
|
6071
|
+
end
|
6072
|
+
end
|
6073
|
+
end
|
6074
|
+
|
5953
6075
|
# LBrace represents the use of a left brace, i.e., {.
|
5954
6076
|
class LBrace < Node
|
5955
6077
|
# [String] the left brace
|
@@ -8293,7 +8415,7 @@ module SyntaxTree
|
|
8293
8415
|
end
|
8294
8416
|
|
8295
8417
|
opening_quote, closing_quote =
|
8296
|
-
if !Quotes.locked?(self)
|
8418
|
+
if !Quotes.locked?(self, q.quote)
|
8297
8419
|
[q.quote, q.quote]
|
8298
8420
|
elsif quote.start_with?("%")
|
8299
8421
|
[quote, Quotes.matching(quote[/%[qQ]?(.)/, 1])]
|
data/lib/syntax_tree/parser.rb
CHANGED
@@ -548,13 +548,6 @@ module SyntaxTree
|
|
548
548
|
parts[0].location.to(parts[-1].location)
|
549
549
|
end
|
550
550
|
|
551
|
-
# If there's the optional then keyword, then we'll delete that and use it
|
552
|
-
# as the end bounds of the location.
|
553
|
-
if (token = find_token(Kw, "then", consume: false))
|
554
|
-
tokens.delete(token)
|
555
|
-
location = location.to(token.location)
|
556
|
-
end
|
557
|
-
|
558
551
|
# If there is a plain *, then we're going to fix up the location of it
|
559
552
|
# here because it currently doesn't have anything to use for its precise
|
560
553
|
# location. If we hit a comma, then we've gone too far.
|
@@ -1698,12 +1691,6 @@ module SyntaxTree
|
|
1698
1691
|
end
|
1699
1692
|
end
|
1700
1693
|
|
1701
|
-
# Delete the optional then keyword
|
1702
|
-
if (token = find_token(Kw, "then", consume: false))
|
1703
|
-
parts << token
|
1704
|
-
tokens.delete(token)
|
1705
|
-
end
|
1706
|
-
|
1707
1694
|
HshPtn.new(
|
1708
1695
|
constant: constant,
|
1709
1696
|
keywords: keywords || [],
|
@@ -1940,6 +1927,41 @@ module SyntaxTree
|
|
1940
1927
|
token.location.start_char > beginning.location.start_char
|
1941
1928
|
end
|
1942
1929
|
|
1930
|
+
# We need to do some special mapping here. Since ripper doesn't support
|
1931
|
+
# capturing lambda var until 3.2, we need to normalize all of that here.
|
1932
|
+
params =
|
1933
|
+
case params
|
1934
|
+
in Paren[contents: Params]
|
1935
|
+
# In this case we've gotten to the <3.2 parentheses wrapping a set of
|
1936
|
+
# parameters case. Here we need to manually scan for lambda locals.
|
1937
|
+
range = (params.location.start_char + 1)...params.location.end_char
|
1938
|
+
locals = lambda_locals(source[range])
|
1939
|
+
|
1940
|
+
location = params.contents.location
|
1941
|
+
location = location.to(locals.last.location) if locals.any?
|
1942
|
+
|
1943
|
+
Paren.new(
|
1944
|
+
lparen: params.lparen,
|
1945
|
+
contents:
|
1946
|
+
LambdaVar.new(
|
1947
|
+
params: params.contents,
|
1948
|
+
locals: locals,
|
1949
|
+
location: location
|
1950
|
+
),
|
1951
|
+
location: params.location,
|
1952
|
+
comments: params.comments
|
1953
|
+
)
|
1954
|
+
in Params
|
1955
|
+
# In this case we've gotten to the <3.2 plain set of parameters. In
|
1956
|
+
# this case there cannot be lambda locals, so we will wrap the
|
1957
|
+
# parameters into a lambda var that has no locals.
|
1958
|
+
LambdaVar.new(params: params, locals: [], location: params.location)
|
1959
|
+
in LambdaVar
|
1960
|
+
# In this case we've gotten to 3.2+ lambda var. In this case we don't
|
1961
|
+
# need to do anything and can just the value as given.
|
1962
|
+
params
|
1963
|
+
end
|
1964
|
+
|
1943
1965
|
if braces
|
1944
1966
|
opening = find_token(TLamBeg)
|
1945
1967
|
closing = find_token(RBrace)
|
@@ -1962,6 +1984,83 @@ module SyntaxTree
|
|
1962
1984
|
)
|
1963
1985
|
end
|
1964
1986
|
|
1987
|
+
# :call-seq:
|
1988
|
+
# on_lambda_var: (Params params, Array[ Ident ] locals) -> LambdaVar
|
1989
|
+
def on_lambda_var(params, locals)
|
1990
|
+
location = params.location
|
1991
|
+
location = location.to(locals.last.location) if locals.any?
|
1992
|
+
|
1993
|
+
LambdaVar.new(params: params, locals: locals || [], location: location)
|
1994
|
+
end
|
1995
|
+
|
1996
|
+
# Ripper doesn't support capturing lambda local variables until 3.2. To
|
1997
|
+
# mitigate this, we have to parse that code for ourselves. We use the range
|
1998
|
+
# from the parentheses to find where we _should_ be looking. Then we check
|
1999
|
+
# if the resulting tokens match a pattern that we determine means that the
|
2000
|
+
# declaration has block-local variables. Once it does, we parse those out
|
2001
|
+
# and convert them into Ident nodes.
|
2002
|
+
def lambda_locals(source)
|
2003
|
+
tokens = Ripper.lex(source)
|
2004
|
+
|
2005
|
+
# First, check that we have a semi-colon. If we do, then we can start to
|
2006
|
+
# parse the tokens _after_ the semicolon.
|
2007
|
+
index = tokens.rindex { |token| token[1] == :on_semicolon }
|
2008
|
+
return [] unless index
|
2009
|
+
|
2010
|
+
# Next, map over the tokens and convert them into Ident nodes. Bail out
|
2011
|
+
# midway through if we encounter a token we didn't expect. Basically we're
|
2012
|
+
# making our own mini-parser here. To do that we'll walk through a small
|
2013
|
+
# state machine:
|
2014
|
+
#
|
2015
|
+
# ┌────────┐ ┌────────┐ ┌─────────┐
|
2016
|
+
# │ │ │ │ │┌───────┐│
|
2017
|
+
# ──> │ item │ ─── ident ──> │ next │ ─── rparen ──> ││ final ││
|
2018
|
+
# │ │ <── comma ─── │ │ │└───────┘│
|
2019
|
+
# └────────┘ └────────┘ └─────────┘
|
2020
|
+
# │ ^ │ ^
|
2021
|
+
# └──┘ └──┘
|
2022
|
+
# ignored_nl, sp nl, sp
|
2023
|
+
#
|
2024
|
+
state = :item
|
2025
|
+
transitions = {
|
2026
|
+
item: {
|
2027
|
+
on_ignored_nl: :item,
|
2028
|
+
on_sp: :item,
|
2029
|
+
on_ident: :next
|
2030
|
+
},
|
2031
|
+
next: {
|
2032
|
+
on_nl: :next,
|
2033
|
+
on_sp: :next,
|
2034
|
+
on_comma: :item,
|
2035
|
+
on_rparen: :final
|
2036
|
+
},
|
2037
|
+
final: {
|
2038
|
+
}
|
2039
|
+
}
|
2040
|
+
|
2041
|
+
tokens[(index + 1)..].each_with_object([]) do |token, locals|
|
2042
|
+
(lineno, column), type, value, = token
|
2043
|
+
|
2044
|
+
# Make the state transition for the parser. If there isn't a transition
|
2045
|
+
# from the current state to a new state for this type, then we're in a
|
2046
|
+
# pattern that isn't actually locals. In that case we can return [].
|
2047
|
+
state = transitions[state].fetch(type) { return [] }
|
2048
|
+
|
2049
|
+
# If we hit an identifier, then add it to our list.
|
2050
|
+
next if type != :on_ident
|
2051
|
+
|
2052
|
+
location =
|
2053
|
+
Location.token(
|
2054
|
+
line: lineno,
|
2055
|
+
char: line_counts[lineno - 1][column],
|
2056
|
+
column: column,
|
2057
|
+
size: value.size
|
2058
|
+
)
|
2059
|
+
|
2060
|
+
locals << Ident.new(value: value, location: location)
|
2061
|
+
end
|
2062
|
+
end
|
2063
|
+
|
1965
2064
|
# :call-seq:
|
1966
2065
|
# on_lbrace: (String value) -> LBrace
|
1967
2066
|
def on_lbrace(value)
|
@@ -2901,6 +3000,11 @@ module SyntaxTree
|
|
2901
3000
|
# (StringEmbExpr | StringDVar | TStringContent) part
|
2902
3001
|
# ) -> StringContent
|
2903
3002
|
def on_string_add(string, part)
|
3003
|
+
# Due to some eccentricities in how ripper works, you need this here in
|
3004
|
+
# case you have a syntax error with an embedded expression that doesn't
|
3005
|
+
# finish, as in: "#{"
|
3006
|
+
return string if part.is_a?(String)
|
3007
|
+
|
2904
3008
|
location =
|
2905
3009
|
string.parts.any? ? string.location.to(part.location) : part.location
|
2906
3010
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/tasklib"
|
5
|
+
|
6
|
+
require "syntax_tree"
|
7
|
+
require "syntax_tree/cli"
|
8
|
+
|
9
|
+
module SyntaxTree
|
10
|
+
module Rake
|
11
|
+
# A Rake task that runs check on a set of source files.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'syntax_tree/rake/check_task'
|
16
|
+
#
|
17
|
+
# SyntaxTree::Rake::CheckTask.new do |t|
|
18
|
+
# t.source_files = '{app,config,lib}/**/*.rb'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# This will create task that can be run with:
|
22
|
+
#
|
23
|
+
# rake stree_check
|
24
|
+
#
|
25
|
+
class CheckTask < ::Rake::TaskLib
|
26
|
+
# Name of the task.
|
27
|
+
# Defaults to :"stree:check".
|
28
|
+
attr_accessor :name
|
29
|
+
|
30
|
+
# Glob pattern to match source files.
|
31
|
+
# Defaults to 'lib/**/*.rb'.
|
32
|
+
attr_accessor :source_files
|
33
|
+
|
34
|
+
# The set of plugins to require.
|
35
|
+
# Defaults to [].
|
36
|
+
attr_accessor :plugins
|
37
|
+
|
38
|
+
def initialize(
|
39
|
+
name = :"stree:check",
|
40
|
+
source_files = ::Rake::FileList["lib/**/*.rb"],
|
41
|
+
plugins = []
|
42
|
+
)
|
43
|
+
@name = name
|
44
|
+
@source_files = source_files
|
45
|
+
@plugins = plugins
|
46
|
+
|
47
|
+
yield self if block_given?
|
48
|
+
define_task
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def define_task
|
54
|
+
desc "Runs `stree check` over source files"
|
55
|
+
task(name) { run_task }
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_task
|
59
|
+
arguments = ["check"]
|
60
|
+
arguments << "--plugins=#{plugins.join(",")}" if plugins.any?
|
61
|
+
|
62
|
+
SyntaxTree::CLI.run(arguments + Array(source_files))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/tasklib"
|
5
|
+
|
6
|
+
require "syntax_tree"
|
7
|
+
require "syntax_tree/cli"
|
8
|
+
|
9
|
+
module SyntaxTree
|
10
|
+
module Rake
|
11
|
+
# A Rake task that runs format on a set of source files.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'syntax_tree/rake/write_task'
|
16
|
+
#
|
17
|
+
# SyntaxTree::Rake::WriteTask.new do |t|
|
18
|
+
# t.source_files = '{app,config,lib}/**/*.rb'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# This will create task that can be run with:
|
22
|
+
#
|
23
|
+
# rake stree_write
|
24
|
+
#
|
25
|
+
class WriteTask < ::Rake::TaskLib
|
26
|
+
# Name of the task.
|
27
|
+
# Defaults to :"stree:write".
|
28
|
+
attr_accessor :name
|
29
|
+
|
30
|
+
# Glob pattern to match source files.
|
31
|
+
# Defaults to 'lib/**/*.rb'.
|
32
|
+
attr_accessor :source_files
|
33
|
+
|
34
|
+
# The set of plugins to require.
|
35
|
+
# Defaults to [].
|
36
|
+
attr_accessor :plugins
|
37
|
+
|
38
|
+
def initialize(
|
39
|
+
name = :"stree:write",
|
40
|
+
source_files = ::Rake::FileList["lib/**/*.rb"],
|
41
|
+
plugins = []
|
42
|
+
)
|
43
|
+
@name = name
|
44
|
+
@source_files = source_files
|
45
|
+
@plugins = plugins
|
46
|
+
|
47
|
+
yield self if block_given?
|
48
|
+
define_task
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def define_task
|
54
|
+
desc "Runs `stree write` over source files"
|
55
|
+
task(name) { run_task }
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_task
|
59
|
+
arguments = ["write"]
|
60
|
+
arguments << "--plugins=#{plugins.join(",")}" if plugins.any?
|
61
|
+
|
62
|
+
SyntaxTree::CLI.run(arguments + Array(source_files))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/syntax_tree/version.rb
CHANGED
@@ -49,9 +49,7 @@ module SyntaxTree
|
|
49
49
|
# of circumstances, like when visiting the list of optional parameters
|
50
50
|
# defined on a method.
|
51
51
|
#
|
52
|
-
class FieldVisitor <
|
53
|
-
attr_reader :q
|
54
|
-
|
52
|
+
class FieldVisitor < BasicVisitor
|
55
53
|
def visit_aref(node)
|
56
54
|
node(node, "aref") do
|
57
55
|
field("collection", node.collection)
|
@@ -586,6 +584,14 @@ module SyntaxTree
|
|
586
584
|
end
|
587
585
|
end
|
588
586
|
|
587
|
+
def visit_lambda_var(node)
|
588
|
+
node(node, "lambda_var") do
|
589
|
+
field("params", node.params)
|
590
|
+
list("locals", node.locals) if node.locals.any?
|
591
|
+
comments(node)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
589
595
|
def visit_lbrace(node)
|
590
596
|
visit_token(node, "lbrace")
|
591
597
|
end
|
data/lib/syntax_tree/visitor.rb
CHANGED
@@ -4,72 +4,7 @@ module SyntaxTree
|
|
4
4
|
# Visitor is a parent class that provides the ability to walk down the tree
|
5
5
|
# and handle a subset of nodes. By defining your own subclass, you can
|
6
6
|
# explicitly handle a node type by defining a visit_* method.
|
7
|
-
class Visitor
|
8
|
-
# This is raised when you use the Visitor.visit_method method and it fails.
|
9
|
-
# It is correctable to through DidYouMean.
|
10
|
-
class VisitMethodError < StandardError
|
11
|
-
attr_reader :visit_method
|
12
|
-
|
13
|
-
def initialize(visit_method)
|
14
|
-
@visit_method = visit_method
|
15
|
-
super("Invalid visit method: #{visit_method}")
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# This class is used by DidYouMean to offer corrections to invalid visit
|
20
|
-
# method names.
|
21
|
-
class VisitMethodChecker
|
22
|
-
attr_reader :visit_method
|
23
|
-
|
24
|
-
def initialize(error)
|
25
|
-
@visit_method = error.visit_method
|
26
|
-
end
|
27
|
-
|
28
|
-
def corrections
|
29
|
-
@corrections ||=
|
30
|
-
DidYouMean::SpellChecker.new(
|
31
|
-
dictionary: Visitor.visit_methods
|
32
|
-
).correct(visit_method)
|
33
|
-
end
|
34
|
-
|
35
|
-
DidYouMean.correct_error(VisitMethodError, self)
|
36
|
-
end
|
37
|
-
|
38
|
-
class << self
|
39
|
-
# This method is here to help folks write visitors.
|
40
|
-
#
|
41
|
-
# It's not always easy to ensure you're writing the correct method name in
|
42
|
-
# the visitor since it's perfectly valid to define methods that don't
|
43
|
-
# override these parent methods.
|
44
|
-
#
|
45
|
-
# If you use this method, you can ensure you're writing the correct method
|
46
|
-
# name. It will raise an error if the visit method you're defining isn't
|
47
|
-
# actually a method on the parent visitor.
|
48
|
-
def visit_method(method_name)
|
49
|
-
return if visit_methods.include?(method_name)
|
50
|
-
|
51
|
-
raise VisitMethodError, method_name
|
52
|
-
end
|
53
|
-
|
54
|
-
# This is the list of all of the valid visit methods.
|
55
|
-
def visit_methods
|
56
|
-
@visit_methods ||=
|
57
|
-
Visitor.instance_methods.grep(/^visit_(?!child_nodes)/)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def visit(node)
|
62
|
-
node&.accept(self)
|
63
|
-
end
|
64
|
-
|
65
|
-
def visit_all(nodes)
|
66
|
-
nodes.map { |node| visit(node) }
|
67
|
-
end
|
68
|
-
|
69
|
-
def visit_child_nodes(node)
|
70
|
-
visit_all(node.child_nodes)
|
71
|
-
end
|
72
|
-
|
7
|
+
class Visitor < BasicVisitor
|
73
8
|
# Visit an ARef node.
|
74
9
|
alias visit_aref visit_child_nodes
|
75
10
|
|
@@ -301,6 +236,9 @@ module SyntaxTree
|
|
301
236
|
# Visit a Lambda node.
|
302
237
|
alias visit_lambda visit_child_nodes
|
303
238
|
|
239
|
+
# Visit a LambdaVar node.
|
240
|
+
alias visit_lambda_var visit_child_nodes
|
241
|
+
|
304
242
|
# Visit a LBrace node.
|
305
243
|
alias visit_lbrace visit_child_nodes
|
306
244
|
|
data/lib/syntax_tree.rb
CHANGED
@@ -10,6 +10,8 @@ require_relative "syntax_tree/formatter"
|
|
10
10
|
require_relative "syntax_tree/node"
|
11
11
|
require_relative "syntax_tree/parser"
|
12
12
|
require_relative "syntax_tree/version"
|
13
|
+
|
14
|
+
require_relative "syntax_tree/basic_visitor"
|
13
15
|
require_relative "syntax_tree/visitor"
|
14
16
|
require_relative "syntax_tree/visitor/field_visitor"
|
15
17
|
require_relative "syntax_tree/visitor/json_visitor"
|
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.
|
4
|
+
version: 2.7.1
|
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-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prettier_print
|
@@ -121,14 +121,18 @@ files:
|
|
121
121
|
- doc/logo.svg
|
122
122
|
- exe/stree
|
123
123
|
- lib/syntax_tree.rb
|
124
|
+
- lib/syntax_tree/basic_visitor.rb
|
124
125
|
- lib/syntax_tree/cli.rb
|
125
126
|
- lib/syntax_tree/formatter.rb
|
126
|
-
- lib/syntax_tree/formatter/single_quotes.rb
|
127
127
|
- lib/syntax_tree/language_server.rb
|
128
128
|
- lib/syntax_tree/language_server/inlay_hints.rb
|
129
129
|
- lib/syntax_tree/node.rb
|
130
130
|
- lib/syntax_tree/parser.rb
|
131
131
|
- lib/syntax_tree/plugin/single_quotes.rb
|
132
|
+
- lib/syntax_tree/plugin/trailing_comma.rb
|
133
|
+
- lib/syntax_tree/rake/check_task.rb
|
134
|
+
- lib/syntax_tree/rake/write_task.rb
|
135
|
+
- lib/syntax_tree/rake_tasks.rb
|
132
136
|
- lib/syntax_tree/version.rb
|
133
137
|
- lib/syntax_tree/visitor.rb
|
134
138
|
- lib/syntax_tree/visitor/field_visitor.rb
|
@@ -156,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
160
|
- !ruby/object:Gem::Version
|
157
161
|
version: '0'
|
158
162
|
requirements: []
|
159
|
-
rubygems_version: 3.
|
163
|
+
rubygems_version: 3.4.0.dev
|
160
164
|
signing_key:
|
161
165
|
specification_version: 4
|
162
166
|
summary: A parser based on ripper
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SyntaxTree
|
4
|
-
class Formatter
|
5
|
-
# This module overrides the quote method on the formatter to use single
|
6
|
-
# quotes for everything instead of double quotes.
|
7
|
-
module SingleQuotes
|
8
|
-
def quote
|
9
|
-
"'"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|