syntax_tree 2.0.0 → 2.1.1

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: 7275d7d2b3ec2fa399a51438f4fd3acd5f3d6e841552b67627f8a5899e5e91fd
4
- data.tar.gz: accab14f12d6b954ec21d162e60407d7cdbbc487e1752cb285c04254097e3900
3
+ metadata.gz: bd341a51ce36b230703bfbe86078641e7670058a3e41d0dbb125548c0821a65a
4
+ data.tar.gz: 9ebdca60f2956e24f83a58749266c89eb0eed88003a06699432956a0ecfa735a
5
5
  SHA512:
6
- metadata.gz: b6c2bd9df0666ed9956df6db652b6b8edaed7f2a27bb1198eaab84e41018300a8ca8e0071ca598bddfd1e768cbd2c57f8b7e87ae412b231f2ee22701943822fa
7
- data.tar.gz: 6693b2cdd4793ce9c2268b65b72e81a54dca3130f2bd10d7b3e249b542dcdf513f9e6f8803db916b3ac183b095aa0ad6b14895a556622762263b2004f3e2b6ce
6
+ metadata.gz: b8292d8020afa5ece8afdbaa60e959902357e769f098c75894d2b85686a20f824cf510563b2cd31e86b3f8f781f74cf8aaafac76e0e156e26897999ccb72ae36
7
+ data.tar.gz: 968a355401a9bd1ca6a3e67d60e5587485de9ad3ad2a5f9e8cbe7add95622364e4c094a757035482c35377ab75cb14926af1f5ab8a0df0e5069ba69f7bed1851
@@ -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: '3.1'
24
+ ruby-version: ${{ matrix.ruby }}
17
25
  - name: Test
18
26
  run: bundle exec rake test
19
27
  automerge:
data/.gitignore CHANGED
@@ -6,5 +6,6 @@
6
6
  /rdocs/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /vendor/
9
10
 
10
11
  test.rb
