syntax_tree 1.2.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/gh-pages.yml +26 -0
- data/.github/workflows/main.yml +9 -1
- data/.gitignore +2 -1
- data/CHANGELOG.md +47 -6
- data/Gemfile.lock +7 -5
- data/README.md +273 -32
- data/bin/bench +11 -11
- data/bin/console +3 -3
- data/bin/profile +3 -3
- data/doc/logo.svg +284 -0
- data/exe/stree +1 -0
- data/lib/syntax_tree/cli.rb +122 -49
- data/lib/syntax_tree/formatter.rb +71 -0
- data/lib/syntax_tree/language_server/inlay_hints.rb +77 -0
- data/lib/syntax_tree/language_server.rb +99 -0
- data/lib/syntax_tree/node.rb +9376 -0
- data/lib/syntax_tree/parser.rb +3223 -0
- data/lib/syntax_tree/version.rb +2 -4
- data/lib/syntax_tree/visitor/json_visitor.rb +1335 -0
- data/lib/syntax_tree/visitor/pretty_print_visitor.rb +1213 -0
- data/lib/syntax_tree/visitor.rb +548 -0
- data/lib/syntax_tree.rb +21 -13956
- metadata +13 -4
- data/bin/setup +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46b573afdda378e664e11b1f5acab2c89ce0fdd2c8b3d4c6400d74e133aaffc3
|
4
|
+
data.tar.gz: 6be0570ce471fe3f34c53730d7095d9b81be5b7e3e2e84ac5724ba95d7ac7362
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36236536a1dda5e6e2907b82c383b74f207f5e2b64f282490cf26161d62d748d1cdf51b68c393cd60f1777d2c5434c294ad2e243f2575faae34c42ac44155f76
|
7
|
+
data.tar.gz: 5c2e0ee682e5847287cee5a6ca4357c51e4172bacf7aa98b780e427fb7d0912b6bd829bbf6236baa294a919be9be5730d8c7e74b27a0ca630a29a91c37540324
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: Github Pages (rdoc)
|
2
|
+
on: [push]
|
3
|
+
jobs:
|
4
|
+
build-and-deploy:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
steps:
|
7
|
+
- name: Checkout 🛎️
|
8
|
+
uses: actions/checkout@master
|
9
|
+
|
10
|
+
- name: Set up Ruby 💎
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
bundler-cache: true
|
14
|
+
ruby-version: '3.1'
|
15
|
+
|
16
|
+
- name: Install rdoc and generate docs 🔧
|
17
|
+
run: |
|
18
|
+
gem install rdoc
|
19
|
+
rdoc --main README.md --op rdocs --exclude={Gemfile,Rakefile,"coverage/*","vendor/*","bin/*","test/*","tmp/*"}
|
20
|
+
cp -r doc rdocs/doc
|
21
|
+
|
22
|
+
- name: Deploy 🚀
|
23
|
+
uses: peaceiris/actions-gh-pages@v3
|
24
|
+
with:
|
25
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
26
|
+
publish_dir: ./rdocs
|
data/.github/workflows/main.yml
CHANGED
@@ -4,6 +4,14 @@ on:
|
|
4
4
|
- pull_request_target
|
5
5
|
jobs:
|
6
6
|
ci:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
ruby:
|
11
|
+
- '2.7'
|
12
|
+
- '3.0'
|
13
|
+
- '3.1'
|
14
|
+
- head
|
7
15
|
name: CI
|
8
16
|
runs-on: ubuntu-latest
|
9
17
|
env:
|
@@ -13,7 +21,7 @@ jobs:
|
|
13
21
|
- uses: ruby/setup-ruby@v1
|
14
22
|
with:
|
15
23
|
bundler-cache: true
|
16
|
-
ruby-version:
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
17
25
|
- name: Test
|
18
26
|
run: bundle exec rake test
|
19
27
|
automerge:
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,44 @@ 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.1.0] - 2022-04-12
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- The `SyntaxTree::Visitor` class now implements the visitor pattern for Ruby nodes.
|
14
|
+
- The `SyntaxTree::Visitor.visit_method(name)` method.
|
15
|
+
- Support for Ruby 2.7.
|
16
|
+
- Support for comments on `rescue` and `else` keywords.
|
17
|
+
- `SyntaxTree::Location` now additionally has `start_column` and `end_column`.
|
18
|
+
- The CLI now accepts content over STDIN for the `ast`, `check`, `debug`, `doc`, `format`, and `write` commands.
|
19
|
+
|
20
|
+
### Removed
|
21
|
+
|
22
|
+
- The missing hash value inlay hints have been removed.
|
23
|
+
|
24
|
+
## [2.0.1] - 2022-03-31
|
25
|
+
|
26
|
+
### Changed
|
27
|
+
|
28
|
+
- Move the `SyntaxTree.register_handler` method to the correct location.
|
29
|
+
|
30
|
+
## [2.0.0] - 2022-03-31
|
31
|
+
|
32
|
+
### Added
|
33
|
+
|
34
|
+
- The new `SyntaxTree.register_handler` hook for plugins.
|
35
|
+
- The new `--plugins=` option on the CLI.
|
36
|
+
|
37
|
+
### Changed
|
38
|
+
|
39
|
+
- Changed `SyntaxTree` from being a class to being a module. The parser functionality is moved into `SyntaxTree::Parser`.
|
40
|
+
- There is now a parent class for all of the nodes named `SyntaxTree::Node`.
|
41
|
+
- The `Implicits` class has been renamed to `InlayHints` to match the new LSP spec.
|
42
|
+
|
43
|
+
### Removed
|
44
|
+
|
45
|
+
- The disassembly code action has been removed to limit the scope of this project overall.
|
46
|
+
|
9
47
|
## [1.2.0] - 2022-01-09
|
10
48
|
|
11
49
|
### Added
|
@@ -105,9 +143,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
105
143
|
|
106
144
|
- 🎉 Initial release! 🎉
|
107
145
|
|
108
|
-
[unreleased]: https://github.com/
|
109
|
-
[1.
|
110
|
-
[
|
111
|
-
[
|
112
|
-
[1.
|
113
|
-
[
|
146
|
+
[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.0...HEAD
|
147
|
+
[2.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.1...v2.1.0
|
148
|
+
[2.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.0...v2.0.1
|
149
|
+
[2.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.2.0...v2.0.0
|
150
|
+
[1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.1...v1.2.0
|
151
|
+
[1.1.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.0...v1.1.1
|
152
|
+
[1.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.0.0...v1.1.0
|
153
|
+
[1.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v0.1.0...v1.0.0
|
154
|
+
[0.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/8aa1f5...v0.1.0
|
data/Gemfile.lock
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
syntax_tree (1.
|
4
|
+
syntax_tree (2.1.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
ast (2.4.2)
|
10
|
-
benchmark-ips (2.
|
10
|
+
benchmark-ips (2.10.0)
|
11
11
|
docile (1.4.0)
|
12
12
|
minitest (5.15.0)
|
13
|
-
parser (3.1.
|
13
|
+
parser (3.1.2.0)
|
14
14
|
ast (~> 2.4.1)
|
15
15
|
rake (13.0.6)
|
16
|
-
ruby_parser (3.
|
16
|
+
ruby_parser (3.19.1)
|
17
17
|
sexp_processor (~> 4.16)
|
18
18
|
sexp_processor (4.16.0)
|
19
19
|
simplecov (0.21.2)
|
@@ -22,9 +22,11 @@ GEM
|
|
22
22
|
simplecov_json_formatter (~> 0.1)
|
23
23
|
simplecov-html (0.12.3)
|
24
24
|
simplecov_json_formatter (0.1.3)
|
25
|
-
stackprof (0.2.
|
25
|
+
stackprof (0.2.19)
|
26
26
|
|
27
27
|
PLATFORMS
|
28
|
+
arm64-darwin-21
|
29
|
+
ruby
|
28
30
|
x86_64-darwin-19
|
29
31
|
x86_64-darwin-21
|
30
32
|
x86_64-linux
|
data/README.md
CHANGED
@@ -1,13 +1,56 @@
|
|
1
|
+
<div align="center">
|
2
|
+
<img alt="Syntax Tree" height="400px" src="./doc/logo.svg">
|
3
|
+
</div>
|
4
|
+
|
1
5
|
# SyntaxTree
|
2
6
|
|
3
|
-
[![Build Status](https://github.com/
|
7
|
+
[![Build Status](https://github.com/ruby-syntax-tree/syntax_tree/actions/workflows/main.yml/badge.svg)](https://github.com/ruby-syntax-tree/syntax_tree/actions/workflows/main.yml)
|
4
8
|
[![Gem Version](https://img.shields.io/gem/v/syntax_tree.svg)](https://rubygems.org/gems/syntax_tree)
|
5
9
|
|
6
|
-
|
10
|
+
Syntax Tree is a suite of tools built on top of the internal CRuby parser. It provides the ability to generate a syntax tree from source, as well as the tools necessary to inspect and manipulate that syntax tree. It can be used to build formatters, linters, language servers, and more.
|
11
|
+
|
12
|
+
It is built with only standard library dependencies. It additionally ships with a plugin system so that you can build your own syntax trees from other languages and incorporate these tools.
|
13
|
+
|
14
|
+
- [Installation](#installation)
|
15
|
+
- [CLI](#cli)
|
16
|
+
- [ast](#ast)
|
17
|
+
- [check](#check)
|
18
|
+
- [format](#format)
|
19
|
+
- [write](#write)
|
20
|
+
- [Library](#library)
|
21
|
+
- [SyntaxTree.read(filepath)](#syntaxtreereadfilepath)
|
22
|
+
- [SyntaxTree.parse(source)](#syntaxtreeparsesource)
|
23
|
+
- [SyntaxTree.format(source)](#syntaxtreeformatsource)
|
24
|
+
- [Nodes](#nodes)
|
25
|
+
- [child_nodes](#child_nodes)
|
26
|
+
- [Pattern matching](#pattern-matching)
|
27
|
+
- [pretty_print(q)](#pretty_printq)
|
28
|
+
- [to_json(*opts)](#to_jsonopts)
|
29
|
+
- [format(q)](#formatq)
|
30
|
+
- [Visitor](#visitor)
|
31
|
+
- [visit_method](#visit_method)
|
32
|
+
- [Language server](#language-server)
|
33
|
+
- [textDocument/formatting](#textdocumentformatting)
|
34
|
+
- [textDocument/inlayHints](#textdocumentinlayhints)
|
35
|
+
- [syntaxTree/visualizing](#syntaxtreevisualizing)
|
36
|
+
- [Contributing](#contributing)
|
37
|
+
- [License](#license)
|
7
38
|
|
8
39
|
## Installation
|
9
40
|
|
10
|
-
|
41
|
+
Syntax Tree is both a command-line interface and a library. If you're only looking to use the command-line interface, then we recommend installing the gem globally, as in:
|
42
|
+
|
43
|
+
```sh
|
44
|
+
gem install syntax_tree
|
45
|
+
```
|
46
|
+
|
47
|
+
To run the CLI with the gem installed globally, you would run:
|
48
|
+
|
49
|
+
```sh
|
50
|
+
stree version
|
51
|
+
```
|
52
|
+
|
53
|
+
If you're planning on using Syntax Tree as a library within your own project, we recommend installing it as part of your gem bundle. First, add this line to your application's Gemfile:
|
11
54
|
|
12
55
|
```ruby
|
13
56
|
gem "syntax_tree"
|
@@ -15,61 +58,259 @@ gem "syntax_tree"
|
|
15
58
|
|
16
59
|
And then execute:
|
17
60
|
|
18
|
-
|
61
|
+
```sh
|
62
|
+
bundle install
|
63
|
+
```
|
64
|
+
|
65
|
+
To run the CLI with the gem installed in your gem bundle, you would run:
|
66
|
+
|
67
|
+
```sh
|
68
|
+
bundle exec stree version
|
69
|
+
```
|
19
70
|
|
20
|
-
|
71
|
+
## CLI
|
21
72
|
|
22
|
-
|
73
|
+
Syntax Tree ships with the `stree` CLI, which can be used to inspect and manipulate Ruby code. Below are listed all of the commands built into the CLI that you can use. Note that for all commands that operate on files, you can also pass in content through STDIN.
|
23
74
|
|
24
|
-
|
75
|
+
### ast
|
25
76
|
|
26
|
-
|
77
|
+
This command will print out a textual representation of the syntax tree associated with each of the files it finds. To execute, run:
|
27
78
|
|
28
|
-
```
|
29
|
-
|
79
|
+
```sh
|
80
|
+
stree ast path/to/file.rb
|
81
|
+
```
|
82
|
+
|
83
|
+
For a file that contains `1 + 1`, you will receive:
|
30
84
|
|
31
|
-
pp SyntaxTree.parse(source) # print out the AST
|
32
|
-
puts SyntaxTree.format(source) # format the AST
|
33
85
|
```
|
86
|
+
(program (statements (binary (int "1") + (int "1"))))
|
87
|
+
```
|
88
|
+
|
89
|
+
### check
|
34
90
|
|
35
|
-
|
91
|
+
This command is meant to be used in the context of a continuous integration or git hook. It checks each file given to make sure that it matches the expected format. It can be used to ensure unformatted content never makes it into a codebase.
|
36
92
|
|
37
93
|
```sh
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
94
|
+
stree check path/to/file.rb
|
95
|
+
```
|
96
|
+
|
97
|
+
For a file that matches the expected format, you will receive:
|
98
|
+
|
99
|
+
```
|
100
|
+
All files matched expected format.
|
101
|
+
```
|
102
|
+
|
103
|
+
If there are files with unformatted code, you will receive:
|
104
|
+
|
42
105
|
```
|
106
|
+
[warn] path/to/file.rb
|
107
|
+
The listed files did not match the expected format.
|
108
|
+
```
|
109
|
+
|
110
|
+
### format
|
43
111
|
|
44
|
-
|
112
|
+
This command will output the formatted version of each of the listed files. Importantly, it will not write that content back to the source files. It is meant to display the formatted version only.
|
45
113
|
|
46
114
|
```sh
|
47
|
-
|
48
|
-
|
49
|
-
|
115
|
+
stree format path/to/file.rb
|
116
|
+
```
|
117
|
+
|
118
|
+
For a file that contains `1 + 1`, you will receive:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
1 + 1
|
50
122
|
```
|
51
123
|
|
52
|
-
|
124
|
+
### write
|
125
|
+
|
126
|
+
This command will format the listed files and write that formatted version back to the source files. Note that this overwrites the original content, to be sure to be using a version control system.
|
53
127
|
|
54
128
|
```sh
|
55
|
-
|
56
|
-
|
129
|
+
stree write path/to/file.rb
|
130
|
+
```
|
131
|
+
|
132
|
+
This will list every file that is being formatted. It will output light gray if the file already matches the expected format. It will output in regular color if it does not.
|
133
|
+
|
134
|
+
```
|
135
|
+
path/to/file.rb 0ms
|
136
|
+
```
|
137
|
+
|
138
|
+
## Library
|
139
|
+
|
140
|
+
Syntax Tree can be used as a library to access the syntax tree underlying Ruby source code.
|
141
|
+
|
142
|
+
### SyntaxTree.read(filepath)
|
143
|
+
|
144
|
+
This function takes a filepath and returns a string associated with the content of that file. It is similar in functionality to `File.read`, except htat it takes into account Ruby-level file encoding (through magic comments at the top of the file).
|
145
|
+
|
146
|
+
### SyntaxTree.parse(source)
|
147
|
+
|
148
|
+
This function takes an input string containing Ruby code and returns the syntax tree associated with it. The top-level node is always a `SyntaxTree::Program`, which contains a list of top-level expression nodes.
|
149
|
+
|
150
|
+
### SyntaxTree.format(source)
|
151
|
+
|
152
|
+
This function takes an input string containing Ruby code, parses it into its underlying syntax tree, and formats it back out to a string.
|
153
|
+
|
154
|
+
## Nodes
|
155
|
+
|
156
|
+
There are many different node types in the syntax tree. They are meant to be treated as immutable structs containing links to child nodes with minimal logic contained within their implementation. However, for the most part they all respond to a certain set of APIs, listed below.
|
157
|
+
|
158
|
+
### child_nodes
|
159
|
+
|
160
|
+
One of the easiest ways to descend the tree is to use the `child_nodes` function. It is implemented on every node type (leaf nodes return an empty array). If the goal is to simply walk through the tree, this is the easiest way to go.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
program = SyntaxTree.parse("1 + 1")
|
164
|
+
program.child_nodes.first.child_nodes.first
|
165
|
+
# => (binary (int "1") :+ (int "1"))
|
57
166
|
```
|
58
167
|
|
59
|
-
|
168
|
+
### Pattern matching
|
60
169
|
|
61
|
-
|
170
|
+
Pattern matching is another way to descend the tree which is more specific than using `child_nodes`. Using Ruby's built-in pattern matching, you can extract the same information but be as specific about your constraints as you like. For example, with minimal constraints:
|
62
171
|
|
63
|
-
|
172
|
+
```ruby
|
173
|
+
program = SyntaxTree.parse("1 + 1")
|
174
|
+
program => { statements: { body: [binary] } }
|
175
|
+
binary
|
176
|
+
# => (binary (int "1") :+ (int "1"))
|
177
|
+
```
|
178
|
+
|
179
|
+
Or, with more constraints on the types to ensure we're getting exactly what we expect:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
program = SyntaxTree.parse("1 + 1")
|
183
|
+
program => SyntaxTree::Program[statements: SyntaxTree::Statements[body: [SyntaxTree::Binary => binary]]]
|
184
|
+
binary
|
185
|
+
# => (binary (int "1") :+ (int "1"))
|
186
|
+
```
|
187
|
+
|
188
|
+
### pretty_print(q)
|
189
|
+
|
190
|
+
Every node responds to the `pretty_print` Ruby interface, which makes it usable by the `pp` library. You _can_ use this API manually, but it's mostly there for compatibility and not meant to be directly invoked. For example:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
pp SyntaxTree.parse("1 + 1")
|
194
|
+
# (program (statements (binary (int "1") + (int "1"))))
|
195
|
+
```
|
196
|
+
|
197
|
+
### to_json(*opts)
|
198
|
+
|
199
|
+
Every node responds to the `to_json` Ruby interface, which makes it usable by the `json` library. Much like `pretty_print`, you could use this API manually, but it's mostly used by `JSON` to dump the nodes to a serialized format. For example:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
program = SyntaxTree.parse("1 + 1")
|
203
|
+
program => { statements: { body: [{ left: }] } }
|
204
|
+
puts JSON.dump(left)
|
205
|
+
# {"type":"int","value":"1","loc":[1,0,1,1],"cmts":[]}
|
206
|
+
```
|
207
|
+
|
208
|
+
### format(q)
|
209
|
+
|
210
|
+
Every node responds to `format`, which formats the content nicely. The API mirrors that used by the `pretty_print` gem in that it accepts a formatter object and calls methods on it to generate its own internal representation of the text that will be outputted. Because of this, it's easier to not use this API directly and instead to call `SyntaxTree.format`. You _can_ however use this directly if you create the formatter yourself, as in:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
source = "1+1"
|
214
|
+
program = SyntaxTree.parse(source)
|
215
|
+
program => { statements: { body: [binary] } }
|
216
|
+
|
217
|
+
formatter = SyntaxTree::Formatter.new(source, [])
|
218
|
+
binary.format(formatter)
|
219
|
+
|
220
|
+
formatter.flush
|
221
|
+
formatter.output.join
|
222
|
+
# => "1 + 1"
|
223
|
+
```
|
224
|
+
|
225
|
+
## Visitor
|
226
|
+
|
227
|
+
If you want to operate over a set of nodes in the tree but don't want to walk the tree manually, the `Visitor` class makes it easy. `SyntaxTree::Visitor` is an implementation of the double dispatch visitor pattern. It works by the user defining visit methods that process nodes in the tree, which then call back to other visit methods to continue the descent. This is easier shown in code.
|
228
|
+
|
229
|
+
Let's say, for instance, that you wanted to find every place in source where you have an arithmetic problem between two integers (this is pretty contrived, but it's just for illustration). You could define a visitor that only explicitly visits the `SyntaxTree::Binary` node, as in:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
class ArithmeticVisitor < SyntaxTree::Visitor
|
233
|
+
def visit_binary(node)
|
234
|
+
if node in { left: SyntaxTree::Int, operator: :+ | :- | :* | :/, right: SyntaxTree::Int }
|
235
|
+
puts "The result is: #{node.left.value.to_i.public_send(node.operator, node.right.value.to_i)}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
visitor = ArithmeticVisitor.new
|
241
|
+
visitor.visit(SyntaxTree.parse("1 + 1"))
|
242
|
+
# The result is: 2
|
243
|
+
```
|
244
|
+
|
245
|
+
With visitors, you only define handlers for the nodes that you need. You can find the names of the methods that you will need to define within the base visitor, as they're all aliased to the default behavior (visiting the child nodes). Note that when you define a handler for a node, you have to tell Syntax Tree how to walk further. In the example above, we don't need to go any further because we already know the child nodes are `SyntaxTree::Int`, so they can't possibly contain more `SyntaxTree::Binary` nodes. In other circumstances you may not know though, so you can either:
|
246
|
+
|
247
|
+
* call `super` (which will do the default and visit all child nodes)
|
248
|
+
* call `visit_child_nodes` manually
|
249
|
+
* call `visit(child)` with each child that you want to visit
|
250
|
+
* call nothing if you're sure you don't want to descend further
|
251
|
+
|
252
|
+
There are a couple of visitors that ship with Syntax Tree that can be used as examples. They live in the [lib/syntax_tree/visitor](lib/syntax_tree/visitor) directory.
|
253
|
+
|
254
|
+
### visit_method
|
255
|
+
|
256
|
+
When you're creating a visitor, it's very easy to accidentally mistype a visit method. Unfortunately, there's no way to tell Ruby to explicitly override a parent method, so it would then be easy to define a method that never gets called. To mitigate this risk, there's `Visitor.visit_method(name)`. This method accepts a symbol that is checked against the list of known visit methods. If it's not in the list, then an error will be raised. It's meant to be used like:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
class ArithmeticVisitor < SyntaxTree::Visitor
|
260
|
+
visit_method def visit_binary(node)
|
261
|
+
# ...
|
262
|
+
end
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
This will only be checked once when the file is first required. If there is a typo in your method name (or the method no longer exists for whatever reason), you will receive an error like so:
|
267
|
+
|
268
|
+
```
|
269
|
+
~/syntax_tree/lib/syntax_tree/visitor.rb:46:in `visit_method': Invalid visit method: visit_binar (SyntaxTree::Visitor::VisitMethodError)
|
270
|
+
Did you mean? visit_binary
|
271
|
+
visit_in
|
272
|
+
visit_ivar
|
273
|
+
from (irb):2:in `<class:ArithmeticVisitor>'
|
274
|
+
from (irb):1:in `<main>'
|
275
|
+
from bin/console:8:in `<main>'
|
276
|
+
```
|
277
|
+
|
278
|
+
## Language server
|
279
|
+
|
280
|
+
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:
|
281
|
+
|
282
|
+
```sh
|
283
|
+
stree lsp
|
284
|
+
```
|
285
|
+
|
286
|
+
By default, the language server is relatively minimal, mostly meant to provide a registered formatter for the Ruby language. However there are a couple of additional niceties baked in. There are related projects that configure and use this language server within IDEs. For example, to use this code with VSCode, see [ruby-syntax-tree/vscode-syntax-tree](https://github.com/ruby-syntax-tree/vscode-syntax-tree).
|
287
|
+
|
288
|
+
### textDocument/formatting
|
289
|
+
|
290
|
+
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.
|
291
|
+
|
292
|
+
### textDocument/inlayHints
|
293
|
+
|
294
|
+
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:
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
1 + 2 * 3
|
298
|
+
```
|
299
|
+
|
300
|
+
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:
|
301
|
+
|
302
|
+
```ruby
|
303
|
+
1 + ₍2 * 3₎
|
304
|
+
```
|
305
|
+
|
306
|
+
### syntaxTree/visualizing
|
307
|
+
|
308
|
+
The language server additionally includes this custom request to return a textual representation of the syntax tree underlying the source code of a file. Language server clients can use this to (for example) open an additional tab with this information displayed.
|
64
309
|
|
65
310
|
## Contributing
|
66
311
|
|
67
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
312
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-syntax-tree/syntax_tree.
|
68
313
|
|
69
314
|
## License
|
70
315
|
|
71
316
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
72
|
-
|
73
|
-
## Code of Conduct
|
74
|
-
|
75
|
-
Everyone interacting in the syntax_tree project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/kddnewton/syntax_tree/blob/main/CODE_OF_CONDUCT.md).
|
data/bin/bench
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "benchmark/ips"
|
6
6
|
|
7
|
-
require_relative
|
8
|
-
require
|
9
|
-
require
|
7
|
+
require_relative "../lib/syntax_tree"
|
8
|
+
require "ruby_parser"
|
9
|
+
require "parser/current"
|
10
10
|
|
11
11
|
def compare(filepath)
|
12
|
-
prefix = "#{File.expand_path(
|
12
|
+
prefix = "#{File.expand_path("..", __dir__)}/"
|
13
13
|
puts "=== #{filepath.delete_prefix(prefix)} ==="
|
14
14
|
|
15
15
|
source = File.read(filepath)
|
16
16
|
|
17
17
|
Benchmark.ips do |x|
|
18
|
-
x.report(
|
19
|
-
x.report(
|
20
|
-
x.report(
|
18
|
+
x.report("syntax_tree") { SyntaxTree.parse(source) }
|
19
|
+
x.report("parser") { Parser::CurrentRuby.parse(source) }
|
20
|
+
x.report("ruby_parser") { RubyParser.new.parse(source) }
|
21
21
|
x.compare!
|
22
22
|
end
|
23
23
|
end
|
@@ -29,8 +29,8 @@ filepaths = ARGV
|
|
29
29
|
# file).
|
30
30
|
if filepaths.empty?
|
31
31
|
filepaths = [
|
32
|
-
File.expand_path(
|
33
|
-
File.expand_path(
|
32
|
+
File.expand_path("bench", __dir__),
|
33
|
+
File.expand_path("../lib/syntax_tree.rb", __dir__)
|
34
34
|
]
|
35
35
|
end
|
36
36
|
|
data/bin/console
CHANGED
data/bin/profile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "stackprof"
|
6
6
|
|
7
|
-
filepath = File.expand_path(
|
7
|
+
filepath = File.expand_path("../lib/syntax_tree", __dir__)
|
8
8
|
require_relative filepath
|
9
9
|
|
10
10
|
GC.disable
|