syntax_tree 2.3.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5855cfed255d36a243d697083688e553ae02aa5373ec08af6bc730eea66ab995
4
- data.tar.gz: 15da5f28c3cff6953eb1204abbd42ac22b2c5ea78546242f543034360b39fb62
3
+ metadata.gz: 3b1505fca44f379d951132dafb8122a2591fe46607c8402d096bab67bd53d0a3
4
+ data.tar.gz: 0d58e60b6779ec1acd22ca776f3610f290c4b048cc1bf41b425f9c5a74e80db8
5
5
  SHA512:
6
- metadata.gz: 49bd5ad54611d46c9379e98f53c0ae789949d5104b225af51a54e7d4780e274a9a9bbcff1322c5af641dadd4c5d8d633b94b1271436ef1767bd9eb869dcb6be5
7
- data.tar.gz: 12cd7e5be2218aeaf9a0833dd95ce40be9d2ee248933962e3f24e854ab5335dd25b2a86b9f6ecb2c6a7e24759dcf316a9db0e8aca8841a5365e6c8030882ac76
6
+ metadata.gz: 42d4dea2571b21b3cf877a129f0a840f30125eb918c558b25b5bc303c915bb758b731151cdf2cfb6e70e92cbc4957d915095d1284312529675fec71f3a6af3a7
7
+ data.tar.gz: 5178b395ddcfdcf795c18f7c986e86a12f6ca2bdec871792de2c21220dffc7c2a2238a02926cece3021635f42859ab5084ba4dba21932ce7f1791c2a17a5bc49
@@ -24,9 +24,28 @@ jobs:
24
24
  ruby-version: ${{ matrix.ruby }}
25
25
  - name: Test
26
26
  run: bundle exec rake test
27
+
28
+ check:
29
+ name: Check
30
+ runs-on: ubuntu-latest
31
+ env:
32
+ CI: true
33
+ steps:
34
+ - uses: actions/checkout@master
35
+ - uses: ruby/setup-ruby@v1
36
+ with:
37
+ bundler-cache: true
38
+ ruby-version: '3.1'
39
+ - name: Check
40
+ run: |
41
+ bundle exec rake check
42
+ bundle exec rubocop
43
+
27
44
  automerge:
28
45
  name: AutoMerge
29
- needs: ci
46
+ needs:
47
+ - ci
48
+ - check
30
49
  runs-on: ubuntu-latest
31
50
  if: github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]'
32
51
  steps:
