Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support passing rewriters to CLI #47

Merged
merged 8 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## master

- Support passing rewriters to CLI. ([@sl4vr][])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍
Could you please also add the actual link to the bottom of this file? (Right now it's not working, cause shortcut is not defined)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops I thought it's Github feature or something 🙄


Use `nextify --list-rewriters` to view all available rewriters.
Use `nextify` with `--rewrite=REWRITERS...` option to specify which particular rewriters to use.

## 0.9.1 (2020-06-05)

- Keep `ruby-next` version in sync with `ruby-next-core`. ([@palkan][])
Expand Down Expand Up @@ -195,3 +200,4 @@ p a #=> 1

[@palkan]: https://github.com/palkan
[backports]: https://github.com/marcandre/backports
[@sl4vr]: https://github.com/sl4vr
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,15 @@ It has the following interface:
```sh
$ ruby-next nextify
Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
-o, --output=OUTPUT Specify output directory or file or stdout (use -o stdout for that)
-o, --output=OUTPUT Specify output directory or file or stdout
--min-version=VERSION Specify the minimum Ruby version to support
--single-version Only create one version of a file (for the earliest Ruby version)
--enable-method-reference Enable reverted method reference syntax (requires custom parser)
--edge Enable edge (master) Ruby features
--proposed Enable proposed/experimental Ruby features
--transpile-mode=MODE Transpiler mode (ast or rewrite). Default: ast
--[no-]refine Do not inject `using RubyNext`
--list-rewriters List available rewriters
--rewrite=REWRITERS... Specify particular Ruby features to rewrite
-h, --help Print help
-V Turn on verbose mode
--dry-run Print verbose output without generating files
Expand Down
4 changes: 4 additions & 0 deletions forspell.dict
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ mruby
polyfills
pragma
pre-transpiling
Rewriter
Rewriters
rewriter
rewriters
Startless
Transpile
transpile
Expand Down
44 changes: 40 additions & 4 deletions lib/ruby-next/commands/nextify.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ module Commands
class Nextify < Base
using RubyNext

attr_reader :lib_path, :paths, :out_path, :min_version, :single_version
attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters

def run
log "RubyNext core strategy: #{RubyNext::Core.strategy}"
log "RubyNext transpile mode: #{RubyNext::Language.mode}"

remove_rbnext!

@min_version ||= MIN_SUPPORTED_VERSION

paths.each do |path|
contents = File.read(path)
transpile path, contents
Expand All @@ -26,7 +28,8 @@ def run

def parse!(args)
print_help = false
@min_version = MIN_SUPPORTED_VERSION
print_rewriters = false
rewriter_names = []
@single_version = false

optparser = base_parser do |opts|
Expand Down Expand Up @@ -63,6 +66,14 @@ def parse!(args)
Core.strategy = :core_ext unless val
end

opts.on("--list-rewriters", "List available rewriters") do |val|
print_rewriters = true
end

opts.on("--rewrite=REWRITERS...", "Specify particular Ruby features to rewrite") do |val|
rewriter_names << val
end

opts.on("-h", "--help", "Print help") do
print_help = true
end
Expand All @@ -77,12 +88,35 @@ def parse!(args)
exit 0
end

if print_rewriters
Language.rewriters.each do |rewriter|
$stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
end
exit 0
end

unless lib_path&.then(&File.method(:exist?))
$stdout.puts "Path not found: #{lib_path}"
$stdout.puts optparser.help
exit 2
end

if rewriter_names.any? && min_version
$stdout.puts "--rewrite cannot be used with --min-version simultaneously"
exit 2
end

@specified_rewriters =
if rewriter_names.any?
begin
Language.select_rewriters(*rewriter_names)
rescue Language::RewriterNotFoundError => error
$stdout.puts error.message
$stdout.puts "Try --list-rewriters to see list of available rewriters"
exit 2
end
end

@paths =
if File.directory?(lib_path)
Dir[File.join(lib_path, "**/*.rb")]
Expand All @@ -96,7 +130,7 @@ def parse!(args)
private

def transpile(path, contents, version: min_version)
rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }

context = Language::TransformContext.new

Expand Down Expand Up @@ -156,7 +190,9 @@ def stdout?
out_path == "stdout"
end

alias single_version? single_version
def single_version?
single_version || specified_rewriters
end
end
end
end
12 changes: 12 additions & 0 deletions lib/ruby-next/language.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ module Language
require "ruby-next/language/parser"
require "ruby-next/language/unparser"

RewriterNotFoundError = Class.new(StandardError)

class TransformContext
attr_reader :versions, :use_ruby_next

Expand Down Expand Up @@ -150,6 +152,16 @@ def current_rewriters
@current_rewriters ||= rewriters.select(&:unsupported_syntax?)
end

# This method guarantees that rewriters will be returned in order they defined in Language module
def select_rewriters(*names)
rewriters_delta = names - rewriters.map { |rewriter| rewriter::NAME }
if rewriters_delta.any?
raise RewriterNotFoundError, "Rewriters not found: #{rewriters_delta.join(",")}"
end

rewriters.select { |rewriter| names.include?(rewriter::NAME) }
end

private

attr_writer :watch_dirs
Expand Down
1 change: 1 addition & 0 deletions lib/ruby-next/language/rewriters/args_forward.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module RubyNext
module Language
module Rewriters
class ArgsForward < Base
NAME = "args-forward"
SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")

Expand Down
1 change: 1 addition & 0 deletions lib/ruby-next/language/rewriters/endless_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module RubyNext
module Language
module Rewriters
class EndlessMethod < Base
NAME = "endless-method"
SYNTAX_PROBE = "obj = Object.new; def obj.foo() = 42"
MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")

Expand Down
1 change: 1 addition & 0 deletions lib/ruby-next/language/rewriters/endless_range.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module RubyNext
module Language
module Rewriters
class EndlessRange < Base
NAME = "endless-range"
SYNTAX_PROBE = "[0, 1][1..]"
MIN_SUPPORTED_VERSION = Gem::Version.new("2.6.0")

Expand Down
1 change: 1 addition & 0 deletions lib/ruby-next/language/rewriters/method_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module RubyNext
module Language
module Rewriters
class MethodReference < Base
NAME = "method-reference"
SYNTAX_PROBE = "Language.:transform"
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")

Expand Down
1 change: 1 addition & 0 deletions lib/ruby-next/language/rewriters/numbered_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Rewriters
class NumberedParams < Base
using RubyNext

NAME = "numbered-params"
SYNTAX_PROBE = "proc { _1 }.call(1)"
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")

Expand Down
1 change: 1 addition & 0 deletions lib/ruby-next/language/rewriters/pattern_matching.rb
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ def hash_key(node, key)
end

class PatternMatching < Base
NAME = "pattern-matching"
SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")

Expand Down
1 change: 1 addition & 0 deletions lib/ruby-next/language/rewriters/right_hand_assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module RubyNext
module Language
module Rewriters
class RightHandAssignment < Base
NAME = "right-hand-assignment"
SYNTAX_PROBE = "1 + 2 => a"
MIN_SUPPORTED_VERSION = Gem::Version.new("2.8.0")

Expand Down
3 changes: 3 additions & 0 deletions spec/cli/dummy/method_reference.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

Language.:transform
4 changes: 4 additions & 0 deletions spec/cli/dummy/right_hand_assignment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

1 + 2 => a
a + 1
99 changes: 99 additions & 0 deletions spec/cli/nextify_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,71 @@
end
end

it "generates one version if --rewrite is provided" do
run_ruby_next(
"nextify #{File.join(__dir__, "dummy")} " \
"--rewrite=endless-range --rewrite=pattern-matching"
) do |_status, _output, err|
File.exist?(File.join(__dir__, "dummy", ".rbnext", "right_hand_assignment.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "method_reference.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "transpile_me.rb")).should equal true
File.exist?(File.join(__dir__, "dummy", ".rbnext", "endless_pattern.rb")).should equal true
File.exist?(File.join(__dir__, "dummy", ".rbnext", "namespaced", "endless_nameless.rb")).should equal true
File.exist?(File.join(__dir__, "dummy", ".rbnext", "namespaced", "pattern_matching.rb")).should equal true
end
end

it "returns error if --rewrite is provided with wrong rewriter" do
run_ruby_next(
"nextify #{File.join(__dir__, "dummy")} " \
"--rewrite=right-hand-assignment",
env: {"RUBY_NEXT_EDGE" => "0", "RUBY_NEXT_PROPOSED" => "0"},
should_fail: true
) do |_status, output, err|
output.should include("Rewriters not found: right-hand-assignment")
end
end

it "generates one version if --rewrite is provided with rewriter from edge along with --edge option" do
run_ruby_next(
"nextify #{File.join(__dir__, "dummy")} " \
"--rewrite=right-hand-assignment --edge",
env: {"RUBY_NEXT_EDGE" => "0", "RUBY_NEXT_PROPOSED" => "0"}
) do |_status, _output, err|
File.exist?(File.join(__dir__, "dummy", ".rbnext", "right_hand_assignment.rb")).should equal true
File.exist?(File.join(__dir__, "dummy", ".rbnext", "method_reference.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "transpile_me.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "endless_pattern.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "namespaced", "endless_nameless.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "namespaced", "pattern_matching.rb")).should equal false
end
end

it "generates one version if --rewrite is provided with rewriter from proposed along with --proposed option" do
run_ruby_next(
"nextify #{File.join(__dir__, "dummy")} " \
"--rewrite=method-reference --proposed",
env: {"RUBY_NEXT_EDGE" => "0", "RUBY_NEXT_PROPOSED" => "0"}
) do |_status, _output, err|
File.exist?(File.join(__dir__, "dummy", ".rbnext", "right_hand_assignment.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "method_reference.rb")).should equal true
File.exist?(File.join(__dir__, "dummy", ".rbnext", "transpile_me.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "endless_pattern.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "namespaced", "endless_nameless.rb")).should equal false
File.exist?(File.join(__dir__, "dummy", ".rbnext", "namespaced", "pattern_matching.rb")).should equal false
end
end

it "returns error if --rewrite is provided along with --min-version" do
run_ruby_next(
"nextify #{File.join(__dir__, "dummy")} " \
"--rewrite=endless-range --min-version=2.5",
should_fail: true
) do |_status, output, err|
output.should include("--rewrite cannot be used with --min-version simultaneously")
end
end

it "supports --no-refine" do
run_ruby_next(
"nextify #{File.join(__dir__, "dummy", "transpile_me.rb")} " \
Expand Down Expand Up @@ -216,4 +281,38 @@
end
end
end

it "--list-rewriters" do
run_ruby_next(
"nextify --list-rewriters",
env: {"RUBY_NEXT_PROPOSED" => "0", "RUBY_NEXT_EDGE" => "0"}
) do |_status, output, err|
output.should include('args-forward ("obj = Object.new; def obj.foo(...) super(...); end")')
output.should include('numbered-params ("proc { _1 }.call(1)")')
output.should include('pattern-matching ("case 0; in 0; true; else; 1; end")')
output.should include('endless-range ("[0, 1][1..]")')
output.should_not include('endless-method ("obj = Object.new; def obj.foo() = 42")')
output.should_not include('right-hand-assignment ("1 + 2 => a")')
output.should_not include('method-reference ("Language.:transform")')
end
end

it "--list-rewriters --edge" do
run_ruby_next(
"nextify --list-rewriters --edge",
env: {"RUBY_NEXT_PROPOSED" => "0", "RUBY_NEXT_EDGE" => "0"}
) do |_status, output, err|
output.should include('endless-method ("obj = Object.new; def obj.foo() = 42")')
output.should include('right-hand-assignment ("1 + 2 => a")')
end
end

it "--list-rewriters --proposed" do
run_ruby_next(
"nextify --list-rewriters --proposed",
env: {"RUBY_NEXT_PROPOSED" => "0", "RUBY_NEXT_EDGE" => "0"}
) do |_status, output, err|
output.should include('method-reference ("Language.:transform")')
end
end
end