syntax_tree 3.2.1 → 3.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: 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
  - - ">="