module RubyNext::Language

Language module contains tools to transpile newer Ruby syntax into an older one.

It works the following way:

- Takes a Ruby source code as input
- Generates the AST using the edge parser (via the `parser` gem)
- Pass this AST through the list of processors (one feature = one processor)
- Each processor may modify the AST
- Generates a transpiled source code from the transformed AST (via the `unparser` gem)

Constants

MODES
RewriterNotFoundError

Attributes

mode[R]
rewriters[RW]
strategy[RW]
watch_dirs[RW]

Public Class Methods

ast?() click to toggle source
# File lib/ruby-next/language.rb, line 83
def ast?
  mode == :ast
end
current_rewriters() click to toggle source

Rewriters required for the current version

# File lib/ruby-next/language.rb, line 151
def current_rewriters
  @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
end
mode=(val) click to toggle source
# File lib/ruby-next/language.rb, line 74
def mode=(val)
  raise ArgumentError, "Unknown mode: #{val}. Available: #{MODES.join(",")}" unless MODES.include?(val)
  @mode = val
end
parse(source, file = "(string)") click to toggle source
# File lib/ruby-next/language/parser.rb, line 36
def parse(source, file = "(string)")
  buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
    buffer.source = source
  end

  parser.parse(buffer)
rescue ::Parser::SyntaxError => e
  raise ::SyntaxError, e.message
end
parse_with_comments(source, file = "(string)") click to toggle source
# File lib/ruby-next/language/parser.rb, line 46
def parse_with_comments(source, file = "(string)")
  buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
    buffer.source = source
  end

  parser.parse_with_comments(buffer)
rescue ::Parser::SyntaxError => e
  raise ::SyntaxError, e.message
end
parser() click to toggle source
# File lib/ruby-next/language/parser.rb, line 28
def parser
  ::Parser::RubyNext.new(Builder.new).tap do |prs|
    prs.diagnostics.tap do |diagnostics|
      diagnostics.all_errors_are_fatal = true
    end
  end
end
regenerate(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new) click to toggle source
# File lib/ruby-next/language.rb, line 112
def regenerate(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
  parse_with_comments(source).then do |(ast, comments)|
    rewriters.inject(ast) do |tree, rewriter|
      rewriter.new(context).process(tree)
    end.then do |new_ast|
      next source unless context.dirty?

      Unparser.unparse(new_ast, comments)
    end.then do |source|
      next source unless RubyNext::Core.refine?
      next source unless using && context.use_ruby_next?

      Core.inject! source.dup
    end
  end
end
rewrite(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new) click to toggle source
# File lib/ruby-next/language.rb, line 129
def rewrite(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
  rewriters.inject(source) do |src, rewriter|
    buffer = Parser::Source::Buffer.new("<dynamic>")
    buffer.source = src

    rewriter.new(context).rewrite(buffer, parse(src))
  end.then do |new_source|
    next source unless context.dirty?
    new_source
  end.then do |source|
    next source unless RubyNext::Core.refine?
    next source unless using && context.use_ruby_next?

    Core.inject! source.dup
  end
end
rewrite?() click to toggle source
# File lib/ruby-next/language.rb, line 79
def rewrite?
  mode == :rewrite?
end
runtime!() click to toggle source
# File lib/ruby-next/language.rb, line 87
def runtime!
  require "ruby-next/language/rewriters/runtime"

  @runtime = true
end
runtime?() click to toggle source
# File lib/ruby-next/language.rb, line 93
def runtime?
  @runtime
end
select_rewriters(*names) click to toggle source

This method guarantees that rewriters will be returned in order they defined in Language module

# File lib/ruby-next/language.rb, line 156
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
setup_gem_load_path(lib_dir = "lib", rbnext_dir: RUBY_NEXT_DIR, transpile: false) click to toggle source
# File lib/ruby-next/language/setup.rb, line 31
def setup_gem_load_path(lib_dir = "lib", rbnext_dir: RUBY_NEXT_DIR, transpile: false)
  called_from = caller_locations(1, 1).first.path
  dirname = File.realpath(File.dirname(called_from))

  loop do
    basename = File.basename(dirname)
    raise "Couldn't find gem's load dir: #{lib_dir}" if basename == dirname

    break if basename == lib_dir

    dirname = File.dirname(basename)
  end

  dirname = File.realpath(dirname)

  return if Language.runtime? && Language.watch_dirs.include?(dirname)

  next_dirname = File.join(dirname, rbnext_dir)

  GemTranspiler.maybe_transpile(File.dirname(dirname), lib_dir, next_dirname) if transpile

  current_index = $LOAD_PATH.find_index do |load_path|
    Pathname.new(load_path).cleanpath.to_s == dirname
  end

  raise "Gem's lib is not in the $LOAD_PATH: #{dirname}" if current_index.nil?

  version = RubyNext.next_ruby_version

  loop do
    break unless version

    version_dir = File.join(next_dirname, version.segments[0..1].join("."))

    if File.exist?(version_dir)
      $LOAD_PATH.insert current_index, version_dir
      current_index += 1
    end

    version = RubyNext.next_ruby_version(version)
  end
end
transform(*args, **kwargs) click to toggle source
# File lib/ruby-next/language.rb, line 97
def transform(*args, **kwargs)
  if mode == :rewrite
    rewrite(*args, **kwargs)
  else
    regenerate(*args, **kwargs)
  end
rescue Unparser::UnknownNodeError
  if Gem::Version.new(::RubyNext.current_ruby_version) >= Gem::Version.new("3.0.0")
    RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 3.0+ AST yet.\n" \
      "See https://github.com/mbj/unparser/issues/168"
    self.mode = :rewrite
  end
  rewrite(*args, **kwargs)
end
transformable?(path) click to toggle source
# File lib/ruby-next/language.rb, line 146
def transformable?(path)
  watch_dirs.any? { |dir| path.start_with?(dir) }
end