syntax_tree 3.2.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e903c6474622266f71a8116d7a94a5a4cc437bb16bb7543f7c7de4d23ce15764
4
- data.tar.gz: 33dd3eec6312d18285d7e1f5e04048fc0ac36dfb7fe51295ead1357f9d19c8e4
3
+ metadata.gz: f4410889ecf31d320edb7e45b5491c97ec1af42f782e486cf946cad4672ee5cd
4
+ data.tar.gz: c81d09784f746b0b115af55409196a640d17d972a91e7d116a45b9b69bcb7c14
5
5
  SHA512:
6
- metadata.gz: 6cd1ea0a06ad365c1f7a24c0eefe0df70a7995d173c82c60fb3ec3a06c794960f7cabbbd6521c2b44b583227ed3cd27391ffbb0f8dd4812a4493606386b4355a
7
- data.tar.gz: e42148794773a87f2dc862ff3ef346817074e6f67381cbaf248c53bbc3258d9062a073b5e5c3516ab21646276bc669adadf177d6879d862526d53d291c7be2cd
6
+ metadata.gz: 1c6badd15f529d426d771679f3e8ebbd690a705f66027caa6510bf01c2788b3968fd1c3523bfa3534d3dd048604b968c8c01059e5a2c6d2148130aebd77d8530
7
+ data.tar.gz: e4567e0f5259ceb1b840f4c10c50fac526e972932e22340a5456fc1f394872c04b5f5651b63859bff40fc4e40da19e9040db6c51a70b6beb27559fcbe1597987
@@ -0,0 +1,22 @@
1
+ name: Dependabot auto-merge
2
+ on: pull_request
3
+
4
+ permissions:
5
+ contents: write
6
+ pull-requests: write
7
+
8
+ jobs:
9
+ dependabot:
10
+ runs-on: ubuntu-latest
11
+ if: ${{ github.actor == 'dependabot[bot]' }}
12
+ steps:
13
+ - name: Dependabot metadata
14
+ id: metadata
15
+ uses: dependabot/fetch-metadata@v1.3.3
16
+ with:
17
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
18
+ - name: Enable auto-merge for Dependabot PRs
19
+ run: gh pr merge --auto --merge "$PR_URL"
20
+ env:
21
+ PR_URL: ${{github.event.pull_request.html_url}}
22
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
@@ -1,14 +1,14 @@
1
1
  name: Main
2
2
  on:
3
3
  - push
4
- - pull_request_target
4
+ - pull_request
5
5
  jobs:
6
6
  ci:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  ruby:
11
- - '2.7'
11
+ - '2.7.0'
12
12
  - '3.0'
13
13
  - '3.1'
14
14
  - head
@@ -40,20 +40,3 @@ jobs:
40
40
  run: |
41
41
  bundle exec rake stree:check
42
42
  bundle exec rubocop
43
-
44
- automerge:
45
- name: AutoMerge
46
- needs:
47
- - ci
48
- - check
49
- runs-on: ubuntu-latest
50
- if: github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]'
51
- steps:
52
- - uses: actions/github-script@v3
53
- with:
54
- script: |
55
- github.pulls.merge({
56
- owner: context.payload.repository.owner.login,
57
- repo: context.payload.repository.name,
58
- pull_number: context.payload.pull_request.number
59
- })
data/.rubocop.yml CHANGED
@@ -44,7 +44,7 @@ Style/ExplicitBlockArgument:
44
44
  Enabled: false
45
45
 
46
46
  Style/FormatString:
47
- EnforcedStyle: percent
47
+ Enabled: false
48
48
 
49
49
  Style/GuardClause:
