Skip to content
Draft
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
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ruby-3.2, ruby-3.3, ruby-3.4]
ruby: [ruby-3.3, ruby-3.4]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
Expand All @@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ruby-3.2, ruby-3.3, ruby-3.4]
ruby: [ruby-3.3, ruby-3.4]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
Expand All @@ -53,7 +53,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ruby-3.2, ruby-3.3, ruby-3.4]
ruby: [ruby-3.3, ruby-3.4]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
Expand All @@ -69,7 +69,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ruby-3.2, ruby-3.3, ruby-3.4]
ruby: [ruby-3.3, ruby-3.4]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
Expand All @@ -85,7 +85,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ruby-3.2, ruby-3.3, ruby-3.4]
ruby: [ruby-3.3, ruby-3.4]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ AllCops:
- '**/*.rake'
- 'Gemfile'
- 'Gemfile.triage'
TargetRubyVersion: 3.1
TargetRubyVersion: 3.3
Exclude:
- tmp/**/*
- vendor/**/*
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ unparser
![CI](https://github.com/mbj/unparser/workflows/CI/badge.svg)
[![Gem Version](https://img.shields.io/gem/v/unparser.svg)](https://rubygems.org/gems/unparser)

Generate equivalent source for ASTs from [parser](https://github.com/whitequark/parser).
Generate equivalent source for ASTs from [prism](https://github.com/ruby/prism) via its parser translation layer.

**Parser Backend**: Unparser uses [Prism](https://github.com/ruby/prism) (Ruby's official parser) with its [parser translation layer](https://github.com/ruby/prism/blob/main/docs/parser_translation.md) to produce `Parser::AST::Node` compatible ASTs. This requires both the `prism` and `parser` gems as dependencies.

The following constraints apply:

* No support for macruby extensions
* Only support for the [modern AST](https://github.com/whitequark/parser/#usage) format
* Only support for Ruby >= 3.2
* Only support for Ruby >= 3.3

Notable Users:

Expand All @@ -30,7 +32,6 @@ Usage
-----

```ruby
require 'parser/current'
require 'unparser'

ast = Unparser.parse('your(ruby(code))')
Expand All @@ -41,7 +42,6 @@ Unparser.unparse(ast) # => 'your(ruby(code))'
To preserve the comments from the source:

```ruby
require 'parser/current'
require 'unparser'

ast, comments = Unparser.parser.parse_with_comments(Unparser.buffer('your(ruby(code)) # with comments'))
Expand Down
7 changes: 2 additions & 5 deletions bin/parser-round-trip-test
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

if Gem::Version.new(RUBY_VERSION) <= '3.4'
load 'bin/parser-whitequark-round-trip-test'
else
load 'bin/parser-prism-round-trip-test'
end
# Unparser now uses Prism translation layer for all Ruby 3.3+ versions
load 'bin/parser-prism-round-trip-test'
63 changes: 26 additions & 37 deletions lib/unparser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,41 @@

# Library namespace
module Unparser # rubocop:disable Metrics/ModuleLength
# Unparser specific AST builder defaulting to modern AST format
if Gem::Version.new(RUBY_VERSION) <= '3.4'
require 'parser/current'
class Builder < Parser::Builders::Default
modernize

# mutant:disable
def initialize
super
require 'prism'

self.emit_file_line_as_literals = false
end
end
else
require 'prism'
class Builder < Prism::Translation::Parser::Builder
modernize
# Unparser specific AST builder defaulting to modern AST format
class Builder < Prism::Translation::Parser::Builder
modernize

# mutant:disable
def initialize
super
# mutant:disable
def initialize
super

self.emit_file_line_as_literals = false
end
self.emit_file_line_as_literals = false
end
end

# Select appropriate Prism translation parser based on Ruby version
PARSER_CLASS =
if Gem::Version.new(RUBY_VERSION) <= '3.4'
Class.new(Parser::CurrentRuby) do
def declare_local_variable(local_variable)
static_env.declare(local_variable)
end
end
if RUBY_VERSION >= '3.5'
Prism::Translation::Parser35
elsif RUBY_VERSION >= '3.4'
Prism::Translation::Parser34
else
Class.new(Prism::Translation::Parser34) do
def declare_local_variable(local_variable)
(@local_variables ||= Set.new) << local_variable
end

def prism_options
super.merge(scopes: [@local_variables.to_a])
end
end
Prism::Translation::Parser33
end

# Create parser instance with local variable declaration support
PARSER_INSTANCE_CLASS = Class.new(PARSER_CLASS) do
def declare_local_variable(local_variable)
(@local_variables ||= Set.new) << local_variable
end

def prism_options
super.merge(scopes: [@local_variables.to_a])
end
end

EMPTY_STRING = ''.freeze
EMPTY_ARRAY = [].freeze

Expand Down Expand Up @@ -238,7 +227,7 @@ def self.parse_ast(source, static_local_variables: Set.new)
# @api private
# mutant:disable
def self.parser
PARSER_CLASS.new(Builder.new).tap do |parser|
PARSER_INSTANCE_CLASS.new(Builder.new).tap do |parser|
parser.diagnostics.tap do |diagnostics|
diagnostics.all_errors_are_fatal = true
end
Expand Down
17 changes: 1 addition & 16 deletions lib/unparser/emitter/primitive.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,8 @@ class Symbol < self

private

# mutant:disable
def dispatch
if inspect_breaks_parsing?
write(":#{value.name.inspect}")
else
write(value.inspect)
end
end

# mutant:disable
def inspect_breaks_parsing?
return false unless RUBY_VERSION < '3.2.'

Unparser.parse(value.inspect)
false
rescue Parser::SyntaxError
true
write(value.inspect)
end
end # Symbol

Expand Down
2 changes: 0 additions & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
require 'mutant'
require 'yaml'

require 'parser/current'

RSpec.shared_examples_for 'a command method' do
it 'returns self' do
should equal(object)
Expand Down
30 changes: 7 additions & 23 deletions spec/unit/unparser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -459,29 +459,13 @@ def foo(bar)
let(:version_excludes) do
excludes = []

if RUBY_VERSION >= '3.4.'
excludes.concat(
%w[
test/corpus/literal/before/34.rb
]
)
end

if RUBY_VERSION < '3.2.'
excludes.concat(
%w[
test/corpus/literal/since/32.rb
]
)
end

if RUBY_VERSION < '3.1.'
excludes.concat(
%w[
test/corpus/literal/since/31.rb
]
)
end
# "before/34.rb" contains syntax valid before Ruby 3.4 but invalid in 3.4+
# So we need to exclude it on ALL versions (3.3 can't parse it, 3.4+ rejects it)
excludes.concat(
%w[
test/corpus/literal/before/34.rb
]
)

excludes.flat_map { |file| ['--ignore', file] }
end
Expand Down
2 changes: 1 addition & 1 deletion unparser.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Gem::Specification.new do |gem|
gem.extra_rdoc_files = %w[README.md]
gem.executables = %w[unparser]

gem.required_ruby_version = '>= 3.1'
gem.required_ruby_version = '>= 3.3'

gem.add_dependency('diff-lcs', '~> 1.6')
gem.add_dependency('parser', '>= 3.3.0')
Expand Down
Loading