data/CHANGELOG.md CHANGED
@@ -6,6 +6,37 @@ 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.1] - 2022-04-16
10
+
11
+ ### Changed
12
+
13
+ - [#45](https://github.com/ruby-syntax-tree/syntax_tree/issues/45) - Fix parsing expressions like `foo.instance_exec(&T.must(block))`, where there are two `args_add_block` calls with a single `&`. Previously it was associating the `&` with the wrong block.
14
+ - [#47](https://github.com/ruby-syntax-tree/syntax_tree/pull/47) - Handle expressions like `not()`.
15
+ - [#48](https://github.com/ruby-syntax-tree/syntax_tree/pull/48) - Handle special call syntax with `::` operator.
16
+ - [#49](https://github.com/ruby-syntax-tree/syntax_tree/pull/49) - Handle expressions like `case foo; in {}; end`.
17
+ - [#50](https://github.com/ruby-syntax-tree/syntax_tree/pull/50) - Parsing expressions like `case foo; in **nil; end`.
18
+
19
+ ## [2.1.0] - 2022-04-12
20
+
21
+ ### Added
22
+
23
+ - The `SyntaxTree::Visitor` class now implements the visitor pattern for Ruby nodes.
24
+ - The `SyntaxTree::Visitor.visit_method(name)` method.
25
+ - Support for Ruby 2.7.
26
+ - Support for comments on `rescue` and `else` keywords.
27
+ - `SyntaxTree::Location` now additionally has `start_column` and `end_column`.
28
+ - The CLI now accepts content over STDIN for the `ast`, `check`, `debug`, `doc`, `format`, and `write` commands.
29
+
30
+ ### Removed
31
+
32
+ - The missing hash value inlay hints have been removed.
33
+
34
+ ## [2.0.1] - 2022-03-31
35
+
36
+ ### Changed
37
+
38
+ - Move the `SyntaxTree.register_handler` method to the correct location.
39
+
9
40
  ## [2.0.0] - 2022-03-31
10
41
 
11
42
  ### Added
@@ -122,9 +153,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
122
153
 
123
154
  - 🎉 Initial release! 🎉
124
155
 
125
- [unreleased]: https://github.com/kddnewton/syntax_tree/compare/v1.2.0...HEAD
126
- [1.2.0]: https://github.com/kddnewton/syntax_tree/compare/v1.1.1...v1.2.0
127
- [1.1.1]: https://github.com/kddnewton/syntax_tree/compare/v1.1.0...v1.1.1
128
- [1.1.0]: https://github.com/kddnewton/syntax_tree/compare/v1.0.0...v1.1.0
129
- [1.0.0]: https://github.com/kddnewton/syntax_tree/compare/v0.1.0...v1.0.0
130
- [0.1.0]: https://github.com/kddnewton/syntax_tree/compare/8aa1f5...v0.1.0
156
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.1...HEAD
157
+ [2.1.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.0...v2.1.1
158
+ [2.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.1...v2.1.0
159
+ [2.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.0...v2.0.1
160
+ [2.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.2.0...v2.0.0
161
+ [1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.1...v1.2.0
162
+ [1.1.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.0...v1.1.1
163
+ [1.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.0.0...v1.1.0
164
+ [1.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v0.1.0...v1.0.0
165
+ [0.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/8aa1f5...v0.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (2.0.0)
4
+ syntax_tree (2.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,10 +10,10 @@ GEM
10
10
  benchmark-ips (2.10.0)
11
11
  docile (1.4.0)
12
12
  minitest (5.15.0)
13
- parser (3.1.1.0)
13
+ parser (3.1.2.0)
14
14
  ast (~> 2.4.1)
15
15
  rake (13.0.6)
16
- ruby_parser (3.19.0)
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)
@@ -25,6 +25,8 @@ GEM
25
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
@@ -4,14 +4,54 @@
4
4
 
5
5
  # SyntaxTree
6
6
 
7
- [![Build Status](https://github.com/kddnewton/syntax_tree/workflows/Main/badge.svg)](https://github.com/kddnewton/syntax_tree/actions)
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)
8
8
  [![Gem Version](https://img.shields.io/gem/v/syntax_tree.svg)](https://rubygems.org/gems/syntax_tree)
9
9
 
10
- A fast Ruby parser and formatter with only standard library dependencies.
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
+ - [Plugins](#plugins)
37
+ - [Contributing](#contributing)
38
+ - [License](#license)
11
39
 
12
40
  ## Installation
13
41
 
14
- Add this line to your application's Gemfile:
42
+ 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:
43
+
44
+ ```sh
45
+ gem install syntax_tree
46
+ ```
47
+
48
+ To run the CLI with the gem installed globally, you would run:
49
+
50
+ ```sh
51
+ stree version
52
+ ```
53
+
54
+ 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:
15
55
 
16
56
  ```ruby
17
57
  gem "syntax_tree"
@@ -19,61 +59,279 @@ gem "syntax_tree"
19
59
 
20
60
  And then execute:
21
61
 
22
- $ bundle install
62
+ ```sh
63
+ bundle install
64
+ ```
65
+
66
+ To run the CLI with the gem installed in your gem bundle, you would run:
23
67
 
24
- Or install it yourself as:
68
+ ```sh
69
+ bundle exec stree version
70
+ ```
25
71
 
26
- $ gem install syntax_tree
72
+ ## CLI
27
73
 
28
- ## Usage
74
+ 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.
29
75
 
30
- From code:
76
+ ### ast
31
77
 
32
- ```ruby
33
- require "syntax_tree"
78
+ This command will print out a textual representation of the syntax tree associated with each of the files it finds. To execute, run:
79
+
80
+ ```sh
81
+ stree ast path/to/file.rb
82
+ ```
83
+
84
+ For a file that contains `1 + 1`, you will receive:
34
85
 
35
- pp SyntaxTree.parse(source) # print out the AST
36
- puts SyntaxTree.format(source) # format the AST
37
86
  ```
87
+ (program (statements (binary (int "1") + (int "1"))))
88
+ ```
89
+
90
+ ### check
38
91
 
39
- From the CLI:
92
+ 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.
40
93
 
41
94
  ```sh
42
- $ stree ast program.rb
43
- (program
44
- (statements
45
- ...
95
+ stree check path/to/file.rb
96
+ ```
97
+
98
+ For a file that matches the expected format, you will receive:
99
+
100
+ ```
101
+ All files matched expected format.
102
+ ```
103
+
104
+ If there are files with unformatted code, you will receive:
105
+
106
+ ```
107
+ [warn] path/to/file.rb
108
+ The listed files did not match the expected format.
46
109
  ```
47
110
 
48
- or
111
+ ### format
112
+
113
+ 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.
49
114
 
50
115
  ```sh
51
- $ stree format program.rb
52
- class MyClass
53
- ...
116
+ stree format path/to/file.rb
117
+ ```
118
+
119
+ For a file that contains `1 + 1`, you will receive:
120
+
121
+ ```ruby
122
+ 1 + 1
54
123
  ```
55
124
 
56
- or
125
+ ### write
126
+
127
+ 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.
57
128
 
58
129
  ```sh
59
- $ stree write program.rb
60
- program.rb 1ms
130
+ stree write path/to/file.rb
131
+ ```
132
+
133
+ 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.
134
+
135
+ ```
136
+ path/to/file.rb 0ms
137
+ ```
138
+
139
+ ## Library
140
+
141
+ Syntax Tree can be used as a library to access the syntax tree underlying Ruby source code.
142
+
143
+ ### SyntaxTree.read(filepath)
144
+
145
+ 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).
146
+
147
+ ### SyntaxTree.parse(source)
148
+
149
+ 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.
150
+
151
+ ### SyntaxTree.format(source)
152
+
153
+ This function takes an input string containing Ruby code, parses it into its underlying syntax tree, and formats it back out to a string.
154
+
155
+ ## Nodes
156
+
157
+ 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.
158
+
159
+ ### child_nodes
160
+
161
+ 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.
162
+
163
+ ```ruby
164
+ program = SyntaxTree.parse("1 + 1")
165
+ program.child_nodes.first.child_nodes.first
166
+ # => (binary (int "1") :+ (int "1"))
167
+ ```
168
+
169
+ ### Pattern matching
170
+
171
+ 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:
172
+
173
+ ```ruby
174
+ program = SyntaxTree.parse("1 + 1")
175
+ program => { statements: { body: [binary] } }
176
+ binary
177
+ # => (binary (int "1") :+ (int "1"))
61
178
  ```
62
179
 
63
- ## Development
180
+ Or, with more constraints on the types to ensure we're getting exactly what we expect:
64
181
 
65
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
182
+ ```ruby
183
+ program = SyntaxTree.parse("1 + 1")
184
+ program => SyntaxTree::Program[statements: SyntaxTree::Statements[body: [SyntaxTree::Binary => binary]]]
185
+ binary
186
+ # => (binary (int "1") :+ (int "1"))
187
+ ```
66
188
 
67
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
189
+ ### pretty_print(q)
190
+
191
+ 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:
192
+
193
+ ```ruby
194
+ pp SyntaxTree.parse("1 + 1")
195
+ # (program (statements (binary (int "1") + (int "1"))))
196
+ ```
197
+
198
+ ### to_json(*opts)
199
+
200
+ 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:
201
+
202
+ ```ruby
203
+ program = SyntaxTree.parse("1 + 1")
204
+ program => { statements: { body: [{ left: }] } }
205
+ puts JSON.dump(left)
206
+ # {"type":"int","value":"1","loc":[1,0,1,1],"cmts":[]}
207
+ ```
208
+
209
+ ### format(q)
210
+
211
+ 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:
212
+
213
+ ```ruby
214
+ source = "1+1"
215
+ program = SyntaxTree.parse(source)
216
+ program => { statements: { body: [binary] } }
217
+
218
+ formatter = SyntaxTree::Formatter.new(source, [])
219
+ binary.format(formatter)
220
+
221
+ formatter.flush
222
+ formatter.output.join
223
+ # => "1 + 1"
224
+ ```
225
+
226
+ ## Visitor
227
+
228
+ 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.
229
+
230
+ 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:
231
+
232
+ ```ruby
233
+ class ArithmeticVisitor < SyntaxTree::Visitor
234
+ def visit_binary(node)
235
+ if node in { left: SyntaxTree::Int, operator: :+ | :- | :* | :/, right: SyntaxTree::Int }
236
+ puts "The result is: #{node.left.value.to_i.public_send(node.operator, node.right.value.to_i)}"
237
+ end
238
+ end
239
+ end
240
+
241
+ visitor = ArithmeticVisitor.new
242
+ visitor.visit(SyntaxTree.parse("1 + 1"))
243
+ # The result is: 2
244
+ ```
245
+
246
+ 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:
247
+
248
+ * call `super` (which will do the default and visit all child nodes)
249
+ * call `visit_child_nodes` manually
250
+ * call `visit(child)` with each child that you want to visit
251
+ * call nothing if you're sure you don't want to descend further
252
+
253
+ 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.
254
+
255
+ ### visit_method
256
+
257
+ 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:
258
+
259
+ ```ruby
260
+ class ArithmeticVisitor < SyntaxTree::Visitor
261
+ visit_method def visit_binary(node)
262
+ # ...
263
+ end
264
+ end
265
+ ```
266
+
267
+ 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:
268
+
269
+ ```
270
+ ~/syntax_tree/lib/syntax_tree/visitor.rb:46:in `visit_method': Invalid visit method: visit_binar (SyntaxTree::Visitor::VisitMethodError)
271
+ Did you mean? visit_binary
272
+ visit_in
273
+ visit_ivar
274
+ from (irb):2:in `<class:ArithmeticVisitor>'
275
+ from (irb):1:in `<main>'
276
+ from bin/console:8:in `<main>'
277
+ ```
278
+
279
+ ## Language server
280
+
281
+ 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:
282
+
283
+ ```sh
284
+ stree lsp
285
+ ```
286
+
287
+ 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).
288
+
289
+ ### textDocument/formatting
290
+
291
+ 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.
292
+
293
+ ### textDocument/inlayHints
294
+
295
+ 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:
296
+
297
+ ```ruby
298
+ 1 + 2 * 3
299
+ ```
300
+
301
+ 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:
302
+
303
+ ```ruby
304
+ 1 + ₍2 * 3₎
305
+ ```
306
+
307
+ ### syntaxTree/visualizing
308
+
309
+ 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.
310
+
311
+ ## Plugins
312
+
313
+ You can register additional languages that can flow through the same CLI with Syntax Tree's plugin system. To register a new language, call:
314
+
315
+ ```ruby
316
+ SyntaxTree.register_handler(".mylang", MyLanguage)
317
+ ```
318
+
319
+ In this case, whenever the CLI encounters a filepath that ends with the given extension, it will invoke methods on `MyLanguage` instead of `SyntaxTree` itself. To make sure your object conforms to each of the necessary APIs, it should implement:
320
+
321
+ * `MyLanguage.read(filepath)` - usually this is just an alias to `File.read(filepath)`, but if you need anything else that hook is here.
322
+ * `MyLanguage.parse(source)` - this should return the syntax tree corresponding to the given source. Those objects should implement the `pretty_print` interface.
323
+ * `MyLanguage.format(source)` - this should return the formatted version of the given source.
324
+
325
+ Below are listed all of the "official" plugins hosted under the same GitHub organization, which can be used as references for how to implement other plugins.
326
+
327
+ * [SyntaxTree::Haml](https://github.com/ruby-syntax-tree/syntax_tree-haml) for the [Haml template language](https://haml.info/).
328
+ * [SyntaxTree::JSON](https://github.com/ruby-syntax-tree/syntax_tree-json) for JSON.
329
+ * [SyntaxTree::RBS](https://github.com/ruby-syntax-tree/syntax_tree-rbs) for the [RBS type language](https://github.com/ruby/rbs).
68
330
 
69
331
  ## Contributing
70
332
 
71
- Bug reports and pull requests are welcome on GitHub at https://github.com/kddnewton/syntax_tree. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/kddnewton/syntax_tree/blob/main/CODE_OF_CONDUCT.md).
333
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-syntax-tree/syntax_tree.
72
334
 
73
335
  ## License
74
336
 
75
337
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
76
-
77
- ## Code of Conduct
78
-
79
- 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).
@@ -2,17 +2,6 @@
2
2
 
3
3
  module SyntaxTree
4
4
  module CLI
5
- # This holds references to objects that respond to both #parse and #format
6
- # so that we can use them in the CLI.
7
- HANDLERS = {}
8
- HANDLERS.default = SyntaxTree
9
-
10
- # This is a hook provided so that plugins can register themselves as the
11
- # handler for a particular file type.
12
- def self.register_handler(extension, handler)
13
- HANDLERS[extension] = handler
14
- end
15
-
16
5
  # A utility wrapper around colored strings in the output.
17
6
  class Color
18
7
  attr_reader :value, :code
@@ -135,7 +124,7 @@ module SyntaxTree
135
124
  start = Time.now
136
125
 
137
126
  formatted = handler.format(source)
138
- File.write(filepath, formatted)
127
+ File.write(filepath, formatted) if filepath != :stdin
139
128
 
140
129
  color = source == formatted ? Color.gray(filepath) : filepath
141
130
  delta = ((Time.now - start) * 1000).round
@@ -202,11 +191,6 @@ module SyntaxTree
202
191
  return 0
203
192
  end
204
193
 
205
- if arguments.empty?
206
- warn(HELP)
207
- return 1
208
- end
209
-
210
194
  action =
211
195
  case name
212
196
  when "a", "ast"
@@ -226,6 +210,13 @@ module SyntaxTree
226
210
  return 1
227
211
  end
228
212
 
213
+ # If we're not reading from stdin and the user didn't supply and
214
+ # filepaths to be read, then we exit with the usage message.
215
+ if STDIN.tty? && arguments.empty?
216
+ warn(HELP)
217
+ return 1
218
+ end
219
+
229
220
  # If there are any plugins specified on the command line, then load them
230
221
  # by requiring them here. We do this by transforming something like
231
222
  #
@@ -235,40 +226,34 @@ module SyntaxTree
235
226
  #
236
227
  # require "syntax_tree/haml"
237
228
  #
238
- if arguments.first.start_with?("--plugins=")
229
+ if arguments.first&.start_with?("--plugins=")
239
230
  plugins = arguments.shift[/^--plugins=(.*)$/, 1]
240
231
  plugins.split(",").each { |plugin| require "syntax_tree/#{plugin}" }
241
232
  end
242
233
 
234
+ # Track whether or not there are any errors from any of the files that
235
+ # we take action on so that we can properly clean up and exit.
243
236
  errored = false
244
- arguments.each do |pattern|
245
- Dir.glob(pattern).each do |filepath|
246
- next unless File.file?(filepath)
247
-
248
- handler = HANDLERS[File.extname(filepath)]
249
- source = handler.read(filepath)
250
-
251
- begin
252
- action.run(handler, filepath, source)
253
- rescue Parser::ParseError => error
254
- warn("Error: #{error.message}")
255
-
256
- if error.lineno
257
- highlight_error(error, source)
258
- else
259
- warn(error.message)
260
- warn(error.backtrace)
261
- end
262
-
263
- errored = true
264
- rescue Check::UnformattedError, Debug::NonIdempotentFormatError
265
- errored = true
266
- rescue => error
267
- warn(error.message)
268
- warn(error.backtrace)
269
- errored = true
270
- end
237
+
238
+ each_file(arguments) do |handler, filepath, source|
239
+ action.run(handler, filepath, source)
240
+ rescue Parser::ParseError => error
241
+ warn("Error: #{error.message}")
242
+
243
+ if error.lineno
244
+ highlight_error(error, source)
245
+ else
246
+ warn(error.message)
247
+ warn(error.backtrace)
271
248
  end
249
+
250
+ errored = true
251
+ rescue Check::UnformattedError, Debug::NonIdempotentFormatError
252
+ errored = true
253
+ rescue => error
254
+ warn(error.message)
255
+ warn(error.backtrace)
256
+ errored = true
272
257
  end
273
258
 
274
259
  if errored
@@ -282,6 +267,22 @@ module SyntaxTree
282
267
 
283
268
  private
284
269
 
270
+ def each_file(arguments)
271
+ if STDIN.tty?
272
+ arguments.each do |pattern|
273
+ Dir.glob(pattern).each do |filepath|
274
+ next unless File.file?(filepath)
275
+
276
+ handler = HANDLERS[File.extname(filepath)]
277
+ source = handler.read(filepath)
278
+ yield handler, filepath, source
279
+ end
280
+ end
281
+ else
282
+ yield HANDLERS[".rb"], :stdin, STDIN.read
283
+ end
284
+ end
285
+
285
286
  # Highlights a snippet from a source and parse error.
286
287
  def highlight_error(error, source)
287
288
  lines = source.lines
@@ -27,20 +27,6 @@ module SyntaxTree
27
27
  after[location.start_char + "rescue".length] << " StandardError"
28
28
  end
29
29
 
30
- # Adds the implicitly referenced value (local variable or method call)
31
- # that is added into a hash when the value of a key-value pair is omitted.
32
- # For example,
33
- #
34
- # { value: }
35
- #
36
- # becomes
37
- #
38
- # { value: value }
39
- #
40
- def missing_hash_value(key, location)
41
- after[location.end_char] << " #{key}"
42
- end
43
-
44
30
  # Adds implicit parentheses around certain expressions to make it clear
45
31
  # which subexpression will be evaluated first. For example,
46
32
  #
@@ -69,8 +55,6 @@ module SyntaxTree
69
55
  case [parent_node, child_node]
70
56
  in _, Rescue[exception: nil, location:]
71
57
  inlay_hints.bare_rescue(location)
72
- in _, Assoc[key: Label[value: key], value: nil, location:]
73
- inlay_hints.missing_hash_value(key[0...-1], location)
74
58
  in Assign | Binary | IfOp | OpAssign, IfOp[location:]
75
59
  inlay_hints.precedence_parentheses(location)
76
60
  in Assign | OpAssign, Binary[location:]