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/text.rb,
lib/ruby-next/language/paco_parsers/base.rb,
lib/ruby-next/language/rewriters/2.4/dir.rb,
lib/ruby-next/language/rewriters/abstract.rb,
lib/ruby-next/language/paco_parsers/comments.rb,
lib/ruby-next/language/rewriters/edge/it_param.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/paco_parsers/string_literals.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/3.2/anonymous_restargs.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, PacoParsers, 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

.exclude_patternsObject

Returns the value of attribute exclude_patterns.



70
71
72
# File 'lib/ruby-next/language.rb', line 70

def exclude_patterns
  @exclude_patterns
end

.include_patternsObject

Returns the value of attribute include_patterns.



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

def include_patterns
  @include_patterns
end

.modeObject

Returns the value of attribute mode.



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

def mode
  @mode
end

.parser_classObject

Returns the value of attribute parser_class.



38
39
40
# File 'lib/ruby-next/language/parser.rb', line 38

def parser_class
  @parser_class
end

.parser_syntax_errorsObject

Returns the value of attribute parser_syntax_errors.



38
39
40
# File 'lib/ruby-next/language/parser.rb', line 38

def parser_syntax_errors
  @parser_syntax_errors
end

.rewritersObject

Returns the value of attribute rewriters.



77
78
79
# File 'lib/ruby-next/language.rb', line 77

def rewriters
  @rewriters
end

.strategyObject

Returns the value of attribute strategy.



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

def strategy
  @strategy
end

.watch_dirsObject



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

def watch_dirs
  warn "[DEPRECATED] Use `RubyNext::Language.include_patterns` instead of `RubyNext::Language.watch_dirs`"
  @watch_dirs
end

Class Method Details

.ast?Boolean

Returns:

  • (Boolean)


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

def ast?
  mode == :ast
end

.current_rewritersObject

Rewriters required for the current version



150
151
152
# File 'lib/ruby-next/language.rb', line 150

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

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



48
49
50
51
52
53
54
55
56
# File 'lib/ruby-next/language/parser.rb', line 48

def parse(source, file = "(string)")
  buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
    buffer.source = source
  end

  parser.parse(buffer)
rescue *parser_syntax_errors => e
  raise ::SyntaxError, e.message, e.backtrace
end

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



58
59
60
61
62
63
64
65
66
# File 'lib/ruby-next/language/parser.rb', line 58

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_syntax_errors => e
  raise ::SyntaxError, e.message, e.backtrace
end

.parserObject



40
41
42
43
44
45
46
# File 'lib/ruby-next/language/parser.rb', line 40

def parser
  parser_class.new(Builder.new).tap do |prs|
    prs.diagnostics.tap do |diagnostics|
      diagnostics.all_errors_are_fatal = true
    end
  end
end

.rewrite?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/ruby-next/language.rb', line 90

def rewrite?
  mode == :rewrite?
end

.runtime!Object



98
99
100
101
102
# File 'lib/ruby-next/language.rb', line 98

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

  @runtime = true
end

.runtime?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/ruby-next/language.rb', line 104

def runtime?
  @runtime
end

.select_rewriters(*names) ⇒ Object

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



155
156
157
158
159
160
161
162
# File 'lib/ruby-next/language.rb', line 155

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



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
74
75
76
# File 'lib/ruby-next/language/setup.rb', line 34

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.target_dir?(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

.target_dir?(dirname) ⇒ Boolean

Returns:

  • (Boolean)


136
137
138
139
140
141
142
# File 'lib/ruby-next/language.rb', line 136

def target_dir?(dirname)
  # fnmatch? requires a file name, not a folder
  fname = File.join(dirname, "x.rb")

  include_patterns.any? { |pattern| File.fnmatch?(pattern, fname) } &&
    exclude_patterns.none? { |pattern| File.fnmatch?(pattern, fname) }
end

.transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, path: nil, context: TransformContext.new(path: path)) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/ruby-next/language.rb', line 108

def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, path: nil, context: TransformContext.new(path: path))
  text_rewriters, ast_rewriters = rewriters.partition(&:text?)

  retried = 0
  new_source = text_rewrite(source, rewriters: text_rewriters, using: using, context: context)

  begin
    new_source =
      if mode == :rewrite
        rewrite(new_source, rewriters: ast_rewriters, using: using, context: context)
      else
        regenerate(new_source, rewriters: ast_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)


144
145
146
147
# File 'lib/ruby-next/language.rb', line 144

def transformable?(path)
  include_patterns.any? { |pattern| File.fnmatch?(pattern, path) } &&
    exclude_patterns.none? { |pattern| File.fnmatch?(pattern, path) }
end

Instance Method Details

#runtime?Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/ruby-next/language/setup.rb', line 29

def runtime?
  false
end