data/.rubocop.yml ADDED
@@ -0,0 +1,80 @@
1
+ inherit_from: config/rubocop.yml
2
+
3
+ AllCops:
4
+ DisplayCopNames: true
5
+ DisplayStyleGuide: true
6
+ NewCops: enable
7
+ SuggestExtensions: false
8
+ TargetRubyVersion: 2.7
9
+ Exclude:
10
+ - '{bin,coverage,pkg,test/fixtures,vendor,tmp}/**/*'
11
+ - test.rb
12
+
13
+ Layout/LineLength:
14
+ Max: 80
15
+
16
+ Lint/DuplicateBranch:
17
+ Enabled: false
18
+
19
+ Lint/EmptyBlock:
20
+ Enabled: false
21
+
22
+ Lint/InterpolationCheck:
23
+ Enabled: false
24
+
25
+ Lint/MissingSuper:
26
+ Enabled: false
27
+
28
+ Lint/UnusedMethodArgument:
29
+ AllowUnusedKeywordArguments: true
30
+
31
+ Metrics:
32
+ Enabled: false
33
+
34
+ Naming/MethodName:
35
+ Enabled: false
36
+
37
+ Naming/MethodParameterName:
38
+ Enabled: false
39
+
40
+ Naming/RescuedExceptionsVariableName:
41
+ PreferredName: error
42
+
43
+ Style/ExplicitBlockArgument:
44
+ Enabled: false
45
+
46
+ Style/FormatString:
47
+ EnforcedStyle: percent
48
+
49
+ Style/GuardClause:
50
+ Enabled: false
51
+
52
+ Style/IdenticalConditionalBranches:
53
+ Enabled: false
54
+
55
+ Style/IfInsideElse:
56
+ Enabled: false
57
+
58
+ Style/KeywordParametersOrder:
59
+ Enabled: false
60
+
61
+ Style/MissingRespondToMissing:
62
+ Enabled: false
63
+
64
+ Style/MutableConstant:
65
+ Enabled: false
66
+
67
+ Style/NegatedIfElseCondition:
68
+ Enabled: false
69
+
70
+ Style/NumericPredicate:
71
+ Enabled: false
72
+
73
+ Style/ParallelAssignment:
74
+ Enabled: false
75
+
76
+ Style/PerlBackrefs:
77
+ Enabled: false
78
+
79
+ Style/SpecialGlobalVars:
80
+ Enabled: false
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.5.0] - 2022-05-13
10
+
11
+ ### Added
12
+
13
+ - [#79](https://github.com/ruby-syntax-tree/syntax_tree/pull/79) - Support an optional `maxwidth` second argument to `SyntaxTree.format`.
14
+
15
+ ### Changed
16
+
17
+ - [#77](https://github.com/ruby-syntax-tree/syntax_tree/pull/77) - Correct the pattern for checking if a dynamic symbol can be converted into a label as a hash key.
18
+ - [#72](https://github.com/ruby-syntax-tree/syntax_tree/pull/72) - Disallow conditionals with `not` without parentheses in the predicate from turning into a ternary.
19
+
20
+ ## [2.4.1] - 2022-05-10
21
+
22
+ - [#73](https://github.com/ruby-syntax-tree/syntax_tree/pull/73) - Fix nested hash patterns from accidentally adding a `then` to their output.
23
+
24
+ ## [2.4.0] - 2022-05-07
25
+
26
+ ### Added
27
+
28
+ - [#65](https://github.com/ruby-syntax-tree/syntax_tree/pull/65) - Add a rubocop config at `config/rubocop.yml` that we can ship with the gem so folks can inherit from it to get their styling correct.
29
+ - [#65](https://github.com/ruby-syntax-tree/syntax_tree/pull/65) - Improve hash pattern formatting by a lot - multiple lines are now not so ugly.
30
+ - [#62](https://github.com/ruby-syntax-tree/syntax_tree/issues/62) - Add `options` as a method on `SyntaxTree::RegexpLiteral`, add it to pattern matching, and describe it using the `SyntaxTree::Visitor::FieldVisitor` class.
31
+ - [#69](https://github.com/ruby-syntax-tree/syntax_tree/pull/69) - The `construct_keys` option has been added to every `SyntaxTree::Node` descendant. This allows building a pattern match expression that can be used later. It is meant as a reflection API, not necessarily something that should be eval'd.
32
+ - [#69](https://github.com/ruby-syntax-tree/syntax_tree/pull/69) - You can now call `stree json` to get a JSON representation of your syntax tree.
33
+ - [#69](https://github.com/ruby-syntax-tree/syntax_tree/pull/69) - You can now call `stree match` to get a Ruby pattern matching expression to match against the given input.
34
+
35
+ ### Changed
36
+
37
+ - [#69](https://github.com/ruby-syntax-tree/syntax_tree/pull/69) - Fixed a long-standing bug with pretty-print where if certain things were required in different orders you could end up with a bug in `PP` when calling pretty-print with a confusing error referring to inspect keys.
38
+ - [#69](https://github.com/ruby-syntax-tree/syntax_tree/pull/69) - `SyntaxTree.read` can now handle an empty file.
39
+
9
40
  ## [2.3.1] - 2022-04-22
10
41
 
11
42
  ### Changed
@@ -193,7 +224,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
193
224
 
194
225
  - 🎉 Initial release! 🎉
195
226
 
196
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.3.0...HEAD
227
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.5.0...HEAD
228
+ [2.5.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.4.1...v2.5.0
229
+ [2.4.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.4.0...v2.4.1
230
+ [2.4.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.3.1...v2.4.0
231
+ [2.3.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.3.0...v2.3.1
197
232
  [2.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.2.0...v2.3.0
198
233
  [2.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.1...v2.2.0
199
234
  [2.1.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.0...v2.1.1
data/Gemfile.lock CHANGED
@@ -1,20 +1,42 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (2.3.1)
4
+ syntax_tree (2.5.0)
5
+ prettier_print
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
10
+ ast (2.4.2)
9
11
  docile (1.4.0)
10
12
  minitest (5.15.0)
13
+ parallel (1.22.1)
14
+ parser (3.1.2.0)
15
+ ast (~> 2.4.1)
16
+ prettier_print (0.1.0)
17
+ rainbow (3.1.1)
11
18
  rake (13.0.6)
19
+ regexp_parser (2.4.0)
20
+ rexml (3.2.5)
21
+ rubocop (1.29.1)
22
+ parallel (~> 1.10)
23
+ parser (>= 3.1.0.0)
24
+ rainbow (>= 2.2.2, < 4.0)
25
+ regexp_parser (>= 1.8, < 3.0)
26
+ rexml (>= 3.2.5, < 4.0)
27
+ rubocop-ast (>= 1.17.0, < 2.0)
28
+ ruby-progressbar (~> 1.7)
29
+ unicode-display_width (>= 1.4.0, < 3.0)
30
+ rubocop-ast (1.17.0)
31
+ parser (>= 3.1.1.0)
32
+ ruby-progressbar (1.11.0)
12
33
  simplecov (0.21.2)
13
34
  docile (~> 1.1)
14
35
  simplecov-html (~> 0.11)
15
36
  simplecov_json_formatter (~> 0.1)
16
37
  simplecov-html (0.12.3)
17
38
  simplecov_json_formatter (0.1.4)
39
+ unicode-display_width (2.1.0)
18
40
 
19
41
  PLATFORMS
20
42
  arm64-darwin-21
@@ -27,6 +49,7 @@ DEPENDENCIES
27
49
  bundler
28
50
  minitest
29
51
  rake
52
+ rubocop
30
53
  simplecov
31
54
  syntax_tree!
32
55
 
data/README.md CHANGED
@@ -16,6 +16,8 @@ It is built with only standard library dependencies. It additionally ships with
16
16
  - [ast](#ast)
17
17
  - [check](#check)
18
18
  - [format](#format)
19
+ - [json](#json)
20
+ - [match](#match)
19
21
  - [write](#write)
20
22
  - [Library](#library)
21
23
  - [SyntaxTree.read(filepath)](#syntaxtreereadfilepath)
@@ -27,6 +29,7 @@ It is built with only standard library dependencies. It additionally ships with
27
29
  - [pretty_print(q)](#pretty_printq)
28
30
  - [to_json(*opts)](#to_jsonopts)
29
31
  - [format(q)](#formatq)
32
+ - [construct_keys](#construct_keys)
30
33
  - [Visitor](#visitor)
31
34
  - [visit_method](#visit_method)
32
35
  - [Language server](#language-server)
@@ -34,6 +37,9 @@ It is built with only standard library dependencies. It additionally ships with
34
37
  - [textDocument/inlayHints](#textdocumentinlayhints)
35
38
  - [syntaxTree/visualizing](#syntaxtreevisualizing)
36
39
  - [Plugins](#plugins)
40
+ - [Integration](#integration)
41
+ - [RuboCop](#rubocop)
42
+ - [VSCode](#vscode)
37
43
  - [Contributing](#contributing)
38
44
  - [License](#license)
39
45
 
@@ -122,6 +128,73 @@ For a file that contains `1 + 1`, you will receive:
122
128
  1 + 1
123
129
  ```
124
130
 
131
+ ### json
132
+
133
+ This command will output a JSON representation of the syntax tree that is functionally equivalent to the input. This is mostly used in contexts where you need to access the tree from JavaScript or serialize it over a network.
134
+
135
+ ```sh
136
+ stree json path/to/file.rb
137
+ ```
138
+
139
+ For a file that contains `1 + 1`, you will receive:
140
+
141
+ ```json
142
+ {
143
+ "type": "program",
144
+ "location": [1, 0, 1, 6],
145
+ "statements": {
146
+ "type": "statements",
147
+ "location": [1, 0, 1, 6],
148
+ "body": [
149
+ {
150
+ "type": "binary",
151
+ "location": [1, 0, 1, 5],
152
+ "left": {
153
+ "type": "int",
154
+ "location": [1, 0, 1, 1],
155
+ "value": "1",
156
+ "comments": []
157
+ },
158
+ "operator": "+",
159
+ "right": {
160
+ "type": "int",
161
+ "location": [1, 4, 1, 5],
162
+ "value": "1",
163
+ "comments": []
164
+ },
165
+ "comments": []
166
+ }
167
+ ],
168
+ "comments": []
169
+ },
170
+ "comments": []
171
+ }
172
+ ```
173
+
174
+ ### match
175
+
176
+ This command will output a Ruby case-match expression that would match correctly against the input.
177
+
178
+ ```sh
179
+ stree match path/to/file.rb
180
+ ```
181
+
182
+ For a file that contains `1 + 1`, you will receive:
183
+
184
+ ```ruby
185
+ SyntaxTree::Program[
186
+ statements: SyntaxTree::Statements[
187
+ body: [
188
+ SyntaxTree::Binary[
189
+ left: SyntaxTree::Int[value: "1"],
190
+ operator: :+,
191
+ right: SyntaxTree::Int[value: "1"]
192
+ ]
193
+ ]
194
+ ]
195
+ ]
196
+ ```
197
+
125
198
  ### write
126
199
 
127
200
  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.
@@ -223,6 +296,27 @@ formatter.output.join
223
296
  # => "1 + 1"
224
297
  ```
225
298
 
299
+ ### construct_keys
300
+
301
+ Every node responds to `construct_keys`, which will return a string that contains a Ruby pattern-matching expression that could be used to match against the current node. It's meant to be used in tooling and through the CLI mostly.
302
+
303
+ ```ruby
304
+ program = SyntaxTree.parse("1 + 1")
305
+ puts program.construct_keys
306
+
307
+ # SyntaxTree::Program[
308
+ # statements: SyntaxTree::Statements[
309
+ # body: [
310
+ # SyntaxTree::Binary[
311
+ # left: SyntaxTree::Int[value: "1"],
312
+ # operator: :+,
313
+ # right: SyntaxTree::Int[value: "1"]
314
+ # ]
315
+ # ]
316
+ # ]
317
+ # ]
318
+ ```
319
+
226
320
  ## Visitor
227
321
 
228
322
  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.
@@ -310,7 +404,17 @@ The language server additionally includes this custom request to return a textua
310
404
 
311
405
  ## Plugins
312
406
 
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:
407
+ You can register additional configuration and additional languages that can flow through the same CLI with Syntax Tree's plugin system. When invoking the CLI, you pass through the list of plugins with the `--plugins` options to the commands that accept them. They should be a comma-delimited list. When the CLI first starts, it will require the files corresponding to those names.
408
+
409
+ ### Configuration
410
+
411
+ To register additional configuration, define a file somewhere in your load path named `syntax_tree/my_plugin` directory. Then when invoking the CLI, you will pass `--plugins=my_plugin`. That will get required. In this way, you can modify Syntax Tree however you would like. Some plugins ship with Syntax Tree itself. They are:
412
+
413
+ * `plugin/single_quotes` - This will change all of your string literals to use single quotes instead of the default double quotes.
414
+
415
+ ### Languages
416
+
417
+ To register a new language, call:
314
418
 
315
419
  ```ruby
316
420
  SyntaxTree.register_handler(".mylang", MyLanguage)
@@ -322,11 +426,28 @@ In this case, whenever the CLI encounters a filepath that ends with the given ex
322
426
  * `MyLanguage.parse(source)` - this should return the syntax tree corresponding to the given source. Those objects should implement the `pretty_print` interface.
323
427
  * `MyLanguage.format(source)` - this should return the formatted version of the given source.
324
428
 
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.
429
+ Below are listed all of the "official" language plugins hosted under the same GitHub organization, which can be used as references for how to implement other plugins.
430
+
431
+ * [haml](https://github.com/ruby-syntax-tree/syntax_tree-haml) for the [Haml template language](https://haml.info/).
432
+ * [json](https://github.com/ruby-syntax-tree/syntax_tree-json) for JSON.
433
+ * [rbs](https://github.com/ruby-syntax-tree/syntax_tree-rbs) for the [RBS type language](https://github.com/ruby/rbs).
434
+
435
+ ## Integration
436
+
437
+ 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
+
439
+ ### RuboCop
440
+
441
+ 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`:
442
+
443
+ ```yaml
444
+ inherit_gem:
445
+ syntax_tree: config/rubocop.yml
446
+ ```
447
+
448
+ ### VSCode
326
449
 
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).
450
+ To integrate Syntax Tree into VSCode, you should use the official VSCode extension [ruby-syntax-tree/vscode-syntax-tree](https://github.com/ruby-syntax-tree/vscode-syntax-tree).
330
451
 
331
452
  ## Contributing
332
453
 
data/Rakefile CHANGED
@@ -1,12 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
5
 
6
6
  Rake::TestTask.new(:test) do |t|
7
- t.libs << 'test'
8
- t.libs << 'lib'
9
- t.test_files = FileList['test/**/*_test.rb']
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
10
  end
11
11
 
12
12
  task default: :test
13
+
14
+ FILEPATHS = %w[
15
+ Gemfile
16
+ Rakefile
17
+ syntax_tree.gemspec
18
+ lib/**/*.rb
19
+ test/*.rb
20
+ ].freeze
21
+
22
+ task :syntax_tree do
23
+ $:.unshift File.expand_path("lib", __dir__)
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
@@ -0,0 +1,64 @@
1
+ # Disabling all Layout/* rules, as they're unnecessary when the user is using
2
+ # Syntax Tree to handle all of the formatting.
3
+ Layout:
4
+ Enabled: false
5
+
6
+ # Re-enable Layout/LineLength because certain cops that most projects use
7
+ # (e.g. Style/IfUnlessModifier) require Layout/LineLength to be enabled.
8
+ # By leaving it disabled, those rules will mis-fire.
9
+ #
10
+ # Users can always override these defaults in their own rubocop.yml files.
11
+ # https://github.com/prettier/plugin-ruby/issues/825
12
+ Layout/LineLength:
13
+ Enabled: true
14
+
15
+ Style/MultilineIfModifier:
16
+ Enabled: false
17
+
18
+ # Syntax Tree will expand empty methods to put the end keyword on the subsequent
19
+ # line to reduce git diff noise.
20
+ Style/EmptyMethod:
21
+ EnforcedStyle: expanded
22
+
23
+ # lambdas that are constructed with the lambda method call cannot be safely
24
+ # turned into lambda literals without removing a method call.
25
+ Style/Lambda:
26
+ Enabled: false
27
+
28
+ # When method chains with multiple blocks are chained together, rubocop will let
29
+ # them pass if they're using braces but not if they're using do and end
30
+ # keywords. Because we will break individual blocks down to using keywords if
31
+ # they are multiline, this conflicts with rubocop.
32
+ Style/MultilineBlockChain:
33
+ Enabled: false
34
+
35
+ # Syntax Tree by default uses double quotes, so changing the configuration here
36
+ # to match that.
37
+ Style/StringLiterals:
38
+ EnforcedStyle: double_quotes
39
+
40
+ Style/StringLiteralsInInterpolation:
41
+ EnforcedStyle: double_quotes
42
+
43
+ Style/QuotedSymbols:
44
+ EnforcedStyle: double_quotes
45
+
46
+ # We let users have a little more freedom with symbol and words arrays. If the
47
+ # user only has an individual item like ["value"] then we don't bother
48
+ # converting it because it ends up being just noise.
49
+ Style/SymbolArray:
50
+ Enabled: false
51
+
52
+ Style/WordArray:
53
+ Enabled: false
54
+
55
+ # We don't support trailing commas in Syntax Tree by default, so just turning
56
+ # these off for now.
57
+ Style/TrailingCommaInArguments:
58
+ Enabled: false
59
+
60
+ Style/TrailingCommaInArrayLiteral:
61
+ Enabled: false
62
+
63
+ Style/TrailingCommaInHashLiteral:
64
+ Enabled: false
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
+ # Syntax Tree ships with the `stree` CLI, which can be used to inspect and
5
+ # manipulate Ruby code. This module is responsible for powering that CLI.
4
6
  module CLI
5
7
  # A utility wrapper around colored strings in the output.
6
8
  class Color
@@ -46,7 +48,7 @@ module SyntaxTree
46
48
 
47
49
  # An action of the CLI that prints out the AST for the given source.
48
50
  class AST < Action
49
- def run(handler, filepath, source)
51
+ def run(handler, _filepath, source)
50
52
  pp handler.parse(source)
51
53
  end
52
54
  end
@@ -83,9 +85,7 @@ module SyntaxTree
83
85
  warning = "[#{Color.yellow("warn")}] #{filepath}"
84
86
  formatted = handler.format(source)
85
87
 
86
- if formatted != handler.format(formatted)
87
- raise NonIdempotentFormatError
88
- end
88
+ raise NonIdempotentFormatError if formatted != handler.format(formatted)
89
89
  rescue StandardError
90
90
  warn(warning)
91
91
  raise
@@ -102,8 +102,8 @@ module SyntaxTree
102
102
 
103
103
  # An action of the CLI that prints out the doc tree IR for the given source.
104
104
  class Doc < Action
105
- def run(handler, filepath, source)
106
- formatter = Formatter.new([])
105
+ def run(handler, _filepath, source)
106
+ formatter = Formatter.new(source, [])
107
107
  handler.parse(source).format(formatter)
108
108
  pp formatter.groups.first
109
109
  end
@@ -111,11 +111,28 @@ module SyntaxTree
111
111
 
112
112
  # An action of the CLI that formats the input source and prints it out.
113
113
  class Format < Action
114
- def run(handler, filepath, source)
114
+ def run(handler, _filepath, source)
115
115
  puts handler.format(source)
116
116
  end
117
117
  end
118
118
 
119
+ # An action of the CLI that converts the source into its equivalent JSON
120
+ # representation.
121
+ class Json < Action
122
+ def run(handler, _filepath, source)
123
+ object = Visitor::JSONVisitor.new.visit(handler.parse(source))
124
+ puts JSON.pretty_generate(object)
125
+ end
126
+ end
127
+
128
+ # An action of the CLI that outputs a pattern-matching Ruby expression that
129
+ # would match the input given.
130
+ class Match < Action
131
+ def run(handler, _filepath, source)
132
+ puts handler.parse(source).construct_keys
133
+ end
134
+ end
135
+
119
136
  # An action of the CLI that formats the input source and writes the
120
137
  # formatted output back to the file.
121
138
  class Write < Action
@@ -154,6 +171,12 @@ module SyntaxTree
154
171
  #{Color.bold("stree format [OPTIONS] [FILE]")}
155
172
  Print out the formatted version of the given files
156
173
 
174
+ #{Color.bold("stree json [OPTIONS] [FILE]")}
175
+ Print out the JSON representation of the given files
176
+
177
+ #{Color.bold("stree match [OPTIONS] [FILE]")}
178
+ Print out a pattern-matching Ruby expression that would match the given files
179
+
157
180
  #{Color.bold("stree help")}
158
181
  Display this help message
159
182
 
@@ -201,6 +224,10 @@ module SyntaxTree
201
224
  Debug.new
202
225
  when "doc"
203
226
  Doc.new
227
+ when "j", "json"
228
+ Json.new
229
+ when "m", "match"
230
+ Match.new
204
231
  when "f", "format"
205
232
  Format.new
206
233
  when "w", "write"
@@ -212,7 +239,7 @@ module SyntaxTree
212
239
 
213
240
  # If we're not reading from stdin and the user didn't supply and
214
241
  # filepaths to be read, then we exit with the usage message.
215
- if STDIN.tty? && arguments.empty?
242
+ if $stdin.tty? && arguments.empty?
216
243
  warn(HELP)
217
244
  return 1
218
245
  end
@@ -239,18 +266,11 @@ module SyntaxTree
239
266
  action.run(handler, filepath, source)
240
267
  rescue Parser::ParseError => error
241
268
  warn("Error: #{error.message}")
242
-
243
- if error.lineno
244
- highlight_error(error, source)
245
- else
246
- warn(error.message)
247
- warn(error.backtrace)
248
- end
249
-
269
+ highlight_error(error, source)
250
270
  errored = true
251
271
  rescue Check::UnformattedError, Debug::NonIdempotentFormatError
252
272
  errored = true
253
- rescue => error
273
+ rescue StandardError => error
254
274
  warn(error.message)
255
275
  warn(error.backtrace)
256
276
  errored = true
@@ -268,18 +288,20 @@ module SyntaxTree
268
288
  private
269
289
 
270
290
  def each_file(arguments)
271
- if STDIN.tty?
291
+ if $stdin.tty? || arguments.any?
272
292
  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
293
+ Dir
294
+ .glob(pattern)
295
+ .each do |filepath|
296
+ next unless File.file?(filepath)
297
+
298
+ handler = HANDLERS[File.extname(filepath)]
299
+ source = handler.read(filepath)
300
+ yield handler, filepath, source
301
+ end
280
302
  end
281
303
  else
282
- yield HANDLERS[".rb"], :stdin, STDIN.read
304
+ yield HANDLERS[".rb"], :stdin, $stdin.read
283
305
  end
284
306
  end
285
307
 
@@ -310,7 +332,21 @@ module SyntaxTree
310
332
  # Take a line of Ruby source and colorize the output.
311
333
  def colorize_line(line)
312
334
  require "irb"
313
- IRB::Color.colorize_code(line, complete: false, ignore_error: true)
335
+ IRB::Color.colorize_code(line, **colorize_options)
336
+ end
337
+
338
+ # These are the options we're going to pass into IRB::Color.colorize_code.
339
+ # Since we support multiple versions of IRB, we're going to need to do
340
+ # some reflection to make sure we always pass valid options.
341
+ def colorize_options
342
+ options = { complete: false }
343
+
344
+ parameters = IRB::Color.method(:colorize_code).parameters
345
+ if parameters.any? { |(_type, name)| name == :ignore_error }
346
+ options[:ignore_error] = true
347
+ end
348
+
349
+ options
314
350
  end
315
351
  end
316
352
  end