diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed3c51fd..d707f33c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: ruby: - - '2.7' + - '2.7.0' - '3.0' - '3.1' - head diff --git a/Rakefile b/Rakefile index 6ba17fe9..4973d45e 100644 --- a/Rakefile +++ b/Rakefile @@ -12,8 +12,17 @@ end task default: :test -SOURCE_FILES = - FileList[%w[Gemfile Rakefile syntax_tree.gemspec lib/**/*.rb test/*.rb]] +configure = ->(task) do + task.source_files = + FileList[%w[Gemfile Rakefile syntax_tree.gemspec lib/**/*.rb test/*.rb]] -SyntaxTree::Rake::CheckTask.new { |t| t.source_files = SOURCE_FILES } -SyntaxTree::Rake::WriteTask.new { |t| t.source_files = SOURCE_FILES } + # Since Syntax Tree supports back to Ruby 2.7.0, we need to make sure that we + # format our code such that it's compatible with that version. This actually + # has very little effect on the output, the only change at the moment is that + # Ruby < 2.7.3 didn't allow a newline before the closing brace of a hash + # pattern. + task.target_ruby_version = Gem::Version.new("2.7.0") +end + +SyntaxTree::Rake::CheckTask.new(&configure) +SyntaxTree::Rake::WriteTask.new(&configure) diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index 65c71f4d..2de20e78 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -53,10 +53,13 @@ def source end end - # An item of work that corresponds to a script content passed via the command line. + # An item of work that corresponds to a script content passed via the + # command line. class ScriptItem FILEPATH = :script + attr_reader :source + def initialize(source) @source = source end @@ -68,10 +71,6 @@ def handler def filepath FILEPATH end - - def source - @source - end end # The parent action class for the CLI that implements the basics. @@ -197,7 +196,7 @@ def run(item) source = item.source formatted = item.handler.format(source, options.print_width) - File.write(filepath, formatted) if FileItem === item + File.write(filepath, formatted) if item.filepath != :script color = source == formatted ? Color.gray(filepath) : filepath delta = ((Time.now - start) * 1000).round @@ -259,13 +258,19 @@ def run(item) # responsible for parsing the list and then returning the file paths at the # end. class Options - attr_reader :print_width, :scripts + attr_reader :plugins, :print_width, :scripts, :target_ruby_version def initialize(print_width: DEFAULT_PRINT_WIDTH) + @plugins = [] @print_width = print_width @scripts = [] + @target_ruby_version = nil end + # TODO: This function causes a couple of side-effects that I really don't + # like to have here. It mutates the global state by requiring the plugins, + # and mutates the global options hash by adding the target ruby version. + # That should be done on a config-by-config basis, not here. def parse(arguments) parser.parse!(arguments) end @@ -285,7 +290,8 @@ def parser # require "syntax_tree/haml" # opts.on("--plugins=PLUGINS") do |plugins| - plugins.split(",").each { |plugin| require "syntax_tree/#{plugin}" } + @plugins = plugins.split(",") + @plugins.each { |plugin| require "syntax_tree/#{plugin}" } end # If there is a print width specified on the command line, then @@ -296,8 +302,13 @@ def parser # If there is a script specified on the command line, then parse # it and add it to the list of scripts to run. - opts.on("-e SCRIPT") do |script| - @scripts << script + opts.on("-e SCRIPT") { |script| @scripts << script } + + # If there is a target ruby version specified on the command line, + # parse that out and use it when formatting. + opts.on("--target-ruby-version=VERSION") do |version| + @target_ruby_version = Gem::Version.new(version) + Formatter::OPTIONS[:target_ruby_version] = @target_ruby_version end end end @@ -395,9 +406,7 @@ def run(argv) queue << FileItem.new(filepath) if File.file?(filepath) end end - options.scripts.each do |script| - queue << ScriptItem.new(script) - end + options.scripts.each { |script| queue << ScriptItem.new(script) } else queue << ScriptItem.new($stdin.read) end diff --git a/lib/syntax_tree/formatter.rb b/lib/syntax_tree/formatter.rb index c52e45ad..4c7a00db 100644 --- a/lib/syntax_tree/formatter.rb +++ b/lib/syntax_tree/formatter.rb @@ -14,7 +14,11 @@ class Formatter < PrettierPrint # Note that we're keeping this in a global-ish hash instead of just # overriding methods on classes so that other plugins can reference this if # necessary. For example, the RBS plugin references the quote style. - OPTIONS = { quote: "\"", trailing_comma: false } + OPTIONS = { + quote: "\"", + trailing_comma: false, + target_ruby_version: Gem::Version.new(RUBY_VERSION) + } COMMENT_PRIORITY = 1 HEREDOC_PRIORITY = 2 @@ -23,14 +27,15 @@ class Formatter < PrettierPrint # These options are overridden in plugins to we need to make sure they are # available here. - attr_reader :quote, :trailing_comma + attr_reader :quote, :trailing_comma, :target_ruby_version alias trailing_comma? trailing_comma def initialize( source, *args, quote: OPTIONS[:quote], - trailing_comma: OPTIONS[:trailing_comma] + trailing_comma: OPTIONS[:trailing_comma], + target_ruby_version: OPTIONS[:target_ruby_version] ) super(*args) @@ -40,6 +45,7 @@ def initialize( # Memoizing these values per formatter to make access faster. @quote = quote @trailing_comma = trailing_comma + @target_ruby_version = target_ruby_version end def self.format(source, node) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index d7b6d6cf..47c534d1 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -2132,8 +2132,7 @@ def format(q) in [ Paren[ contents: { - body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array] - } + body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array] } ] ] # Here we have a single argument that is a set of parentheses wrapping @@ -5116,8 +5115,13 @@ def format(q) q.breakable contents.call end - q.breakable - q.text("}") + + if q.target_ruby_version < Gem::Version.new("2.7.3") + q.text(" }") + else + q.breakable + q.text("}") + end end end end @@ -5204,8 +5208,7 @@ def call(q, node) false in { statements: { body: [truthy] }, - consequent: Else[statements: { body: [falsy] }] - } + consequent: Else[statements: { body: [falsy] }] } ternaryable?(truthy) && ternaryable?(falsy) else false diff --git a/lib/syntax_tree/rake/check_task.rb b/lib/syntax_tree/rake/check_task.rb index afe5013c..48247718 100644 --- a/lib/syntax_tree/rake/check_task.rb +++ b/lib/syntax_tree/rake/check_task.rb @@ -39,16 +39,22 @@ class CheckTask < ::Rake::TaskLib # Defaults to 80. attr_accessor :print_width + # The target Ruby version to use for formatting. + # Defaults to Gem::Version.new(RUBY_VERSION). + attr_accessor :target_ruby_version + def initialize( name = :"stree:check", source_files = ::Rake::FileList["lib/**/*.rb"], plugins = [], - print_width = DEFAULT_PRINT_WIDTH + print_width = DEFAULT_PRINT_WIDTH, + target_ruby_version = Gem::Version.new(RUBY_VERSION) ) @name = name @source_files = source_files @plugins = plugins @print_width = print_width + @target_ruby_version = target_ruby_version yield self if block_given? define_task @@ -64,10 +70,15 @@ 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 + if target_ruby_version != Gem::Version.new(RUBY_VERSION) + arguments << "--target-ruby-version=#{target_ruby_version}" + end + SyntaxTree::CLI.run(arguments + Array(source_files)) end end diff --git a/lib/syntax_tree/rake/write_task.rb b/lib/syntax_tree/rake/write_task.rb index 9a9e8330..69ce97e7 100644 --- a/lib/syntax_tree/rake/write_task.rb +++ b/lib/syntax_tree/rake/write_task.rb @@ -39,16 +39,22 @@ class WriteTask < ::Rake::TaskLib # Defaults to 80. attr_accessor :print_width + # The target Ruby version to use for formatting. + # Defaults to Gem::Version.new(RUBY_VERSION). + attr_accessor :target_ruby_version + def initialize( name = :"stree:write", source_files = ::Rake::FileList["lib/**/*.rb"], plugins = [], - print_width = DEFAULT_PRINT_WIDTH + print_width = DEFAULT_PRINT_WIDTH, + target_ruby_version = Gem::Version.new(RUBY_VERSION) ) @name = name @source_files = source_files @plugins = plugins @print_width = print_width + @target_ruby_version = target_ruby_version yield self if block_given? define_task @@ -64,10 +70,15 @@ 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 + if target_ruby_version != Gem::Version.new(RUBY_VERSION) + arguments << "--target-ruby-version=#{target_ruby_version}" + end + SyntaxTree::CLI.run(arguments + Array(source_files)) end end diff --git a/syntax_tree.gemspec b/syntax_tree.gemspec index 820a61a0..2b461dfd 100644 --- a/syntax_tree.gemspec +++ b/syntax_tree.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| .reject { |f| f.match(%r{^(test|spec|features)/}) } end - spec.required_ruby_version = ">= 2.7.3" + spec.required_ruby_version = ">= 2.7.0" spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/test/cli_test.rb b/test/cli_test.rb index 4e8959e2..aec8f820 100644 --- a/test/cli_test.rb +++ b/test/cli_test.rb @@ -134,12 +134,12 @@ def test_no_arguments_no_tty end def test_inline_script - stdio, = capture_io { SyntaxTree::CLI.run(["format", "-e", "1+1"]) } + stdio, = capture_io { SyntaxTree::CLI.run(%w[format -e 1+1]) } assert_equal("1 + 1\n", stdio) end def test_multiple_inline_scripts - stdio, = capture_io { SyntaxTree::CLI.run(["format", "-e", "1+1", "-e", "2+2"]) } + stdio, = capture_io { SyntaxTree::CLI.run(%w[format -e 1+1 -e 2+2]) } assert_equal("1 + 1\n2 + 2\n", stdio) end diff --git a/test/fixtures/args_forward.rb b/test/fixtures/args_forward.rb index 5ba618a8..cc538f44 100644 --- a/test/fixtures/args_forward.rb +++ b/test/fixtures/args_forward.rb @@ -1,4 +1,4 @@ -% +% # >= 2.7.3 def foo(...) bar(:baz, ...) end diff --git a/test/fixtures/hshptn.rb b/test/fixtures/hshptn.rb index 505336b8..02d1cf75 100644 --- a/test/fixtures/hshptn.rb +++ b/test/fixtures/hshptn.rb @@ -30,7 +30,7 @@ case foo in **bar end -% +% # >= 2.7.3 case foo in { foo:, # comment1 diff --git a/test/fixtures/params.rb b/test/fixtures/params.rb index 67b6ec90..551aa9a5 100644 --- a/test/fixtures/params.rb +++ b/test/fixtures/params.rb @@ -16,7 +16,7 @@ def foo(*) % def foo(*rest) end -% +% # >= 2.7.3 def foo(...) end % diff --git a/test/node_test.rb b/test/node_test.rb index 30776f9d..07c2fe26 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -104,16 +104,18 @@ def test_arg_star end end - def test_args_forward - source = <<~SOURCE - def get(...) - request(:GET, ...) - end - SOURCE + guard_version("2.7.3") do + def test_args_forward + source = <<~SOURCE + def get(...) + request(:GET, ...) + end + SOURCE - at = location(lines: 2..2, chars: 29..32) - assert_node(ArgsForward, source, at: at) do |node| - node.bodystmt.statements.body.first.arguments.arguments.parts.last + at = location(lines: 2..2, chars: 29..32) + assert_node(ArgsForward, source, at: at) do |node| + node.bodystmt.statements.body.first.arguments.arguments.parts.last + end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 895fbc82..80e514f0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -26,7 +26,7 @@ def initialize @called = nil end - def method_missing(called, ...) + def method_missing(called, *, **) @called = called end end