Module: RubyNext::Language

Defined in:
lib/ruby-next/language.rb,
lib/ruby-next/language/eval.rb,
lib/ruby-next/language/setup.rb,
lib/ruby-next/language/parser.rb,
lib/ruby-next/language/runtime.rb,
lib/ruby-next/language/rewriters/base.rb,
lib/ruby-next/language/rewriters/2.4/dir.rb,
lib/ruby-next/language/rewriters/3.0/in_pattern.rb,
lib/ruby-next/language/rewriters/2.7/args_forward.rb,
lib/ruby-next/language/rewriters/3.0/find_pattern.rb,
lib/ruby-next/language/rewriters/2.6/endless_range.rb,
lib/ruby-next/language/rewriters/3.0/endless_method.rb,
lib/ruby-next/language/rewriters/3.1/shorthand_hash.rb,
lib/ruby-next/language/rewriters/2.1/required_kwargs.rb,
lib/ruby-next/language/rewriters/2.3/safe_navigation.rb,
lib/ruby-next/language/rewriters/2.7/numbered_params.rb,
lib/ruby-next/language/rewriters/3.1/anonymous_block.rb,
lib/ruby-next/language/rewriters/2.1/numeric_literals.rb,
lib/ruby-next/language/rewriters/2.3/squiggly_heredoc.rb,
lib/ruby-next/language/rewriters/2.7/pattern_matching.rb,
lib/ruby-next/language/rewriters/3.1/pin_vars_pattern.rb,
lib/ruby-next/language/rewriters/2.5/rescue_within_block.rb,
lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb,
lib/ruby-next/language/rewriters/proposed/method_reference.rb,
lib/ruby-next/language/rewriters/3.1/endless_method_command.rb,
lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb,
lib/ruby-next/language/rewriters/3.1/refinement_import_methods.rb,
lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb

Overview

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)

Defined Under Namespace

Modules: BuilderExt, ClassEval, Eval, GemTranspiler, InstanceEval, KernelEval, Rewriters, Runtime Classes: Builder, TransformContext

Constant Summary collapse

RewriterNotFoundError =
Class.new(StandardError)
MODES =
%i[rewrite ast].freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.modeObject

Returns the value of attribute mode.



72
73
74
# File 'lib/ruby-next/language.rb', line 72

def mode
  @mode
end

.rewritersObject

Returns the value of attribute rewriters.



65
66
67
# File 'lib/ruby-next/language.rb', line 65

def rewriters
  @rewriters
end

.strategyObject

Returns the value of attribute strategy.



68
69
70
# File 'lib/ruby-next/language.rb', line 68

def strategy
  @strategy
end

.watch_dirsObject

Returns the value of attribute watch_dirs.



66
67
68
# File 'lib/ruby-next/language.rb', line 66

def watch_dirs
  @watch_dirs
end

Class Method Details

.ast?Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/ruby-next/language.rb', line 83

def ast?
  mode == :ast
end

.current_rewritersObject

Rewriters required for the current version



127
128
129
# File 'lib/ruby-next/language.rb', line 127

def current_rewriters
  @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
end

.parse(source, file = "(string)") ⇒ Object



36
37
38
39
40
41
42
43
44
# 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)") ⇒ Object



46
47
48
49
50
51
52
53
54
# 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

.parserObject



28
29
30
31
32
33
34
# 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

.rewrite?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/ruby-next/language.rb', line 79

def rewrite?
  mode == :rewrite?
end

.runtime!Object



87
88
89
90
91
# File 'lib/ruby-next/language.rb', line 87

def runtime!
  require "ruby-next/language/rewriters/runtime"

  @runtime = true
end

.runtime?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/ruby-next/language.rb', line 93

def runtime?
  @runtime
end

.select_rewriters(*names) ⇒ Object

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



132
133
134
135
136
137
138
139
# File 'lib/ruby-next/language.rb', line 132

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) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 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|
    pn = Pathname.new(load_path)
    pn.exist? && pn.realpath.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(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/ruby-next/language.rb', line 97

def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
  retried = 0
  new_source = nil
  begin
    new_source =
      if mode == :rewrite
        rewrite(source, rewriters: rewriters, using: using, context: context)
      else
        regenerate(source, rewriters: rewriters, using: using, context: context)
      end
  rescue Unparser::UnknownNodeError => err
    RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since the version of Unparser you use doesn't support some syntax yet: #{err.message}.\n" \
      "Try upgrading the Unparser or set transpiling mode to \"rewrite\" in case you use some edge or experimental syntax."
    self.mode = :rewrite
    retried += 1
    retry unless retried > 1
    raise
  end

  return new_source unless RubyNext::Core.refine?
  return new_source unless using && context.use_ruby_next?

  Core.inject! new_source.dup
end

.transformable?(path) ⇒ Boolean

Returns:

  • (Boolean)


122
123
124
# File 'lib/ruby-next/language.rb', line 122

def transformable?(path)
  watch_dirs.any? { |dir| path.start_with?(dir) }
end

Instance Method Details

#runtime?Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/ruby-next/language/setup.rb', line 26

def runtime?
  false
end