50
50
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -6,6 +6,38 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.5.0] - 2022-08-26
10
+
11
+ ### Added
12
+
13
+ - [#148](https://github.com/ruby-syntax-tree/syntax_tree/pull/148) - Support Ruby 2.7.0 (previously we only supported back to 2.7.3).
14
+ - [#152](https://github.com/ruby-syntax-tree/syntax_tree/pull/152) - Support the `-e` inline script option for the `stree` CLI.
15
+
16
+ ### Changed
17
+
18
+ - [#141](https://github.com/ruby-syntax-tree/syntax_tree/pull/141) - Use `q.format` for `SyntaxTree.format` so that the main node gets pushed onto the stack for checking parent nodes.
19
+ - [#147](https://github.com/ruby-syntax-tree/syntax_tree/pull/147) - Fix rightward assignment token management such that `in` and `=>` stay the same regardless of their context.
20
+
21
+ ## [3.4.0] - 2022-08-19
22
+
23
+ ### Added
24
+
25
+ - [#127](https://github.com/ruby-syntax-tree/syntax_tree/pull/127) - Allow the language server to handle other file extensions if it is activated for those extensions.
26
+ - [#133](https://github.com/ruby-syntax-tree/syntax_tree/pull/133) - Add documentation on supporting vim and neovim.
27
+
28
+ ### Changed
29
+
30
+ - [#132](https://github.com/ruby-syntax-tree/syntax_tree/pull/132) - Provide better error messages when end quotes and end keywords are missing from tokens.
31
+ - [#134](https://github.com/ruby-syntax-tree/syntax_tree/pull/134) - Ensure the correct `end` keyword is getting removed by `begin..rescue` clauses.
32
+ - [#137](https://github.com/ruby-syntax-tree/syntax_tree/pull/137) - Better support regular expressions with no ending token.
33
+
34
+ ## [3.3.0] - 2022-08-02
35
+
36
+ ### Added
37
+
38
+ - [#123](https://github.com/ruby-syntax-tree/syntax_tree/pull/123) - Allow the rake tasks to configure print width.
39
+ - [#125](https://github.com/ruby-syntax-tree/syntax_tree/pull/125) - Add support for an `.streerc` file in the current working directory to configure the CLI.
40
+
9
41
  ## [3.2.1] - 2022-07-22
10
42
 
11
43
  ### Changed
@@ -312,7 +344,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
312
344
 
313
345
  - 🎉 Initial release! 🎉
314
346
 
315
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.2.1...HEAD
347
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.5.0...HEAD
348
+ [3.5.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.4.0...v3.5.0
349
+ [3.4.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.3.0...v3.4.0
350
+ [3.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.2.1...v3.3.0
316
351
  [3.2.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.2.0...v3.2.1
317
352
  [3.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.1.0...v3.2.0
318
353
  [3.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.0.1...v3.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (3.2.1)
4
+ syntax_tree (3.5.0)
5
5
  prettier_print
6
6
 
7
7
  GEM
@@ -10,26 +10,26 @@ GEM
10
10
  ast (2.4.2)
11
11
  docile (1.4.0)
12
12
  json (2.6.2)
13
- minitest (5.16.2)
13
+ minitest (5.16.3)
14
14
  parallel (1.22.1)
15
- parser (3.1.2.0)
15
+ parser (3.1.2.1)
16
16
  ast (~> 2.4.1)
17
17
  prettier_print (0.1.0)
18
18
  rainbow (3.1.1)
19
19
  rake (13.0.6)
20
20
  regexp_parser (2.5.0)
21
21
  rexml (3.2.5)
22
- rubocop (1.32.0)
22
+ rubocop (1.35.1)
23
23
  json (~> 2.3)
24
24
  parallel (~> 1.10)
25
- parser (>= 3.1.0.0)
25
+ parser (>= 3.1.2.1)
26
26
  rainbow (>= 2.2.2, < 4.0)
27
27
  regexp_parser (>= 1.8, < 3.0)
28
28
  rexml (>= 3.2.5, < 4.0)
29
- rubocop-ast (>= 1.19.1, < 2.0)
29
+ rubocop-ast (>= 1.20.1, < 2.0)
30
30
  ruby-progressbar (~> 1.7)
31
31
  unicode-display_width (>= 1.4.0, < 3.0)
32
- rubocop-ast (1.19.1)
32
+ rubocop-ast (1.21.0)
33
33
  parser (>= 3.1.1.0)
34
34
  ruby-progressbar (1.11.0)
35
35
  simplecov (0.21.2)
data/README.md CHANGED
@@ -19,6 +19,8 @@ It is built with only standard library dependencies. It additionally ships with
19
19
  - [json](#json)
20
20
  - [match](#match)
21
21
  - [write](#write)
22
+ - [Configuration](#configuration)
23
+ - [Globbing](#globbing)
22
24
  - [Library](#library)
23
25
  - [SyntaxTree.read(filepath)](#syntaxtreereadfilepath)
24
26
  - [SyntaxTree.parse(source)](#syntaxtreeparsesource)
@@ -43,7 +45,7 @@ It is built with only standard library dependencies. It additionally ships with
43
45
  - [Integration](#integration)
44
46
  - [Rake](#rake)
45
47
  - [RuboCop](#rubocop)
46
- - [VSCode](#vscode)
48
+ - [Editors](#editors)
47
49
  - [Contributing](#contributing)
48
50
  - [License](#license)
49
51
 
@@ -231,6 +233,37 @@ To change the print width that you are writing with, specify the `--print-width`
231
233
  stree write --print-width=100 path/to/file.rb
232
234
  ```
233
235
 
236
+ ### Configuration
237
+
238
+ Any of the above CLI commands can also read configuration options from a `.streerc` file in the directory where the commands are executed.
239
+
240
+ This should be a text file with each argument on a separate line.
241
+
242
+ ```txt
243
+ --print-width=100
244
+ --plugins=plugin/trailing_comma
245
+ ```
246
+
247
+ If this file is present, it will _always_ be used for CLI commands. You can also pass options from the command line as in the examples above. The options in the `.streerc` file are passed to the CLI first, then the arguments from the command line. In the case of exclusive options (e.g. `--print-width`), this means that the command line options override what's in the config file. In the case of options that can take multiple inputs (e.g. `--plugins`), the effect is additive. That is, the plugins passed from the command line will be loaded _in addition to_ the plugins in the config file.
248
+
249
+ ### Globbing
250
+
251
+ When running commands with `stree`, it's common to pass in lists of files. For example:
252
+
253
+ ```sh
254
+ stree write 'lib/*.rb' 'test/*.rb'
255
+ ```
256
+
257
+ The commands in the CLI accept any number of arguments. This means you _could_ pass `**/*.rb` (note the lack of quotes). This would make your shell expand out the file paths listed according to its own rules. (For example, [here](https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html) are the rules for GNU bash.)
258
+
259
+ However, it's recommended to instead use quotes, which means that Ruby is responsible for performing the file path expansion instead. This ensures a consistent experience across different environments and shells. The globs must follow the Ruby-specific globbing syntax as specified in the documentation for [Dir](https://ruby-doc.org/core-3.1.1/Dir.html#method-c-glob).
260
+
261
+ Baked into this syntax is the ability to provide exceptions to file name patterns as well. For example, if you are in a Rails app and want to exclude files named `schema.rb` but write all other Ruby files, you can use the following syntax:
262
+
263
+ ```shell
264
+ stree write "**/{[!schema]*,*}.rb"
265
+ ```
266
+
234
267
  ## Library
235
268
 
236
269
  Syntax Tree can be used as a library to access the syntax tree underlying Ruby source code.
@@ -505,6 +538,16 @@ SyntaxTree::Rake::WriteTask.new do |t|
505
538
  end
506
539
  ```
507
540
 
541
+ #### `print_width`
542
+
543
+ If you want to use a different print width from the default (80), you can pass that to the `print_width` field, as in:
544
+
545
+ ```ruby
546
+ SyntaxTree::Rake::WriteTask.new do |t|
547
+ t.print_width = 100
548
+ end
549
+ ```
550
+
508
551
  #### `plugins`
509
552
 
510
553
  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:
@@ -524,9 +567,11 @@ inherit_gem:
524
567
  syntax_tree: config/rubocop.yml
525
568
  ```
526
569
 
527
- ### VSCode
570
+ ### Editors
528
571
 
529
- 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).
572
+ * [Neovim](https://neovim.io/) - [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig).
573
+ * [Vim](https://www.vim.org/) - [dense-analysis/ale](https://github.com/dense-analysis/ale).
574
+ * [VSCode](https://code.visualstudio.com/) - [ruby-syntax-tree/vscode-syntax-tree](https://github.com/ruby-syntax-tree/vscode-syntax-tree).
530
575
 
531
576
  ## Contributing
532
577
 
data/Rakefile CHANGED
@@ -12,8 +12,17 @@ end
12
12
 
13
13
  task default: :test
14
14
 
15
- SOURCE_FILES =
16
- FileList[%w[Gemfile Rakefile syntax_tree.gemspec lib/**/*.rb test/*.rb]]
15
+ configure = ->(task) do
16
+ task.source_files =
17
+ FileList[%w[Gemfile Rakefile syntax_tree.gemspec lib/**/*.rb test/*.rb]]
17
18
 
18
- SyntaxTree::Rake::CheckTask.new { |t| t.source_files = SOURCE_FILES }
19
- SyntaxTree::Rake::WriteTask.new { |t| t.source_files = SOURCE_FILES }
19
+ # Since Syntax Tree supports back to Ruby 2.7.0, we need to make sure that we
20
+ # format our code such that it's compatible with that version. This actually
21
+ # has very little effect on the output, the only change at the moment is that
22
+ # Ruby < 2.7.3 didn't allow a newline before the closing brace of a hash
23
+ # pattern.
24
+ task.target_ruby_version = Gem::Version.new("2.7.0")
25
+ end
26
+
27
+ SyntaxTree::Rake::CheckTask.new(&configure)
28
+ SyntaxTree::Rake::WriteTask.new(&configure)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "optparse"
4
+
3
5
  module SyntaxTree
4
6
  # Syntax Tree ships with the `stree` CLI, which can be used to inspect and
5
7
  # manipulate Ruby code. This module is responsible for powering that CLI.
@@ -51,23 +53,34 @@ module SyntaxTree
51
53
  end
52
54
  end
53
55
 
54
- # An item of work that corresponds to the stdin content.
55
- class STDINItem
56
+ # An item of work that corresponds to a script content passed via the
57
+ # command line.
58
+ class ScriptItem
59
+ FILEPATH = :script
60
+
61
+ attr_reader :source
62
+
63
+ def initialize(source)
64
+ @source = source
65
+ end
66
+
56
67
  def handler
57
68
  HANDLERS[".rb"]
58
69
  end
59
70
 
60
71
  def filepath
61
- :stdin
62
- end
63
-
64
- def source
65
- $stdin.read
72
+ FILEPATH
66
73
  end
67
74
  end
68
75
 
69
76
  # The parent action class for the CLI that implements the basics.
70
77
  class Action
78
+ attr_reader :options
79
+
80
+ def initialize(options)
81
+ @options = options
82
+ end
83
+
71
84
  def run(item)
72
85
  end
73
86
 
@@ -91,15 +104,9 @@ module SyntaxTree
91
104
  class UnformattedError < StandardError
92
105
  end
93
106
 
94
- attr_reader :print_width
95
-
96
- def initialize(print_width:)
97
- @print_width = print_width
98
- end
99
-
100
107
  def run(item)
101
108
  source = item.source
102
- if source != item.handler.format(source, print_width)
109
+ if source != item.handler.format(source, options.print_width)
103
110
  raise UnformattedError
104
111
  end
105
112
  rescue StandardError
@@ -122,19 +129,13 @@ module SyntaxTree
122
129
  class NonIdempotentFormatError < StandardError
123
130
  end
124
131
 
125
- attr_reader :print_width
126
-
127
- def initialize(print_width:)
128
- @print_width = print_width
129
- end
130
-
131
132
  def run(item)
132
133
  handler = item.handler
133
134
 
134
135
  warning = "[#{Color.yellow("warn")}] #{item.filepath}"
135
- formatted = handler.format(item.source, print_width)
136
+ formatted = handler.format(item.source, options.print_width)
136
137
 
137
- if formatted != handler.format(formatted, print_width)
138
+ if formatted != handler.format(formatted, options.print_width)
138
139
  raise NonIdempotentFormatError
139
140
  end
140
141
  rescue StandardError
@@ -164,14 +165,8 @@ module SyntaxTree
164
165
 
165
166
  # An action of the CLI that formats the input source and prints it out.
166
167
  class Format < Action
167
- attr_reader :print_width
168
-
169
- def initialize(print_width:)
170
- @print_width = print_width
171
- end
172
-
173
168
  def run(item)
174
- puts item.handler.format(item.source, print_width)
169
+ puts item.handler.format(item.source, options.print_width)
175
170
  end
176
171
  end
177
172
 
@@ -195,19 +190,13 @@ module SyntaxTree
195
190
  # An action of the CLI that formats the input source and writes the
196
191
  # formatted output back to the file.
197
192
  class Write < Action
198
- attr_reader :print_width
199
-
200
- def initialize(print_width:)
201
- @print_width = print_width
202
- end
203
-
204
193
  def run(item)
205
194
  filepath = item.filepath
206
195
  start = Time.now
207
196
 
208
197
  source = item.source
209
- formatted = item.handler.format(source, print_width)
210
- File.write(filepath, formatted) if filepath != :stdin
198
+ formatted = item.handler.format(source, options.print_width)
199
+ File.write(filepath, formatted) if item.filepath != :script
211
200
 
212
201
  color = source == formatted ? Color.gray(filepath) : filepath
213
202
  delta = ((Time.now - start) * 1000).round
@@ -222,25 +211,25 @@ module SyntaxTree
222
211
  # The help message displayed if the input arguments are not correctly
223
212
  # ordered or formatted.
224
213
  HELP = <<~HELP
225
- #{Color.bold("stree ast [--plugins=...] [--print-width=NUMBER] FILE")}
214
+ #{Color.bold("stree ast [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
226
215
  Print out the AST corresponding to the given files
227
216
 
228
- #{Color.bold("stree check [--plugins=...] [--print-width=NUMBER] FILE")}
217
+ #{Color.bold("stree check [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
229
218
  Check that the given files are formatted as syntax tree would format them
230
219
 
231
- #{Color.bold("stree debug [--plugins=...] [--print-width=NUMBER] FILE")}
220
+ #{Color.bold("stree debug [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
232
221
  Check that the given files can be formatted idempotently
233
222
 
234
- #{Color.bold("stree doc [--plugins=...] FILE")}
223
+ #{Color.bold("stree doc [--plugins=...] [-e SCRIPT] FILE")}
235
224
  Print out the doc tree that would be used to format the given files
236
225
 
237
- #{Color.bold("stree format [--plugins=...] [--print-width=NUMBER] FILE")}
226
+ #{Color.bold("stree format [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
238
227
  Print out the formatted version of the given files
239
228
 
240
- #{Color.bold("stree json [--plugins=...] FILE")}
229
+ #{Color.bold("stree json [--plugins=...] [-e SCRIPT] FILE")}
241
230
  Print out the JSON representation of the given files
242
231
 
243
- #{Color.bold("stree match [--plugins=...] FILE")}
232
+ #{Color.bold("stree match [--plugins=...] [-e SCRIPT] FILE")}
244
233
  Print out a pattern-matching Ruby expression that would match the given files
245
234
 
246
235
  #{Color.bold("stree help")}
@@ -252,7 +241,7 @@ module SyntaxTree
252
241
  #{Color.bold("stree version")}
253
242
  Output the current version of syntax tree
254
243
 
255
- #{Color.bold("stree write [--plugins=...] [--print-width=NUMBER] FILE")}
244
+ #{Color.bold("stree write [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
256
245
  Read, format, and write back the source of the given files
257
246
 
258
247
  --plugins=...
@@ -260,71 +249,138 @@ module SyntaxTree
260
249
 
261
250
  --print-width=NUMBER
262
251
  The maximum line width to use when formatting.
252
+
253
+ -e SCRIPT
254
+ Parse an inline Ruby string.
263
255
  HELP
264
256
 
257
+ # This represents all of the options that can be passed to the CLI. It is
258
+ # responsible for parsing the list and then returning the file paths at the
259
+ # end.
260
+ class Options
261
+ attr_reader :plugins, :print_width, :scripts, :target_ruby_version
262
+
263
+ def initialize(print_width: DEFAULT_PRINT_WIDTH)
264
+ @plugins = []
265
+ @print_width = print_width
266
+ @scripts = []
267
+ @target_ruby_version = nil
268
+ end
269
+
270
+ # TODO: This function causes a couple of side-effects that I really don't
271
+ # like to have here. It mutates the global state by requiring the plugins,
272
+ # and mutates the global options hash by adding the target ruby version.
273
+ # That should be done on a config-by-config basis, not here.
274
+ def parse(arguments)
275
+ parser.parse!(arguments)
276
+ end
277
+
278
+ private
279
+
280
+ def parser
281
+ OptionParser.new do |opts|
282
+ # If there are any plugins specified on the command line, then load
283
+ # them by requiring them here. We do this by transforming something
284
+ # like
285
+ #
286
+ # stree format --plugins=haml template.haml
287
+ #
288
+ # into
289
+ #
290
+ # require "syntax_tree/haml"
291
+ #
292
+ opts.on("--plugins=PLUGINS") do |plugins|
293
+ @plugins = plugins.split(",")
294
+ @plugins.each { |plugin| require "syntax_tree/#{plugin}" }
295
+ end
296
+
297
+ # If there is a print width specified on the command line, then
298
+ # parse that out here and use it when formatting.
299
+ opts.on("--print-width=NUMBER", Integer) do |print_width|
300
+ @print_width = print_width
301
+ end
302
+
303
+ # If there is a script specified on the command line, then parse
304
+ # it and add it to the list of scripts to run.
305
+ opts.on("-e SCRIPT") { |script| @scripts << script }
306
+
307
+ # If there is a target ruby version specified on the command line,
308
+ # parse that out and use it when formatting.
309
+ opts.on("--target-ruby-version=VERSION") do |version|
310
+ @target_ruby_version = Gem::Version.new(version)
311
+ Formatter::OPTIONS[:target_ruby_version] = @target_ruby_version
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ # We allow a minimal configuration file to act as additional command line
318
+ # arguments to the CLI. Each line of the config file should be a new
319
+ # argument, as in:
320
+ #
321
+ # --plugins=plugin/single_quote
322
+ # --print-width=100
323
+ #
324
+ # When invoking the CLI, we will read this config file and then parse it if
325
+ # it exists in the current working directory.
326
+ class ConfigFile
327
+ FILENAME = ".streerc"
328
+
329
+ attr_reader :filepath
330
+
331
+ def initialize
332
+ @filepath = File.join(Dir.pwd, FILENAME)
333
+ end
334
+
335
+ def exists?
336
+ File.readable?(filepath)
337
+ end
338
+
339
+ def arguments
340
+ exists? ? File.readlines(filepath, chomp: true) : []
341
+ end
342
+ end
343
+
265
344
  class << self
266
345
  # Run the CLI over the given array of strings that make up the arguments
267
346
  # passed to the invocation.
268
347
  def run(argv)
269
348
  name, *arguments = argv
270
- print_width = DEFAULT_PRINT_WIDTH
271
-
272
- while arguments.first&.start_with?("--")
273
- case (argument = arguments.shift)
274
- when /^--plugins=(.+)$/
275
- # If there are any plugins specified on the command line, then load
276
- # them by requiring them here. We do this by transforming something
277
- # like
278
- #
279
- # stree format --plugins=haml template.haml
280
- #
281
- # into
282
- #
283
- # require "syntax_tree/haml"
284
- #
285
- $1.split(",").each { |plugin| require "syntax_tree/#{plugin}" }
286
- when /^--print-width=(\d+)$/
287
- # If there is a print width specified on the command line, then
288
- # parse that out here and use it when formatting.
289
- print_width = Integer($1)
290
- else
291
- warn("Unknown CLI option: #{argument}")
292
- warn(HELP)
293
- return 1
294
- end
295
- end
296
349
 
297
- case name
298
- when "help"
299
- puts HELP
300
- return 0
301
- when "lsp"
302
- require "syntax_tree/language_server"
303
- LanguageServer.new(print_width: print_width).run
304
- return 0
305
- when "version"
306
- puts SyntaxTree::VERSION
307
- return 0
308
- end
350
+ config_file = ConfigFile.new
351
+ arguments.unshift(*config_file.arguments)
352
+
353
+ options = Options.new
354
+ options.parse(arguments)
309
355
 
310
356
  action =
311
357
  case name
312
358
  when "a", "ast"
313
- AST.new
359
+ AST.new(options)
314
360
  when "c", "check"
315
- Check.new(print_width: print_width)
361
+ Check.new(options)
316
362
  when "debug"
317
- Debug.new(print_width: print_width)
363
+ Debug.new(options)
318
364
  when "doc"
319
- Doc.new
365
+ Doc.new(options)
366
+ when "help"
367
+ puts HELP
368
+ return 0
320
369
  when "j", "json"
321
- Json.new
370
+ Json.new(options)
371
+ when "lsp"
372
+ require "syntax_tree/language_server"
373
+ LanguageServer.new(print_width: options.print_width).run
374
+ return 0
322
375
  when "m", "match"
323
- Match.new
376
+ Match.new(options)
324
377
  when "f", "format"
325
- Format.new(print_width: print_width)
378
+ Format.new(options)
379
+ when "version"
380
+ puts SyntaxTree::VERSION
381
+ return 0
326
382
  when "w", "write"
327
- Write.new(print_width: print_width)
383
+ Write.new(options)
328
384
  else
329
385
  warn(HELP)
330
386
  return 1
@@ -332,7 +388,7 @@ module SyntaxTree
332
388
 
333
389
  # If we're not reading from stdin and the user didn't supply and
334
390
  # filepaths to be read, then we exit with the usage message.
335
- if $stdin.tty? && arguments.empty?
391
+ if $stdin.tty? && arguments.empty? && options.scripts.empty?
336
392
  warn(HELP)
337
393
  return 1
338
394
  end
@@ -342,7 +398,7 @@ module SyntaxTree
342
398
 
343
399
  # If we're reading from stdin, then we'll just add the stdin object to
344
400
  # the queue. Otherwise, we'll add each of the filepaths to the queue.
345
- if $stdin.tty? || arguments.any?
401
+ if $stdin.tty? && (arguments.any? || options.scripts.any?)
346
402
  arguments.each do |pattern|
347
403
  Dir
348
404
  .glob(pattern)
@@ -350,8 +406,9 @@ module SyntaxTree
350
406
  queue << FileItem.new(filepath) if File.file?(filepath)
351
407
  end
352
408
  end
409
+ options.scripts.each { |script| queue << ScriptItem.new(script) }
353
410
  else
354
- queue << STDINItem.new
411
+ queue << ScriptItem.new($stdin.read)
355
412
  end
356
413
 
357
414
  # At the end, we're going to return whether or not this worker ever
@@ -14,7 +14,11 @@ module SyntaxTree
14
14
  # Note that we're keeping this in a global-ish hash instead of just
15
15
  # overriding methods on classes so that other plugins can reference this if
16
16
  # necessary. For example, the RBS plugin references the quote style.
17
- OPTIONS = { quote: "\"", trailing_comma: false }
17
+ OPTIONS = {
18
+ quote: "\"",
19
+ trailing_comma: false,
20
+ target_ruby_version: Gem::Version.new(RUBY_VERSION)
21
+ }
18
22
 
19
23
  COMMENT_PRIORITY = 1
20
24
  HEREDOC_PRIORITY = 2
@@ -23,14 +27,15 @@ module SyntaxTree
23
27
 
24
28
  # These options are overridden in plugins to we need to make sure they are
25
29
  # available here.
26
- attr_reader :quote, :trailing_comma
30
+ attr_reader :quote, :trailing_comma, :target_ruby_version
27
31
  alias trailing_comma? trailing_comma
28
32
 
29
33
  def initialize(
30
34
  source,
31
35
  *args,
32
36
  quote: OPTIONS[:quote],
33
- trailing_comma: OPTIONS[:trailing_comma]
37
+ trailing_comma: OPTIONS[:trailing_comma],
38
+ target_ruby_version: OPTIONS[:target_ruby_version]
34
39
  )
35
40
  super(*args)
36
41
 
@@ -40,13 +45,14 @@ module SyntaxTree
40
45
  # Memoizing these values per formatter to make access faster.
41
46
  @quote = quote
42
47
  @trailing_comma = trailing_comma
48
+ @target_ruby_version = target_ruby_version
43
49
  end
44
50
 
45
51
  def self.format(source, node)
46
- formatter = new(source, [])
47
- node.format(formatter)
48
- formatter.flush
49
- formatter.output.join
52
+ q = new(source, [])
53
+ q.format(node)
54
+ q.flush
55
+ q.output.join
50
56
  end
51
57
 
52
58
  def format(node, stackable: true)
@@ -56,7 +56,7 @@ module SyntaxTree
56
56
  store.delete(uri)
57
57
  in { method: "textDocument/formatting", id:, params: { textDocument: { uri: } } }
58
58
  contents = store[uri]
59
- write(id: id, result: contents ? [format(store[uri])] : nil)
59
+ write(id: id, result: contents ? [format(store[uri], uri.split(".").last)] : nil)
60
60
  in { method: "textDocument/inlayHint", id:, params: { textDocument: { uri: } } }
61
61
  contents = store[uri]
62
62
  write(id: id, result: contents ? inlay_hints(store[uri]) : nil)
@@ -86,7 +86,9 @@ module SyntaxTree
86
86
  }
87
87
  end
88
88
 
89
- def format(source)
89
+ def format(source, extension)
90
+ text = SyntaxTree::HANDLERS[".#{extension}"].format(source, print_width)
91
+
90
92
  {
91
93
  range: {
92
94
  start: {
@@ -98,7 +100,7 @@ module SyntaxTree
98
100
  character: 0
99
101
  }
100
102
  },
101
- newText: SyntaxTree.format(source, print_width)
103
+ newText: text
102
104
  }
103
105
  end
104
106
 
@@ -117,5 +119,9 @@ module SyntaxTree
117
119
  output.print("Content-Length: #{response.bytesize}\r\n\r\n#{response}")
118
120
  output.flush
119
121
  end
122
+
123
+ def log(message)
124
+ write(method: "window/logMessage", params: { type: 4, message: message })
125
+ end
120
126
  end
121
127
  end
@@ -1978,7 +1978,7 @@ module SyntaxTree
1978
1978
  # If we hit a statements, then we're safe to use whatever since we
1979
1979
  # know for certain we're going to get split over multiple lines
1980
1980
  # anyway.
1981
- break false if parent.is_a?(Statements)
1981
+ break false if parent.is_a?(Statements) || parent.is_a?(ArgParen)
1982
1982
 
1983
1983
  [Command, CommandCall].include?(parent.class)
1984
1984
  end
@@ -2132,8 +2132,7 @@ module SyntaxTree
2132
2132
  in [
2133
2133
  Paren[
2134
2134
  contents: {
2135
- body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array]
2136
- }
2135
+ body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array] }
2137
2136
  ]
2138
2137
  ]
2139
2138
  # Here we have a single argument that is a set of parentheses wrapping
@@ -5116,8 +5115,13 @@ module SyntaxTree
5116
5115
  q.breakable
5117
5116
  contents.call
5118
5117
  end
5119
- q.breakable
5120
- q.text("}")
5118
+
5119
+ if q.target_ruby_version < Gem::Version.new("2.7.3")
5120
+ q.text(" }")
5121
+ else
5122
+ q.breakable
5123
+ q.text("}")
5124
+ end
5121
5125
  end
5122
5126
  end
5123
5127
  end
@@ -5204,8 +5208,7 @@ module SyntaxTree
5204
5208
  false
5205
5209
  in {
5206
5210
  statements: { body: [truthy] },
5207
- consequent: Else[statements: { body: [falsy] }]
5208
- }
5211
+ consequent: Else[statements: { body: [falsy] }] }
5209
5212
  ternaryable?(truthy) && ternaryable?(falsy)
5210
5213
  else
5211
5214
  false
@@ -57,6 +57,26 @@ module SyntaxTree
57
57
  end
58
58
  end
59
59
 
60
+ # This represents all of the tokens coming back from the lexer. It is
61
+ # replacing a simple array because it keeps track of the last deleted token
62
+ # from the list for better error messages.
63
+ class TokenList < SimpleDelegator
64
+ attr_reader :last_deleted
65
+
66
+ def initialize(object)
67
+ super
68
+ @last_deleted = nil
69
+ end
70
+
71
+ def delete(value)
72
+ @last_deleted = super || @last_deleted
73
+ end
74
+
75
+ def delete_at(index)
76
+ @last_deleted = super
77
+ end
78
+ end
79
+
60
80
  # [String] the source being parsed
61
81
  attr_reader :source
62
82
 
@@ -124,7 +144,7 @@ module SyntaxTree
124
144
  # Most of the time, when a parser event consumes one of these events, it
125
145
  # will be deleted from the list. So ideally, this list stays pretty short
126
146
  # over the course of parsing a source string.
127
- @tokens = []
147
+ @tokens = TokenList.new([])
128
148
 
129
149
  # Here we're going to build up a list of SingleByteString or
130
150
  # MultiByteString objects. They're each going to represent a string in the
@@ -174,6 +194,33 @@ module SyntaxTree
174
194
  line[column].to_i - line.start
175
195
  end
176
196
 
197
+ # Returns the current location that is being looked at for the parser for
198
+ # the purpose of locating the error.
199
+ def find_token_error(location)
200
+ if location
201
+ # If we explicitly passed a location into this find_token_error method,
202
+ # that means that's the source of the error, so we'll use that
203
+ # information for our error object.
204
+ lineno = location.start_line
205
+ [lineno, location.start_char - line_counts[lineno - 1].start]
206
+ elsif lineno && column
207
+ # If there is a line number associated with the current ripper state,
208
+ # then we'll use that information to generate the error.
209
+ [lineno, column]
210
+ elsif (location = tokens.last_deleted&.location)
211
+ # If we've already deleted a token from the list of tokens that we are
212
+ # consuming, then we'll fall back to that token's location.
213
+ lineno = location.start_line
214
+ [lineno, location.start_char - line_counts[lineno - 1].start]
215
+ else
216
+ # Finally, it's possible that when we hit this error the parsing thread
217
+ # for ripper has died. In that case, lineno and column both return nil.
218
+ # So we're just going to set it to line 1, column 0 in the hopes that
219
+ # that makes any sense.
220
+ [1, 0]
221
+ end
222
+ end
223
+
177
224
  # As we build up a list of tokens, we'll periodically need to go backwards
178
225
  # and find the ones that we've already hit in order to determine the
179
226
  # location information for nodes that use them. For example, if you have a
@@ -201,14 +248,7 @@ module SyntaxTree
201
248
  unless index
202
249
  token = value == :any ? type.name.split("::", 2).last : value
203
250
  message = "Cannot find expected #{token}"
204
-
205
- if location
206
- lineno = location.start_line
207
- column = location.start_char - line_counts[lineno - 1].start
208
- raise ParseError.new(message, lineno, column)
209
- else
210
- raise ParseError.new(message, lineno, column)
211
- end
251
+ raise ParseError.new(message, *find_token_error(location))
212
252
  end
213
253
 
214
254
  tokens.delete_at(index)
@@ -677,8 +717,7 @@ module SyntaxTree
677
717
  else
678
718
  keyword = find_token(Kw, "begin")
679
719
  end_location =
680
- if bodystmt.rescue_clause || bodystmt.ensure_clause ||
681
- bodystmt.else_clause
720
+ if bodystmt.else_clause
682
721
  bodystmt.location
683
722
  else
684
723
  find_token(Kw, "end").location
@@ -871,7 +910,12 @@ module SyntaxTree
871
910
  location: keyword.location.to(consequent.location)
872
911
  )
873
912
  else
874
- operator = find_token(Kw, "in", consume: false) || find_token(Op, "=>")
913
+ operator =
914
+ if (keyword = find_token(Kw, "in", consume: false))
915
+ tokens.delete(keyword)
916
+ else
917
+ find_token(Op, "=>")
918
+ end
875
919
 
876
920
  RAssign.new(
877
921
  value: value,
@@ -2798,14 +2842,21 @@ module SyntaxTree
2798
2842
  # :call-seq:
2799
2843
  # on_regexp_literal: (
2800
2844
  # RegexpContent regexp_content,
2801
- # RegexpEnd ending
2845
+ # (nil | RegexpEnd) ending
2802
2846
  # ) -> RegexpLiteral
2803
2847
  def on_regexp_literal(regexp_content, ending)
2848
+ location = regexp_content.location
2849
+
2850
+ if ending.nil?
2851
+ message = "Cannot find expected regular expression ending"
2852
+ raise ParseError.new(message, *find_token_error(location))
2853
+ end
2854
+
2804
2855
  RegexpLiteral.new(
2805
2856
  beginning: regexp_content.beginning,
2806
2857
  ending: ending.value,
2807
2858
  parts: regexp_content.parts,
2808
- location: regexp_content.location.to(ending.location)
2859
+ location: location.to(ending.location)
2809
2860
  )
2810
2861
  end
2811
2862
 
@@ -35,14 +35,26 @@ module SyntaxTree
35
35
  # Defaults to [].
36
36
  attr_accessor :plugins
37
37
 
38
+ # Max line length.
39
+ # Defaults to 80.
40
+ attr_accessor :print_width
41
+
42
+ # The target Ruby version to use for formatting.
43
+ # Defaults to Gem::Version.new(RUBY_VERSION).
44
+ attr_accessor :target_ruby_version
45
+
38
46
  def initialize(
39
47
  name = :"stree:check",
40
48
  source_files = ::Rake::FileList["lib/**/*.rb"],
41
- plugins = []
49
+ plugins = [],
50
+ print_width = DEFAULT_PRINT_WIDTH,
51
+ target_ruby_version = Gem::Version.new(RUBY_VERSION)
42
52
  )
43
53
  @name = name
44
54
  @source_files = source_files
45
55
  @plugins = plugins
56
+ @print_width = print_width
57
+ @target_ruby_version = target_ruby_version
46
58
 
47
59
  yield self if block_given?
48
60
  define_task
@@ -59,6 +71,14 @@ module SyntaxTree
59
71
  arguments = ["check"]
60
72
  arguments << "--plugins=#{plugins.join(",")}" if plugins.any?
61
73
 
74
+ if print_width != DEFAULT_PRINT_WIDTH
75
+ arguments << "--print-width=#{print_width}"
76
+ end
77
+
78
+ if target_ruby_version != Gem::Version.new(RUBY_VERSION)
79
+ arguments << "--target-ruby-version=#{target_ruby_version}"
80
+ end
81
+
62
82
  SyntaxTree::CLI.run(arguments + Array(source_files))
63
83
  end
64
84
  end
@@ -35,14 +35,26 @@ module SyntaxTree
35
35
  # Defaults to [].
36
36
  attr_accessor :plugins
37
37
 
38
+ # Max line length.
39
+ # Defaults to 80.
40
+ attr_accessor :print_width
41
+
42
+ # The target Ruby version to use for formatting.
43
+ # Defaults to Gem::Version.new(RUBY_VERSION).
44
+ attr_accessor :target_ruby_version
45
+
38
46
  def initialize(
39
47
  name = :"stree:write",
40
48
  source_files = ::Rake::FileList["lib/**/*.rb"],
41
- plugins = []
49
+ plugins = [],
50
+ print_width = DEFAULT_PRINT_WIDTH,
51
+ target_ruby_version = Gem::Version.new(RUBY_VERSION)
42
52
  )
43
53
  @name = name
44
54
  @source_files = source_files
45
55
  @plugins = plugins
56
+ @print_width = print_width
57
+ @target_ruby_version = target_ruby_version
46
58
 
47
59
  yield self if block_given?
48
60
  define_task
@@ -59,6 +71,14 @@ module SyntaxTree
59
71
  arguments = ["write"]
60
72
  arguments << "--plugins=#{plugins.join(",")}" if plugins.any?
61
73
 
74
+ if print_width != DEFAULT_PRINT_WIDTH
75
+ arguments << "--print-width=#{print_width}"
76
+ end
77
+
78
+ if target_ruby_version != Gem::Version.new(RUBY_VERSION)
79
+ arguments << "--target-ruby-version=#{target_ruby_version}"
80
+ end
81
+
62
82
  SyntaxTree::CLI.run(arguments + Array(source_files))
63
83
  end
64
84
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "3.2.1"
4
+ VERSION = "3.5.0"
5
5
  end
data/lib/syntax_tree.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
3
4
  require "etc"
4
5
  require "json"
5
6
  require "pp"
data/syntax_tree.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  .reject { |f| f.match(%r{^(test|spec|features)/}) }
20
20
  end
21
21
 
22
- spec.required_ruby_version = ">= 2.7.3"
22
+ spec.required_ruby_version = ">= 2.7.0"
23
23
 
24
24
  spec.bindir = "exe"
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
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: 3.2.1
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-22 00:00:00.000000000 Z
11
+ date: 2022-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prettier_print
@@ -103,6 +103,7 @@ extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
105
  - ".github/dependabot.yml"
106
+ - ".github/workflows/auto-merge.yml"
106
107
  - ".github/workflows/gh-pages.yml"
107
108
  - ".github/workflows/main.yml"
108
109
  - ".gitignore"
@@ -153,7 +154,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
154
  requirements:
154
155
  - - ">="
155
156
  - !ruby/object:Gem::Version
156
- version: 2.7.3
157
+ version: 2.7.0
157
158
  required_rubygems_version: !ruby/object:Gem::Requirement
158
159
  requirements:
159
160
  - - ">="