diff --git a/CHANGELOG.md b/CHANGELOG.md index 6634f331..cf774ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.3.0] - 2022-08-02 + +### Added + +- [#123](https://github.com/ruby-syntax-tree/syntax_tree/pull/123) - Allow the rake tasks to configure print width. +- [#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. + ## [3.2.1] - 2022-07-22 ### Changed @@ -312,7 +319,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.2.1...HEAD +[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.3.0...HEAD +[3.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.2.1...v3.3.0 [3.2.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.2.0...v3.2.1 [3.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v3.0.1...v3.1.0 diff --git a/Gemfile.lock b/Gemfile.lock index 0fbbbf84..55a6b335 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - syntax_tree (3.2.1) + syntax_tree (3.3.0) prettier_print GEM diff --git a/README.md b/README.md index 9c33fd42..fb1a49cd 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ It is built with only standard library dependencies. It additionally ships with - [json](#json) - [match](#match) - [write](#write) + - [Configuration](#configuration) - [Library](#library) - [SyntaxTree.read(filepath)](#syntaxtreereadfilepath) - [SyntaxTree.parse(source)](#syntaxtreeparsesource) @@ -231,6 +232,19 @@ To change the print width that you are writing with, specify the `--print-width` stree write --print-width=100 path/to/file.rb ``` +### Configuration + +Any of the above CLI commands can also read configuration options from a `.streerc` file in the directory where the commands are executed. + +This should be a text file with each argument on a separate line. + +```txt +--print-width=100 +--plugins=plugin/trailing_comma +``` + +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. + ## Library Syntax Tree can be used as a library to access the syntax tree underlying Ruby source code. @@ -505,6 +519,16 @@ SyntaxTree::Rake::WriteTask.new do |t| end ``` +#### `print_width` + +If you want to use a different print width from the default (80), you can pass that to the `print_width` field, as in: + +```ruby +SyntaxTree::Rake::WriteTask.new do |t| + t.print_width = 100 +end +``` + #### `plugins` 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: diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index cad4fc35..fb2e4554 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -4,6 +4,8 @@ module SyntaxTree # Syntax Tree ships with the `stree` CLI, which can be used to inspect and # manipulate Ruby code. This module is responsible for powering that CLI. module CLI + CONFIG_FILE = ".streerc" + # A utility wrapper around colored strings in the output. class Color attr_reader :value, :code @@ -269,6 +271,11 @@ def run(argv) name, *arguments = argv print_width = DEFAULT_PRINT_WIDTH + config_file = File.join(Dir.pwd, CONFIG_FILE) + if File.readable?(config_file) + arguments.unshift(*File.readlines(config_file, chomp: true)) + end + while arguments.first&.start_with?("--") case (argument = arguments.shift) when /^--plugins=(.+)$/ diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index b633a1c9..d7b6d6cf 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -1978,7 +1978,7 @@ def unchangeable_bounds?(q) # If we hit a statements, then we're safe to use whatever since we # know for certain we're going to get split over multiple lines # anyway. - break false if parent.is_a?(Statements) + break false if parent.is_a?(Statements) || parent.is_a?(ArgParen) [Command, CommandCall].include?(parent.class) end diff --git a/lib/syntax_tree/rake/check_task.rb b/lib/syntax_tree/rake/check_task.rb index 354cd172..afe5013c 100644 --- a/lib/syntax_tree/rake/check_task.rb +++ b/lib/syntax_tree/rake/check_task.rb @@ -35,14 +35,20 @@ class CheckTask < ::Rake::TaskLib # Defaults to []. attr_accessor :plugins + # Max line length. + # Defaults to 80. + attr_accessor :print_width + def initialize( name = :"stree:check", source_files = ::Rake::FileList["lib/**/*.rb"], - plugins = [] + plugins = [], + print_width = DEFAULT_PRINT_WIDTH ) @name = name @source_files = source_files @plugins = plugins + @print_width = print_width yield self if block_given? define_task @@ -58,6 +64,9 @@ def define_task def run_task arguments = ["check"] arguments << "--plugins=#{plugins.join(",")}" if plugins.any? + if print_width != DEFAULT_PRINT_WIDTH + arguments << "--print-width=#{print_width}" + end SyntaxTree::CLI.run(arguments + Array(source_files)) end diff --git a/lib/syntax_tree/rake/write_task.rb b/lib/syntax_tree/rake/write_task.rb index 5a957480..9a9e8330 100644 --- a/lib/syntax_tree/rake/write_task.rb +++ b/lib/syntax_tree/rake/write_task.rb @@ -35,14 +35,20 @@ class WriteTask < ::Rake::TaskLib # Defaults to []. attr_accessor :plugins + # Max line length. + # Defaults to 80. + attr_accessor :print_width + def initialize( name = :"stree:write", source_files = ::Rake::FileList["lib/**/*.rb"], - plugins = [] + plugins = [], + print_width = DEFAULT_PRINT_WIDTH ) @name = name @source_files = source_files @plugins = plugins + @print_width = print_width yield self if block_given? define_task @@ -58,6 +64,9 @@ def define_task def run_task arguments = ["write"] arguments << "--plugins=#{plugins.join(",")}" if plugins.any? + if print_width != DEFAULT_PRINT_WIDTH + arguments << "--print-width=#{print_width}" + end SyntaxTree::CLI.run(arguments + Array(source_files)) end diff --git a/lib/syntax_tree/version.rb b/lib/syntax_tree/version.rb index f920098f..6bc508fe 100644 --- a/lib/syntax_tree/version.rb +++ b/lib/syntax_tree/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxTree - VERSION = "3.2.1" + VERSION = "3.3.0" end diff --git a/test/cli_test.rb b/test/cli_test.rb index 31e4b7e2..21991e53 100644 --- a/test/cli_test.rb +++ b/test/cli_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "test_helper" +require "securerandom" module SyntaxTree class CLITest < Minitest::Test @@ -20,7 +21,7 @@ def test_handler file = Tempfile.new(%w[test- .test]) file.puts("test") - result = run_cli("ast", file: file) + result = run_cli("ast", contents: file) assert_equal("\"test\\n\" + \"test\\n\"\n", result.stdio) ensure SyntaxTree::HANDLERS.delete(".test") @@ -32,10 +33,7 @@ def test_ast end def test_ast_syntax_error - file = Tempfile.new(%w[test- .rb]) - file.puts("foo\n<>\nbar\n") - - result = run_cli("ast", file: file) + result = run_cli("ast", contents: "foo\n<>\nbar\n") assert_includes(result.stderr, "syntax error") end @@ -45,18 +43,13 @@ def test_check end def test_check_unformatted - file = Tempfile.new(%w[test- .rb]) - file.write("foo") - - result = run_cli("check", file: file) + result = run_cli("check", contents: "foo") assert_includes(result.stderr, "expected") end def test_check_print_width - file = Tempfile.new(%w[test- .rb]) - file.write("#{"a" * 40} + #{"b" * 40}\n") - - result = run_cli("check", "--print-width=100", file: file) + contents = "#{"a" * 40} + #{"b" * 40}\n" + result = run_cli("check", "--print-width=100", contents: contents) assert_includes(result.stdio, "match") end @@ -104,15 +97,12 @@ def test_write file = Tempfile.new(%w[test- .test]) filepath = file.path - result = run_cli("write", file: file) + result = run_cli("write", contents: file) assert_includes(result.stdio, filepath) end def test_write_syntax_tree - file = Tempfile.new(%w[test- .rb]) - file.write("<>") - - result = run_cli("write", file: file) + result = run_cli("write", contents: "<>") assert_includes(result.stderr, "syntax error") end @@ -146,20 +136,15 @@ def test_no_arguments_no_tty def test_generic_error SyntaxTree.stub(:format, ->(*) { raise }) do result = run_cli("format") + refute_equal(0, result.status) end end def test_plugins - Dir.mktmpdir do |directory| - Dir.mkdir(File.join(directory, "syntax_tree")) - $:.unshift(directory) - - File.write( - File.join(directory, "syntax_tree", "plugin.rb"), - "puts 'Hello, world!'" - ) - result = run_cli("format", "--plugins=plugin") + with_plugin_directory do |directory| + plugin = directory.plugin("puts 'Hello, world!'") + result = run_cli("format", "--plugins=#{plugin}") assert_equal("Hello, world!\ntest\n", result.stdio) end @@ -180,26 +165,114 @@ def test_language_server $stdout = prev_stdout end + def test_config_file + with_plugin_directory do |directory| + plugin = directory.plugin("puts 'Hello, world!'") + config = <<~TXT + --print-width=100 + --plugins=#{plugin} + TXT + + with_config_file(config) do + contents = "#{"a" * 40} + #{"b" * 40}\n" + result = run_cli("format", contents: contents) + + assert_equal("Hello, world!\n#{contents}", result.stdio) + end + end + end + + def test_print_width_args_with_config_file + with_config_file("--print-width=100") do + result = run_cli("check", contents: "#{"a" * 40} + #{"b" * 40}\n") + + assert_includes(result.stdio, "match") + end + end + + def test_print_width_args_with_config_file_override + with_config_file("--print-width=100") do + contents = "#{"a" * 40} + #{"b" * 40}\n" + result = run_cli("check", "--print-width=82", contents: contents) + + assert_includes(result.stderr, "expected") + end + end + + def test_plugin_args_with_config_file + with_plugin_directory do |directory| + plugin1 = directory.plugin("puts 'Hello, world!'") + + with_config_file("--plugins=#{plugin1}") do + plugin2 = directory.plugin("puts 'Bye, world!'") + result = run_cli("format", "--plugins=#{plugin2}") + + assert_equal("Hello, world!\nBye, world!\ntest\n", result.stdio) + end + end + end + private Result = Struct.new(:status, :stdio, :stderr, keyword_init: true) - def run_cli(command, *args, file: nil) - if file.nil? - file = Tempfile.new(%w[test- .rb]) - file.puts("test") - end + def run_cli(command, *args, contents: :default) + tempfile = + case contents + when :default + Tempfile.new(%w[test- .rb]).tap { |file| file.puts("test") } + when String + Tempfile.new(%w[test- .rb]).tap { |file| file.write(contents) } + else + contents + end - file.rewind + tempfile.rewind status = nil stdio, stderr = - capture_io { status = SyntaxTree::CLI.run([command, *args, file.path]) } + capture_io do + status = SyntaxTree::CLI.run([command, *args, tempfile.path]) + end Result.new(status: status, stdio: stdio, stderr: stderr) ensure - file.close - file.unlink + tempfile.close + tempfile.unlink + end + + def with_config_file(contents) + filepath = File.join(Dir.pwd, SyntaxTree::CLI::CONFIG_FILE) + File.write(filepath, contents) + + yield + ensure + FileUtils.rm(filepath) + end + + class PluginDirectory + attr_reader :directory + + def initialize(directory) + @directory = directory + end + + def plugin(contents) + name = SecureRandom.hex + File.write(File.join(directory, "#{name}.rb"), contents) + name + end + end + + def with_plugin_directory + Dir.mktmpdir do |directory| + $:.unshift(directory) + + plugin_directory = File.join(directory, "syntax_tree") + Dir.mkdir(plugin_directory) + + yield PluginDirectory.new(plugin_directory) + end end end end diff --git a/test/fixtures/command_call.rb b/test/fixtures/command_call.rb index 4a0f60f0..7c055e8d 100644 --- a/test/fixtures/command_call.rb +++ b/test/fixtures/command_call.rb @@ -34,3 +34,43 @@ bar baz % foo.bar baz ? qux : qaz +% +expect foo, bar.map { |i| { quux: bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz } } +- +expect foo, + bar.map { |i| + { + quux: + bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + } + } +% +expect(foo, bar.map { |i| {quux: bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz} }) +- +expect( + foo, + bar.map do |i| + { + quux: + bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + } + end +) +% +expect(foo.map { |i| { bar: i.bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz } } ).to match(baz.map { |i| { bar: i.bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz } }) +- +expect( + foo.map do |i| + { + bar: + i.bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + } + end +).to match( + baz.map do |i| + { + bar: + i.bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + } + end +)