New Upstream Release - ruby-unparser
Ready changes
Summary
Merged new upstream version: 0.6.8 (was: 0.4.7).
Diff
diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 226ac39..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-defaults: &defaults
- working_directory: ~/unparser
- docker:
- - image: circleci/ruby:2.6.5
-version: 2
-jobs:
- unit_specs:
- <<: *defaults
- steps:
- - checkout
- - run: bundle install
- - run: bundle exec rspec spec/unit
- integration_specs:
- <<: *defaults
- steps:
- - checkout
- - run: bundle install
- - run: bundle exec rspec spec/integration
- metrics:
- <<: *defaults
- steps:
- - checkout
- - run: bundle install
- - run: bundle exec rake metrics:rubocop
- - run: bundle exec rake metrics:reek
- - run: bundle exec rake metrics:flay
- - run: bundle exec rake metrics:flog
- mutant:
- <<: *defaults
- steps:
- - checkout
- - run: bundle install
- - run: |
- bundle \
- exec \
- mutant \
- --ignore-subject 'Unparser::AST::LocalVariableScope*' \
- --since origin/master \
- --zombie \
- -- \
- 'Unparser*'
-workflows:
- version: 2
- test:
- jobs:
- - unit_specs
- - integration_specs
- - metrics
- - mutant
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 3a9c57e..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,37 +0,0 @@
-## MAC OS
-.DS_Store
-
-## TEXTMATE
-*.tmproj
-tmtags
-
-## EMACS
-*~
-\#*
-.\#*
-
-## VIM
-*.sw[op]
-
-## Rubinius
-*.rbc
-.rbx
-
-## PROJECT::GENERAL
-*.gem
-coverage
-profiling
-turbulence
-rdoc
-pkg
-tmp
-doc
-log
-.yardoc
-measurements
-
-## BUNDLER
-.bundle
-
-## PROJECT::SPECIFIC
-/vendor
diff --git a/.rspec b/.rspec
deleted file mode 100644
index 2ac7934..0000000
--- a/.rspec
+++ /dev/null
@@ -1,4 +0,0 @@
---color
---format progress
---warnings
---order random
diff --git a/.rubocop.yml b/.rubocop.yml
deleted file mode 100644
index edae650..0000000
--- a/.rubocop.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-AllCops:
- Include:
- - 'Gemfile'
- Exclude:
- - 'Gemfile.devtools'
- - 'vendor/**/*'
- - 'benchmarks/**/*'
- - 'tmp/**/*'
- TargetRubyVersion: 2.5
diff --git a/Changelog.md b/Changelog.md
deleted file mode 100644
index eca2e64..0000000
--- a/Changelog.md
+++ /dev/null
@@ -1,156 +0,0 @@
-# v0.4.7 2020-01-03
-
-* Add support for endless ranges
-* Change to allow parser 2.7, even while syntax is not yet supported.
- This reduces downstream complexity.
-
-# v0.4.6 2020-01-02
-
-* Upgrades to allow parser dependency to ~> 2.6.5
-
-# v0.4.5 2019-05-10
-
-* Bump parser dependency to ~> 2.6.3
-
-# v0.4.4 2019-03-27
-
-* Bump parser dependency to ~> 2.6.2
-
-# v0.4.3 2019-02-24
-
-* Bump parser dependency to ~> 2.6.0
-
-# v0.4.2 2018-12-04
-
-* Drop hard ruby version requirement. Still officially I'll only support 2.5.
-
-# v0.4.1 2018-12-03
-
-* Fix unparsing of `def foo(bar: bar())`
-
-# v0.4.0 2018-12-03
-
-* Change to modern AST format.
-* Add experimental `Unparser.{parser,parse,parse_with_comments}`
-
-# v0.3.0 2018-11-16
-
-* Drop support for Ruby < 2.5
-
-# v0.2.7 2018-07-18
-
-* Add emitters for `__FILE__` and `__LINE__`
- https://github.com/mbj/unparser/pull/70
-
-# v0.2.7 2018-02-09
-
-* Allow ruby_parser 2.5
-
-# v0.2.6 2017-05-30
-
-* Reduce memory consumption via not requirering all possible parsers
-* Allow ruby 2.4
-* Update parser dependency
-
-# v0.2.5 2016-01-24
-
-* Add support for ruby 2.3
-* Bump parser dependency to ~>2.3.0
-* Trade uglier for more correct dstring / dsyms
-* Drop support for ruby < 2.1
-
-# v0.2.4 2015-05-30
-
-* Relax parser dependency to ~>2.2.2
-
-# v0.2.3 2015-04-28
-
-* Compatibility with parser ~>2.2.2, >2.2.2.2
-
-# v0.2.2 2015-01-14
-
-* Really add back unofficial support for 1.9.3
-
-# v0.2.1 2015-01-14
-
-* Add back unofficial support for 1.9.3
-
-# v0.2.0 2015-01-12
-
-* Bump required ruby version to 2.0.0
-
-# v0.1.17 2015-01-10
-
-* Fix jruby complex / rational generation edge case
-* Support generation under MRI 2.2
-
-# v0.1.16 2014-11-07
-
-* Add emitter for complex and rational literals
-* Fix edge cases for MLHS
-* Fix differencies from 2.2.pre7 series of parser
-
-# v0.1.15 2014-09-24
-
-* Handle syntax edge case for MRI 2.1.3 parser.
-
-# v0.1.14 2014-06-15
-
-* Fix emitter to correctly unparse foo[] = 1
-
-# v0.1.13 2014-06-08
-
-* Add support for rubinius.
-
-# v0.1.12 2014-04-13
-
-* Add support for 2.1 kwsplat
-
-# v0.1.11 2014-04-11
-
-* Fix performance on local variable scope inspection
-
-# v0.1.10 2014-04-06
-
-* Fix emit of inline rescues in combination with control flow keywords.
-* Begin corpus testing on rake ci against rubyspec
-
-# v0.1.9 2014-01-14
-
-* Fix emit of proc { |(a)| }
-
-# v0.1.8 2014-01-11
-
-* Fix all bugs found while round tripping rubyspec.
-
-# v0.1.7 2014-01-03
-
-* Add back support for root nodes of type resbody https://github.com/mbj/unparser/issues/24
-
-# v0.1.6 2013-12-31
-
-* Emit 1.9 style hashes where possible: https://github.com/mbj/unparser/pull/23
-* Fix invalid quoting of hash keys: https://github.com/mbj/unparser/issues/22
-* Fix crash on take before introduced by code refactorings: https://github.com/mbj/unparser/issues/20
-* Fix crash on comment reproduction https://github.com/mbj/unparser/issues/17
-
-# v0.1.5 2013-11-01
-
-* Fix crash with comment reproduction.
-
-# v0.1.4 2013-11-01
-
-* Code cleanups.
-* Remove warnings.
-
-# v0.0.3 2013-06-17
-
-* Adjust to changes in parser 2.0.0.beta5 => beta6
-
-# v0.0.2 2013-06-17
-
-Crappy release
-
-# v0.0.1 2013-06-15
-
-Initial release
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index 1419592..0000000
--- a/Gemfile
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-source 'https://rubygems.org'
-
-gemspec
-
-source 'https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev' do
- gem 'mutant-license'
-end
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index 18e6ee0..0000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,181 +0,0 @@
-PATH
- remote: .
- specs:
- unparser (0.4.7)
- abstract_type (~> 0.0.7)
- adamantium (~> 0.2.0)
- concord (~> 0.1.5)
- diff-lcs (~> 1.3)
- equalizer (~> 0.0.9)
- parser (>= 2.6.5)
- procto (~> 0.0.2)
-
-GEM
- remote: https://rubygems.org/
- remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/
- specs:
- abstract_type (0.0.7)
- adamantium (0.2.0)
- ice_nine (~> 0.11.0)
- memoizable (~> 0.4.0)
- anima (0.3.1)
- abstract_type (~> 0.0.7)
- adamantium (~> 0.2)
- equalizer (~> 0.0.11)
- ast (2.4.0)
- axiom-types (0.1.1)
- descendants_tracker (~> 0.0.4)
- ice_nine (~> 0.11.0)
- thread_safe (~> 0.3, >= 0.3.1)
- codeclimate-engine-rb (0.4.1)
- virtus (~> 1.0)
- coercible (1.0.0)
- descendants_tracker (~> 0.0.1)
- concord (0.1.5)
- adamantium (~> 0.2.0)
- equalizer (~> 0.0.9)
- descendants_tracker (0.0.4)
- thread_safe (~> 0.3, >= 0.3.1)
- devtools (0.1.24)
- abstract_type (~> 0.0.7)
- adamantium (~> 0.2.0)
- anima (~> 0.3.0)
- concord (~> 0.1.5)
- flay (~> 2.12.0)
- flog (~> 4.6.2)
- procto (~> 0.0.3)
- rake (~> 12.3.0)
- reek (~> 5.3.0)
- rspec (~> 3.8.0)
- rspec-core (~> 3.8.0)
- rspec-its (~> 1.2.0)
- rubocop (~> 0.61.1)
- simplecov (~> 0.16.1)
- yard (~> 0.9.16)
- yardstick (~> 0.9.9)
- diff-lcs (1.3)
- docile (1.3.2)
- equalizer (0.0.11)
- erubis (2.7.0)
- flay (2.12.1)
- erubis (~> 2.7.0)
- path_expander (~> 1.0)
- ruby_parser (~> 3.0)
- sexp_processor (~> 4.0)
- flog (4.6.4)
- path_expander (~> 1.0)
- ruby_parser (~> 3.1, > 3.1.0)
- sexp_processor (~> 4.8)
- ice_nine (0.11.2)
- jaro_winkler (1.5.4)
- json (2.3.0)
- kwalify (0.7.2)
- memoizable (0.4.2)
- thread_safe (~> 0.3, >= 0.3.1)
- morpher (0.2.6)
- abstract_type (~> 0.0.7)
- adamantium (~> 0.2.0)
- anima (~> 0.3.0)
- ast (~> 2.2)
- concord (~> 0.1.5)
- equalizer (~> 0.0.9)
- ice_nine (~> 0.11.0)
- procto (~> 0.0.2)
- mprelude (0.1.0)
- abstract_type (~> 0.0.7)
- adamantium (~> 0.2.0)
- concord (~> 0.1.5)
- equalizer (~> 0.0.9)
- ice_nine (~> 0.11.1)
- procto (~> 0.0.2)
- mutant (0.9.4)
- abstract_type (~> 0.0.7)
- adamantium (~> 0.2.0)
- anima (~> 0.3.1)
- ast (~> 2.2)
- concord (~> 0.1.5)
- diff-lcs (~> 1.3)
- equalizer (~> 0.0.9)
- ice_nine (~> 0.11.1)
- memoizable (~> 0.4.2)
- mprelude (~> 0.1.0)
- parser (~> 2.6.5)
- procto (~> 0.0.2)
- unparser (~> 0.4.6)
- mutant-license (0.1.0)
- mutant-rspec (0.9.4)
- mutant (~> 0.9.4)
- rspec-core (>= 3.8.0, < 4.0.0)
- parallel (1.19.1)
- parser (2.6.5.0)
- ast (~> 2.4.0)
- path_expander (1.1.0)
- powerpack (0.1.2)
- procto (0.0.3)
- psych (3.1.0)
- rainbow (3.0.0)
- rake (12.3.3)
- reek (5.3.2)
- codeclimate-engine-rb (~> 0.4.0)
- kwalify (~> 0.7.0)
- parser (>= 2.5.0.0, < 2.7, != 2.5.1.1)
- psych (~> 3.1.0)
- rainbow (>= 2.0, < 4.0)
- rspec (3.8.0)
- rspec-core (~> 3.8.0)
- rspec-expectations (~> 3.8.0)
- rspec-mocks (~> 3.8.0)
- rspec-core (3.8.2)
- rspec-support (~> 3.8.0)
- rspec-expectations (3.8.6)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.8.0)
- rspec-its (1.2.0)
- rspec-core (>= 3.0.0)
- rspec-expectations (>= 3.0.0)
- rspec-mocks (3.8.2)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.8.0)
- rspec-support (3.8.3)
- rubocop (0.61.1)
- jaro_winkler (~> 1.5.1)
- parallel (~> 1.10)
- parser (>= 2.5, != 2.5.1.1)
- powerpack (~> 0.1)
- rainbow (>= 2.2.2, < 4.0)
- ruby-progressbar (~> 1.7)
- unicode-display_width (~> 1.4.0)
- ruby-progressbar (1.10.1)
- ruby_parser (3.14.1)
- sexp_processor (~> 4.9)
- sexp_processor (4.13.0)
- simplecov (0.16.1)
- docile (~> 1.1)
- json (>= 1.8, < 3)
- simplecov-html (~> 0.10.0)
- simplecov-html (0.10.2)
- thread_safe (0.3.6)
- unicode-display_width (1.4.1)
- virtus (1.0.5)
- axiom-types (~> 0.1)
- coercible (~> 1.0)
- descendants_tracker (~> 0.0, >= 0.0.3)
- equalizer (~> 0.0, >= 0.0.9)
- yard (0.9.22)
- yardstick (0.9.9)
- yard (~> 0.8, >= 0.8.7.2)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- anima (~> 0.3.1)
- devtools (~> 0.1.23)
- morpher (~> 0.2.6)
- mutant (~> 0.9.4)
- mutant-license!
- mutant-rspec (~> 0.9.4)
- unparser!
-
-BUNDLED WITH
- 1.17.3
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 44863d7..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2013 Markus Schirp
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 4d171ba..368ca18 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,24 @@
unparser
========
-[![Build Status](https://secure.travis-ci.org/mbj/unparser.svg?branch=master)](http://travis-ci.org/mbj/unparser)
-[![Code Climate](https://codeclimate.com/github/mbj/unparser.svg)](https://codeclimate.com/github/mbj/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 whitequarks [parser](https://github.com/whitequark/parser).
+Generate equivalent source for ASTs from [parser](https://github.com/whitequark/parser).
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 >= 2.5
+* Only support for Ruby >= 2.7
-It serves well for [mutant](https://github.com/mbj/mutant) mutators and the in-memory vendoring for self hosting,
-and other tooling.
+Notable Users:
+
+* [mutant](https://github.com/mbj/mutant) - Code review engine via mutation testing.
+* [ruby-next](https://github.com/ruby-next/ruby-next) - Ruby Syntax Backports.
+* Many other [reverse-dependencies](https://rubygems.org/gems/unparser/reverse_dependencies).
+
+(if you want your tool to be mentioned here please PR the addition with a TLDR of your use case).
Public API:
-----------
@@ -87,16 +91,26 @@ RUBY
generated = Unparser.unparse(node) # ["foo", "bar"], NOT %w[foo bar] !
-code == generated # false, not identical code
+code == generated # false, not identical code
Unparser.parse(generated) == node # true, but identical AST
```
Summary: unparser does not reproduce your source! It produces equivalent source.
+Ruby Versions:
+--------------
+
+Unparsers primay reason for existance is mutant and its
+supported [Ruby-Versions](https://github.com/mbj/mutant#ruby-versions).
+
+Basically: All non EOL MRI releases.
+
+If you need to generate Ruby Syntax outside of this band feel free to contact me (email in gemspec).
+
Testing:
--------
-Unparser currently successfully round trips almost all ruby code around. Using MRI-2.5.x.
+Unparser currently successfully round trips almost all ruby code around. Using Ruby >= 2.6.
If there is a non round trippable example that is NOT subjected to known [Limitations](#limitations).
please report a bug.
@@ -154,6 +168,18 @@ People
Various people contributed to this repository. See [Contributors](https://github.com/mbj/unparser/graphs/contributors).
+Included Libraries
+------------------
+
+For dependency reduction reasons unparser ships vendored (and reduced) versions of:
+
+* [abstract_type](https://github.com/mbj/concord) -> Unparser::AbstractType
+* [adamantium](https://github.com/dkubb/adamantium) -> Unparser::Adamantium
+* [anima](https://github.com/mbj/concord) -> Unparser::Anima
+* [concord](https://github.com/mbj/concord) -> Unparser::Concord
+* [memoizable](https://github.com/dkubb/memoizable) -> Unparser::Adamantium
+* [mprelude](https://github.com/dkubb/memoizable) -> Unparser::Either
+
Contributing
-------------
@@ -161,10 +187,15 @@ Contributing
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a
future version unintentionally.
-* Commit, do not mess with Rakefile or version
+* Commit, do not mess with version
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.
+Known Users
+-------------
+
+* [RailsRocket](https://www.railsrocket.app) - A no-code app builder that creates Rails apps
+
License
-------
diff --git a/Rakefile b/Rakefile
deleted file mode 100644
index 4e68916..0000000
--- a/Rakefile
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'devtools'
-Devtools.init_rake_tasks
-
-Rake.application.load_imports
-task('metrics:mutant').clear
-
-namespace :metrics do
- task mutant: :coverage do
- args = %w[
- bundle exec mutant
- --ignore-subject Unparser::Buffer#initialize
- --include lib
- --require unparser
- --use rspec
- --zombie
- --since HEAD~1
- ]
- args.concat(%w[--jobs 4]) if ENV.key?('CIRCLECI')
-
- system(*args.concat(%w[-- Unparser*])) or fail "Mutant task failed"
- end
-end
diff --git a/bin/unparser b/bin/unparser
index 1258efa..26d049e 100755
--- a/bin/unparser
+++ b/bin/unparser
@@ -2,9 +2,9 @@
# frozen_string_literal: true
trap('INT') do |status|
- exit! 128 + status
+ exit! status + 128
end
-require 'unparser/cli'
+require 'unparser'
exit Unparser::CLI.run(ARGV)
diff --git a/config/devtools.yml b/config/devtools.yml
deleted file mode 100644
index f7e1f61..0000000
--- a/config/devtools.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-unit_test_timeout: 1.0
diff --git a/config/flay.yml b/config/flay.yml
deleted file mode 100644
index 7b5e3d1..0000000
--- a/config/flay.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-threshold: 13
-total_score: 638
diff --git a/config/flog.yml b/config/flog.yml
deleted file mode 100644
index 1174a8f..0000000
--- a/config/flog.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-threshold: 21.3
diff --git a/config/mutant.yml b/config/mutant.yml
deleted file mode 100644
index 8c4cfe4..0000000
--- a/config/mutant.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-includes:
-- lib
-integration: rspec
-requires:
-- unparser
diff --git a/config/reek.yml b/config/reek.yml
deleted file mode 100644
index 3826d7f..0000000
--- a/config/reek.yml
+++ /dev/null
@@ -1,98 +0,0 @@
----
-detectors:
- Attribute:
- enabled: false
- exclude: []
- BooleanParameter:
- enabled: true
- exclude: []
- ClassVariable:
- enabled: true
- exclude: []
- ControlParameter:
- enabled: true
- exclude: []
- DataClump:
- enabled: true
- exclude: []
- max_copies: 2
- min_clump_size: 2
- DuplicateMethodCall:
- enabled: false
- exclude: []
- max_calls: 1
- allow_calls: []
- FeatureEnvy:
- enabled: false
- # Buggy smell detector
- IrresponsibleModule:
- enabled: false
- exclude: []
- LongParameterList:
- enabled: true
- exclude: []
- max_params: 2
- LongYieldList:
- enabled: true
- exclude: []
- max_params: 2
- NestedIterators:
- enabled: true
- exclude: []
- max_allowed_nesting: 1
- ignore_iterators: []
- NilCheck:
- enabled: false
- RepeatedConditional:
- enabled: true
- exclude: []
- max_ifs: 1
- TooManyInstanceVariables:
- enabled: true
- exclude: []
- max_instance_variables: 3
- TooManyMethods:
- enabled: true
- exclude: []
- max_methods: 10
- TooManyStatements:
- enabled: true
- exclude: []
- max_statements: 7
- UncommunicativeMethodName:
- enabled: true
- exclude: []
- reject:
- - '/^[a-z]$/'
- - '/[0-9]$/'
- - '/[A-Z]/'
- accept: []
- UncommunicativeModuleName:
- enabled: true
- exclude: []
- reject:
- - '/^.$/'
- - '/[0-9]$/'
- accept: []
- UncommunicativeParameterName:
- enabled: true
- exclude: []
- reject:
- - '/^.$/'
- - '/[0-9]$/'
- - '/[A-Z]/'
- accept: []
- UncommunicativeVariableName:
- enabled: true
- exclude: []
- reject:
- - '/^.$/'
- - '/[0-9]$/'
- - '/[A-Z]/'
- accept: ['force_utf32']
- UnusedParameters:
- enabled: true
- exclude: []
- UtilityFunction:
- enabled: true
- exclude: []
diff --git a/config/rubocop.yml b/config/rubocop.yml
deleted file mode 100644
index eb0647a..0000000
--- a/config/rubocop.yml
+++ /dev/null
@@ -1,122 +0,0 @@
-inherit_from: ../.rubocop.yml
-
-AllCops:
- Include:
- - 'lib/unparser.rb'
- - 'lib/unparser/**/*.rb'
- - '**/*.rake'
- - 'Gemfile'
- - 'Gemfile.triage'
-
-# Avoid parameter lists longer than five parameters.
-ParameterLists:
- Max: 3
- CountKeywordArgs: true
-
-MethodLength:
- CountComments: false
- Max: 17
-
-AbcSize:
- Max: 18
-
-# Avoid more than `Max` levels of nesting.
-BlockNesting:
- Max: 3
-
-# Align with the style guide.
-CollectionMethods:
- PreferredMethods:
- collect: 'map'
- inject: 'reduce'
- find: 'detect'
- find_all: 'select'
-
-# Limit line length
-LineLength:
- Max: 113 # TODO: lower to 79 once the rubocop branch in shared/Gemfile is removed
-
-ClassLength:
- Max: 204
-
-# Prefer modifiers and explicit if statements over returning early for small methods
-GuardClause:
- Enabled: false
-
-Metrics/BlockLength:
- Exclude:
- # Ignore RSpec DSL
- - spec/**/*
-
-# Flags freezes for singletons that could still be mutated like Regexps
-RedundantFreeze:
- Enabled: false
-
-# Allow Fixnum and Bignum. This Gem supports versions before 2.4
-UnifiedInteger:
- Enabled: false
-
-# Disabled because of indenting with private keyword in class bodies.
-IndentationWidth:
- Enabled: false
-
-# I like raise more
-SignalException:
- Enabled: false
-
-# False positive in unparser source
-OneLineConditional:
- Enabled: false
-
-Documentation:
- Enabled: false
-
-# Disable documentation checking until a class needs to be documented once
-Documentation:
- Enabled: false
-
-# Do not favor modifier if/unless usage when you have a single-line body
-IfUnlessModifier:
- Enabled: false
-
-# Allow case equality operator (in limited use within the specs)
-CaseEquality:
- Enabled: false
-
-# Constants do not always have to use SCREAMING_SNAKE_CASE
-ConstantName:
- Enabled: false
-
-# Not all trivial readers/writers can be defined with attr_* methods
-TrivialAccessors:
- Enabled: false
-
-# I like to have an empty line before closing the currently opened body
-EmptyLinesAroundBlockBody:
- Enabled: false
-
-EmptyLinesAroundClassBody:
- Enabled: false
-
-EmptyLinesAroundModuleBody:
- Enabled: false
-
-# I like my style more
-AccessModifierIndentation:
- Enabled: false
-
-Style/CommentedKeyword:
- Enabled: false
-
-Style/MixinGrouping:
- Enabled: false
-
-Lint/BooleanSymbol:
- Enabled: false
-
-Style/AccessModifierDeclarations:
- Enabled: false
-
-Layout/AlignHash:
- EnforcedColonStyle: table
- EnforcedHashRocketStyle: table
diff --git a/config/yardstick.yml b/config/yardstick.yml
deleted file mode 100644
index a6b63e8..0000000
--- a/config/yardstick.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-threshold: 100
diff --git a/debian/changelog b/debian/changelog
index 377444c..29b5824 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ruby-unparser (0.6.8-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Wed, 28 Jun 2023 01:26:01 -0000
+
ruby-unparser (0.4.7-2) unstable; urgency=medium
* Source-only upload.
diff --git a/lib/unparser.rb b/lib/unparser.rb
index 18fd26a..0938a81 100644
--- a/lib/unparser.rb
+++ b/lib/unparser.rb
@@ -1,22 +1,50 @@
# frozen_string_literal: true
-require 'set'
-require 'abstract_type'
-require 'procto'
-require 'concord'
+require 'diff/lcs'
+require 'diff/lcs/hunk'
+require 'optparse'
require 'parser/current'
+require 'set'
+
+require 'unparser/equalizer'
+require 'unparser/adamantium'
+require 'unparser/adamantium/method_builder'
+require 'unparser/abstract_type'
+
+require 'unparser/concord'
+require 'unparser/either'
+require 'unparser/anima'
+require 'unparser/anima/attribute'
+require 'unparser/anima/error'
# Library namespace
module Unparser
# Unparser specific AST builder defaulting to modern AST format
class Builder < Parser::Builders::Default
modernize
+
+ def initialize
+ super
+
+ self.emit_file_line_as_literals = false
+ end
end
EMPTY_STRING = ''.freeze
EMPTY_ARRAY = [].freeze
- private_constant(*constants(false))
+ private_constant(*constants(false) - %i[Adamantium AbstractType Anima Concord Either Equalizer Memoizable])
+
+ # Error raised when unparser encounters an invalid AST
+ class InvalidNodeError < RuntimeError
+ attr_reader :node
+
+ def initialize(message, node)
+ super(message)
+ @node = node
+ freeze
+ end
+ end
# Unparse an AST (and, optionally, comments) into a string
#
@@ -25,26 +53,70 @@ module Unparser
#
# @return [String]
#
- # @api private
+ # @raise InvalidNodeError
+ # if the node passed is invalid
#
+ # @api public
def self.unparse(node, comment_array = [])
- node = Preprocessor.run(node)
- buffer = Buffer.new
- comments = Comments.new(comment_array)
- root = Emitter::Root.new(Parser::AST::Node.new(:root, [node]), buffer, comments)
- Emitter.emitter(node, root).write_to_buffer
- buffer.content
+ return '' if node.nil?
+
+ Buffer.new.tap do |buffer|
+ Emitter::Root.new(
+ buffer,
+ node,
+ Comments.new(comment_array)
+ ).write_to_buffer
+ end.content
+ end
+
+ # Unparse with validation
+ #
+ # @param [Parser::AST::Node, nil] node
+ # @param [Array] comment_array
+ #
+ # @return [Either<Validation,String>]
+ def self.unparse_validate(node, comment_array = [])
+ generated = unparse(node, comment_array)
+ validation = Validation.from_string(generated)
+
+ if validation.success?
+ Either::Right.new(generated)
+ else
+ Either::Left.new(validation)
+ end
+ end
+
+ # Unparse capturing errors
+ #
+ # This is mostly useful for writing testing tools against unparser.
+ #
+ # @param [Parser::AST::Node, nil] node
+ #
+ # @return [Either<Exception, String>]
+ def self.unparse_either(node)
+ Either.wrap_error(Exception) { unparse(node) }
end
# Parse string into AST
#
# @param [String] source
#
- # @return [Parser::AST::Node]
+ # @return [Parser::AST::Node, nil]
def self.parse(source)
parser.parse(buffer(source))
end
+ # Parse string into either syntax error or AST
+ #
+ # @param [String] source
+ #
+ # @return [Either<Parser::SyntaxError, (Parser::ASTNode, nil)>]
+ def self.parse_either(source)
+ Either.wrap_error(Parser::SyntaxError) do
+ parser.parse(buffer(source))
+ end
+ end
+
# Parse string into AST, with comments
#
# @param [String] source
@@ -59,98 +131,110 @@ module Unparser
# @return [Parser::Base]
#
# @api private
- #
- # ignore :reek:NestedIterators
def self.parser
Parser::CurrentRuby.new(Builder.new).tap do |parser|
parser.diagnostics.tap do |diagnostics|
diagnostics.all_errors_are_fatal = true
- diagnostics.consumer = method(:consume_diagnostic)
end
end
end
- # Consume diagnostic
- #
- # @param [Parser::Diagnostic] diagnostic
- #
- # @return [undefined]
- def self.consume_diagnostic(diagnostic)
- Kernel.warn(diagnostic.render)
- end
- private_class_method :consume_diagnostic
-
# Construct a parser buffer from string
#
# @param [String] source
#
# @return [Parser::Source::Buffer]
- def self.buffer(source)
- Parser::Source::Buffer.new('(string)').tap do |buffer|
- buffer.source = source
- end
+ def self.buffer(source, identification = '(string)')
+ Parser::Source::Buffer.new(identification, source: source)
end
end # Unparser
-require 'unparser/buffer'
require 'unparser/node_helpers'
-require 'unparser/preprocessor'
+require 'unparser/ast'
+require 'unparser/ast/local_variable_scope'
+require 'unparser/buffer'
+require 'unparser/generation'
+require 'unparser/color'
require 'unparser/comments'
require 'unparser/constants'
+require 'unparser/diff'
require 'unparser/dsl'
-require 'unparser/ast'
-require 'unparser/ast/local_variable_scope'
require 'unparser/emitter'
-require 'unparser/emitter/literal'
-require 'unparser/emitter/literal/primitive'
-require 'unparser/emitter/literal/singleton'
-require 'unparser/emitter/literal/dynamic'
-require 'unparser/emitter/literal/regexp'
-require 'unparser/emitter/literal/array'
-require 'unparser/emitter/literal/hash'
-require 'unparser/emitter/literal/range'
-require 'unparser/emitter/literal/dynamic_body'
-require 'unparser/emitter/literal/execute_string'
-require 'unparser/emitter/meta'
-require 'unparser/emitter/send'
-require 'unparser/emitter/send/unary'
-require 'unparser/emitter/send/binary'
-require 'unparser/emitter/send/regular'
-require 'unparser/emitter/send/conditional'
-require 'unparser/emitter/send/attribute_assignment'
-require 'unparser/emitter/block'
-require 'unparser/emitter/assignment'
-require 'unparser/emitter/variable'
-require 'unparser/emitter/splat'
-require 'unparser/emitter/cbase'
+require 'unparser/emitter/alias'
+require 'unparser/emitter/args'
require 'unparser/emitter/argument'
+require 'unparser/emitter/array'
+require 'unparser/emitter/array_pattern'
+require 'unparser/emitter/assignment'
require 'unparser/emitter/begin'
-require 'unparser/emitter/flow_modifier'
-require 'unparser/emitter/undef'
-require 'unparser/emitter/def'
+require 'unparser/emitter/binary'
+require 'unparser/emitter/block'
+require 'unparser/emitter/case'
+require 'unparser/emitter/case_guard'
+require 'unparser/emitter/case_match'
+require 'unparser/emitter/cbase'
require 'unparser/emitter/class'
-require 'unparser/emitter/module'
-require 'unparser/emitter/op_assign'
+require 'unparser/emitter/const_pattern'
+require 'unparser/emitter/def'
require 'unparser/emitter/defined'
+require 'unparser/emitter/dstr'
+require 'unparser/emitter/dsym'
+require 'unparser/emitter/flipflop'
+require 'unparser/emitter/float'
+require 'unparser/emitter/flow_modifier'
+require 'unparser/emitter/for'
+require 'unparser/emitter/hash'
+require 'unparser/emitter/hash_pattern'
require 'unparser/emitter/hookexe'
-require 'unparser/emitter/super'
-require 'unparser/emitter/retry'
-require 'unparser/emitter/redo'
require 'unparser/emitter/if'
-require 'unparser/emitter/alias'
-require 'unparser/emitter/yield'
-require 'unparser/emitter/binary'
-require 'unparser/emitter/case'
-require 'unparser/emitter/for'
-require 'unparser/emitter/repetition'
-require 'unparser/emitter/root'
-require 'unparser/emitter/match'
-require 'unparser/emitter/empty'
-require 'unparser/emitter/flipflop'
-require 'unparser/emitter/rescue'
-require 'unparser/emitter/resbody'
-require 'unparser/emitter/ensure'
+require 'unparser/emitter/in_match'
+require 'unparser/emitter/in_pattern'
require 'unparser/emitter/index'
+require 'unparser/emitter/kwbegin'
require 'unparser/emitter/lambda'
+require 'unparser/emitter/masgn'
+require 'unparser/emitter/match'
+require 'unparser/emitter/match_alt'
+require 'unparser/emitter/match_as'
+require 'unparser/emitter/match_rest'
+require 'unparser/emitter/match_var'
+require 'unparser/emitter/mlhs'
+require 'unparser/emitter/module'
+require 'unparser/emitter/op_assign'
+require 'unparser/emitter/pin'
+require 'unparser/emitter/primitive'
+require 'unparser/emitter/range'
+require 'unparser/emitter/regexp'
+require 'unparser/emitter/repetition'
+require 'unparser/emitter/rescue'
+require 'unparser/emitter/root'
+require 'unparser/emitter/send'
+require 'unparser/emitter/simple'
+require 'unparser/emitter/splat'
+require 'unparser/emitter/super'
+require 'unparser/emitter/undef'
+require 'unparser/emitter/variable'
+require 'unparser/emitter/xstr'
+require 'unparser/emitter/yield'
+require 'unparser/emitter/kwargs'
+require 'unparser/emitter/pair'
+require 'unparser/emitter/find_pattern'
+require 'unparser/emitter/match_pattern'
+require 'unparser/emitter/match_pattern_p'
+require 'unparser/writer'
+require 'unparser/writer/binary'
+require 'unparser/writer/dynamic_string'
+require 'unparser/writer/resbody'
+require 'unparser/writer/rescue'
+require 'unparser/writer/send'
+require 'unparser/writer/send/attribute_assignment'
+require 'unparser/writer/send/binary'
+require 'unparser/writer/send/regular'
+require 'unparser/writer/send/unary'
+require 'unparser/node_details'
+require 'unparser/node_details/send'
+require 'unparser/cli'
+
+require 'unparser/validation'
# make it easy for zombie
require 'unparser/finalize'
diff --git a/lib/unparser/abstract_type.rb b/lib/unparser/abstract_type.rb
new file mode 100644
index 0000000..2a91ed4
--- /dev/null
+++ b/lib/unparser/abstract_type.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+module Unparser
+ # Module to allow class and methods to be abstract
+ #
+ # Original code before vendoring and reduction from: https://github.com/dkubb/abstract_type.
+ module AbstractType
+
+ # Hook called when module is included
+ #
+ # @param [Module] descendant
+ # the module or class including AbstractType
+ #
+ # @return [undefined]
+ #
+ # @api private
+ def self.included(descendant)
+ super
+ create_new_method(descendant)
+ descendant.extend(AbstractMethodDeclarations)
+ end
+
+ private_class_method :included
+
+ # Define the new method on the abstract type
+ #
+ # Ensures that the instance cannot be of the abstract type
+ # and must be a descendant.
+ #
+ # @param [Class] abstract_class
+ #
+ # @return [undefined]
+ #
+ # @api private
+ def self.create_new_method(abstract_class)
+ abstract_class.define_singleton_method(:new) do |*args, &block|
+ if equal?(abstract_class)
+ fail NotImplementedError, "#{self} is an abstract type"
+ else
+ super(*args, &block)
+ end
+ end
+ end
+
+ private_class_method :create_new_method
+
+ module AbstractMethodDeclarations
+
+ # Create abstract instance methods
+ #
+ # @example
+ # class Foo
+ # include AbstractType
+ #
+ # # Create an abstract instance method
+ # abstract_method :some_method
+ # end
+ #
+ # @param [Array<#to_s>] names
+ #
+ # @return [self]
+ #
+ # @api public
+ def abstract_method(*names)
+ names.each(&method(:create_abstract_instance_method))
+ self
+ end
+
+ # Create abstract singleton methods
+ #
+ # @example
+ # class Foo
+ # include AbstractType
+ #
+ # # Create an abstract instance method
+ # abstract_singleton_method :some_method
+ # end
+ #
+ # @param [Array<#to_s>] names
+ #
+ # @return [self]
+ #
+ # @api private
+ def abstract_singleton_method(*names)
+ names.each(&method(:create_abstract_singleton_method))
+ self
+ end
+
+ private
+
+ # Create abstract singleton method
+ #
+ # @param [#to_s] name
+ # the name of the method to create
+ #
+ # @return [undefined]
+ #
+ # @api private
+ def create_abstract_singleton_method(name)
+ define_singleton_method(name) do |*|
+ fail NotImplementedError, "#{self}.#{name} is not implemented"
+ end
+ end
+
+ # Create abstract instance method
+ #
+ # @param [#to_s] name
+ # the name of the method to create
+ #
+ # @return [undefined]
+ #
+ # @api private
+ def create_abstract_instance_method(name)
+ define_method(name) do |*|
+ fail NotImplementedError, "#{self.class}##{name} is not implemented"
+ end
+ end
+
+ end # AbstractMethodDeclarations
+ end # AbstractType
+end # Unparser
diff --git a/lib/unparser/adamantium.rb b/lib/unparser/adamantium.rb
new file mode 100644
index 0000000..f63c446
--- /dev/null
+++ b/lib/unparser/adamantium.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+module Unparser
+ # Allows objects to be made immutable
+ #
+ # Original code before vendoring and reduction from: https://github.com/dkubb/adamantium.
+ module Adamantium
+ module InstanceMethods
+ # A noop #dup for immutable objects
+ #
+ # @return [self]
+ #
+ # @api public
+ def dup
+ self
+ end
+
+ # Freeze the object
+ #
+ # @return [Object]
+ #
+ # @api public
+ def freeze
+ memoized_method_cache
+ super()
+ end
+
+ private
+
+ def memoized_method_cache
+ @memoized_method_cache ||= Memory.new({})
+ end
+
+ end # InstanceMethods
+
+ # Storage for memoized methods
+ class Memory
+
+ # Initialize the memory storage for memoized methods
+ #
+ # @return [undefined]
+ #
+ # @api private
+ def initialize(values)
+ @values = values
+ @monitor = Monitor.new
+ freeze
+ end
+
+ # Fetch the value from memory, or evaluate if it does not exist
+ #
+ # @param [Symbol] name
+ #
+ # @yieldreturn [Object]
+ # the value to memoize
+ #
+ # @api public
+ def fetch(name)
+ @values.fetch(name) do # check for the key
+ @monitor.synchronize do # acquire a lock if the key is not found
+ @values.fetch(name) do # recheck under lock
+ @values[name] = yield # set the value
+ end
+ end
+ end
+ end
+ end # Memory
+
+ # Methods mixed in to adamantium classes
+ module ClassMethods
+
+ # Instantiate a new frozen object
+ #
+ # @return [Object]
+ #
+ # @api public
+ def new(*)
+ super.freeze
+ end
+
+ end # ClassMethods
+
+ # Methods mixed in to adamantium modules
+ module ModuleMethods
+
+ # Memoize a list of methods
+ #
+ # @param [Array<#to_s>] methods
+ # a list of methods to memoize
+ #
+ # @return [self]
+ #
+ # @api public
+ def memoize(*methods)
+ methods.each(&method(:memoize_method))
+ self
+ end
+
+ # Test if method is memoized
+ #
+ # @param [Symbol] name
+ #
+ # @return [Bool]
+ def memoized?(method_name)
+ memoized_methods.key?(method_name)
+ end
+
+ # Return unmemoized instance method
+ #
+ # @param [Symbol] name
+ #
+ # @return [UnboundMethod]
+ # the memoized method
+ #
+ # @raise [NameError]
+ # raised if the method is unknown
+ #
+ # @api public
+ def unmemoized_instance_method(method_name)
+ memoized_methods.fetch(method_name) do
+ fail ArgumentError, "##{method_name} is not memoized"
+ end
+ end
+
+ private
+
+ def memoize_method(method_name)
+ if memoized_methods.key?(method_name)
+ fail ArgumentError, "##{method_name} is already memoized"
+ end
+
+ memoized_methods[method_name] = MethodBuilder.new(self, method_name).call
+ end
+
+ def memoized_methods
+ @memoized_methods ||= {}
+ end
+
+ end # ModuleMethods
+
+ def self.included(descendant)
+ descendant.class_eval do
+ include InstanceMethods
+ extend ModuleMethods
+ extend ClassMethods if instance_of?(Class)
+ end
+ end
+ private_class_method :included
+ end # Adamantium
+end # Unparser
diff --git a/lib/unparser/adamantium/method_builder.rb b/lib/unparser/adamantium/method_builder.rb
new file mode 100644
index 0000000..7ae4b22
--- /dev/null
+++ b/lib/unparser/adamantium/method_builder.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Adamantium
+ # Build the memoized method
+ class MethodBuilder
+
+ # Raised when the method arity is invalid
+ class InvalidArityError < ArgumentError
+
+ # Initialize an invalid arity exception
+ #
+ # @param [Module] descendant
+ # @param [Symbol] method
+ # @param [Integer] arity
+ #
+ # @api private
+ def initialize(descendant, method, arity)
+ super("Cannot memoize #{descendant}##{method}, its arity is #{arity}")
+ end
+
+ end # InvalidArityError
+
+ # Raised when a block is passed to a memoized method
+ class BlockNotAllowedError < ArgumentError
+
+ # Initialize a block not allowed exception
+ #
+ # @param [Module] descendant
+ # @param [Symbol] method
+ #
+ # @api private
+ def initialize(descendant, method)
+ super("Cannot pass a block to #{descendant}##{method}, it is memoized")
+ end
+
+ end # BlockNotAllowedError
+
+ # Initialize an object to build a memoized method
+ #
+ # @param [Module] descendant
+ # @param [Symbol] method_name
+ #
+ # @return [undefined]
+ #
+ # @api private
+ def initialize(descendant, method_name)
+ @descendant = descendant
+ @method_name = method_name
+ @original_visibility = visibility
+ @original_method = @descendant.instance_method(@method_name)
+ assert_arity(@original_method.arity)
+ end
+
+ # Build a new memoized method
+ #
+ # @example
+ # method_builder.call # => creates new method
+ #
+ # @return [UnboundMethod]
+ #
+ # @api public
+ def call
+ remove_original_method
+ create_memoized_method
+ set_method_visibility
+ @original_method
+ end
+
+ private
+
+ def assert_arity(arity)
+ if arity.nonzero?
+ fail InvalidArityError.new(@descendant, @method_name, arity)
+ end
+ end
+
+ def remove_original_method
+ name = @method_name
+ @descendant.module_eval { undef_method(name) }
+ end
+
+ def create_memoized_method
+ name = @method_name
+ method = @original_method
+ @descendant.module_eval do
+ define_method(name) do |&block|
+ fail BlockNotAllowedError.new(self.class, name) if block
+
+ memoized_method_cache.fetch(name) do
+ method.bind(self).call.freeze
+ end
+ end
+ end
+ end
+
+ def set_method_visibility
+ @descendant.__send__(@original_visibility, @method_name)
+ end
+
+ def visibility
+ if @descendant.private_method_defined?(@method_name) then :private
+ elsif @descendant.protected_method_defined?(@method_name) then :protected
+ else
+ :public
+ end
+ end
+
+ end # MethodBuilder
+ end # Adamantium
+end # Unparser
diff --git a/lib/unparser/anima.rb b/lib/unparser/anima.rb
new file mode 100644
index 0000000..8618c9f
--- /dev/null
+++ b/lib/unparser/anima.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+module Unparser
+ # Original code before vendoring and reduction from: https://github.com/mbj/anima.
+ class Anima < Module
+ include Adamantium, Equalizer.new(:attributes)
+
+ # Return names
+ #
+ # @return [AttributeSet]
+ attr_reader :attributes
+
+ # Initialize object
+ #
+ # @return [undefined]
+ #
+ # rubocop:disable Lint/MissingSuper
+ def initialize(*names)
+ @attributes = names.uniq.map(&Attribute.public_method(:new)).freeze
+ end
+ # rubocop:enable Lint/MissingSuper
+
+ # Return new anima with attributes added
+ #
+ # @return [Anima]
+ #
+ # @example
+ # anima = Anima.new(:foo)
+ # anima.add(:bar) # equals Anima.new(:foo, :bar)
+ #
+ def add(*names)
+ new(attribute_names + names)
+ end
+
+ # Return new anima with attributes removed
+ #
+ # @return [Anima]
+ #
+ # @example
+ # anima = Anima.new(:foo, :bar)
+ # anima.remove(:bar) # equals Anima.new(:foo)
+ #
+ def remove(*names)
+ new(attribute_names - names)
+ end
+
+ # Return attributes hash for instance
+ #
+ # @param [Object] object
+ #
+ # @return [Hash]
+ def attributes_hash(object)
+ attributes.each_with_object({}) do |attribute, attributes_hash|
+ attributes_hash[attribute.name] = attribute.get(object)
+ end
+ end
+
+ # Return attribute names
+ #
+ # @return [Enumerable<Symbol>]
+ def attribute_names
+ attributes.map(&:name)
+ end
+ memoize :attribute_names
+
+ # Initialize instance
+ #
+ # @param [Object] object
+ #
+ # @param [Hash] attribute_hash
+ #
+ # @return [self]
+ def initialize_instance(object, attribute_hash)
+ assert_known_attributes(object.class, attribute_hash)
+ attributes.each do |attribute|
+ attribute.load(object, attribute_hash)
+ end
+ self
+ end
+
+ # Static instance methods for anima infected classes
+ module InstanceMethods
+ # Initialize an anima infected object
+ #
+ # @param [#to_h] attributes
+ # a hash that matches anima defined attributes
+ #
+ # @return [undefined]
+ #
+ # rubocop:disable Lint/MissingSuper
+ def initialize(attributes)
+ self.class.anima.initialize_instance(self, attributes)
+ end
+ # rubocop:enable Lint/MissingSuper
+
+ # Return a hash representation of an anima infected object
+ #
+ # @example
+ # anima.to_h # => { :foo => : bar }
+ #
+ # @return [Hash]
+ #
+ # @api public
+ def to_h
+ self.class.anima.attributes_hash(self)
+ end
+
+ # Return updated instance
+ #
+ # @example
+ # klass = Class.new do
+ # include Anima.new(:foo, :bar)
+ # end
+ #
+ # foo = klass.new(:foo => 1, :bar => 2)
+ # updated = foo.with(:foo => 3)
+ # updated.foo # => 3
+ # updated.bar # => 2
+ #
+ # @param [Hash] attributes
+ #
+ # @return [Anima]
+ #
+ # @api public
+ def with(attributes)
+ self.class.new(to_h.update(attributes))
+ end
+ end # InstanceMethods
+
+ private
+
+ # Infect the instance with anima
+ #
+ # @param [Class, Module] scope
+ #
+ # @return [undefined]
+ def included(descendant)
+ descendant.instance_exec(self, attribute_names) do |anima, names|
+ # Define anima method
+ define_singleton_method(:anima) { anima }
+
+ # Define instance methods
+ include InstanceMethods
+
+ # Define attribute readers
+ attr_reader(*names)
+
+ # Define equalizer
+ include Equalizer.new(*names)
+ end
+ end
+
+ # Fail unless keys in +attribute_hash+ matches #attribute_names
+ #
+ # @param [Class] klass
+ # the class being initialized
+ #
+ # @param [Hash] attribute_hash
+ # the attributes to initialize +object+ with
+ #
+ # @return [undefined]
+ #
+ # @raise [Error]
+ def assert_known_attributes(klass, attribute_hash)
+ keys = attribute_hash.keys
+
+ unknown = keys - attribute_names
+ missing = attribute_names - keys
+
+ unless unknown.empty? && missing.empty?
+ fail Error.new(klass, missing, unknown)
+ end
+ end
+
+ # Return new instance
+ #
+ # @param [Enumerable<Symbol>] attributes
+ #
+ # @return [Anima]
+ def new(attributes)
+ self.class.new(*attributes)
+ end
+ end # Anima
+end # Unparser
diff --git a/lib/unparser/anima/attribute.rb b/lib/unparser/anima/attribute.rb
new file mode 100644
index 0000000..ff6f0c4
--- /dev/null
+++ b/lib/unparser/anima/attribute.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Anima
+ # An attribute
+ class Attribute
+ include Adamantium, Equalizer.new(:name)
+
+ # Initialize attribute
+ #
+ # @param [Symbol] name
+ def initialize(name)
+ @name = name
+ @instance_variable_name = :"@#{name}"
+ end
+
+ # Return attribute name
+ #
+ # @return [Symbol]
+ attr_reader :name
+
+ # Return instance variable name
+ #
+ # @return [Symbol]
+ attr_reader :instance_variable_name
+
+ # Load attribute
+ #
+ # @param [Object] object
+ # @param [Hash] attributes
+ #
+ # @return [self]
+ def load(object, attributes)
+ set(object, attributes.fetch(name))
+ end
+
+ # Get attribute value from object
+ #
+ # @param [Object] object
+ #
+ # @return [Object]
+ def get(object)
+ object.public_send(name)
+ end
+
+ # Set attribute value in object
+ #
+ # @param [Object] object
+ # @param [Object] value
+ #
+ # @return [self]
+ def set(object, value)
+ object.instance_variable_set(instance_variable_name, value)
+
+ self
+ end
+ end # Attribute
+ end # Anima
+end # Unparser
diff --git a/lib/unparser/anima/error.rb b/lib/unparser/anima/error.rb
new file mode 100644
index 0000000..f77daa8
--- /dev/null
+++ b/lib/unparser/anima/error.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Anima
+ # Abstract base class for anima errors
+ class Error < RuntimeError
+ FORMAT = '%s attributes missing: %s, unknown: %s'.freeze
+ private_constant(*constants(false))
+
+ # Initialize object
+ #
+ # @param [Class] klass
+ # the class being initialized
+ # @param [Enumerable<Symbol>] missing
+ # @param [Enumerable<Symbol>] unknown
+ #
+ # @return [undefined]
+ def initialize(klass, missing, unknown)
+ super(format(FORMAT, klass, missing, unknown))
+ end
+ end # Error
+ end # Anima
+end # Unparser
diff --git a/lib/unparser/ast.rb b/lib/unparser/ast.rb
index 61ec3f9..976cd9b 100644
--- a/lib/unparser/ast.rb
+++ b/lib/unparser/ast.rb
@@ -2,9 +2,7 @@
module Unparser
# Namespace for AST processing tools
- # :reek:TooManyConstants
module AST
-
FIRST_CHILD = ->(node) { node.children.first }.freeze
TAUTOLOGY = ->(_node) { true }.freeze
@@ -80,7 +78,7 @@ module Unparser
# AST enumerator
class Enumerator
- include Adamantium::Flat, Concord.new(:node, :controller), Enumerable
+ include Adamantium, Concord.new(:node, :controller), Enumerable
# Return new instance
#
diff --git a/lib/unparser/ast/local_variable_scope.rb b/lib/unparser/ast/local_variable_scope.rb
index c4e3c88..cfd029b 100644
--- a/lib/unparser/ast/local_variable_scope.rb
+++ b/lib/unparser/ast/local_variable_scope.rb
@@ -61,35 +61,22 @@ module Unparser
#
# @api private
#
- def first_assignment_in_body_and_used_in_condition?(body, condition)
- condition_reads = AST.local_variable_reads(condition)
+ def first_assignment_in?(left, right)
+ condition_reads = AST.local_variable_reads(right)
- candidates = AST.local_variable_assignments(body).select do |node|
- name = node.children.first
- condition_reads.include?(name)
+ candidates = AST.local_variable_assignments(left).select do |node|
+ condition_reads.include?(node.children.first)
end
- candidates.any? do |node|
- first_assignment?(node)
- end
+ candidates.any?(&public_method(:first_assignment?))
end
private
- # Match node
- #
- # @param [Parser::AST::Node] needle
- # if block given
- #
- # @return [Boolean]
- #
- # @api private
- #
def match(needle)
@items.each do |node, current, before|
return yield(current, before) if node.equal?(needle)
end
- false
end
end # LocalVariableScope
@@ -137,25 +124,10 @@ module Unparser
private
- # Return current set of local variables
- #
- # @return [Set<Symbol>]
- #
- # @api private
- #
def current
@stack.last
end
- # Visit node and record local variable state
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- # ignore :reek:LongYieldList
def visit(node, &block)
before = current.dup
enter(node)
@@ -166,75 +138,33 @@ module Unparser
leave(node)
end
- # Record local variable state
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
def enter(node)
case node.type
when *RESET_NODES
push_reset
- when *ASSIGN_NODES
+ when ASSIGN_NODES
define(node.children.first)
when *INHERIT_NODES
push_inherit
end
end
- # Pop from local variable state
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
def leave(node)
pop if CLOSE_NODES.include?(node.type)
end
- # Define a local variable on current stack
- #
- # @param [Symbol] name
- #
- # @return [undefined]
- #
- # @api private
- #
def define(name)
current << name
end
- # Push reset scope on stack
- #
- # @return [undefined]
- #
- # @api private
- #
def push_reset
@stack << Set.new
end
- # Push inherited lvar scope on stack
- #
- # @return [undefined]
- #
- # @api private
- #
def push_inherit
@stack << current.dup
end
- # Pop lvar scope from stack
- #
- # @return [undefined]
- #
- # @api private
- #
def pop
@stack.pop
end
diff --git a/lib/unparser/buffer.rb b/lib/unparser/buffer.rb
index d60d80f..ebb4dca 100644
--- a/lib/unparser/buffer.rb
+++ b/lib/unparser/buffer.rb
@@ -3,8 +3,6 @@
module Unparser
# Buffer used to emit into
- #
- # ignore :reek:TooManyMethods
class Buffer
NL = "\n".freeze
@@ -46,7 +44,6 @@ module Unparser
#
def append_without_prefix(string)
write(string)
- self
end
# Increase indent
@@ -79,7 +76,13 @@ module Unparser
#
def nl
write(NL)
- self
+ end
+
+ def root_indent
+ before = @indent
+ @indent = 0
+ yield
+ @indent = before
end
# Test for a fresh line
@@ -102,39 +105,25 @@ module Unparser
@content.dup.freeze
end
- # Capture the content written to the buffer within the block
+ # Write raw fragment to buffer
#
- # @return [String]
+ # Does not do indentation logic.
#
- # @api private
+ # @param [String] fragment
#
- def capture_content
- size_before = @content.size
- yield
- @content[size_before..-1]
+ # @return [self]
+ def write(fragment)
+ @content << fragment
+ self
end
private
INDENT_SPACE = ' '.freeze
- # Write prefix
- #
- # @return [String]
- #
- # @api private
- #
def prefix
write(INDENT_SPACE * @indent)
end
- # Write to content buffer
- #
- # @param [String] fragment
- #
- def write(fragment)
- @content << fragment
- end
-
end # Buffer
end # Unparser
diff --git a/lib/unparser/cli.rb b/lib/unparser/cli.rb
index a5b1c15..c1fa779 100644
--- a/lib/unparser/cli.rb
+++ b/lib/unparser/cli.rb
@@ -1,29 +1,61 @@
# frozen_string_literal: true
-require 'unparser'
-require 'optparse'
-require 'diff/lcs'
-require 'diff/lcs/hunk'
-
-require 'unparser/cli/source'
-require 'unparser/cli/differ'
-require 'unparser/cli/color'
-
module Unparser
# Unparser CLI implementation
- #
- # :reek:InstanceVariableAssumption
- # :reek:TooManyInstanceVariables
class CLI
EXIT_SUCCESS = 0
EXIT_FAILURE = 1
+ class Target
+ include AbstractType
+
+ # Path target
+ class Path < self
+ include Concord.new(:path)
+
+ # Validation for this target
+ #
+ # @return [Validation]
+ def validation
+ Validation.from_path(path)
+ end
+
+ # Literal for this target
+ #
+ # @return [Validation]
+ def literal_validation
+ Validation::Literal.from_path(path)
+ end
+ end
+
+ # String target
+ class String
+ include Concord.new(:string)
+
+ # Validation for this target
+ #
+ # @return [Validation]
+ def validation
+ Validation.from_string(string)
+ end
+
+ # Literal for this target
+ #
+ # @return [Validation]
+ def literal_validation
+ Validation::Literal.from_string(path)
+ end
+ end # String
+ end # Target
+
+ private_constant(*constants(false))
+
# Run CLI
#
# @param [Array<String>] arguments
#
- # @return [Fixnum]
+ # @return [Integer]
# the exit status
#
# @api private
@@ -39,22 +71,22 @@ module Unparser
# @return [undefined]
#
# @api private
- #
- # ignore :reek:TooManyStatements
def initialize(arguments)
- @sources = []
- @ignore = Set.new
+ @ignore = Set.new
+ @targets = []
- @success = true
- @fail_fast = false
- @verbose = false
+ @fail_fast = false
+ @start_with = nil
+ @success = true
+ @validation = :validation
+ @verbose = false
opts = OptionParser.new do |builder|
add_options(builder)
end
opts.parse!(arguments).each do |name|
- @sources.concat(sources(name))
+ @targets.concat(targets(name))
end
end
@@ -66,38 +98,40 @@ module Unparser
#
# @api private
#
- # ignore :reek:TooManyStatements
+ # rubocop:disable Metrics/MethodLength
def add_options(builder)
builder.banner = 'usage: unparse [options] FILE [FILE]'
builder.separator('')
builder.on('-e', '--evaluate SOURCE') do |source|
- @sources << Source::String.new(source)
+ @targets << Target::String.new(source)
end
- builder.on('--start-with FILE') do |file|
- @start_with = sources(file).first
+ builder.on('--start-with FILE') do |path|
+ @start_with = targets(path).first
end
builder.on('-v', '--verbose') do
@verbose = true
end
+ builder.on('-l', '--literal') do
+ @validation = :literal_validation
+ end
builder.on('--ignore FILE') do |file|
- @ignore.merge(sources(file))
+ @ignore.merge(targets(file))
end
builder.on('--fail-fast') do
@fail_fast = true
end
end
+ # rubocop:enable Metrics/MethodLength
# Return exit status
#
- # @return [Fixnum]
+ # @return [Integer]
#
# @api private
#
def exit_status
- effective_sources.each do |source|
- next if @ignore.include?(source)
-
- process_source(source)
+ effective_targets.each do |target|
+ process_target(target)
break if @fail_fast && !@success
end
@@ -106,67 +140,41 @@ module Unparser
private
- # Process source
- #
- # @param [CLI::Source] source
- #
- # @return [undefined]
- #
- # @api private
- #
- def process_source(source)
- if source.success?
- puts source.report if @verbose
- puts "Success: #{source.identification}"
+ def process_target(target)
+ validation = target.public_send(@validation)
+ if validation.success?
+ puts validation.report if @verbose
+ puts "Success: #{validation.identification}"
else
- puts source.report
- puts "Error: #{source.identification}"
+ puts validation.report
+ puts "Error: #{validation.identification}"
@success = false
end
end
- # Return effective sources
- #
- # @return [Enumerable<CLI::Source>]
- #
- # @api private
- #
- def effective_sources
+ def effective_targets
if @start_with
reject = true
- @sources.reject do |source|
- if reject && source.eql?(@start_with)
+ @targets.reject do |targets|
+ if reject && targets.eql?(@start_with)
reject = false
end
reject
end
else
- @sources
- end
+ @targets
+ end.reject(&@ignore.method(:include?))
end
- # Return sources for file name
- #
- # @param [String] file_name
- #
- # @return [Enumerable<CLI::Source>]
- #
- # @api private
- #
- # ignore :reek:UtilityFunction
- def sources(file_name)
- files =
- if File.directory?(file_name)
- Dir.glob(File.join(file_name, '**/*.rb')).sort
- elsif File.file?(file_name)
- [file_name]
- else
- Dir.glob(file_name).sort
- end
-
- files.map(&Source::File.method(:new))
+ def targets(file_name)
+ if File.directory?(file_name)
+ Dir.glob(File.join(file_name, '**/*.rb')).sort
+ elsif File.file?(file_name)
+ [file_name]
+ else
+ Dir.glob(file_name).sort
+ end.map { |file| Target::Path.new(Pathname.new(file)) }
end
-
end # CLI
end # Unparser
diff --git a/lib/unparser/cli/differ.rb b/lib/unparser/cli/differ.rb
deleted file mode 100644
index cf6beb4..0000000
--- a/lib/unparser/cli/differ.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class CLI
- # Class to create diffs from source code
- class Differ
- include Adamantium::Flat, Concord.new(:old, :new), Procto.call(:colorized_diff)
-
- CONTEXT_LINES = 5
-
- # Return new object
- #
- # @param [String] old
- # @param [String] new
- #
- # @return [Differ]
- #
- # @api private
- #
- def self.build(old, new)
- new(lines(old), lines(new))
- end
-
- # Return colorized diff line
- #
- # @param [String] line
- #
- # @return [String]
- #
- # @api private
- #
- def self.colorize_line(line)
- case line[0]
- when '+'
- Color::GREEN
- when '-'
- Color::RED
- else
- Color::NONE
- end.format(line)
- end
-
- # Break up source into lines
- #
- # @param [String] source
- #
- # @return [Array<String>]
- #
- # @api private
- #
- def self.lines(source)
- source.lines.map(&:chomp)
- end
- private_class_method :lines
-
- # Return hunks
- #
- # @return [Array<Diff::LCS::Hunk>]
- #
- # @api private
- #
- def hunks
- file_length_difference = new.length - old.length
- diffs.map do |piece|
- hunk = Diff::LCS::Hunk.new(old, new, piece, CONTEXT_LINES, file_length_difference)
- file_length_difference = hunk.file_length_difference
- hunk
- end
- end
-
- # Return collapsed hunks
- #
- # @return [Enumerable<Diff::LCS::Hunk>]
- #
- # @api private
- #
- def collapsed_hunks
- hunks.each_with_object([]) do |hunk, output|
- last = output.last
-
- if last && hunk.merge(last)
- output.pop
- end
-
- output << hunk
- end
- end
-
- # Return source diff
- #
- # @return [String]
- # if there is a diff
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def diff
- output = +''
-
- collapsed_hunks.each do |hunk|
- output << hunk.diff(:unified) << "\n"
- end
-
- output
- end
- memoize :diff
-
- # Return colorized source diff
- #
- # @return [String]
- # if there is a diff
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def colorized_diff
- diff.lines.map do |line|
- self.class.colorize_line(line)
- end.join
- end
- memoize :colorized_diff
-
- private
-
- # Return diffs
- #
- # @return [Array<Array>]
- #
- # @api private
- #
- def diffs
- Diff::LCS.diff(old, new)
- end
- memoize :diffs
-
- # Return max length
- #
- # @return [Fixnum]
- #
- # @api private
- #
- def max_length
- [old, new].map(&:length).max
- end
-
- end # CLI
- end # Differ
-end # Unparser
diff --git a/lib/unparser/cli/source.rb b/lib/unparser/cli/source.rb
deleted file mode 100644
index 7195960..0000000
--- a/lib/unparser/cli/source.rb
+++ /dev/null
@@ -1,267 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class CLI
- # Source representation for CLI sources
- #
- # ignore :reek:TooManyMethods
- class Source
- include AbstractType, Adamantium::Flat, NodeHelpers
-
- # Source state generated after first unparse
- class Generated
- include Concord::Public.new(:source, :ast, :error)
-
- # Test if source was generated successfully
- #
- # @return [Boolean]
- #
- # @api private
- #
- def success?
- !error
- end
-
- # Build generated source
- #
- # @param [Parser::AST::Node] ast
- #
- # @api private
- #
- def self.build(ast)
- source = Unparser.unparse(ast)
- new(source, ast, nil)
- rescue StandardError => exception
- new(nil, ast, exception)
- end
- end
-
- # Test if source could be unparsed successfully
- #
- # @return [Boolean]
- #
- # @api private
- #
- def success?
- generated.success? && original_ast && generated_ast && original_ast.eql?(generated_ast)
- end
-
- # Return error report
- #
- # @return [String]
- #
- # @api private
- #
- def report
- if original_ast && generated_ast
- report_with_ast_diff
- elsif !original_ast
- report_original
- elsif !generated.success?
- report_unparser
- elsif !generated_ast
- report_generated
- else
- raise
- end
- end
- memoize :report
-
- private
-
- # Return generated source
- #
- # @return [String]
- #
- # @api private
- #
- def generated
- Source::Generated.build(original_ast)
- end
- memoize :generated
-
- # Return stripped source
- #
- # @param [String] source
- #
- # @return [String]
- #
- # @api private
- #
- # ignore :reek:UtilityFunction
- def strip(source)
- source = source.rstrip
- indent = source.scan(/^\s*/).first
- source.gsub(/^#{indent}/, '')
- end
-
- # Return error report for parsing original
- #
- # @return [String]
- #
- # @api private
- #
- def report_original
- strip(<<-MESSAGE)
- Parsing of original source failed:
- #{original_source}
- MESSAGE
- end
-
- # Report unparser bug
- #
- # @return [String]
- #
- # @api private
- #
- def report_unparser
- message = ['Unparsing parsed AST failed']
- error = generated.error
- message << error
- error.backtrace.take(20).each(&message.method(:<<))
- message << 'Original-AST:'
- message << original_ast.inspect
- message.join("\n")
- end
-
- # Return error report for parsing generated
- #
- # @return [String]
- #
- # @api private
- #
- def report_generated
- strip(<<-MESSAGE)
- Parsing of generated source failed:
- Original-source:
- #{original_source}
- Original-AST:
- #{original_ast.inspect}
- Source:
- #{generated.source}
- MESSAGE
- end
-
- # Return error report with AST difference
- #
- # @return [String]
- #
- # @api private
- #
- def report_with_ast_diff
- strip(<<-MESSAGE)
- #{ast_diff}
- Original-Source:\n#{original_source}
- Original-AST:\n#{original_ast.inspect}
- Generated-Source:\n#{generated.source}
- Generated-AST:\n#{generated_ast.inspect}
- MESSAGE
- end
-
- # Return ast diff
- #
- # @return [String]
- #
- # @api private
- #
- def ast_diff
- Differ.call(
- original_ast.inspect.lines.map(&:chomp),
- generated_ast.inspect.lines.map(&:chomp)
- )
- end
-
- # Return generated AST
- #
- # @return [Parser::AST::Node]
- # if parser was sucessful for generated ast
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def generated_ast
- generated.success? && Preprocessor.run(Unparser.parse(generated.source))
- rescue Parser::SyntaxError
- nil
- end
- memoize :generated_ast
-
- # Return original AST
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def original_ast
- Preprocessor.run(Unparser.parse(original_source))
- rescue Parser::SyntaxError
- nil
- end
- memoize :original_ast
-
- # CLI source from string
- class String < self
- include Concord.new(:original_source)
-
- # Return identification
- #
- # @return [String]
- #
- # @api private
- #
- def identification
- '(string)'
- end
-
- end # String
-
- # CLI source from file
- class File < self
- include Concord.new(:file_name)
-
- # Return identification
- #
- # @return [String]
- #
- # @api private
- #
- def identification
- "(#{file_name})"
- end
-
- private
-
- # Return original source
- #
- # @return [String]
- #
- # @api private
- #
- def original_source
- ::File.read(file_name)
- end
- memoize :original_source
-
- end # File
-
- # Source passed in as node
- class Node < self
- include Concord.new(:original_ast)
-
- # Return original source
- #
- # @return [String]
- #
- # @api private
- #
- def original_source
- Unparser.unparse(original_ast)
- end
- memoize :original_source
- end # Node
-
- end # Source
- end # CLI
-end # Unparser
diff --git a/lib/unparser/cli/color.rb b/lib/unparser/color.rb
similarity index 66%
rename from lib/unparser/cli/color.rb
rename to lib/unparser/color.rb
index 9a1ca63..9120f8c 100644
--- a/lib/unparser/cli/color.rb
+++ b/lib/unparser/color.rb
@@ -3,16 +3,13 @@
module Unparser
# Class to colorize strings
class Color
- include Adamantium::Flat, Concord.new(:code)
+ include Adamantium, Concord.new(:code)
# Format text with color
#
# @param [String] text
#
# @return [String]
- #
- # @api private
- #
def format(text)
"\e[#{code}m#{text}\e[0m"
end
@@ -25,28 +22,23 @@ module Unparser
#
# @return [String]
# the argument string
- #
- # @api private
- #
def format(text)
text
end
private
- # Initialize null color
- #
- # @return [undefined]
- #
- # @api private
- #
+ # Well rubocop you are static so you do not have a clue here ;)
+ # rubocop:disable Style/RedundantInitialize
+ # rubocop:disable Style/MissingSuper
def initialize; end
+ # rubocop:enable Style/RedundantInitialize
+ # rubocop:enable Style/MissingSuper
end.new
RED = Color.new(31)
GREEN = Color.new(32)
- BLUE = Color.new(34)
end # Color
end # Unparser
diff --git a/lib/unparser/comments.rb b/lib/unparser/comments.rb
index 1c97668..5a7696b 100644
--- a/lib/unparser/comments.rb
+++ b/lib/unparser/comments.rb
@@ -3,8 +3,6 @@
module Unparser
# Holds the comments that remain to be emitted
- #
- # ignore :reek:RepeatedConditional
class Comments
# Proxy to singleton
@@ -113,39 +111,15 @@ module Unparser
private
- # Take comments while the provided block returns true
- #
- # @yield [Parser::Source::Comment]
- #
- # @return [Array]
- #
- # @api private
- #
def take_while
number_to_take = @comments.index { |comment| !yield(comment) } || @comments.size
@comments.shift(number_to_take)
end
- # Take comments up to the line number
- #
- # @param [Fixnum] line
- #
- # @return [Array]
- #
- # @api private
- #
def take_up_to_line(line)
take_while { |comment| comment.location.expression.line <= line }
end
- # Unshift document comments and return the rest
- #
- # @param [Array] comments
- #
- # @return [Array]
- #
- # @api private
- #
def unshift_documents(comments)
doc_comments, other_comments = comments.partition(&:document?)
doc_comments.reverse_each { |comment| @comments.unshift(comment) }
diff --git a/lib/unparser/concord.rb b/lib/unparser/concord.rb
new file mode 100644
index 0000000..f43227d
--- /dev/null
+++ b/lib/unparser/concord.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+module Unparser
+ # A mixin to define a composition
+ #
+ # Original code before vendoring and reduction from: https://github.com/mbj/concord.
+ class Concord < Module
+ include Adamantium, Equalizer.new(:names)
+
+ # The maximum number of objects the hosting class is composed of
+ MAX_NR_OF_OBJECTS = 3
+
+ # Return names
+ #
+ # @return [Enumerable<Symbol>]
+ #
+ # @api private
+ #
+ attr_reader :names
+
+ private
+
+ # Initialize object
+ #
+ # @return [undefined]
+ #
+ # @api private
+ #
+ # rubocop:disable Lint/MissingSuper
+ def initialize(*names)
+ if names.length > MAX_NR_OF_OBJECTS
+ fail "Composition of more than #{MAX_NR_OF_OBJECTS} objects is not allowed"
+ end
+
+ @names = names
+ define_initialize
+ define_readers
+ define_equalizer
+ end
+ # rubocop:enable Lint/MissingSuper
+
+ # Define equalizer
+ #
+ # @return [undefined]
+ #
+ # @api private
+ #
+ def define_equalizer
+ include(Equalizer.new(*names))
+ end
+
+ # Define readers
+ #
+ # @return [undefined]
+ #
+ # @api private
+ #
+ def define_readers
+ attribute_names = names
+ attr_reader(*attribute_names)
+
+ protected(*attribute_names) if attribute_names.any?
+ end
+
+ # Define initialize method
+ #
+ # @return [undefined]
+ #
+ # @api private
+ #
+ #
+ def define_initialize
+ ivars = instance_variable_names
+ size = names.size
+
+ define_method :initialize do |*args|
+ args_size = args.size
+ unless args_size.equal?(size)
+ fail ArgumentError, "wrong number of arguments (#{args_size} for #{size})"
+ end
+
+ ivars.zip(args) { |ivar, arg| instance_variable_set(ivar, arg) }
+ end
+ end
+
+ # Return instance variable names
+ #
+ # @return [String]
+ #
+ # @api private
+ #
+ def instance_variable_names
+ names.map { |name| "@#{name}" }
+ end
+
+ # Mixin for public attribute readers
+ class Public < self
+
+ # Hook called when module is included
+ #
+ # @param [Class,Module] descendant
+ #
+ # @return [undefined]
+ #
+ # @api private
+ #
+ def included(descendant)
+ names.each do |name|
+ descendant.__send__(:public, name)
+ end
+ end
+ end # Public
+ end # Concord
+end # Unparser
diff --git a/lib/unparser/constants.rb b/lib/unparser/constants.rb
index eb789e0..41a7fb8 100644
--- a/lib/unparser/constants.rb
+++ b/lib/unparser/constants.rb
@@ -2,66 +2,19 @@
module Unparser
# All unparser constants maybe included in other libraries.
- #
- # False positive since constants are frozen dynamically
- # to avoid duplication of `.freeze` calls
- #
- # :reek:TooManyConstants
module Constants
- # Return frozen symbol set from enumerable
- #
- # @param [Enumerable] enumerable
- #
- # @return [Set<Symbol>]
- #
- # @api private
- #
- def self.symbol_set(enumerable)
- enumerable.map(&:to_sym).freeze
- end
- private_class_method :symbol_set
-
- BRACKETS_CURLY = IceNine.deep_freeze(%w[{ }])
- BRACKETS_ROUND = IceNine.deep_freeze(%w[( )])
- BRACKETS_SQUARE = IceNine.deep_freeze(%w([ ]))
-
# All unary operators of the ruby language
- UNARY_OPERATORS = symbol_set %w[
+ UNARY_OPERATORS = %i[
! ~ -@ +@
- ]
+ ].to_set.freeze
# All binary operators of the ruby language
- BINARY_OPERATORS = symbol_set %w[
+ BINARY_OPERATORS = %i[
+ - * / & | && || << >> ==
=== != <= < <=> > >= =~ !~ ^
** %
- ]
-
- COMMENT = '#'
-
- WS = ' '
- NL = "\n"
- T_DOT = '.'
- T_LT = '<'
- T_DLT = '<<'
- T_AMP = '&'
- T_ASN = '='
- T_SPLAT = '*'
- T_DSPLAT = '**'
- T_ASR = '=>'
- T_PIPE = '|'
- T_DCL = '::'
- T_NEG = '!'
- T_OR = '||'
- T_AND = '&&'
- T_COLON = ':'
-
- M_PO = '('
- M_PC = ')'
-
- SNGL_QUOTE = "'"
- DBL_QUOTE = '"'
+ ].to_set.freeze
# Keywords
K_DO = 'do'
@@ -107,8 +60,6 @@ module Unparser
K_FILE = '__FILE__'
K_THEN = 'then'
- DEFAULT_DELIMITER = ', '.freeze
-
KEYWORDS = constants.each_with_object([]) do |name, keywords|
value = const_get(name).freeze
next unless name.to_s.start_with?('K_')
diff --git a/lib/unparser/diff.rb b/lib/unparser/diff.rb
new file mode 100644
index 0000000..52a2904
--- /dev/null
+++ b/lib/unparser/diff.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module Unparser
+ # Class to create diffs from source code
+ class Diff
+ include Adamantium, Concord.new(:old, :new)
+
+ ADDITION = '+'
+ DELETION = '-'
+ NEWLINE = "\n"
+
+ # Unified source diff between old and new
+ #
+ # @return [String]
+ # if there is exactly one diff
+ #
+ # @return [nil]
+ # otherwise
+ def diff
+ return if diffs.empty?
+
+ minimized_hunk.diff(:unified) + NEWLINE
+ end
+ memoize :diff
+
+ # Colorized unified source diff between old and new
+ #
+ # @return [String]
+ # if there is a diff
+ #
+ # @return [nil]
+ # otherwise
+ def colorized_diff
+ return unless diff
+
+ diff.lines.map(&self.class.method(:colorize_line)).join
+ end
+ memoize :colorized_diff
+
+ # Build new object from source strings
+ #
+ # @param [String] old
+ # @param [String] new
+ #
+ # @return [Diff]
+ def self.build(old, new)
+ new(lines(old), lines(new))
+ end
+
+ # Break up source into lines
+ #
+ # @param [String] source
+ #
+ # @return [Array<String>]
+ def self.lines(source)
+ source.lines.map(&:chomp)
+ end
+ private_class_method :lines
+
+ private
+
+ def diffs
+ ::Diff::LCS.diff(old, new)
+ end
+
+ def hunks
+ diffs.map do |diff|
+ ::Diff::LCS::Hunk.new(old.map(&:dup), new, diff, max_length, 0)
+ end
+ end
+
+ def minimized_hunk
+ head, *tail = hunks
+
+ tail.reduce(head) do |left, right|
+ right.merge(left)
+ right
+ end
+ end
+
+ def max_length
+ [old, new].map(&:length).max
+ end
+
+ def self.colorize_line(line)
+ case line[0]
+ when ADDITION
+ Color::GREEN
+ when DELETION
+ Color::RED
+ else
+ Color::NONE
+ end.format(line)
+ end
+ private_class_method :colorize_line
+
+ end # Diff
+end # Unparser
diff --git a/lib/unparser/dsl.rb b/lib/unparser/dsl.rb
index 77eb220..48b2927 100644
--- a/lib/unparser/dsl.rb
+++ b/lib/unparser/dsl.rb
@@ -6,14 +6,6 @@ module Unparser
private
- # Define remaining children
- #
- # @param [Enumerable<Symbol>] names
- #
- # @return [undefined]
- #
- # @api private
- #
def define_remaining_children(names)
range = names.length..-1
define_method(:remaining_children) do
@@ -22,15 +14,6 @@ module Unparser
private :remaining_children
end
- # Define named child
- #
- # @param [Symbol] name
- # @param [Fixnum] index
- #
- # @return [undefined]
- #
- # @api private
- #
def define_child(name, index)
define_method(name) do
children.at(index)
@@ -38,15 +21,6 @@ module Unparser
private name
end
- # Define a group of children
- #
- # @param [Symbol] name
- # @param [Range] range
- #
- # @return [undefined]
- #
- # @api private
- #
def define_group(name, range)
define_method(name) do
children[range]
@@ -55,12 +29,6 @@ module Unparser
memoize(name)
end
- # Create name helpers
- #
- # @return [undefined]
- #
- # @api private
- #
def children(*names)
define_remaining_children(names)
diff --git a/lib/unparser/either.rb b/lib/unparser/either.rb
new file mode 100644
index 0000000..924f731
--- /dev/null
+++ b/lib/unparser/either.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+module Unparser
+ module RequireBlock
+
+ private
+
+ # Raise error unless block is provided
+ #
+ # @raise [MissingBlockError]
+ # if no block is given
+ #
+ # @return [self]
+ def require_block
+ fail LocalJumpError unless block_given?
+
+ self
+ end
+ end # RequireBLock
+
+ class Either
+ include(
+ Adamantium,
+ Concord.new(:value),
+ RequireBlock
+ )
+
+ # Execute block and wrap error in left
+ #
+ # @param [Class<Exception>] exception
+ #
+ # @return [Either<Exception, Object>]
+ def self.wrap_error(*exceptions)
+ Right.new(yield)
+ rescue *exceptions => error
+ Left.new(error)
+ end
+
+ # Test for left constructor
+ #
+ # @return [Boolean]
+ def left?
+ instance_of?(Left)
+ end
+
+ # Test for right constructor
+ #
+ # @return [Boolean]
+ def right?
+ instance_of?(Right)
+ end
+
+ class Left < self
+ # Evaluate functor block
+ #
+ # @return [Either::Left<Object>]
+ def fmap(&block)
+ require_block(&block)
+ end
+
+ # Evaluate applicative block
+ #
+ # @return [Either::Left<Object>]
+ def bind(&block)
+ require_block(&block)
+ end
+
+ # Unwrap value from left
+ #
+ # @return [Object]
+ def from_left
+ value
+ end
+
+ # Unwrap value from right
+ #
+ # @return [Object]
+ #
+ def from_right
+ if block_given?
+ yield(value)
+ else
+ fail "Expected right value, got #{inspect}"
+ end
+ end
+
+ # Map over left value
+ #
+ # @return [Either::Right<Object>]
+ def lmap
+ Left.new(yield(value))
+ end
+
+ # Evaluate left side of branch
+ #
+ # @param [#call] left
+ # @param [#call] _right
+ def either(left, _right)
+ left.call(value)
+ end
+ end # Left
+
+ class Right < self
+ # Evaluate functor block
+ #
+ # @return [Either::Right<Object>]
+ def fmap
+ Right.new(yield(value))
+ end
+
+ # Evaluate applicative block
+ #
+ # @return [Either<Object>]
+ def bind
+ yield(value)
+ end
+
+ # Unwrap value from left
+ #
+ # @return [Object]
+ #
+ def from_left
+ if block_given?
+ yield(value)
+ else
+ fail "Expected left value, got #{inspect}"
+ end
+ end
+
+ # Unwrap value from right
+ #
+ # @return [Object]
+ def from_right
+ value
+ end
+
+ # Map over left value
+ #
+ # @return [Either::Right<Object>]
+ def lmap(&block)
+ require_block(&block)
+ end
+
+ # Evaluate right side of branch
+ #
+ # @param [#call] _left
+ # @param [#call] right
+ def either(_left, right)
+ right.call(value)
+ end
+ end # Right
+ end # Either
+end # Unparser
diff --git a/lib/unparser/emitter.rb b/lib/unparser/emitter.rb
index 8aa9c3c..aedc517 100644
--- a/lib/unparser/emitter.rb
+++ b/lib/unparser/emitter.rb
@@ -1,36 +1,23 @@
# frozen_string_literal: true
module Unparser
+ UnknownNodeError = Class.new(ArgumentError)
# Emitter base class
- #
- # buggy, argument values are sends to self
- #
- # ignore :reek:TooManyMethods
class Emitter
- include Adamantium::Flat, AbstractType, Constants, NodeHelpers
- include Concord.new(:node, :parent)
- extend DSL
+ include Adamantium, AbstractType, Constants, Generation, NodeHelpers
+ include Anima.new(:buffer, :comments, :node, :local_variable_scope)
- # Registry for node emitters
- REGISTRY = {} # rubocop:disable MutableConstant
+ public :node
- NOINDENT = %i[rescue ensure].to_set.freeze
+ extend DSL
- module Unterminated
- def terminated?
- false
- end
- end
+ # Registry for node emitters
+ REGISTRY = {} # rubocop:disable Style/MutableConstant
- module Terminated
- def terminated?
- true
- end
- end
+ NO_INDENT = %i[ensure rescue].freeze
module LocalVariableRoot
-
# Return local variable root
#
# @return [Parser::AST::Node]
@@ -46,33 +33,8 @@ module Unparser
memoize :local_variable_scope
end
end
-
end # LocalVariableRoot
- # Return local variable root
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def local_variable_scope
- parent.local_variable_scope
- end
-
- # Return assigned lvars
- #
- # @return [Array<Symbol>]
- #
- # @api private
- #
- abstract_method :local_variables
-
- # Return node type
- #
- # @return [Symbol]
- #
- # @api private
- #
def node_type
node.type
end
@@ -87,25 +49,16 @@ module Unparser
#
def self.handle(*types)
types.each do |type|
+ fail "Handler for type: #{type} already registered" if REGISTRY.key?(type)
+
REGISTRY[type] = self
end
end
private_class_method :handle
- # Trigger write to buffer
- #
- # @return [self]
- #
- # @api private
- #
- def write_to_buffer
- emit_comments_before if buffer.fresh_line?
+ def emit_mlhs
dispatch
- comments.consume(node)
- emit_eof_comments if parent.is_a?(Root)
- self
end
- memoize :write_to_buffer
# Return emitter
#
@@ -113,384 +66,30 @@ module Unparser
#
# @api private
#
- def self.emitter(node, parent)
+ # rubocop:disable Metrics/ParameterLists
+ def self.emitter(buffer:, comments:, node:, local_variable_scope:)
type = node.type
- klass = REGISTRY.fetch(type) do
- raise ArgumentError, "No emitter for node: #{type.inspect}"
- end
- klass.new(node, parent)
- end
-
- # Dispatch node
- #
- # @return [undefined]
- #
- # @api private
- #
- abstract_method :dispatch
-
- # Test if node is emitted as terminated expression
- #
- # @return [Boolean]
- #
- # @api private
- #
- abstract_method :terminated?
- protected
-
- # Return buffer
- #
- # @return [Buffer] buffer
- #
- # @api private
- #
- def buffer
- parent.buffer
- end
- memoize :buffer, freezer: :noop
-
- # Return comments
- #
- # @return [Comments] comments
- #
- # @api private
- #
- def comments
- parent.comments
- end
- memoize :comments, freezer: :noop
-
- private
-
- # Emit contents of block within parentheses
- #
- # @return [undefined]
- #
- # @api private
- #
- def parentheses(open = M_PO, close = M_PC)
- write(open)
- yield
- write(close)
- end
-
- # Visit node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit_plain(node)
- emitter = emitter(node)
- emitter.write_to_buffer
- end
-
- # Visit ambiguous node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit(node)
- emitter = emitter(node)
- conditional_parentheses(!emitter.terminated?) do
- emitter.write_to_buffer
- end
- end
-
- # Visit within parentheses
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit_parentheses(node, *arguments)
- parentheses(*arguments) do
- visit_plain(node)
- end
- end
-
- # Call block in optional parentheses
- #
- # @param [true, false] flag
- #
- # @return [undefined]
- #
- # @api private
- #
- # ignore :reek:ControlParameter
- def conditional_parentheses(flag)
- if flag
- parentheses { yield }
- else
- yield
- end
- end
-
- # Return emitter for node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [Emitter]
- #
- # @api private
- #
- def emitter(node)
- self.class.emitter(node, self)
- end
-
- # Emit delimited body
- #
- # @param [Enumerable<Parser::AST::Node>] nodes
- #
- # @return [undefined]
- #
- # @api private
- #
- def delimited_plain(nodes)
- delimited(nodes, &method(:visit_plain))
- end
-
- # Emit delimited body
- #
- # @param [Enumerable<Parser::AST::Node>] nodes
- #
- # @return [undefined]
- #
- # @api private
- #
- def delimited(nodes, &block)
- return if nodes.empty?
-
- block ||= method(:visit)
- head, *tail = nodes
- block.call(head)
- tail.each do |node|
- write(DEFAULT_DELIMITER)
- block.call(node)
- end
- end
-
- # Return children of node
- #
- # @return [Array<Parser::AST::Node>]
- #
- # @api private
- #
- def children
- node.children
- end
-
- # Write newline
- #
- # @return [undefined]
- #
- # @api private
- #
- def nl
- emit_eol_comments
- buffer.nl
- end
-
- # Write comments that appeared before source_part in the source
- #
- # @param [Symbol] source_part
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_comments_before(source_part = :expression)
- comments_before = comments.take_before(node, source_part)
- return if comments_before.empty?
-
- emit_comments(comments_before)
- buffer.nl
- end
-
- # Write end-of-line comments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_eol_comments
- comments.take_eol_comments.each do |comment|
- write(WS, comment.text)
- end
- end
-
- # Write end-of-file comments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_eof_comments
- emit_eol_comments
- comments_left = comments.take_all
- return if comments_left.empty?
-
- buffer.nl
- emit_comments(comments_left)
- end
-
- # Write each comment to a separate line
- #
- # @param [Array] comments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_comments(comments)
- max = comments.size - 1
- comments.each_with_index do |comment, index|
- if comment.type.equal?(:document)
- buffer.append_without_prefix(comment.text.chomp)
- else
- write(comment.text)
- end
- buffer.nl if index < max
- end
- end
-
- # Write strings into buffer
- #
- # @return [undefined]
- #
- # @api private
- #
- def write(*strings)
- strings.each do |string|
- buffer.append(string)
- end
- end
-
- # Write end keyword
- #
- # @return [undefined]
- #
- # @api private
- #
- def k_end
- buffer.indent
- emit_comments_before(:end)
- buffer.unindent
- write(K_END)
- end
-
- # Return first child
- #
- # @return [Parser::AST::Node]
- # if present
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def first_child
- children.first
- end
-
- # Write whitespace
- #
- # @return [undefined]
- #
- # @api private
- #
- def ws
- write(WS)
- end
-
- # Call emit contents of block indented
- #
- # @return [undefined]
- #
- # @api private
- #
- # False positive:
- #
- def indented
- buffer = buffer()
- buffer.indent
- nl
- yield
- nl
- buffer.unindent
- end
-
- # Emit non nil body
- #
- # @param [Parser::AST::Node] body
- #
- # @return [undefined]
- #
- # @api private
- #
- # rubocop:disable MethodCallWithoutArgsParentheses
- def emit_body(body = body())
- unless body
- buffer.indent
- nl
- buffer.unindent
- return
- end
- visit_indented(body)
- end
- # rubocop:enable MethodCallWithoutArgsParentheses
-
- # Visit indented node
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit_indented(node)
- if NOINDENT.include?(node.type)
- visit_plain(node)
- else
- indented { visit_plain(node) }
+ klass = REGISTRY.fetch(type) do
+ fail UnknownNodeError, "Unknown node type: #{type.inspect}"
end
- end
- # Return parent type
- #
- # @return [Symbol]
- # if parent is present
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def parent_type
- parent.node_type
+ klass.new(
+ buffer: buffer,
+ comments: comments,
+ local_variable_scope: local_variable_scope,
+ node: node
+ )
end
+ # rubocop:enable Metrics/ParameterLists
- # Delegate to emitter
- #
- # @param [Class:Emitter] emitter
+ # Dispatch node write as statement
#
# @return [undefined]
#
# @api private
#
- # rubocop:disable MethodCallWithoutArgsParentheses
- def run(emitter, node = node())
- emitter.new(node, self).write_to_buffer
- end
- # rubocop:enable MethodCallWithoutArgsParentheses
+ abstract_method :dispatch
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/alias.rb b/lib/unparser/emitter/alias.rb
index f622920..9b9189d 100644
--- a/lib/unparser/emitter/alias.rb
+++ b/lib/unparser/emitter/alias.rb
@@ -11,16 +11,10 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_ALIAS, WS)
+ write('alias ')
visit(target)
- write(WS)
+ ws
visit(source)
end
diff --git a/lib/unparser/emitter/args.rb b/lib/unparser/emitter/args.rb
new file mode 100644
index 0000000..5bc34d7
--- /dev/null
+++ b/lib/unparser/emitter/args.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Arguments emitter
+ class Args < self
+ def emit_block_arguments
+ delimited(normal_arguments)
+
+ write(',') if normal_arguments.one? && n_arg?(normal_arguments.first)
+
+ emit_shadowargs
+ end
+
+ def emit_def_arguments
+ delimited(normal_arguments)
+ end
+
+ def emit_lambda_arguments
+ delimited(normal_arguments)
+ emit_shadowargs
+ end
+
+ private
+
+ def emit_shadowargs
+ return if shadowargs.empty?
+
+ write('; ')
+ delimited(shadowargs)
+ end
+
+ def normal_arguments
+ children.reject(&method(:n_shadowarg?))
+ end
+ memoize :normal_arguments
+
+ def shadowargs
+ children.select(&method(:n_shadowarg?))
+ end
+ memoize :shadowargs
+
+ end # Arguments
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/argument.rb b/lib/unparser/emitter/argument.rb
index 260af9b..0a47bfd 100644
--- a/lib/unparser/emitter/argument.rb
+++ b/lib/unparser/emitter/argument.rb
@@ -2,121 +2,21 @@
module Unparser
class Emitter
-
- # Arg expr (pattern args) emitter
- class ArgExpr < self
-
- handle :arg_expr
-
- children :body
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- visit_parentheses(body)
- end
- end # ArgExpr
-
- # Arguments emitter
- class Arguments < self
- include Terminated
-
- handle :args
-
- SHADOWARGS = ->(node) { node.type.equal?(:shadowarg) }.freeze
- ARG = ->(node) { node.type.equal?(:arg) }.freeze
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- delimited(normal_arguments)
-
- write(', ') if procarg_disambiguator?
-
- return if shadowargs.empty?
-
- write('; ')
- delimited(shadowargs)
- end
-
- # Test for procarg_disambiguator
- #
- # @return [Boolean]
- #
- # @api private
- #
- def procarg_disambiguator?
- regular_block? && normal_arguments.all?(&ARG) && normal_arguments.one?
- end
-
- # Test for regular block
- #
- # @return [Boolean]
- #
- # @api private
- #
- def regular_block?
- parent_type.equal?(:block) && !parent.node.children.first.type.equal?(:lambda)
- end
-
- # Return normal arguments
- #
- # @return [Enumerable<Parser::AST::Node>]
- #
- # @api private
- #
- def normal_arguments
- children.reject(&SHADOWARGS)
- end
- memoize :normal_arguments
-
- # Return shadow args
- #
- # @return [Enumerable<Parser::AST::Node>]
- #
- # @api private
- #
- def shadowargs
- children.select(&SHADOWARGS)
- end
- memoize :shadowargs
-
- end # Arguments
-
- # Emitter for block and kwrestarg arguments
- class Morearg < self
- include Terminated
-
+ # Emitter for forwarding arguments
+ class ForwardArg < self
MAP = {
- blockarg: T_AMP,
- kwrestarg: T_DSPLAT
+ blockarg: '&',
+ forwarded_kwrestarg: '**',
+ forwarded_restarg: '*',
+ kwrestarg: '**'
}.freeze
- handle :blockarg
- handle :kwrestarg
+ handle(*MAP.keys)
children :name
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
write(MAP.fetch(node_type), name.to_s)
end
@@ -125,44 +25,28 @@ module Unparser
# Optional argument emitter
class Optarg < self
- include Terminated
-
handle :optarg
children :name, :value
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(name.to_s, WS, T_ASN, WS)
+ write(name.to_s, ' = ')
visit(value)
end
end
# Optional keyword argument emitter
class KeywordOptional < self
- include Terminated
-
handle :kwoptarg
children :name, :value
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(name.to_s, T_COLON, WS)
+ write(name.to_s, ': ')
visit(value)
end
@@ -170,64 +54,40 @@ module Unparser
# Keyword argument emitter
class Kwarg < self
- include Terminated
-
handle :kwarg
children :name
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(name.to_s, T_COLON)
+ write(name.to_s, ':')
end
end # Restarg
# Rest argument emitter
class Restarg < self
- include Terminated
-
handle :restarg
children :name
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(T_SPLAT, name.to_s)
+ write('*', name.to_s)
end
end # Restarg
# Argument emitter
class Argument < self
- include Terminated
-
handle :arg, :shadowarg
children :name
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
write(name.to_s)
end
@@ -236,20 +96,12 @@ module Unparser
# Progarg emitter
class Procarg < self
- include Terminated
-
handle :procarg0
PARENS = %i[restarg mlhs].freeze
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
if needs_parens?
parentheses do
@@ -269,23 +121,15 @@ module Unparser
# Block pass node emitter
class BlockPass < self
- include Terminated
-
handle :block_pass
children :name
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(T_AMP)
- visit(name)
+ write('&')
+ visit(name) if name
end
end # BlockPass
diff --git a/lib/unparser/emitter/array.rb b/lib/unparser/emitter/array.rb
new file mode 100644
index 0000000..6f710cb
--- /dev/null
+++ b/lib/unparser/emitter/array.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Array literal emitter
+ class Array < self
+ handle :array
+
+ def emit_heredoc_reminders
+ emitters.each(&:emit_heredoc_reminders)
+ end
+
+ private
+
+ def dispatch
+ parentheses('[', ']') do
+ delimited(emitters, &:write_to_buffer)
+ end
+ end
+
+ def emitters
+ children.map(&method(:emitter))
+ end
+ memoize :emitters
+ end # Array
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/array_pattern.rb b/lib/unparser/emitter/array_pattern.rb
new file mode 100644
index 0000000..ae1833d
--- /dev/null
+++ b/lib/unparser/emitter/array_pattern.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for array patterns
+ class ArrayPattern < self
+
+ handle :array_pattern
+ handle :array_pattern_with_tail
+
+ private
+
+ def dispatch
+ write('[')
+ delimited(children, &method(:emit_member))
+ write(', ') if node_type.equal?(:array_pattern_with_tail)
+ write(']')
+ end
+
+ def emit_member(node)
+ if n_match_rest?(node)
+ writer_with(MatchRest, node).emit_array_pattern
+ else
+ visit(node)
+ end
+ end
+ end # Pin
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/assignment.rb b/lib/unparser/emitter/assignment.rb
index 508648d..d907c87 100644
--- a/lib/unparser/emitter/assignment.rb
+++ b/lib/unparser/emitter/assignment.rb
@@ -5,163 +5,72 @@ module Unparser
# Base class for assignment emitters
class Assignment < self
+ BINARY_OPERATOR = %i[and or].freeze
+
+ def symbol_name
+ true
+ end
+
+ def emit_heredoc_reminders
+ return unless right
+
+ emitter(right).emit_heredoc_reminders
+ end
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
emit_left
emit_right
end
- # Single assignment emitter
- class Single < self
-
- # Test for terminated emit
- #
- # @return [Boolean]
- #
- # @api private
- #
- def terminated?
- right.nil?
- end
-
- private
+ def emit_right
+ return unless right
- # Emit right
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_right
- return unless right
+ write(' = ')
- write(WS, T_ASN, WS)
+ if BINARY_OPERATOR.include?(right.type)
+ writer_with(Writer::Binary, right).emit_operator
+ else
visit(right)
end
+ end
- abstract_method :emit_left
-
- # Variable assignment emitter
- class Variable < self
-
- handle :lvasgn, :ivasgn, :cvasgn, :gvasgn
-
- children :name, :right
-
- private
-
- # Emit left
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_left
- write(name.to_s)
- end
-
- end # Variable
-
- # Constant assignment emitter
- class Constant < self
-
- handle :casgn
-
- children :base, :name, :right
-
- private
-
- # Emit left
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_left
- if base
- visit(base)
- write(T_DCL) if base.type != :cbase
- end
- write(name.to_s)
- end
+ abstract_method :emit_left
- end # Constant
- end # Single
+ # Variable assignment emitter
+ class Variable < self
- # Multiple assignment
- class Multiple < self
- include Unterminated
+ handle :lvasgn, :ivasgn, :cvasgn, :gvasgn
- handle :masgn
+ children :name, :right
private
- # Emit left
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_left
- visit_plain(first_child)
+ write(name.to_s)
end
- # Emit right
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_right
- write(WS, T_ASN, WS)
- visit(children.last)
- end
+ end # Variable
- end # Multiple
+ # Constant assignment emitter
+ class Constant < self
- # Emitter for multiple assignment left hand side
- class MLHS < Emitter
- include Unterminated
+ handle :casgn
- handle :mlhs
+ children :base, :name, :right
private
- NO_COMMA = %i[splat restarg].to_set.freeze
- PARENT_MLHS = %i[mlhs masgn].freeze
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- delimited(children)
-
- write(',') if children.one? && mlhs?
- end
-
- # Test for mlhs context
- #
- # @return [undefined]
- #
- # @api private
- #
- def mlhs?
- !NO_COMMA.include?(first_child.type) && PARENT_MLHS.include?(parent_type)
+ def emit_left
+ if base
+ visit(base)
+ write('::') unless n_cbase?(base)
+ end
+ write(name.to_s)
end
- end # MLHS
-
+ end # Constant
end # Assignment
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/begin.rb b/lib/unparser/emitter/begin.rb
index 0749668..8767dbe 100644
--- a/lib/unparser/emitter/begin.rb
+++ b/lib/unparser/emitter/begin.rb
@@ -5,97 +5,22 @@ module Unparser
# Emitter for begin nodes
class Begin < self
-
+ handle :begin
children :body
- private
-
- # Emit inner nodes
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_inner
- children.each_with_index do |child, index|
- visit_plain(child)
- write(NL) if index < children.length - 1
+ def emit_heredoc_reminders
+ children.each do |child|
+ emitter(child).emit_heredoc_reminders
end
end
- # Emitter for implicit begins
- class Implicit < self
-
- handle :begin
-
- # Test if begin is terminated
- #
- # @return [Boolean]
- #
- # @api private
- #
- def terminated?
- children.empty?
- end
-
- TERMINATING_PARENT = %i[root interpolated dyn_str_body].to_set.freeze
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- if terminated? && !TERMINATING_PARENT.include?(parent_type)
- write('()')
- else
- emit_inner
- end
- end
-
- end # Implicit
-
- # Emitter for explicit begins
- class Explicit < self
- include Terminated
-
- handle :kwbegin
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(K_BEGIN)
- emit_body
- k_end
- end
+ private
- # Emit body
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_body
- if body.nil?
- nl
- elsif NOINDENT.include?(body.type)
- emit_inner
- else
- indented { emit_inner }
- end
+ def dispatch
+ parentheses do
+ delimited(children, '; ')
end
-
- end # Explicit
-
+ end
end # Begin
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/binary.rb b/lib/unparser/emitter/binary.rb
index 26025dc..8ad482e 100644
--- a/lib/unparser/emitter/binary.rb
+++ b/lib/unparser/emitter/binary.rb
@@ -2,33 +2,20 @@
module Unparser
class Emitter
- # Base class for binary emitters
+ # Non send binary operator / keyword emitter
class Binary < self
- include Unterminated
-
- children :left, :right
-
- MAP = {
- or: T_OR,
- and: T_AND
- }.freeze
-
- handle(*MAP.keys)
+ handle :and, :or
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- visit(left)
- write(WS, MAP.fetch(node.type), WS)
- visit(right)
+ writer.dispatch
end
+ def writer
+ writer_with(Writer::Binary, node)
+ end
+ memoize :writer
end # Binary
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/block.rb b/lib/unparser/emitter/block.rb
index f360ada..15e9367 100644
--- a/lib/unparser/emitter/block.rb
+++ b/lib/unparser/emitter/block.rb
@@ -4,66 +4,82 @@ module Unparser
class Emitter
# Block emitter
- #
- # ignore :reek:RepeatedConditional
class Block < self
- include Terminated
-
- handle :block
+ handle :block, :numblock
children :target, :arguments, :body
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
emit_target
- write(WS, K_DO)
- emit_block_arguments unless stabby_lambda?
- emit_body
- k_end
+ ws
+ write_open
+ emit_block_arguments unless n_lambda?(target)
+ target_writer.emit_heredoc_reminders if n_send?(target)
+ emit_optional_body_ensure_rescue(body)
+ write_close
end
- # Emit target
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_target
- visit(target)
+ def need_do?
+ body && (n_rescue?(body) || n_ensure?(body))
+ end
+
+ def write_open
+ if need_do?
+ write('do')
+ else
+ write('{')
+ end
+ end
- if stabby_lambda?
- parentheses { visit(arguments) }
+ def write_close
+ if need_do?
+ k_end
+ else
+ write('}')
end
end
- # Test if we are emitting a stabby lambda
- #
- # @return [Boolean]
- #
- # @api private
- #
- def stabby_lambda?
- target.type.equal?(:lambda)
+ def target_writer
+ writer_with(Writer::Send::Regular, target)
+ end
+ memoize :target_writer
+
+ def emit_target
+ case target.type
+ when :send
+ emit_send_target
+ when :lambda
+ visit(target)
+ emit_lambda_arguments unless node.type.equal?(:numblock)
+ else
+ visit(target)
+ end
+ end
+
+ def emit_send_target
+ target_writer.emit_receiver
+ target_writer.emit_selector
+ target_writer.emit_arguments_without_heredoc_body
+ end
+
+ def emit_lambda_arguments
+ parentheses { writer_with(Args, arguments).emit_lambda_arguments }
+ end
+
+ def numblock?
+ node.type.equal?(:numblock)
end
- # Emit arguments
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_block_arguments
- return if arguments.children.empty?
+ return if numblock? || arguments.children.empty?
ws
- visit_parentheses(arguments, T_PIPE, T_PIPE)
+
+ parentheses('|', '|') do
+ writer_with(Args, arguments).emit_block_arguments
+ end
end
end # Block
diff --git a/lib/unparser/emitter/case.rb b/lib/unparser/emitter/case.rb
index 0bc0949..6d0a511 100644
--- a/lib/unparser/emitter/case.rb
+++ b/lib/unparser/emitter/case.rb
@@ -4,8 +4,6 @@ module Unparser
class Emitter
# Emitter for case nodes
class Case < self
- include Terminated
-
handle :case
children :condition
@@ -13,89 +11,49 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_CASE)
+ write('case')
emit_condition
emit_whens
emit_else
k_end
end
- # Emit else
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_else
else_branch = children.last
return unless else_branch
- write(K_ELSE)
- visit_indented(else_branch)
+ write('else')
+ emit_body(else_branch)
end
- # Emit whens
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_whens
nl
whens.each(&method(:visit))
end
- # Emit condition
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_condition
return unless condition
- write(WS)
+ ws
visit(condition)
end
-
end # Case
# Emitter for when nodes
class When < self
- include Terminated
-
handle :when
define_group :captures, 0..-2
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_WHEN, WS)
+ write('when ')
emit_captures
- body = children.last
- emit_body(body)
+ emit_optional_body(children.last)
end
- # Emit captures
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_captures
delimited(captures)
end
diff --git a/lib/unparser/emitter/case_guard.rb b/lib/unparser/emitter/case_guard.rb
new file mode 100644
index 0000000..b507a9e
--- /dev/null
+++ b/lib/unparser/emitter/case_guard.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for case guards
+ class CaseGuard < self
+
+ handle :if_guard, :unless_guard
+
+ MAP = {
+ if_guard: 'if',
+ unless_guard: 'unless'
+ }.freeze
+
+ children :condition
+
+ private
+
+ def dispatch
+ write(MAP.fetch(node_type))
+ ws
+ visit(condition)
+ end
+
+ end # UnlessGuard
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/case_match.rb b/lib/unparser/emitter/case_match.rb
new file mode 100644
index 0000000..5714f2e
--- /dev/null
+++ b/lib/unparser/emitter/case_match.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for case matches
+ class CaseMatch < self
+
+ handle :case_match
+
+ children :target
+
+ define_group :patterns, 1..-2
+
+ private
+
+ def else_branch
+ children.last
+ end
+
+ def dispatch
+ write('case ')
+ visit(target)
+ nl
+ patterns.each(&method(:visit))
+ nl unless buffer.fresh_line?
+ emit_else_branch
+ k_end
+ end
+
+ def emit_else_branch
+ if else_branch
+ write('else')
+ emit_body(else_branch) unless n_empty_else?(else_branch)
+ nl unless buffer.fresh_line?
+ end
+ end
+
+ end # CaseMatch
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/cbase.rb b/lib/unparser/emitter/cbase.rb
index 0ca9f22..219c87a 100644
--- a/lib/unparser/emitter/cbase.rb
+++ b/lib/unparser/emitter/cbase.rb
@@ -4,8 +4,6 @@ module Unparser
class Emitter
# Emitter for toplevel constant reference nodes
class CBase < self
- include Terminated
-
handle :cbase
private
@@ -17,7 +15,7 @@ module Unparser
# @api private
#
def dispatch
- write(T_DCL)
+ write('::')
end
end # CBase
diff --git a/lib/unparser/emitter/class.rb b/lib/unparser/emitter/class.rb
index d92f465..ccdd26b 100644
--- a/lib/unparser/emitter/class.rb
+++ b/lib/unparser/emitter/class.rb
@@ -4,7 +4,7 @@ module Unparser
class Emitter
# Emitter for class nodes
class Class < self
- include LocalVariableRoot, Terminated
+ include LocalVariableRoot
handle :class
@@ -12,30 +12,18 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_CLASS, WS)
+ write('class ')
visit(name)
emit_superclass
- emit_body
+ emit_optional_body(body)
k_end
end
- # Emit superclass
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_superclass
return unless superclass
- write(WS, T_LT, WS)
+ write(' < ')
visit(superclass)
end
@@ -43,24 +31,16 @@ module Unparser
# Emitter for sclass nodes
class SClass < self
- include Terminated
-
handle :sclass
children :object, :body
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_CLASS, WS, T_DLT, WS)
+ write('class << ')
visit(object)
- emit_body
+ emit_optional_body(body)
k_end
end
diff --git a/lib/unparser/emitter/const_pattern.rb b/lib/unparser/emitter/const_pattern.rb
new file mode 100644
index 0000000..f5fcce4
--- /dev/null
+++ b/lib/unparser/emitter/const_pattern.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for const pattern node
+ class ConstPattern < self
+
+ handle :const_pattern
+
+ children :const, :pattern
+
+ private
+
+ def dispatch
+ visit(const)
+ if n_hash_pattern?(pattern)
+ emitter(pattern).emit_const_pattern
+ else
+ visit(pattern)
+ end
+ end
+ end # ConstPattern
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/def.rb b/lib/unparser/emitter/def.rb
index a10b37c..7d75ee1 100644
--- a/lib/unparser/emitter/def.rb
+++ b/lib/unparser/emitter/def.rb
@@ -4,70 +4,40 @@ module Unparser
class Emitter
# Emitter for def node
class Def < self
- include LocalVariableRoot, Terminated
+ include LocalVariableRoot
private
- # Emit name
- #
- # @return [undefined]
- #
- # @api private
- #
abstract_method :emit_name
private :emit_name
- # Return body node
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
abstract_method :body
private :body
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_DEF, WS)
+ write('def ')
emit_name
- comments.consume(node, :name)
emit_arguments
- emit_body
+ emit_optional_body_ensure_rescue(body)
k_end
end
- # Emit arguments
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_arguments
return if arguments.children.empty?
- visit_parentheses(arguments)
+ parentheses do
+ writer_with(Args, arguments).emit_def_arguments
+ end
end
# Instance def emitter
class Instance < self
-
handle :def
children :name, :arguments, :body
private
- # Emit name
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_name
write(name.to_s)
end
@@ -83,25 +53,13 @@ module Unparser
private
- # Return mame
- #
- # @return [String]
- #
- # @api private
- #
def emit_name
conditional_parentheses(!subject_without_parens?) do
visit(subject)
end
- write(T_DOT, name.to_s)
+ write('.', name.to_s)
end
- # Test if subject needs parentheses
- #
- # @return [Boolean]
- #
- # @api private
- #
def subject_without_parens?
case subject.type
when :self
@@ -111,8 +69,6 @@ module Unparser
when :send
receiver, _selector, *arguments = *subject
!receiver && arguments.empty?
- else
- false
end
end
diff --git a/lib/unparser/emitter/defined.rb b/lib/unparser/emitter/defined.rb
index ffa6115..c848741 100644
--- a/lib/unparser/emitter/defined.rb
+++ b/lib/unparser/emitter/defined.rb
@@ -4,25 +4,15 @@ module Unparser
class Emitter
# Emitter for defined? nodes
class Defined < self
- include Terminated
-
handle :defined?
children :subject
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_DEFINED)
- parentheses do
- visit(subject)
- end
+ write('defined?')
+ parentheses { visit(subject) }
end
end # Defined
diff --git a/lib/unparser/emitter/dstr.rb b/lib/unparser/emitter/dstr.rb
new file mode 100644
index 0000000..0095787
--- /dev/null
+++ b/lib/unparser/emitter/dstr.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Dynamic string emitter
+ class DStr < self
+
+ handle :dstr
+
+ def emit_heredoc_reminders
+ writer_with(Writer::DynamicString, node).emit_heredoc_reminder
+ end
+
+ private
+
+ def dispatch
+ writer_with(Writer::DynamicString, node).dispatch
+ end
+
+ end # DStr
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/dsym.rb b/lib/unparser/emitter/dsym.rb
new file mode 100644
index 0000000..f8ee4c8
--- /dev/null
+++ b/lib/unparser/emitter/dsym.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Dynamic symbol literal emitter
+ class DSym < self
+ handle :dsym
+
+ private
+
+ def dispatch
+ write(':"')
+ children.each do |child|
+ case child.type
+ when :str
+ emit_str_child(child)
+ when :begin
+ emit_begin_child(child)
+ end
+ end
+ write('"')
+ end
+
+ def emit_str_child(value)
+ string = value.children.first
+ if string.end_with?("\n")
+ write(string.inspect[1..-4])
+ nl
+ else
+ write(string.inspect[1..-2])
+ end
+ end
+
+ def emit_begin_child(component)
+ write('#{')
+ visit(unwrap_single_begin(component))
+ write('}')
+ end
+ end # DSym
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/empty.rb b/lib/unparser/emitter/empty.rb
deleted file mode 100644
index 2837e97..0000000
--- a/lib/unparser/emitter/empty.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
-
- # Emitter for artifical empty node
- class Empty < self
-
- handle :empty
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch; end
-
- end
- end
-end # Unparser
diff --git a/lib/unparser/emitter/ensure.rb b/lib/unparser/emitter/ensure.rb
deleted file mode 100644
index 320937b..0000000
--- a/lib/unparser/emitter/ensure.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
-
- # Emitter for ensure nodes
- class Ensure < self
-
- handle :ensure
-
- children :body, :ensure_body
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- if body
- visit_indented(body)
- else
- nl
- end
- write(K_ENSURE)
- if ensure_body
- visit_indented(ensure_body)
- else
- nl
- end
- end
-
- end # Ensure
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/find_pattern.rb b/lib/unparser/emitter/find_pattern.rb
new file mode 100644
index 0000000..751519b
--- /dev/null
+++ b/lib/unparser/emitter/find_pattern.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for in pattern nodes
+ class FindPattern < self
+ handle :find_pattern
+
+ private
+
+ def dispatch
+ write('[')
+ delimited(children)
+ write(']')
+ end
+ end # FindPattern
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/flipflop.rb b/lib/unparser/emitter/flipflop.rb
index 07da6f2..d7fd717 100644
--- a/lib/unparser/emitter/flipflop.rb
+++ b/lib/unparser/emitter/flipflop.rb
@@ -4,12 +4,19 @@ module Unparser
class Emitter
# Emitter for flip flops
class FlipFlop < self
- include Unterminated
-
- MAP = IceNine.deep_freeze(
+ MAP = {
iflipflop: '..',
eflipflop: '...'
- ).freeze
+ }.freeze
+
+ SYMBOLS = {
+ eflipflop: :tDOT3,
+ iflipflop: :tDOT2
+ }.freeze
+
+ def symbol_name
+ true
+ end
handle(*MAP.keys)
@@ -17,12 +24,6 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
visit(left)
write(MAP.fetch(node.type))
diff --git a/lib/unparser/emitter/float.rb b/lib/unparser/emitter/float.rb
new file mode 100644
index 0000000..e03c934
--- /dev/null
+++ b/lib/unparser/emitter/float.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emiter for float literals
+ class Float < self
+ handle :float
+
+ children :value
+
+ INFINITY = ::Float::INFINITY
+ NEG_INFINITY = -::Float::INFINITY
+
+ private
+
+ def dispatch
+ case value
+ when INFINITY
+ write('10e1000000000000000000')
+ when NEG_INFINITY
+ write('-10e1000000000000000000')
+ else
+ write(value.inspect)
+ end
+ end
+
+ end # Float
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/flow_modifier.rb b/lib/unparser/emitter/flow_modifier.rb
index 6fec151..ef58c97 100644
--- a/lib/unparser/emitter/flow_modifier.rb
+++ b/lib/unparser/emitter/flow_modifier.rb
@@ -4,77 +4,39 @@ module Unparser
class Emitter
# Emitter control flow modifiers
class FlowModifier < self
-
MAP = {
- return: K_RETURN,
- next: K_NEXT,
- break: K_BREAK
+ return: 'return',
+ next: 'next',
+ break: 'break'
}.freeze
+ private_constant(*constants(false))
+
handle(*MAP.keys)
- def terminated?
- children.empty?
+ def emit_heredoc_reminders
+ children.each do |node|
+ emitter(node).emit_heredoc_reminders
+ end
end
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
write(MAP.fetch(node.type))
- case children.length
- when 0 # rubocop:disable Lint/EmptyWhen
- when 1
- emit_single_argument
+
+ if children.one? && n_if?(children.first)
+ ws
+ emitter(children.first).emit_ternary
else
- emit_arguments
+ emit_arguments unless children.empty?
end
end
- # Emit break or return arguments
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_arguments
ws
- head, *tail = children
- emit_argument(head)
- tail.each do |node|
- write(DEFAULT_DELIMITER)
- emit_argument(node)
- end
+ delimited(children)
end
-
- PARENS = %i[if case begin].to_set.freeze
-
- # Emit argument
- #
- # @param [Parser::AST::Node] node
- #
- # @api private
- #
- def emit_argument(node)
- visit_plain(node)
- end
-
- # Emit single argument
- #
- # @api private
- #
- def emit_single_argument
- ws
- conditional_parentheses(PARENS.include?(first_child.type)) do
- visit_plain(first_child)
- end
- end
-
end # Return
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/for.rb b/lib/unparser/emitter/for.rb
index 1e61db2..5532eb5 100644
--- a/lib/unparser/emitter/for.rb
+++ b/lib/unparser/emitter/for.rb
@@ -4,38 +4,24 @@ module Unparser
class Emitter
# Emitter for for nodes
class For < self
- include Terminated
-
handle :for
children :condition, :assignment, :body
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_FOR, WS)
+ write('for ')
emit_condition
- emit_body
+ emit_optional_body(body)
k_end
end
- # Emit assignment
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_condition
- visit_plain(condition)
- write(WS, K_IN, WS)
+ visit(condition)
+ write(' in ')
visit(assignment)
- write(WS, K_DO)
+ write(' do')
end
end # For
diff --git a/lib/unparser/emitter/hash.rb b/lib/unparser/emitter/hash.rb
new file mode 100644
index 0000000..bd0d3f6
--- /dev/null
+++ b/lib/unparser/emitter/hash.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for Hash literals
+ class Hash < self
+ handle :hash
+
+ def emit_heredoc_reminders
+ children.each(&method(:emit_heredoc_reminder_member))
+ end
+
+ private
+
+ def dispatch
+ if children.empty?
+ write('{}')
+ else
+ parentheses('{', '}') do
+ write(' ')
+ emit_hash_body
+ write(' ')
+ end
+ end
+ end
+
+ def emit_heredoc_reminder_member(node)
+ emitter(node.children.last).emit_heredoc_reminders
+ end
+
+ def emit_hash_body
+ delimited(children)
+ end
+ end # Hash
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/hash_pattern.rb b/lib/unparser/emitter/hash_pattern.rb
new file mode 100644
index 0000000..c03fa91
--- /dev/null
+++ b/lib/unparser/emitter/hash_pattern.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for hash patterns
+ class HashPattern < self
+
+ handle :hash_pattern
+
+ def emit_const_pattern
+ parentheses do
+ emit_hash_body
+ end
+ end
+
+ private
+
+ def dispatch
+ parentheses('{', '}') do
+ emit_hash_body
+ end
+ end
+
+ def emit_hash_body
+ delimited(children, &method(:emit_member))
+ end
+
+ def emit_member(node)
+ case node.type
+ when :pair
+ emit_pair(node)
+ when :match_var
+ emit_match_var(node)
+ when :match_rest
+ writer_with(MatchRest, node).emit_hash_pattern
+ else
+ visit(node)
+ end
+ end
+
+ def emit_match_var(node)
+ write_symbol_body(node.children.first)
+ write(':')
+ end
+
+ def emit_pair(node)
+ key, value = node.children
+
+ if n_sym?(key)
+ write_symbol_body(key.children.first)
+ else
+ visit(s(:dstr, *key))
+ end
+
+ write(':')
+
+ ws
+
+ visit(value)
+ end
+
+ def write_symbol_body(symbol)
+ write(symbol.inspect[1..])
+ end
+ end # Pin
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/hookexe.rb b/lib/unparser/emitter/hookexe.rb
index bb71d1e..de2a2e8 100644
--- a/lib/unparser/emitter/hookexe.rb
+++ b/lib/unparser/emitter/hookexe.rb
@@ -6,8 +6,8 @@ module Unparser
class Hookexe < self
MAP = {
- preexe: K_PREEXE,
- postexe: K_POSTEXE
+ preexe: 'BEGIN',
+ postexe: 'END'
}.freeze
handle(*MAP.keys)
@@ -16,16 +16,10 @@ module Unparser
private
- # Perfrom dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(MAP.fetch(node.type), WS)
- parentheses(*BRACKETS_CURLY) do
- emit_body
+ write(MAP.fetch(node.type), ' ')
+ parentheses('{', '}') do
+ emit_body(body)
end
end
diff --git a/lib/unparser/emitter/if.rb b/lib/unparser/emitter/if.rb
index 11827c3..4919a0f 100644
--- a/lib/unparser/emitter/if.rb
+++ b/lib/unparser/emitter/if.rb
@@ -3,25 +3,21 @@
module Unparser
class Emitter
# Emitter if nodes
- #
- # ignore :reek:RepeatedConditional
class If < self
handle :if
children :condition, :if_branch, :else_branch
- def terminated?
- !postcondition?
+ def emit_ternary
+ visit(condition)
+ write(' ? ')
+ visit(if_branch)
+ write(' : ')
+ visit(else_branch)
end
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
if postcondition?
emit_postcondition
@@ -30,105 +26,53 @@ module Unparser
end
end
- # Test for postcondition
- #
- # @return [Boolean]
- #
- # @api private
- #
def postcondition?
return false unless if_branch.nil? ^ else_branch.nil?
body = if_branch || else_branch
- local_variable_scope.first_assignment_in_body_and_used_in_condition?(body, condition)
+ local_variable_scope.first_assignment_in?(body, condition)
end
- # Emit in postcondition style
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_postcondition
- visit_plain(if_branch || else_branch)
- write(WS, keyword, WS)
+ visit(if_branch || else_branch)
+ write(' ', keyword, ' ')
emit_condition
end
- # Emit in normal style
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_normal
- write(keyword, WS)
+ write(keyword, ' ')
emit_condition
emit_if_branch
emit_else_branch
k_end
end
- # Test if AST can be emitted as unless
- #
- # @return [Boolean]
- #
- # @api private
- #
def unless?
!if_branch && else_branch
end
- # Return keyword
- #
- # @return [String]
- #
- # @api private
- #
def keyword
- unless? ? K_UNLESS : K_IF
+ unless? ? 'unless' : 'if'
end
- # Emit condition
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_condition
- if condition.type.equal?(:match_current_line)
- visit_plain(condition)
- else
- visit(condition)
- end
+ visit(condition)
end
- # Emit if branch
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_if_branch
if if_branch
- visit_indented(if_branch)
+ emit_body(if_branch)
end
nl if !if_branch && !else_branch
end
- # Emit else branch
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_else_branch
return unless else_branch
- write(K_ELSE) unless unless?
- visit_indented(else_branch)
+ write('else') unless unless?
+ emit_body(else_branch)
end
end # If
diff --git a/lib/unparser/emitter/in_match.rb b/lib/unparser/emitter/in_match.rb
new file mode 100644
index 0000000..5027fa0
--- /dev/null
+++ b/lib/unparser/emitter/in_match.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for in pattern nodes
+ class InMatch < self
+
+ handle :in_match
+
+ children :target, :pattern
+
+ private
+
+ def dispatch
+ visit(target)
+ write(' in ')
+ visit(pattern)
+ end
+ end # InMatch
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/in_pattern.rb b/lib/unparser/emitter/in_pattern.rb
new file mode 100644
index 0000000..b1f0d43
--- /dev/null
+++ b/lib/unparser/emitter/in_pattern.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for in pattern nodes
+ class InPattern < self
+
+ handle :in_pattern
+
+ children :target, :unless_guard, :branch, :else_branch
+
+ private
+
+ def dispatch
+ write('in')
+
+ ws
+
+ visit(target)
+
+ if unless_guard
+ ws
+ visit(unless_guard)
+ end
+
+ if branch
+ ws
+ write('then')
+ emit_body(branch)
+ else
+ nl
+ end
+ end
+ end # InPattern
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/index.rb b/lib/unparser/emitter/index.rb
index 42efeeb..dd7a3a7 100644
--- a/lib/unparser/emitter/index.rb
+++ b/lib/unparser/emitter/index.rb
@@ -3,61 +3,29 @@
module Unparser
class Emitter
# Emitter for send to index references
- #
- # ignore :reek:RepeatedConditional
class Index < self
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
+ private
+
def dispatch
emit_receiver
emit_operation
end
- private
-
- # Emit receiver
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_receiver
visit(first_child)
end
- # Test for mlhs
- #
- # @return [Boolean]
- #
- # @api private
- #
- def mlhs?
- parent_type.equal?(:mlhs)
- end
-
class Reference < self
- include Terminated
-
define_group(:indices, 1..-1)
handle :index
private
- # Emit arguments
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_operation
- parentheses(*BRACKETS_SQUARE) do
- delimited_plain(indices)
+ parentheses('[', ']') do
+ delimited(indices)
end
end
end # Reference
@@ -68,67 +36,32 @@ module Unparser
handle :indexasgn
VALUE_RANGE = (1..-2).freeze
- NO_VALUE_PARENT = IceNine.deep_freeze(%i[and_asgn op_asgn or_asgn].to_set)
-
- # Test if assign will be emitted terminated
- #
- # @return [Boolean]
- #
- # @api private
- #
- def terminated?
- !emit_value?
- end
+ NO_VALUE_PARENT = %i[and_asgn op_asgn or_asgn].to_set.freeze
- private
+ private_constant(*constants(false))
- # Emit arguments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_operation
- parentheses(*BRACKETS_SQUARE) do
- delimited_plain(indices)
- end
-
- if emit_value?
- write(WS, T_ASN, WS)
- visit(children.last)
- end
+ def emit_heredoc_reminders
+ emitter(children.last).emit_heredoc_reminders
end
- # The indices
- #
- # @return [Array<Parser::AST::Node>]
- #
- def indices
- if emit_value?
- children[VALUE_RANGE]
- else
- children.drop(1)
- end
+ def dispatch
+ emit_receiver
+ emit_operation(children[VALUE_RANGE])
+ write(' = ')
+ visit(children.last)
end
- # Test if value should be emitted
- #
- # @return [Boolean]
- #
- # @api private
- #
- def emit_value?
- !mlhs? && !no_value_parent?
+ def emit_mlhs
+ emit_receiver
+ emit_operation(children.drop(1))
end
- # Test for no value parent
- #
- # @return [Boolean]
- #
- # @api private
- #
- def no_value_parent?
- NO_VALUE_PARENT.include?(parent_type)
+ private
+
+ def emit_operation(indices)
+ parentheses('[', ']') do
+ delimited(indices)
+ end
end
end # Assign
end # Index
diff --git a/lib/unparser/emitter/kwargs.rb b/lib/unparser/emitter/kwargs.rb
new file mode 100644
index 0000000..bffffc9
--- /dev/null
+++ b/lib/unparser/emitter/kwargs.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ class Kwargs < self
+ handle :kwargs
+
+ def dispatch
+ delimited(children)
+ end
+ end # Kwargs
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/kwbegin.rb b/lib/unparser/emitter/kwbegin.rb
new file mode 100644
index 0000000..06fd712
--- /dev/null
+++ b/lib/unparser/emitter/kwbegin.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for explicit begins
+ class KWBegin < self
+ handle :kwbegin
+
+ private
+
+ def dispatch
+ write('begin')
+
+ if children.one?
+ emit_body_ensure_rescue(children.first)
+ else
+ indented do
+ emit_multiple_body
+ end
+ end
+
+ k_end
+ end
+
+ def emit_multiple_body
+ emit_join(children, method(:emit_body_member), method(:nl))
+ end
+
+ end # KWBegin
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/lambda.rb b/lib/unparser/emitter/lambda.rb
index 2bcb432..4e3bb73 100644
--- a/lib/unparser/emitter/lambda.rb
+++ b/lib/unparser/emitter/lambda.rb
@@ -4,18 +4,10 @@ module Unparser
class Emitter
# Emitter for lambda nodes
class Lambda < self
- include Terminated
-
handle :lambda
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
write('->')
end
diff --git a/lib/unparser/emitter/literal.rb b/lib/unparser/emitter/literal.rb
deleted file mode 100644
index 504a348..0000000
--- a/lib/unparser/emitter/literal.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- # Namespace class for literal emiters
- class Literal < self
- include Terminated
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/array.rb b/lib/unparser/emitter/literal/array.rb
deleted file mode 100644
index a55345a..0000000
--- a/lib/unparser/emitter/literal/array.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
-
- # Array literal emitter
- class Array < self
- handle :array
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- parentheses(*BRACKETS_SQUARE) do
- delimited(children)
- end
- end
-
- end # Array
-
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/dynamic.rb b/lib/unparser/emitter/literal/dynamic.rb
deleted file mode 100644
index 24d89c0..0000000
--- a/lib/unparser/emitter/literal/dynamic.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
-
- # Base class for dynamic literal emitters
- class Dynamic < self
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- util = self.class
- visit_parentheses(dynamic_body, util::OPEN, util::CLOSE)
- end
-
- # Return dynamic body
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def dynamic_body
- Parser::AST::Node.new(:dyn_str_body, children)
- end
-
- # Dynamic string literal emitter
- class String < self
-
- OPEN = CLOSE = '"'.freeze
- handle :dstr
-
- end # String
-
- # Dynamic symbol literal emitter
- class Symbol < self
-
- OPEN = ':"'.freeze
- CLOSE = '"'.freeze
-
- handle :dsym
-
- end # Symbol
- end # Dynamic
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/dynamic_body.rb b/lib/unparser/emitter/literal/dynamic_body.rb
deleted file mode 100644
index 8bdf559..0000000
--- a/lib/unparser/emitter/literal/dynamic_body.rb
+++ /dev/null
@@ -1,132 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
-
- # Emitter for dynamic bodies
- class DynamicBody < self
-
- OPEN = '#{'.freeze
- CLOSE = '}'.freeze
-
- # Emitter for interpolated nodes
- class Interpolation < self
-
- handle :interpolated
-
- children :subject
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(OPEN)
- visit_plain(subject)
- write(CLOSE)
- end
-
- end
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- children.each(&method(:emit_segment))
- end
-
- # Emit segment
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_segment(node)
- emit_interpolated_segment(node)
- end
-
- pairs = Parser::Lexer::ESCAPES.invert.map do |key, value|
- [key, "\\#{value}"] unless key.eql?(WS)
- end.compact
-
- pairs << ['#{', '\#{']
-
- ESCAPES = ::Hash[pairs]
-
- REPLACEMENTS = ::Regexp.union(ESCAPES.keys)
-
- # Emit str segment
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_str_segment(node)
- util = self.class
- string = node.children.first
- segment = string
- .gsub(REPLACEMENTS, ESCAPES)
- .gsub(util::DELIMITER, util::REPLACEMENT)
- write(segment)
- end
-
- # Emit interpolated segment
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_interpolated_segment(node)
- visit_parentheses(node, OPEN, CLOSE)
- end
-
- # Dynamic string body
- class String < self
-
- handle :dyn_str_body
-
- DELIMITER = '"'.freeze
- REPLACEMENT = '\"'.freeze
-
- end # String
-
- # Dynamic regexp body
- class Regexp < self
-
- handle :dyn_regexp_body
-
- DELIMITER = '/'.freeze
- REPLACEMENT = '\/'.freeze
-
- end # Regexp
-
- # Dynamic regexp body
- class ExecuteString < self
-
- handle :dyn_xstr_body
-
- DELIMITER = '`'.freeze
- REPLACEMENT = '\`'.freeze
-
- end # ExecuteString
-
- end # DynamicBody
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/execute_string.rb b/lib/unparser/emitter/literal/execute_string.rb
deleted file mode 100644
index 64b4176..0000000
--- a/lib/unparser/emitter/literal/execute_string.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
- # Emitter for execute strings (xstr) nodes
- class ExecuteString < self
-
- OPEN = CLOSE = '`'.freeze
-
- handle :xstr
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- visit_parentheses(dynamic_body, OPEN, CLOSE)
- end
-
- # Return dynamic body
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def dynamic_body
- Parser::AST::Node.new(:dyn_xstr_body, children)
- end
-
- end # ExecuteString
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/hash.rb b/lib/unparser/emitter/literal/hash.rb
deleted file mode 100644
index fb958ce..0000000
--- a/lib/unparser/emitter/literal/hash.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
-
- # Abstract namespace class for hash pair emitters
- class HashPair < self
-
- children :key, :value
-
- private
-
- # Emit value
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_value
- value_type = value.type
- conditional_parentheses(value_type.equal?(:if)) do
- visit(value)
- end
- end
-
- # Pair emitter that emits hash-rocket separated key values
- class Rocket < self
- HASHROCKET = ' => '.freeze
-
- handle :pair_rocket, :pair
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- visit(key)
- write(HASHROCKET)
- emit_value
- end
-
- end # Rocket
-
- # Pair emitter that emits colon separated key values
- class Colon < self
- COLON = ': '.freeze
-
- handle :pair_colon
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(key.children.first.to_s, COLON)
- emit_value
- end
-
- end # Colon
-
- end # HashPair
-
- # Emitter for hash bodies
- class HashBody < self
-
- BAREWORD = /\A[A-Za-z_][A-Za-z_0-9]*[?!]?\z/.freeze
-
- handle :hash_body
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- delimited(effective_body)
- end
-
- # Return effective body
- #
- # @return [Enumerable<Parser::AST::Node>]
- #
- # @api private
- #
- def effective_body
- children.map do |pair|
- if pair.type.equal?(:kwsplat)
- pair
- else
- key, _value = *pair
- if key.type.equal?(:sym) && key.children.first.to_s =~ BAREWORD
- n(:pair_colon, pair.children)
- else
- n(:pair_rocket, pair.children)
- end
- end
- end
- end
-
- end # HashBody
-
- # Emitter for Hash literals
- class Hash < self
-
- DELIMITER = ', '.freeze
- OPEN = '{'.freeze
- CLOSE = '}'.freeze
-
- handle :hash
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- if children.empty?
- write(OPEN, CLOSE)
- else
- emit_hash_body
- end
- end
-
- # Emit hash body
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_hash_body
- parentheses(OPEN, CLOSE) do
- write(WS)
- run(HashBody)
- write(WS)
- end
- end
-
- end # Hash
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/primitive.rb b/lib/unparser/emitter/literal/primitive.rb
deleted file mode 100644
index a9b69ac..0000000
--- a/lib/unparser/emitter/literal/primitive.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
-
- # Base class for primitive emitters
- class Primitive < self
-
- children :value
-
- # Emitter for primitives based on Object#inspect
- class Inspect < self
-
- handle :sym, :str
-
- private
-
- # Dispatch value
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(value.inspect)
- end
-
- end # Inspect
-
- # Emitter for complex literals
- class Complex < self
-
- handle :complex
-
- RATIONAL_FORMAT = 'i'.freeze
-
- MAP =
- if 0.class.equal?(Integer)
- IceNine.deep_freeze(
- Float => :float,
- Rational => :rational,
- Integer => :int
- )
- else
- IceNine.deep_freeze(
- Float => :float,
- Rational => :rational,
- Fixnum => :int,
- Bignum => :int
- )
- end
-
- private
-
- # Dispatch value
- #
- # @return [undefined]
- #
- def dispatch
- emit_imaginary
- write(RATIONAL_FORMAT)
- end
-
- # Emit imaginary component
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_imaginary
- visit(imaginary_node)
- end
-
- # Return imaginary node
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def imaginary_node
- imaginary = value.imaginary
- s(MAP.fetch(imaginary.class), imaginary)
- end
-
- end # Rational
-
- # Emitter for rational literals
- class Rational < self
-
- handle :rational
-
- RATIONAL_FORMAT = 'r'.freeze
-
- private
-
- # Dispatch value
- #
- # @return [undefined]
- #
- def dispatch
- integer = value.to_i
- float = value.to_f
-
- write_rational(integer.to_f.eql?(float) ? integer : float)
- end
-
- # Write rational format
- #
- # @param [#to_s] value
- #
- # @return [undefined]
- #
- # @api private
- #
- def write_rational(value)
- write(value.to_s, RATIONAL_FORMAT)
- end
-
- end # Rational
-
- # Emiter for numeric literals
- class Numeric < self
-
- handle :int, :float
-
- private
-
- # Dispatch value
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- conditional_parentheses(parent.is_a?(Send) && value.negative?) do
- write(value.inspect)
- end
- end
-
- end # Numeric
- end # Primitive
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/range.rb b/lib/unparser/emitter/literal/range.rb
deleted file mode 100644
index a1160ba..0000000
--- a/lib/unparser/emitter/literal/range.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
- # Abstract base class for literal range emitter
- class Range < self
- include Unterminated
-
- TOKENS = IceNine.deep_freeze(
- irange: '..',
- erange: '...'
- )
-
- handle(*TOKENS.keys)
-
- children :begin_node, :end_node
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- visit(begin_node)
- write(TOKENS.fetch(node.type))
- visit(end_node) if end_node
- end
-
- end # Range
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/regexp.rb b/lib/unparser/emitter/literal/regexp.rb
deleted file mode 100644
index 13db5ea..0000000
--- a/lib/unparser/emitter/literal/regexp.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
-
- # Emitter for regexp literals
- class Regexp < self
- DELIMITER = '/'.freeze
- ESCAPED_DELIMITER = '\/'.freeze
-
- handle :regexp
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- parentheses(DELIMITER, DELIMITER) do
- body.each(&method(:write_body))
- end
- visit(children.last)
- end
-
- # Return non regopt children
- #
- # @return [Array<Parser::AST::Node>]
- #
- # @api private
- #
- def body
- children[0..-2]
- end
-
- # Write specific body component
- #
- # @param [Parser::AST::Node] node
- #
- # @return [undefined]
- #
- # @api private
- #
- def write_body(node)
- case node.type
- when :str
- buffer.append_without_prefix(escape(node).children.first)
- else
- visit(s(:interpolated, node))
- end
- end
-
- # Return dynamic body
- #
- # @return [undefined]
- #
- # @api private
- #
- def dynamic_body
- Parser::AST::Node.new(:dyn_regexp_body, dynamic_body_children)
- end
-
- # Return dynamic body children
- #
- # @return [Enumerable<Parser::AST::Node>]
- #
- # @api private
- #
- def dynamic_body_children
- children[0..-2].map do |child|
- escape(child)
- end
- end
-
- # Return escaped child
- #
- # @param [Parser::AST::Node] child
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def escape(child)
- source = child.children.first
- s(:str, source.gsub(DELIMITER, ESCAPED_DELIMITER))
- end
-
- end # Regexp
-
- # Emitter for regexp options
- class Regopt < self
-
- handle :regopt
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(children.map(&:to_s).join)
- end
-
- end # Regopt
-
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/singleton.rb b/lib/unparser/emitter/literal/singleton.rb
deleted file mode 100644
index cf664d4..0000000
--- a/lib/unparser/emitter/literal/singleton.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Literal
- # Emiter for literal singletons
- class Singleton < self
-
- handle :self, :true, :false, :nil
-
- private
-
- # Perform dispatco
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- buffer.append(node.type.to_s)
- end
-
- end # Singleton
- end # Literal
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/masgn.rb b/lib/unparser/emitter/masgn.rb
new file mode 100644
index 0000000..7eb2d49
--- /dev/null
+++ b/lib/unparser/emitter/masgn.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for multiple assignment nodes
+ class MASGN < self
+ handle :masgn
+
+ children :target, :source
+
+ private
+
+ def dispatch
+ visit(target)
+ write(' = ')
+ visit(source)
+ end
+ end # MLHS
+ end # Emitter
+end # Unaprser
diff --git a/lib/unparser/emitter/match.rb b/lib/unparser/emitter/match.rb
index 1af6418..ec37bf3 100644
--- a/lib/unparser/emitter/match.rb
+++ b/lib/unparser/emitter/match.rb
@@ -5,10 +5,6 @@ module Unparser
# Base class for special match node emitters
class Match < self
- include Unterminated
-
- OPERATOR = '=~'.freeze
-
# Emitter for match with local variable assignment
class Lvasgn < self
handle :match_with_lvasgn
@@ -17,15 +13,9 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
visit(regexp)
- write(WS, OPERATOR, WS)
+ write(' =~ ')
visit(lvasgn)
end
@@ -37,12 +27,8 @@ module Unparser
children :regexp
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
+ private
+
def dispatch
visit(regexp)
end
diff --git a/lib/unparser/emitter/match_alt.rb b/lib/unparser/emitter/match_alt.rb
new file mode 100644
index 0000000..ff877ab
--- /dev/null
+++ b/lib/unparser/emitter/match_alt.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for in pattern nodes
+ class MatchAlt < self
+
+ handle :match_alt
+
+ children :left, :right
+
+ private
+
+ def dispatch
+ visit(left)
+ ws
+ write('|')
+ ws
+ visit(right)
+ end
+ end # MatchAlt
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_as.rb b/lib/unparser/emitter/match_as.rb
new file mode 100644
index 0000000..59eb0d8
--- /dev/null
+++ b/lib/unparser/emitter/match_as.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for in pattern nodes
+ class MatchAs < self
+
+ handle :match_as
+
+ children :left, :right
+
+ private
+
+ def dispatch
+ visit(left)
+ write(' => ')
+ visit(right)
+ end
+ end # MatchAs
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_pattern.rb b/lib/unparser/emitter/match_pattern.rb
new file mode 100644
index 0000000..c9e45dd
--- /dev/null
+++ b/lib/unparser/emitter/match_pattern.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for in pattern nodes
+ class MatchPattern < self
+
+ handle :match_pattern
+
+ children :target, :pattern
+
+ # Modern ast format emits `match_pattern`
+ # node on single line pre 3.0, but 3.0+ uses `match_pattern_p`
+ SYMBOL =
+ if RUBY_VERSION < '3.0'
+ ' in '
+ else
+ ' => '
+ end
+
+ private
+
+ def dispatch
+ visit(target)
+ write(SYMBOL)
+ visit(pattern)
+ end
+ end # MatchPattern
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_pattern_p.rb b/lib/unparser/emitter/match_pattern_p.rb
new file mode 100644
index 0000000..90b6251
--- /dev/null
+++ b/lib/unparser/emitter/match_pattern_p.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ class MatchPatternP < self
+
+ handle :match_pattern_p
+
+ children :target, :pattern
+
+ private
+
+ def dispatch
+ visit(target)
+ write(' in ')
+ visit(pattern)
+ end
+ end # MatchPatternP
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_rest.rb b/lib/unparser/emitter/match_rest.rb
new file mode 100644
index 0000000..b9a2f1c
--- /dev/null
+++ b/lib/unparser/emitter/match_rest.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emiter for match rest nodes
+ class MatchRest < self
+ handle :match_rest
+
+ children :match_var
+
+ def dispatch
+ write('*')
+ visit(match_var) if match_var
+ end
+
+ def emit_array_pattern
+ write('*')
+ emit_match_var
+ end
+
+ def emit_hash_pattern
+ write('**')
+ emit_match_var
+ end
+
+ private
+
+ def emit_match_var
+ visit(match_var) if match_var
+ end
+ end # MatchRest
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_var.rb b/lib/unparser/emitter/match_var.rb
new file mode 100644
index 0000000..5c89590
--- /dev/null
+++ b/lib/unparser/emitter/match_var.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for in pattern nodes
+ class MatchVar < self
+
+ handle :match_var
+
+ children :name
+
+ private
+
+ def dispatch
+ write(name.to_s)
+ end
+ end # MatchVar
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/meta.rb b/lib/unparser/emitter/meta.rb
deleted file mode 100644
index d84078d..0000000
--- a/lib/unparser/emitter/meta.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- # Namespace class for meta emitters
- class Meta < self
- include Terminated
-
- handle(:__ENCODING__, :__FILE__, :__LINE__)
-
- def dispatch
- write(node.type.to_s)
- end
- end # Meta
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/mlhs.rb b/lib/unparser/emitter/mlhs.rb
new file mode 100644
index 0000000..c883539
--- /dev/null
+++ b/lib/unparser/emitter/mlhs.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for multiple assignment left hand side
+ class MLHS < self
+ handle :mlhs
+
+ NO_COMMA = %i[arg splat mlhs restarg].freeze
+
+ private_constant(*constants(false))
+
+ private
+
+ def dispatch
+ if children.one?
+ emit_one_child_mlhs
+ else
+ emit_many
+ end
+ end
+
+ def emit_one_child_mlhs
+ child = children.first
+ parentheses do
+ emitter(child).emit_mlhs
+ write(',') unless NO_COMMA.include?(child.type)
+ end
+ end
+
+ def emit_many
+ parentheses do
+ delimited(children) do |node|
+ emitter(node).emit_mlhs
+ end
+ end
+ end
+ end # MLHS
+ end # Emitter
+end # Unaprser
diff --git a/lib/unparser/emitter/module.rb b/lib/unparser/emitter/module.rb
index 364e248..06b3d9b 100644
--- a/lib/unparser/emitter/module.rb
+++ b/lib/unparser/emitter/module.rb
@@ -4,7 +4,7 @@ module Unparser
class Emitter
# Emitter for module nodes
class Module < self
- include LocalVariableRoot, Terminated
+ include LocalVariableRoot
handle :module
@@ -12,16 +12,10 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_MODULE, WS)
+ write('module ')
visit(name)
- emit_body
+ emit_optional_body(body)
k_end
end
diff --git a/lib/unparser/emitter/op_assign.rb b/lib/unparser/emitter/op_assign.rb
index 0a2d91e..52d3d11 100644
--- a/lib/unparser/emitter/op_assign.rb
+++ b/lib/unparser/emitter/op_assign.rb
@@ -5,28 +5,25 @@ module Unparser
# Base class for and and or op-assign
class BinaryAssign < self
- include Unterminated
-
children :target, :expression
- MAP = IceNine.deep_freeze(
+ MAP = {
and_asgn: '&&=',
or_asgn: '||='
- )
+ }.freeze
handle(*MAP.keys)
+ def emit_heredoc_reminders
+ emitter(target).emit_heredoc_reminders
+ emitter(expression).emit_heredoc_reminders
+ end
+
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- visit(target)
- write(WS, MAP.fetch(node.type), WS)
+ emitter(target).emit_mlhs
+ write(' ', MAP.fetch(node.type), ' ')
visit(expression)
end
@@ -34,32 +31,20 @@ module Unparser
# Emitter for op assign
class OpAssign < self
- include Unterminated
-
handle :op_asgn
+ children :target, :operator, :value
+
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- visit(first_child)
+ emitter(first_child).emit_mlhs
emit_operator
- visit(children[2])
+ visit(value)
end
- # Emit operator
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_operator
- write(WS, children[1].to_s, T_ASN, WS)
+ write(' ', operator.to_s, '= ')
end
end # OpAssign
diff --git a/lib/unparser/emitter/pair.rb b/lib/unparser/emitter/pair.rb
new file mode 100644
index 0000000..04c87d8
--- /dev/null
+++ b/lib/unparser/emitter/pair.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for key value pairs in hash literals or kwargs
+ class Pair < self
+ BAREWORD = /\A[A-Za-z_][A-Za-z_0-9]*[?!]?\z/.freeze
+
+ private_constant(*constants(false))
+
+ handle :pair
+
+ children :key, :value
+
+ private
+
+ def dispatch
+ if colon?(key)
+ write(key.children.first.to_s, ': ')
+ else
+ visit(key)
+ write(' => ')
+ end
+
+ visit(value)
+ end
+
+ def colon?(key)
+ n_sym?(key) && BAREWORD.match?(key.children.first)
+ end
+ end
+ end
+end
diff --git a/lib/unparser/emitter/pin.rb b/lib/unparser/emitter/pin.rb
new file mode 100644
index 0000000..444469e
--- /dev/null
+++ b/lib/unparser/emitter/pin.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for pin nodes
+ class Pin < self
+ handle :pin
+
+ children :target
+
+ private
+
+ def dispatch
+ write('^')
+ visit(target)
+ end
+ end # Pin
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/primitive.rb b/lib/unparser/emitter/primitive.rb
new file mode 100644
index 0000000..e173704
--- /dev/null
+++ b/lib/unparser/emitter/primitive.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Base class for primitive emitters
+ class Primitive < self
+
+ children :value
+
+ # Emitter for primitives based on Object#inspect
+ class Inspect < self
+
+ handle :sym, :str
+
+ private
+
+ def dispatch
+ write(value.inspect)
+ end
+
+ end # Inspect
+
+ # Emitter for complex literals
+ class Complex < self
+
+ handle :complex
+
+ RATIONAL_FORMAT = 'i'.freeze
+
+ MAP =
+ {
+ ::Float => :float,
+ ::Rational => :rational,
+ ::Integer => :int
+ }.freeze
+
+ private
+
+ def dispatch
+ emit_imaginary
+ write(RATIONAL_FORMAT)
+ end
+
+ def emit_imaginary
+ visit(imaginary_node)
+ end
+
+ def imaginary_node
+ imaginary = value.imaginary
+ s(MAP.fetch(imaginary.class), imaginary)
+ end
+
+ end # Rational
+
+ # Emitter for rational literals
+ class Rational < self
+
+ handle :rational
+
+ RATIONAL_FORMAT = 'r'.freeze
+
+ private
+
+ # rubocop:disable Lint/FloatComparison
+ def dispatch
+ integer = Integer(value)
+ float = value.to_f
+
+ write_rational(integer.to_f.equal?(float) ? integer : float)
+ end
+ # rubocop:enable Lint/FloatComparison
+
+ def write_rational(value)
+ write(value.to_s, RATIONAL_FORMAT)
+ end
+
+ end # Rational
+
+ # Emiter for numeric literals
+ class Numeric < self
+
+ handle :int
+
+ private
+
+ def dispatch
+ write(value.inspect)
+ end
+
+ end # Numeric
+ end # Primitive
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/range.rb b/lib/unparser/emitter/range.rb
new file mode 100644
index 0000000..8db822e
--- /dev/null
+++ b/lib/unparser/emitter/range.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Range emitters
+ class Range < self
+ TOKENS = {
+ irange: '..',
+ erange: '...'
+ }.freeze
+
+ SYMBOLS = {
+ erange: :tDOT3,
+ irange: :tDOT2
+ }.freeze
+
+ def symbol_name
+ true
+ end
+
+ handle(*TOKENS.keys)
+
+ children :begin_node, :end_node
+
+ private
+
+ def dispatch
+ visit(begin_node) if begin_node
+ write(TOKENS.fetch(node.type))
+ visit(end_node) if end_node
+ end
+
+ end # Range
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/redo.rb b/lib/unparser/emitter/redo.rb
deleted file mode 100644
index 93faa1a..0000000
--- a/lib/unparser/emitter/redo.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- # Emitter for redo nodes
- class Redo < self
- include Terminated
-
- handle :redo
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(K_REDO)
- end
-
- end # Redo
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/regexp.rb b/lib/unparser/emitter/regexp.rb
new file mode 100644
index 0000000..a9bdc94
--- /dev/null
+++ b/lib/unparser/emitter/regexp.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for regexp literals
+ class Regexp < self
+ handle :regexp
+
+ define_group(:body, 0..-2)
+
+ private
+
+ def dispatch
+ parentheses('/', '/') do
+ body.each(&method(:emit_body))
+ end
+ emit_options
+ end
+
+ def emit_options
+ write(children.last.children.join)
+ end
+
+ def emit_body(node)
+ if n_begin?(node)
+ write('#{')
+ node.children.each(&method(:visit))
+ write('}')
+ else
+ buffer.append_without_prefix(node.children.first.gsub('/', '\/'))
+ end
+ end
+ end # Regexp
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/repetition.rb b/lib/unparser/emitter/repetition.rb
index 870028f..9ecb993 100644
--- a/lib/unparser/emitter/repetition.rb
+++ b/lib/unparser/emitter/repetition.rb
@@ -5,39 +5,29 @@ module Unparser
# Emitter for postconditions
class Post < self
- include Unterminated
-
- handle :while_post, :until_post
-
children :condition, :body
MAP = {
- while_post: K_WHILE,
- until_post: K_UNTIL
+ while_post: 'while',
+ until_post: 'until'
}.freeze
handle(*MAP.keys)
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
+ private
+
def dispatch
visit(body)
- write(WS, MAP.fetch(node.type), WS)
+ write(' ', MAP.fetch(node.type), ' ')
visit(condition)
end
end
- # Base class for while and until emitters
+ # Emitter for while and until nodes
class Repetition < self
- include Terminated
-
MAP = {
- while: K_WHILE,
- until: K_UNTIL
+ while: 'while',
+ until: 'until'
}.freeze
handle(*MAP.keys)
@@ -46,12 +36,6 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
if postcontrol?
emit_postcontrol
@@ -60,54 +44,30 @@ module Unparser
end
end
- # Test if node must be emitted in postcontrol form
- #
- # @return [Boolean]
- #
- # @api private
- #
def postcontrol?
- return nil unless body # greez from falsyness
-
- local_variable_scope.first_assignment_in_body_and_used_in_condition?(body, condition)
+ body && local_variable_scope.first_assignment_in?(body, condition)
end
- # Emit keyword
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_keyword
- write(MAP.fetch(node.type), WS)
+ write(MAP.fetch(node.type), ' ')
end
- # Emit embedded
- #
- # @return [undefned]
- #
- # @api private
- #
def emit_normal
emit_keyword
- conditional_parentheses(condition.type.equal?(:block)) do
- visit(condition)
+ visit(condition)
+ if body
+ emit_body(body)
+ else
+ nl
end
- emit_body
k_end
end
- # Emit postcontrol
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_postcontrol
- visit_plain(body)
+ visit(body)
ws
emit_keyword
- visit_plain(condition)
+ visit(condition)
end
end # Repetition
diff --git a/lib/unparser/emitter/resbody.rb b/lib/unparser/emitter/resbody.rb
deleted file mode 100644
index 33494eb..0000000
--- a/lib/unparser/emitter/resbody.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- # Emitter for rescue body nodes
- class Resbody < self
-
- children :exception, :assignment, :body
-
- # Emitter for resbody in standalone form
- class Standalone < self
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(K_RESCUE, WS)
- visit_plain(body)
- end
- end
-
- # Emitter for resbody in keyworkd-embedded form
- class Embedded < self
-
- handle :resbody
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(K_RESCUE)
- emit_exception
- emit_assignment
- emit_body
- end
-
- # Emit exception
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_exception
- return unless exception
-
- ws
- delimited(exception.children)
- end
-
- # Emit assignment
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_assignment
- return unless assignment
-
- write(WS, T_ASR, WS)
- visit(assignment)
- end
-
- end # Resbody
- end
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/rescue.rb b/lib/unparser/emitter/rescue.rb
index 0846aa3..e4ca0e3 100644
--- a/lib/unparser/emitter/rescue.rb
+++ b/lib/unparser/emitter/rescue.rb
@@ -4,109 +4,13 @@ module Unparser
class Emitter
# Emitter for rescue nodes
class Rescue < self
- include Unterminated
-
handle :rescue
- children :body, :rescue_body
-
- define_group :rescue_bodies, 1..-2
-
- EMBEDDED_TYPES = %i[block def defs kwbegin ensure].to_set.freeze
-
- NOINDENT_STANDALONE_RESCUE = %i[root begin pair_rocket pair_colon lvasgn ivasgn].to_set.freeze
-
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- if standalone?
- if NOINDENT_STANDALONE_RESCUE.include?(parent_type)
- emit_standalone
- else
- indented { emit_standalone }
- end
- else
- emit_embedded
- end
- end
-
- # Test if rescue node ist standalone
- #
- # @return [Boolean]
- #
- # @api private
- #
- def standalone?
- if parent_type.equal?(:ensure)
- !parent.node.children.first.equal?(node)
- else
- !EMBEDDED_TYPES.include?(parent_type)
- end
- end
-
- # Emit standalone form
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_standalone
- visit_plain(body)
- ws
- run(Resbody::Standalone, rescue_body)
+ emit_rescue_postcontrol(node)
end
-
- # Emit embedded form
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_embedded
- if body
- visit_indented(body)
- else
- nl
- end
- rescue_bodies.each do |child|
- run(Resbody::Embedded, child)
- end
- emit_else
- end
-
- # Emit else
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_else
- return unless else_branch
-
- write(K_ELSE)
- visit_indented(else_branch)
- end
-
- # Return else body
- #
- # @return [Parser::AST::Node]
- # if else body is present
- #
- # @return [nil]
- # otherwise
- #
- # @api private
- #
- def else_branch
- children.last
- end
-
end # Rescue
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/retry.rb b/lib/unparser/emitter/retry.rb
deleted file mode 100644
index c8ab4bf..0000000
--- a/lib/unparser/emitter/retry.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- # Emitter for retry nodes
- class Retry < self
- include Terminated
-
- handle :retry
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(K_RETRY)
- end
-
- end # Break
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/root.rb b/lib/unparser/emitter/root.rb
index 5e0bb01..e335f44 100644
--- a/lib/unparser/emitter/root.rb
+++ b/lib/unparser/emitter/root.rb
@@ -4,8 +4,24 @@ module Unparser
class Emitter
# Root emitter a special case
class Root < self
- include Concord::Public.new(:node, :buffer, :comments)
+ include Concord::Public.new(:buffer, :node, :comments)
include LocalVariableRoot
+
+ END_NL = %i[class sclass module begin].freeze
+
+ private_constant(*constants(false))
+
+ def dispatch
+ if children.any?
+ emit_body(node, indent: false)
+ else
+ visit_deep(node)
+ end
+
+ emit_eof_comments
+
+ nl if END_NL.include?(node.type) && !buffer.fresh_line?
+ end
end # Root
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/send.rb b/lib/unparser/emitter/send.rb
index 76518ec..7c598d4 100644
--- a/lib/unparser/emitter/send.rb
+++ b/lib/unparser/emitter/send.rb
@@ -3,236 +3,27 @@
module Unparser
class Emitter
# Emitter for send
- # ignore :reek:TooManyMethods
class Send < self
+ handle :csend, :send
- handle :send
-
- ASSIGN_SUFFIX = '='.freeze
- INDEX_ASSIGN = :'[]='
- INDEX_REFERENCE = :'[]'
- NON_ASSIGN_RANGE = (0..-2).freeze
-
- children :receiver, :selector
+ def emit_mlhs
+ writer.emit_mlhs
+ end
- def terminated?
- effective_emitter.terminated?
+ def emit_heredoc_reminders
+ writer.emit_heredoc_reminders
end
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- effective_emitter.write_to_buffer
- end
-
- # Return effective emitter
- #
- # @return [Emitter]
- #
- # @api private
- #
- def effective_emitter
- effective_emitter_class.new(node, parent)
- end
-
- # Return effective emitter
- #
- # @return [Class:Emitter]
- #
- # @api private
- #
- def effective_emitter_class
- if binary_operator?
- Binary
- elsif unary_operator?
- Unary
- elsif attribute_assignment?
- AttributeAssignment
- else
- Regular
- end
- end
-
- # Return string selector
- #
- # @return [String]
- #
- # @api private
- #
- def string_selector
- selector.to_s
- end
-
- # Test for unary operator implemented as method
- #
- # @return [Boolean]
- #
- # @api private
- #
- def unary_operator?
- UNARY_OPERATORS.include?(selector)
- end
-
- # Test for binary operator implemented as method
- #
- # @return [Boolean]
- #
- # @api private
- #
- def binary_operator?
- BINARY_OPERATORS.include?(selector) && arguments.one? && !arguments.first.type.equal?(:splat)
- end
-
- # Emit selector
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_selector
- write(mlhs? ? non_assignment_selector : string_selector)
- end
-
- # Test for mlhs
- #
- # @return [Boolean]
- #
- # @api private
- #
- def mlhs?
- parent_type.equal?(:mlhs)
- end
-
- # Test for assignment
- #
- # FIXME: This also returns true for <= operator!
- #
- # @return [Boolean]
- #
- # @api private
- #
- def assignment?
- string_selector[-1].eql?(ASSIGN_SUFFIX)
- end
-
- # Test for attribute assignment
- #
- # @return [Boolean]
- #
- # @api private
- #
- def attribute_assignment?
- !BINARY_OPERATORS.include?(selector) && !UNARY_OPERATORS.include?(selector) && assignment? && !mlhs?
- end
-
- # Test for empty arguments
- #
- # @return [Boolean]
- #
- # @api private
- #
- def arguments?
- arguments.any?
- end
-
- # Return argument nodes
- #
- # @return [Array<Parser::AST::Node>]
- #
- # @api private
- #
- def arguments
- children[2..-1]
- end
- memoize :arguments
-
- # Emit arguments
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_arguments
- if arguments.empty?
- write('()') if receiver.nil? && avoid_clash?
- else
- normal_arguments
- end
- end
-
- # Emit normal arguments
- #
- # @return [undefined]
- #
- # @api private
- #
- def normal_arguments
- parentheses do
- delimited_plain(effective_arguments)
- end
- end
-
- # The effective arguments
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def effective_arguments
- last = arguments.length - 1
- arguments.each_with_index.map do |argument, index|
- if last.equal?(index) && argument.type.equal?(:hash) && argument.children.any?
- argument.updated(:hash_body)
- else
- argument
- end
- end
- end
-
- # Test if clash with local variable or constant needs to be avoided
- #
- # @return [Boolean]
- #
- # @api private
- #
- def avoid_clash?
- local_variable_clash? || parses_as_constant?
- end
-
- # Test for local variable clash
- #
- # @return [Boolean]
- #
- # @api private
- #
- def local_variable_clash?
- local_variable_scope.local_variable_defined_for_node?(node, selector)
- end
-
- # The non assignment selector
- #
- # @return [String]
- #
- # @api private
- def non_assignment_selector
- string_selector[NON_ASSIGN_RANGE]
+ writer.dispatch
end
- # Test if selector parses as constant
- #
- # @return [Boolean]
- #
- # @api private
- #
- def parses_as_constant?
- Unparser.parse(selector.to_s).type.equal?(:const)
+ def writer
+ writer_with(Writer::Send, node)
end
+ memoize :writer
end # Send
end # Emitter
end # Unparser
diff --git a/lib/unparser/emitter/send/binary.rb b/lib/unparser/emitter/send/binary.rb
deleted file mode 100644
index 8de416f..0000000
--- a/lib/unparser/emitter/send/binary.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Send
- # Emitter for binary sends
- class Binary < self
- include Unterminated
-
- private
-
- # Return undefined
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- visit(receiver)
- emit_operator
- emit_right
- end
-
- # Emit operator
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_operator
- write(WS, string_selector, WS)
- end
-
- # Return right node
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def right_node
- children[2]
- end
-
- # Emit right
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_right
- visit(right_node)
- end
-
- end # Binary
- end # Send
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/send/conditional.rb b/lib/unparser/emitter/send/conditional.rb
deleted file mode 100644
index ac29782..0000000
--- a/lib/unparser/emitter/send/conditional.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Send
- # Emitter for "conditional" receiver&.selector(arguments...) case
- class Conditional < self
- include Terminated
-
- handle :csend
-
- private
-
- # Perform regular dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- emit_receiver
- emit_selector
- emit_arguments
- end
-
- # Emit receiver
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_receiver
- visit(receiver)
- write(T_AMP, T_DOT)
- end
-
- end # Regular
- end # Send
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/send/regular.rb b/lib/unparser/emitter/send/regular.rb
deleted file mode 100644
index 4ed6145..0000000
--- a/lib/unparser/emitter/send/regular.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- class Emitter
- class Send
- # Emitter for "regular" receiver.selector(arguments...) case
- class Regular < self
- include Terminated
-
- private
-
- # Perform regular dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- emit_receiver
- emit_selector
- emit_arguments
- end
-
- # Emit receiver
- #
- # @return [undefined]
- #
- # @api private
- #
- def emit_receiver
- return unless first_child
-
- visit(receiver)
- write(T_DOT)
- end
-
- end # Regular
- end # Send
- end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/simple.rb b/lib/unparser/emitter/simple.rb
new file mode 100644
index 0000000..b3dfe12
--- /dev/null
+++ b/lib/unparser/emitter/simple.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Emitter for simple nodes that generate a single token
+ class Simple < self
+ MAP = {
+ __ENCODING__: '__ENCODING__',
+ __FILE__: '__FILE__',
+ __LINE__: '__LINE__',
+ false: 'false',
+ forward_arg: '...',
+ forwarded_args: '...',
+ kwnilarg: '**nil',
+ match_nil_pattern: '**nil',
+ nil: 'nil',
+ redo: 'redo',
+ retry: 'retry',
+ self: 'self',
+ true: 'true',
+ zsuper: 'super'
+ }.freeze
+
+ handle(*MAP.keys)
+
+ private
+
+ def dispatch
+ write(MAP.fetch(node_type))
+ end
+ end # Simple
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/splat.rb b/lib/unparser/emitter/splat.rb
index c0944bc..5129fa5 100644
--- a/lib/unparser/emitter/splat.rb
+++ b/lib/unparser/emitter/splat.rb
@@ -4,46 +4,40 @@ module Unparser
class Emitter
# Emitter for splats
class KwSplat < self
- include Terminated
-
handle :kwsplat
children :subject
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(T_SPLAT, T_SPLAT)
+ write('**')
visit(subject)
end
end
# Emitter for splats
class Splat < self
- include Terminated
-
handle :splat
children :subject
+ def emit_mlhs
+ write('*')
+ subject_emitter.emit_mlhs if subject
+ end
+
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(T_SPLAT)
- visit(subject) if subject
+ write('*')
+ subject_emitter.write_to_buffer
+ end
+
+ def subject_emitter
+ emitter(subject)
end
+ memoize :subject_emitter
end
end
end # Unparser
diff --git a/lib/unparser/emitter/super.rb b/lib/unparser/emitter/super.rb
index 669d376..f78729f 100644
--- a/lib/unparser/emitter/super.rb
+++ b/lib/unparser/emitter/super.rb
@@ -3,42 +3,14 @@
module Unparser
class Emitter
- # Emitter for zsuper nodes
- class ZSuper < self
- include Terminated
-
- handle :zsuper
-
- private
-
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
- def dispatch
- write(K_SUPER)
- end
-
- end # ZSuper
-
# Emitter for super nodes
class Super < self
- include Terminated
-
handle :super
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_SUPER)
+ write('super')
parentheses do
delimited(children)
end
diff --git a/lib/unparser/emitter/undef.rb b/lib/unparser/emitter/undef.rb
index fef3c38..cda2297 100644
--- a/lib/unparser/emitter/undef.rb
+++ b/lib/unparser/emitter/undef.rb
@@ -4,20 +4,12 @@ module Unparser
class Emitter
# Emitter for undef nodes
class Undef < self
- include Unterminated
-
handle :undef
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_UNDEF, WS)
+ write('undef ')
delimited(children)
end
diff --git a/lib/unparser/emitter/variable.rb b/lib/unparser/emitter/variable.rb
index 905e767..e4915ba 100644
--- a/lib/unparser/emitter/variable.rb
+++ b/lib/unparser/emitter/variable.rb
@@ -5,20 +5,12 @@ module Unparser
# Emitter for various variable accesses
class Variable < self
- include Terminated
-
handle :ivar, :lvar, :cvar, :gvar, :back_ref
children :name
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
write(name.to_s)
end
@@ -27,43 +19,27 @@ module Unparser
# Emitter for constant access
class Const < self
- include Terminated
-
handle :const
children :scope, :name
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
emit_scope
write(name.to_s)
end
- # Emit parent
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_scope
return unless scope
visit(scope)
- write(T_DCL) unless scope.type.equal?(:cbase)
+ write('::') unless n_cbase?(scope)
end
end
# Emitter for nth_ref nodes (regexp captures)
class NthRef < self
- include Terminated
-
PREFIX = '$'.freeze
handle :nth_ref
@@ -71,12 +47,6 @@ module Unparser
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
write(PREFIX)
write(name.to_s)
diff --git a/lib/unparser/emitter/xstr.rb b/lib/unparser/emitter/xstr.rb
new file mode 100644
index 0000000..3655924
--- /dev/null
+++ b/lib/unparser/emitter/xstr.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module Unparser
+ class Emitter
+ # Dynamic execute string literal emitter
+ class XStr < self
+
+ handle :xstr
+
+ private
+
+ def dispatch
+ if heredoc?
+ emit_heredoc
+ else
+ emit_xstr
+ end
+ end
+
+ def heredoc?
+ children.any? { |node| node.eql?(s(:str, '')) }
+ end
+
+ def emit_heredoc
+ write(%(<<~`HEREDOC`))
+ buffer.indent
+ nl
+ children.each do |child|
+ if n_str?(child)
+ write(child.children.first)
+ else
+ emit_begin(child)
+ end
+ end
+ buffer.unindent
+ write("HEREDOC\n")
+ end
+
+ def emit_xstr
+ write('`')
+ children.each do |child|
+ if n_begin?(child)
+ emit_begin(child)
+ else
+ emit_string(child)
+ end
+ end
+ write('`')
+ end
+
+ def emit_string(value)
+ write(escape_xstr(value.children.first))
+ end
+
+ def escape_xstr(input)
+ input.chars.map do |char|
+ if char.eql?('`')
+ '\\`'
+ else
+ char
+ end
+ end.join
+ end
+
+ def emit_begin(component)
+ write('#{')
+ visit(unwrap_single_begin(component))
+ write('}')
+ end
+ end # XStr
+ end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/yield.rb b/lib/unparser/emitter/yield.rb
index a457b12..4b6f8df 100644
--- a/lib/unparser/emitter/yield.rb
+++ b/lib/unparser/emitter/yield.rb
@@ -5,20 +5,12 @@ module Unparser
# Emitter for yield node
class Yield < self
- include Terminated
-
handle :yield
private
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
- write(K_YIELD)
+ write('yield')
return if children.empty?
parentheses do
diff --git a/lib/unparser/equalizer.rb b/lib/unparser/equalizer.rb
new file mode 100644
index 0000000..ab7e1c7
--- /dev/null
+++ b/lib/unparser/equalizer.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module Unparser
+ # Define equality, equivalence and inspection methods
+ #
+ # Original code before vendoring and reduction from: https://github.com/dkubb/equalizer.
+ class Equalizer < Module
+ # Initialize an Equalizer with the given keys
+ #
+ # Will use the keys with which it is initialized to define #cmp?,
+ # #hash, and #inspect
+ #
+ # @param [Array<Symbol>] keys
+ #
+ # @return [undefined]
+ #
+ # @api private
+ #
+ # rubocop:disable Lint/MissingSuper
+ def initialize(*keys)
+ @keys = keys
+ define_methods
+ freeze
+ end
+ # rubocop:enable Lint/MissingSuper
+
+ private
+
+ def included(descendant)
+ descendant.include(Methods)
+ end
+
+ def define_methods
+ define_cmp_method
+ define_hash_method
+ define_inspect_method
+ end
+
+ def define_cmp_method
+ keys = @keys
+ define_method(:cmp?) do |comparator, other|
+ keys.all? do |key|
+ __send__(key).public_send(comparator, other.__send__(key))
+ end
+ end
+ private :cmp?
+ end
+
+ def define_hash_method
+ keys = @keys
+ define_method(:hash) do
+ keys.map(&public_method(:__send__)).push(self.class).hash
+ end
+ end
+
+ def define_inspect_method
+ keys = @keys
+ define_method(:inspect) do
+ klass = self.class
+ name = klass.name || klass.inspect
+ "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
+ end
+ end
+
+ # The comparison methods
+ module Methods
+ # Compare the object with other object for equality
+ #
+ # @example
+ # object.eql?(other) # => true or false
+ #
+ # @param [Object] other
+ # the other object to compare with
+ #
+ # @return [Boolean]
+ #
+ # @api public
+ def eql?(other)
+ instance_of?(other.class) && cmp?(__method__, other)
+ end
+
+ # Compare the object with other object for equivalency
+ #
+ # @example
+ # object == other # => true or false
+ #
+ # @param [Object] other
+ # the other object to compare with
+ #
+ # @return [Boolean]
+ #
+ # @api public
+ def ==(other)
+ instance_of?(other.class) && cmp?(__method__, other)
+ end
+ end # module Methods
+ end # class Equalizer
+end # Unparser
diff --git a/lib/unparser/generation.rb b/lib/unparser/generation.rb
new file mode 100644
index 0000000..e553c58
--- /dev/null
+++ b/lib/unparser/generation.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+module Unparser
+ # rubocop:disable Metrics/ModuleLength
+ module Generation
+ EXTRA_NL = %i[kwbegin def defs module class sclass].freeze
+
+ private_constant(*constants(false))
+
+ def emit_heredoc_reminders; end
+
+ def symbol_name; end
+
+ def write_to_buffer
+ with_comments { dispatch }
+ self
+ end
+
+ private
+
+ def delimited(nodes, delimiter = ', ', &block)
+ return if nodes.empty?
+
+ emit_join(nodes, block || method(:visit), -> { write(delimiter) })
+ end
+
+ def emit_join(nodes, emit_node, emit_delimiter)
+ return if nodes.empty?
+
+ head, *tail = nodes
+ emit_node.call(head)
+
+ tail.each do |node|
+ emit_delimiter.call
+ emit_node.call(node)
+ end
+ end
+
+ def nl
+ emit_eol_comments
+ buffer.nl
+ end
+
+ def with_comments
+ emit_comments_before if buffer.fresh_line?
+ yield
+ comments.consume(node)
+ end
+
+ def ws
+ write(' ')
+ end
+
+ def emit_eol_comments
+ comments.take_eol_comments.each do |comment|
+ write(' ', comment.text)
+ end
+ end
+
+ def emit_eof_comments
+ emit_eol_comments
+ comments_left = comments.take_all
+ return if comments_left.empty?
+
+ buffer.nl
+ emit_comments(comments_left)
+ end
+
+ def emit_comments_before(source_part = :expression)
+ comments_before = comments.take_before(node, source_part)
+ return if comments_before.empty?
+
+ emit_comments(comments_before)
+ buffer.nl
+ end
+
+ def emit_comments(comments)
+ max = comments.size - 1
+ comments.each_with_index do |comment, index|
+ if comment.type.equal?(:document)
+ buffer.append_without_prefix(comment.text.chomp)
+ else
+ write(comment.text)
+ end
+ buffer.nl if index < max
+ end
+ end
+
+ def write(*strings)
+ strings.each(&buffer.method(:append))
+ end
+
+ def k_end
+ buffer.indent
+ emit_comments_before(:end)
+ buffer.unindent
+ write('end')
+ end
+
+ def parentheses(open = '(', close = ')')
+ write(open)
+ yield
+ write(close)
+ end
+
+ def indented
+ buffer = buffer()
+ buffer.indent
+ nl
+ yield
+ nl
+ buffer.unindent
+ end
+
+ def emit_optional_body(node, indent: true)
+ if node
+ emit_body(node, indent: indent)
+ else
+ nl
+ end
+ end
+
+ def emit_body(node, indent: true)
+ if indent
+ buffer.indent
+ nl
+ end
+
+ if n_begin?(node)
+ if node.children.one?
+ visit_deep(node)
+ else
+ emit_body_inner(node)
+ end
+ else
+ visit_deep(node)
+ end
+
+ if indent
+ buffer.unindent
+ nl
+ end
+ end
+
+ def emit_body_inner(node)
+ head, *tail = node.children
+ emit_body_member(head)
+
+ tail.each do |child|
+ nl
+
+ nl if EXTRA_NL.include?(child.type)
+
+ emit_body_member(child)
+ end
+ end
+
+ def emit_body_member(node)
+ if n_rescue?(node)
+ emit_rescue_postcontrol(node)
+ else
+ visit_deep(node)
+ end
+ end
+
+ def emit_ensure(node)
+ body, ensure_body = node.children
+
+ if body
+ emit_body_rescue(body)
+ else
+ nl
+ end
+
+ write('ensure')
+
+ emit_optional_body(ensure_body)
+ end
+
+ def emit_body_rescue(node)
+ if n_rescue?(node)
+ emit_rescue_regular(node)
+ else
+ emit_body(node)
+ end
+ end
+
+ def emit_optional_body_ensure_rescue(node)
+ if node
+ emit_body_ensure_rescue(node)
+ else
+ nl
+ end
+ end
+
+ def emit_body_ensure_rescue(node)
+ if n_ensure?(node)
+ emit_ensure(node)
+ elsif n_rescue?(node)
+ emit_rescue_regular(node)
+ else
+ emit_body(node)
+ end
+ end
+
+ def emit_rescue_postcontrol(node)
+ writer = writer_with(Writer::Rescue, node)
+ writer.emit_postcontrol
+ writer.emit_heredoc_reminders
+ end
+
+ def emit_rescue_regular(node)
+ writer_with(Writer::Rescue, node).emit_regular
+ end
+
+ def writer_with(klass, node)
+ klass.new(to_h.merge(node: node))
+ end
+
+ def emitter(node)
+ Emitter.emitter(**to_h.merge(node: node))
+ end
+
+ def visit(node)
+ emitter(node).write_to_buffer
+ end
+
+ def visit_deep(node)
+ emitter(node).tap do |emitter|
+ emitter.write_to_buffer
+ emitter.emit_heredoc_reminders
+ end
+ end
+
+ def first_child
+ children.first
+ end
+
+ def conditional_parentheses(flag, &block)
+ if flag
+ parentheses(&block)
+ else
+ block.call
+ end
+ end
+
+ def children
+ node.children
+ end
+ end # Generation
+ # rubocop:enable Metrics/ModuleLength
+end # Unparser
diff --git a/lib/unparser/node_details.rb b/lib/unparser/node_details.rb
new file mode 100644
index 0000000..5193304
--- /dev/null
+++ b/lib/unparser/node_details.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Unparser
+ module NodeDetails
+ include Constants, NodeHelpers
+
+ def self.included(descendant)
+ descendant.class_eval do
+ include Adamantium, Concord.new(:node)
+
+ extend DSL
+ end
+ end
+
+ private
+
+ def children
+ node.children
+ end
+ end # NodeDetails
+end # Unparser
diff --git a/lib/unparser/node_details/send.rb b/lib/unparser/node_details/send.rb
new file mode 100644
index 0000000..082d942
--- /dev/null
+++ b/lib/unparser/node_details/send.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Unparser
+ module NodeDetails
+ class Send
+ include NodeDetails
+
+ ASSIGN_SUFFIX = '='.freeze
+ NON_ASSIGN_RANGE = (0..-2).freeze
+
+ private_constant(*constants(false))
+
+ children :receiver, :selector
+
+ public :receiver, :selector
+
+ def selector_binary_operator?
+ BINARY_OPERATORS.include?(selector)
+ end
+
+ def binary_syntax_allowed?
+ selector_binary_operator? \
+ && n_send?(node) \
+ && arguments.one? \
+ && !n_splat?(arguments.first) \
+ && !n_kwargs?(arguments.first)
+ end
+
+ def selector_unary_operator?
+ UNARY_OPERATORS.include?(selector)
+ end
+
+ def assignment_operator?
+ assignment? && !selector_binary_operator? && !selector_unary_operator?
+ end
+
+ def arguments?
+ arguments.any?
+ end
+
+ def non_assignment_selector
+ if assignment?
+ string_selector[NON_ASSIGN_RANGE]
+ else
+ string_selector
+ end
+ end
+
+ def assignment?
+ string_selector[-1].eql?(ASSIGN_SUFFIX)
+ end
+ memoize :assignment?
+
+ def arguments
+ children[2..]
+ end
+ memoize :arguments
+
+ def string_selector
+ selector.to_s
+ end
+ memoize :string_selector
+
+ end # Send
+ end # NodeDetails
+end # Unparser
diff --git a/lib/unparser/node_helpers.rb b/lib/unparser/node_helpers.rb
index 926feae..bfb8bb7 100644
--- a/lib/unparser/node_helpers.rb
+++ b/lib/unparser/node_helpers.rb
@@ -11,9 +11,6 @@ module Unparser
# @return [Parser::AST::Node]
#
# @api private
- #
- # ignore :reek:UncommunicativeMethodName
- # ignore :reek:UtilityFunction
def s(type, *children)
Parser::AST::Node.new(type, children)
end
@@ -26,12 +23,57 @@ module Unparser
# @param [Array] children
#
# @api private
- #
- # ignore :reek:UncommunicativeMethodName
- # ignore :reek:UtilityFunction
def n(type, children = [])
Parser::AST::Node.new(type, children)
end
+ def n?(type, node)
+ node.type.equal?(type)
+ end
+
+ %i[
+ arg
+ args
+ array
+ array_pattern
+ begin
+ block
+ cbase
+ const
+ dstr
+ empty_else
+ ensure
+ hash
+ hash_pattern
+ if
+ in_pattern
+ int
+ kwarg
+ kwargs
+ kwsplat
+ lambda
+ match_rest
+ pair
+ rescue
+ send
+ shadowarg
+ splat
+ str
+ sym
+ ].each do |type|
+ name = "n_#{type}?"
+ define_method(name) do |node|
+ n?(type, node)
+ end
+ private(name)
+ end
+
+ def unwrap_single_begin(node)
+ if n_begin?(node) && node.children.one?
+ node.children.first
+ else
+ node
+ end
+ end
end # NodeHelpers
end # Unparser
diff --git a/lib/unparser/preprocessor.rb b/lib/unparser/preprocessor.rb
deleted file mode 100644
index 7400661..0000000
--- a/lib/unparser/preprocessor.rb
+++ /dev/null
@@ -1,159 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
- # Preprocessor to normalize AST generated by parser
- class Preprocessor
- include Adamantium::Flat, NodeHelpers, AbstractType, Concord.new(:node, :parent_type), Procto.call(:result)
-
- # Return preprocessor result
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- abstract_method :result
-
- EMPTY = Parser::AST::Node.new(:empty)
-
- # Run preprocessor for node
- #
- # @param [Parser::AST::Node, nil] node
- #
- # @return [Parser::AST::Node, nil]
- #
- # @api private
- #
- def self.run(node, parent_type = nil)
- return EMPTY if node.nil?
-
- REGISTRY.fetch(node.type, [Noop]).reduce(node) do |current, processor|
- processor.call(current, parent_type)
- end
- end
-
- REGISTRY = Hash.new { |hash, key| hash[key] = [] }
-
- # Register preprocessor
- #
- # @param [Symbol] type
- #
- # @return [undefined]
- #
- # @api private
- #
- def self.register(type)
- REGISTRY[type] << self
- end
- private_class_method :register
-
- private
-
- # Visit node
- #
- # @param [Parser::AST::Node] child
- #
- # @return [undefined]
- #
- # @api private
- #
- def visit(child)
- self.class.run(child, node.type)
- end
-
- # Return children
- #
- # @return [Array<Parser::AST::Node>]
- #
- # @api private
- #
- def children
- node.children
- end
-
- # Return visited children
- #
- # @return [Array<Parser::Ast::Node>]
- #
- # @api private
- #
- def visited_children
- children.map do |node|
- if node.is_a?(Parser::AST::Node)
- visit(node)
- else
- node
- end
- end
- end
-
- # Noop preprocessor that just passes node through.
- class Noop < self
-
- register :int
- register :str
-
- # Return preprocessor result
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def result
- node.updated(nil, visited_children)
- end
-
- end # Noop
-
- # Preprocessor transforming numeric nodes with infinity as value to round trippable equivalent.
- class Infinity < self
-
- register :float
- register :int
-
- NEG_INFINITY = -(Float::INFINITY - 1)
-
- # Return preprocessor result
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def result
- value = node.children.first
- case value
- when Float::INFINITY
- s(:const, s(:const, nil, :Float), :INFINITY)
- when NEG_INFINITY
- s(:send, s(:const, s(:const, nil, :Float), :INFINITY), :-@)
- else
- node
- end
- end
- end
-
- # Preprocessor for begin nodes. Removes begin nodes with one child.
- #
- # This reduces the amount of complex logic needed inside unparser to emit "nice" syntax with minimal
- # tokens.
- #
- class Begin < self
-
- register :begin
-
- # Return preprocessor result
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
- def result
- if children.one? && !parent_type.equal?(:regexp)
- visit(children.first)
- else
- Noop.call(node, parent_type)
- end
- end
-
- end # Begin
- end # Preprocessor
-end # Unparser
diff --git a/lib/unparser/validation.rb b/lib/unparser/validation.rb
new file mode 100644
index 0000000..b660079
--- /dev/null
+++ b/lib/unparser/validation.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+module Unparser
+ # Validation of unparser results
+ class Validation
+ include Adamantium, Anima.new(
+ :generated_node,
+ :generated_source,
+ :identification,
+ :original_node,
+ :original_source
+ )
+
+ # Test if source could be unparsed successfully
+ #
+ # @return [Boolean]
+ #
+ # @api private
+ #
+ # rubocop:disable Style/OperatorMethodCall
+ def success?
+ [
+ original_source,
+ original_node,
+ generated_source,
+ generated_node
+ ].all?(&:right?) && generated_node.from_right.==(original_node.from_right)
+ end
+ # rubocop:enable Style/OperatorMethodCall
+
+ # Return error report
+ #
+ # @return [String]
+ #
+ # @api private
+ #
+ def report
+ message = [identification]
+
+ message.concat(make_report('Original-Source', :original_source))
+ message.concat(make_report('Generated-Source', :generated_source))
+ message.concat(make_report('Original-Node', :original_node))
+ message.concat(make_report('Generated-Node', :generated_node))
+ message.concat(node_diff_report)
+
+ message.join("\n")
+ end
+ memoize :report
+
+ # Create validator from string
+ #
+ # @param [String] original_source
+ #
+ # @return [Validator]
+ def self.from_string(original_source)
+ original_node = Unparser
+ .parse_either(original_source)
+
+ generated_source = original_node
+ .lmap(&method(:const_unit))
+ .bind(&Unparser.method(:unparse_either))
+
+ generated_node = generated_source
+ .lmap(&method(:const_unit))
+ .bind(&Unparser.method(:parse_either))
+
+ new(
+ identification: '(string)',
+ original_source: Either::Right.new(original_source),
+ original_node: original_node,
+ generated_source: generated_source,
+ generated_node: generated_node
+ )
+ end
+
+ # Create validator from node
+ #
+ # @param [Parser::AST::Node] original_node
+ #
+ # @return [Validator]
+ def self.from_node(original_node)
+ generated_source = Unparser.unparse_either(original_node)
+
+ generated_node = generated_source
+ .lmap(&method(:const_unit))
+ .bind(&Unparser.public_method(:parse_either))
+
+ new(
+ identification: '(string)',
+ original_source: generated_source,
+ original_node: Either::Right.new(original_node),
+ generated_source: generated_source,
+ generated_node: generated_node
+ )
+ end
+
+ # Create validator from file
+ #
+ # @param [Pathname] path
+ #
+ # @return [Validator]
+ def self.from_path(path)
+ from_string(path.read).with(identification: path.to_s)
+ end
+
+ private
+
+ def make_report(label, attribute_name)
+ ["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
+ end
+
+ def report_exception(exception)
+ if exception
+ [exception.inspect].concat(exception.backtrace.take(20))
+ else
+ ['undefined']
+ end
+ end
+
+ def node_diff_report
+ diff = nil
+
+ original_node.fmap do |original|
+ generated_node.fmap do |generated|
+ diff = Diff.new(
+ original.to_s.lines.map(&:chomp),
+ generated.to_s.lines.map(&:chomp)
+ ).colorized_diff
+ end
+ end
+
+ diff ? ['Node-Diff:', diff] : []
+ end
+
+ def self.const_unit(_value); end
+ private_class_method :const_unit
+
+ class Literal < self
+ def success?
+ original_source.eql?(generated_source)
+ end
+
+ def report
+ message = [identification]
+
+ message.concat(make_report('Original-Source', :original_source))
+ message.concat(make_report('Generated-Source', :generated_source))
+ message.concat(make_report('Original-Node', :original_node))
+ message.concat(make_report('Generated-Node', :generated_node))
+ message.concat(node_diff_report)
+ message.concat(source_diff_report)
+
+ message.join("\n")
+ end
+
+ private
+
+ def source_diff_report
+ diff = nil
+
+ original_source.fmap do |original|
+ generated_source.fmap do |generated|
+ diff = Diff.new(
+ original.split("\n", -1),
+ generated.split("\n", -1)
+ ).colorized_diff
+ end
+ end
+
+ diff ? ['Source-Diff:', diff] : []
+ end
+ end # Literal
+ end # Validation
+end # Unparser
diff --git a/lib/unparser/writer.rb b/lib/unparser/writer.rb
new file mode 100644
index 0000000..94b95bf
--- /dev/null
+++ b/lib/unparser/writer.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ include Generation, NodeHelpers
+
+ def self.included(descendant)
+ descendant.class_eval do
+ include Anima.new(:buffer, :comments, :node, :local_variable_scope)
+
+ extend DSL
+ end
+ end
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/binary.rb b/lib/unparser/writer/binary.rb
new file mode 100644
index 0000000..db2025a
--- /dev/null
+++ b/lib/unparser/writer/binary.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ class Binary
+ include Writer, Adamantium
+
+ children :left, :right
+
+ OPERATOR_TOKENS =
+ {
+ and: '&&',
+ or: '||'
+ }.freeze
+
+ KEYWORD_TOKENS =
+ {
+ and: 'and',
+ or: 'or'
+ }.freeze
+
+ KEYWORD_SYMBOLS =
+ {
+ and: :kAND,
+ or: :kOR
+ }.freeze
+
+ OPERATOR_SYMBOLS =
+ {
+ and: :tANDOP,
+ or: :tOROP
+ }.freeze
+
+ MAP =
+ {
+ kAND: 'and',
+ kOR: 'or',
+ tOROP: '||',
+ tANDOP: '&&'
+ }.freeze
+
+ NEED_KEYWORD = %i[return break next].freeze
+
+ private_constant(*constants(false))
+
+ def emit_operator
+ emit_with(OPERATOR_TOKENS)
+ end
+
+ def symbol_name
+ true
+ end
+
+ def dispatch
+ left_emitter.write_to_buffer
+ write(' ', MAP.fetch(effective_symbol), ' ')
+ visit(right)
+ end
+
+ private
+
+ def effective_symbol
+ if NEED_KEYWORD.include?(right.type) || NEED_KEYWORD.include?(left.type)
+ return keyword_symbol
+ end
+
+ unless left_emitter.symbol_name
+ return operator_symbol
+ end
+
+ keyword_symbol
+ end
+
+ def emit_with(map)
+ visit(left)
+ write(' ', map.fetch(node.type), ' ')
+ visit(right)
+ end
+
+ def keyword_symbol
+ KEYWORD_SYMBOLS.fetch(node.type)
+ end
+
+ def operator_symbol
+ OPERATOR_SYMBOLS.fetch(node.type)
+ end
+
+ def left_emitter
+ emitter(left)
+ end
+ memoize :left_emitter
+
+ def right_emitter
+ emitter(right)
+ end
+ memoize :right_emitter
+ end # Binary
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/dynamic_string.rb b/lib/unparser/writer/dynamic_string.rb
new file mode 100644
index 0000000..fec4627
--- /dev/null
+++ b/lib/unparser/writer/dynamic_string.rb
@@ -0,0 +1,211 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ class DynamicString
+ include Writer, Adamantium
+
+ PATTERNS_2 =
+ [
+ %i[str_empty begin].freeze,
+ %i[begin str_nl].freeze
+ ].freeze
+
+ PATTERNS_3 =
+ [
+ %i[begin str_nl_eol str_nl_eol].freeze,
+ %i[str_nl_eol begin str_nl_eol].freeze,
+ %i[str_ws begin str_nl_eol].freeze
+ ].freeze
+
+ FLAT_INTERPOLATION = %i[ivar cvar gvar nth_ref].to_set.freeze
+
+ private_constant(*constants(false))
+
+ def emit_heredoc_reminder
+ return unless heredoc?
+
+ emit_heredoc_body
+ emit_heredoc_footer
+ end
+
+ def dispatch
+ if heredoc?
+ emit_heredoc_header
+ else
+ emit_dstr
+ end
+ end
+
+ private
+
+ def heredoc_header
+ '<<-HEREDOC'
+ end
+
+ def heredoc?
+ !children.empty? && (nl_last_child? && heredoc_pattern?)
+ end
+
+ def emit_heredoc_header
+ write(heredoc_header)
+ end
+
+ def emit_heredoc_body
+ nl
+ emit_normal_heredoc_body
+ end
+
+ def emit_heredoc_footer
+ write('HEREDOC')
+ end
+
+ def classify(node)
+ if n_str?(node)
+ classify_str(node)
+ else
+ node.type
+ end
+ end
+
+ def classify_str(node)
+ if str_nl?(node)
+ :str_nl
+ elsif node.children.first.end_with?("\n")
+ :str_nl_eol
+ elsif str_ws?(node)
+ :str_ws
+ elsif str_empty?(node)
+ :str_empty
+ end
+ end
+
+ def str_nl?(node)
+ node.eql?(s(:str, "\n"))
+ end
+
+ def str_empty?(node)
+ node.eql?(s(:str, ''))
+ end
+
+ def str_ws?(node)
+ /\A( |\t)+\z/.match?(node.children.first)
+ end
+
+ def heredoc_pattern?
+ heredoc_pattern_2? || heredoc_pattern_3?
+ end
+
+ def heredoc_pattern_3?
+ children.each_cons(3).any? do |group|
+ PATTERNS_3.include?(group.map(&method(:classify)))
+ end
+ end
+
+ def heredoc_pattern_2?
+ children.each_cons(2).any? do |group|
+ PATTERNS_2.include?(group.map(&method(:classify)))
+ end
+ end
+
+ def nl_last_child?
+ last = children.last
+ n_str?(last) && last.children.first[-1].eql?("\n")
+ end
+
+ def emit_normal_heredoc_body
+ buffer.root_indent do
+ children.each do |child|
+ if n_str?(child)
+ write(escape_dynamic(child.children.first))
+ else
+ emit_dynamic(child)
+ end
+ end
+ end
+ end
+
+ def escape_dynamic(string)
+ string.gsub('#', '\#')
+ end
+
+ def emit_dynamic(child)
+ if FLAT_INTERPOLATION.include?(child.type)
+ write('#')
+ visit(child)
+ elsif n_dstr?(child)
+ emit_body(child.children)
+ else
+ write('#{')
+ emit_dynamic_component(child.children.first)
+ write('}')
+ end
+ end
+
+ def emit_dynamic_component(node)
+ visit(node) if node
+ end
+
+ def emit_dstr
+ if children.empty?
+ write('%()')
+ else
+ segments.each_with_index do |children, index|
+ emit_segment(children, index)
+ end
+ end
+ end
+
+ def breakpoint?(child, current)
+ last_type = current.last&.type
+
+ [
+ n_str?(child) && last_type.equal?(:str) && current.none?(&method(:n_begin?)),
+ last_type.equal?(:dstr),
+ n_dstr?(child) && last_type
+ ].any?
+ end
+
+ def segments
+ segments = []
+
+ segments << current = []
+
+ children.each do |child|
+ if breakpoint?(child, current)
+ segments << current = []
+ end
+
+ current << child
+ end
+
+ segments
+ end
+
+ def emit_segment(children, index)
+ write(' ') unless index.zero?
+
+ write('"')
+ emit_body(children)
+ write('"')
+ end
+
+ def emit_body(children)
+ buffer.root_indent do
+ children.each_with_index do |child, index|
+ if n_str?(child)
+ string = child.children.first
+ if string.eql?("\n") && children.fetch(index.pred).type.equal?(:begin)
+ write("\n")
+ else
+ write(string.inspect[1..-2])
+ end
+ else
+ emit_dynamic(child)
+ end
+ end
+ end
+ end
+ end # DynamicString
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/resbody.rb b/lib/unparser/writer/resbody.rb
new file mode 100644
index 0000000..bc38616
--- /dev/null
+++ b/lib/unparser/writer/resbody.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ # Writer for rescue bodies
+ class Resbody
+ include Writer
+
+ children :exception, :assignment, :body
+
+ def emit_postcontrol
+ write(' rescue ')
+ visit(body)
+ end
+
+ def emit_regular
+ write('rescue')
+ emit_exception
+ emit_assignment
+ emit_optional_body(body)
+ end
+
+ private
+
+ def emit_exception
+ return unless exception
+
+ ws
+ delimited(exception.children)
+ end
+
+ def emit_assignment
+ return unless assignment
+
+ write(' => ')
+ visit(assignment)
+ end
+ end # Resbody
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/rescue.rb b/lib/unparser/writer/rescue.rb
new file mode 100644
index 0000000..892b2fb
--- /dev/null
+++ b/lib/unparser/writer/rescue.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ class Rescue
+ include Writer, Adamantium
+
+ children :body, :rescue_body
+
+ define_group :rescue_bodies, 1..-2
+
+ def emit_regular
+ emit_optional_body(body)
+
+ rescue_bodies.each(&method(:emit_rescue_body))
+
+ if else_node
+ write('else')
+ emit_body(else_node)
+ end
+ end
+
+ def emit_heredoc_reminders
+ emitter(body).emit_heredoc_reminders
+ end
+
+ def emit_postcontrol
+ visit(body)
+ writer_with(Resbody, rescue_body).emit_postcontrol
+ end
+
+ private
+
+ def else_node
+ children.last
+ end
+
+ def emit_rescue_body(node)
+ writer_with(Resbody, node).emit_regular
+ end
+ end # Rescue
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/send.rb b/lib/unparser/writer/send.rb
new file mode 100644
index 0000000..f75d30e
--- /dev/null
+++ b/lib/unparser/writer/send.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ # Writer for send
+ class Send
+ include Writer, Adamantium, Constants, Generation
+
+ INDEX_ASSIGN = :[]=
+ INDEX_REFERENCE = :[]
+
+ OPERATORS = {
+ csend: '&.',
+ send: '.'
+ }.freeze
+
+ private_constant(*constants(false))
+
+ children :receiver, :selector
+
+ def dispatch
+ effective_writer.dispatch
+ end
+
+ def emit_mlhs
+ effective_writer.emit_send_mlhs
+ end
+
+ def emit_selector
+ write(details.string_selector)
+ end
+
+ def emit_heredoc_reminders
+ emitter(receiver).emit_heredoc_reminders if receiver
+ arguments.each(&method(:emit_heredoc_reminder))
+ end
+
+ private
+
+ def effective_writer
+ writer_with(effective_writer_class, node)
+ end
+ memoize :effective_writer
+
+ def effective_writer_class
+ if details.binary_syntax_allowed?
+ Binary
+ elsif details.selector_unary_operator? && n_send?(node) && arguments.empty?
+ Unary
+ elsif write_as_attribute_assignment?
+ AttributeAssignment
+ else
+ Regular
+ end
+ end
+
+ def write_as_attribute_assignment?
+ details.assignment_operator?
+ end
+
+ def emit_operator
+ write(OPERATORS.fetch(node.type))
+ end
+
+ def emit_arguments
+ if arguments.empty?
+ write('()') if receiver.nil? && avoid_clash?
+ else
+ emit_normal_arguments
+ end
+ end
+
+ def arguments
+ details.arguments
+ end
+
+ def emit_normal_arguments
+ parentheses { delimited(arguments) }
+ end
+
+ def emit_heredoc_reminder(argument)
+ emitter(argument).emit_heredoc_reminders
+ end
+
+ def avoid_clash?
+ local_variable_clash? || parses_as_constant?
+ end
+
+ def local_variable_clash?
+ local_variable_scope.local_variable_defined_for_node?(node, selector)
+ end
+
+ def parses_as_constant?
+ test = Unparser.parse_either(selector.to_s).from_right do
+ fail InvalidNodeError.new("Invalid selector for send node: #{selector.inspect}", node)
+ end
+
+ n_const?(test)
+ end
+
+ def details
+ NodeDetails::Send.new(node)
+ end
+ memoize :details
+
+ def emit_send_regular(node)
+ if n_send?(node)
+ writer_with(Regular, node).dispatch
+ else
+ visit(node)
+ end
+ end
+ end # Send
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/emitter/send/attribute_assignment.rb b/lib/unparser/writer/send/attribute_assignment.rb
similarity index 51%
rename from lib/unparser/emitter/send/attribute_assignment.rb
rename to lib/unparser/writer/send/attribute_assignment.rb
index 569d577..fe404c5 100644
--- a/lib/unparser/emitter/send/attribute_assignment.rb
+++ b/lib/unparser/writer/send/attribute_assignment.rb
@@ -1,24 +1,16 @@
# frozen_string_literal: true
module Unparser
- class Emitter
+ module Writer
class Send
- # Emitter for send as attribute assignment
+ # Writer for send as attribute assignment
class AttributeAssignment < self
- include Unterminated
-
children :receiver, :selector, :first_argument
- # Perform regular dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
emit_receiver
emit_attribute
- write(T_ASN)
+ write('=')
if arguments.one?
visit(first_argument)
@@ -27,29 +19,22 @@ module Unparser
end
end
+ def emit_send_mlhs
+ emit_receiver
+ write(details.non_assignment_selector)
+ end
+
private
- # Emit receiver
- #
- # @return [Parser::AST::Node]
- #
- # @api private
- #
def emit_receiver
visit(receiver)
- write(T_DOT)
+ emit_operator
end
- # Emit attribute
- #
- # @return [undefined]
- #
- # @api private
- #
def emit_attribute
- write(non_assignment_selector)
+ write(details.non_assignment_selector)
end
end # AttributeAssignment
end # Send
- end # Emitter
+ end # Writer
end # Unparser
diff --git a/lib/unparser/writer/send/binary.rb b/lib/unparser/writer/send/binary.rb
new file mode 100644
index 0000000..c3cc46b
--- /dev/null
+++ b/lib/unparser/writer/send/binary.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ class Send
+ # Writer for binary sends
+ class Binary < self
+ def dispatch
+ visit(receiver)
+ emit_operator
+ emit_right
+ end
+
+ private
+
+ def emit_operator
+ write(' ', details.string_selector, ' ')
+ end
+
+ def emit_right
+ emit_send_regular(children.fetch(2))
+ end
+
+ end # Binary
+ end # Send
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/send/conditional.rb b/lib/unparser/writer/send/conditional.rb
new file mode 100644
index 0000000..c3ddcf6
--- /dev/null
+++ b/lib/unparser/writer/send/conditional.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ class Send
+ # Writer for "conditional" receiver&.selector(arguments...) case
+ class Conditional < self
+
+ private
+
+ def dispatch
+ emit_receiver
+ emit_selector
+ emit_arguments
+ end
+
+ def emit_receiver
+ visit(receiver)
+ write('&.')
+ end
+
+ end # Regular
+ end # Send
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/send/regular.rb b/lib/unparser/writer/send/regular.rb
new file mode 100644
index 0000000..f5642b7
--- /dev/null
+++ b/lib/unparser/writer/send/regular.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+ module Writer
+ class Send
+ # Writer for "regular" receiver.selector(arguments...) case
+ class Regular < self
+ def dispatch
+ emit_receiver
+ emit_selector
+ emit_arguments
+ end
+
+ def emit_send_mlhs
+ dispatch
+ end
+
+ def emit_arguments_without_heredoc_body
+ emit_normal_arguments if arguments.any?
+ end
+
+ def emit_receiver
+ return unless receiver
+
+ emit_send_regular(receiver)
+
+ emit_operator
+ end
+
+ end # Regular
+ end # Send
+ end # Writer
+end # Unparser
diff --git a/lib/unparser/emitter/send/unary.rb b/lib/unparser/writer/send/unary.rb
similarity index 51%
rename from lib/unparser/emitter/send/unary.rb
rename to lib/unparser/writer/send/unary.rb
index 906b9e3..ed40961 100644
--- a/lib/unparser/emitter/send/unary.rb
+++ b/lib/unparser/writer/send/unary.rb
@@ -1,36 +1,29 @@
# frozen_string_literal: true
module Unparser
- class Emitter
+ module Writer
class Send
- # Emitter for unary sends
+ # Writer for unary sends
class Unary < self
- include Unterminated
-
- private
-
- MAP = IceNine.deep_freeze(
+ MAP = {
'-@': '-',
'+@': '+'
- )
+ }.freeze
+
+ private_constant(*constants(false))
- # Perform dispatch
- #
- # @return [undefined]
- #
- # @api private
- #
def dispatch
name = selector
+
write(MAP.fetch(name, name).to_s)
- if receiver.type.equal?(:int) && selector.equal?(:'+@')
+
+ if n_int?(receiver) && selector.equal?(:+@)
write('+')
end
visit(receiver)
end
-
end # Unary
end # Send
- end # Emitter
+ end # Writer
end # Unparser
diff --git a/spec/integration/unparser/corpus_spec.rb b/spec/integration/unparser/corpus_spec.rb
deleted file mode 100644
index 58cc86f..0000000
--- a/spec/integration/unparser/corpus_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'spec_helper'
-describe 'Unparser on ruby corpus', mutant: false do
- ROOT = Pathname.new(__FILE__).parent.parent.parent.parent
-
- TMP = ROOT.join('tmp')
-
- class Project
- include Anima.new(:name, :repo_uri, :repo_ref, :exclude)
-
- # Perform verification via unparser cli
- #
- # @return [self]
- # if successful
- #
- # @raise [Exception]
- # otherwise
- #
- def verify
- checkout
- command = %W(unparser #{repo_path})
- exclude.each do |name|
- command.concat(%W(--ignore #{repo_path.join(name)}))
- end
- system(command) do
- raise "Verifing #{name} failed!"
- end
- self
- end
-
- # Checkout repository
- #
- # @return [self]
- #
- # @api private
- #
- def checkout
- TMP.mkdir unless TMP.directory?
- if repo_path.exist?
- Dir.chdir(repo_path) do
- system(%w(git pull origin master))
- system(%w(git clean -f -d -x))
- end
- else
- system(%W(git clone #{repo_uri} #{repo_path}))
- end
-
- Dir.chdir(repo_path) do
- system(%W(git checkout #{repo_ref}))
- system(%w(git reset --hard))
- system(%w(git clean -f -d -x))
- end
-
- self
- end
-
- private
-
- # Return repository path
- #
- # @return [Pathname]
- #
- # @api private
- #
- def repo_path
- TMP.join(name)
- end
-
- # Helper method to execute system commands
- #
- # @param [Array<String>] arguments
- #
- # @api private
- #
- def system(arguments)
- return if Kernel.system(*arguments)
-
- if block_given?
- yield
- else
- raise "System command #{arguments.inspect} failed!"
- end
- end
-
- LOADER = Morpher.build do
- s(:block,
- s(:guard, s(:primitive, Array)),
- s(:map,
- s(:block,
- s(:guard, s(:primitive, Hash)),
- s(:hash_transform,
- s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
- s(:key_symbolize, :repo_ref, s(:guard, s(:primitive, String))),
- s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
- s(:key_symbolize, :exclude, s(:map, s(:guard, s(:primitive, String))))),
- s(:load_attribute_hash,
- # NOTE: The domain param has no DSL currently!
- Morpher::Evaluator::Transformer::Domain::Param.new(
- Project,
- [:repo_uri, :repo_ref, :name, :exclude]
- )))))
- end
-
- ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
- end
-
- Project::ALL.each do |project|
- specify "unparsing #{project.name}" do
- project.verify
- end
- end
-end
diff --git a/spec/integrations.yml b/spec/integrations.yml
deleted file mode 100644
index e87ff54..0000000
--- a/spec/integrations.yml
+++ /dev/null
@@ -1,92 +0,0 @@
----
-- name: anima
- repo_uri: 'https://github.com/mbj/anima.git'
- repo_ref: 'origin/master'
- exclude: []
-- name: mutant
- repo_uri: 'https://github.com/mbj/mutant.git'
- repo_ref: 'origin/master'
- exclude: []
-- name: yaks
- repo_uri: 'https://github.com/plexus/yaks.git'
- repo_ref: 'origin/master'
- exclude: []
-- name: chassis
- repo_uri: 'https://github.com/ahawkins/chassis.git'
- repo_ref: 'origin/master'
- exclude: []
-- name: rubyspec
- repo_uri: 'https://github.com/ruby/spec.git'
- # Revision of rubyspec on the last CI build of unparser that passed
- repo_ref: 'origin/master'
- exclude:
- - command_line/fixtures/bad_syntax.rb
- - core/array/pack/shared/float.rb
- - core/array/pack/shared/integer.rb
- - core/array/pack/shared/string.rb
- - core/array/pack/{b,c,h,m}_spec.rb
- - core/array/pack/{u,w}_spec.rb
- - core/encoding/compatible_spec.rb
- - core/encoding/converter/convert_spec.rb
- - core/encoding/converter/last_error_spec.rb
- - core/encoding/converter/primitive_convert_spec.rb
- - core/encoding/converter/primitive_errinfo_spec.rb
- - core/encoding/converter/putback_spec.rb
- - core/encoding/fixtures/classes.rb
- - core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb
- - core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb
- - core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb
- - core/encoding/replicate_spec.rb
- - core/env/element_reference_spec.rb
- - core/io/readpartial_spec.rb
- - core/io/shared/gets_ascii.rb
- - core/kernel/shared/sprintf_encoding.rb
- - core/marshal/dump_spec.rb
- - core/marshal/fixtures/marshal_data.rb
- - core/marshal/shared/load.rb
- - core/random/bytes_spec.rb
- - core/regexp/shared/new.rb
- - core/regexp/shared/new_ascii.rb
- - core/regexp/shared/new_ascii_8bit.rb
- - core/regexp/shared/quote.rb
- - core/string/byteslice_spec.rb
- - core/string/casecmp_spec.rb
- - core/string/codepoints_spec.rb
- - core/string/count_spec.rb
- - core/string/encode_spec.rb
- - core/string/inspect_spec.rb
- - core/string/shared/codepoints.rb
- - core/string/shared/each_codepoint_without_block.rb
- - core/string/shared/eql.rb
- - core/string/shared/succ.rb
- - core/string/shared/to_sym.rb
- - core/string/squeeze_spec.rb
- - core/string/unpack/shared/float.rb
- - core/string/unpack/shared/integer.rb
- - core/string/unpack/{b,c,h,m}_spec.rb
- - core/string/unpack/{u,w}_spec.rb
- - core/symbol/casecmp_spec.rb
- - core/time/_dump_spec.rb
- - core/time/_load_spec.rb
- - language/fixtures/binary_symbol.rb
- - language/fixtures/squiggly_heredoc.rb
- - language/for_spec.rb
- - language/regexp/encoding_spec.rb
- - language/regexp/escapes_spec.rb
- - language/source_encoding_spec.rb
- - language/string_spec.rb
- - library/base64/decode64_spec.rb
- - library/digest/md5/shared/constants.rb
- - library/digest/md5/shared/sample.rb
- - library/digest/sha1/shared/constants.rb
- - library/digest/sha256/shared/constants.rb
- - library/digest/sha384/shared/constants.rb
- - library/digest/sha512/shared/constants.rb
- - library/openssl/shared/constants.rb
- - library/socket/basicsocket/recv_spec.rb
- - library/socket/socket/gethostbyname_spec.rb
- - library/stringscanner/getch_spec.rb
- - library/stringscanner/shared/get_byte.rb
- - library/zlib/inflate/set_dictionary_spec.rb
- - optional/capi/integer_spec.rb
- - security/cve_2010_1330_spec.rb
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
deleted file mode 100644
index 83cb9b4..0000000
--- a/spec/spec_helper.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'yaml'
-require 'pathname'
-require 'unparser'
-require 'anima'
-require 'morpher'
-require 'devtools/spec_helper'
-
-require 'parser/current'
-
-module SpecHelper
- def s(type, *children)
- Parser::AST::Node.new(type, children)
- end
-end
-
-RSpec.configure do |config|
- config.include(SpecHelper)
- config.extend(SpecHelper)
- config.raise_errors_for_deprecations!
-end
diff --git a/spec/unit/unparser/buffer/append_spec.rb b/spec/unit/unparser/buffer/append_spec.rb
deleted file mode 100644
index 1c315b6..0000000
--- a/spec/unit/unparser/buffer/append_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#append' do
- subject { object.append(string) }
-
- let(:object) { described_class.new }
- let(:string) { 'foo' }
-
- specify do
- expect { subject }.to change { object.content }.from('').to('foo')
- end
-
- # Yeah duplicate, mutant will be improved ;)
- it 'should prefix with indentation if line is empty' do
- object.append('foo')
- object.nl
- object.indent
- object.append('bar')
- object.append('baz')
- expect(object.content).to eql("foo\n barbaz")
- end
-
- it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/append_without_prefix_spec.rb b/spec/unit/unparser/buffer/append_without_prefix_spec.rb
deleted file mode 100644
index 695c1bb..0000000
--- a/spec/unit/unparser/buffer/append_without_prefix_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#append_without_prefix' do
- subject { object.append_without_prefix(string) }
-
- let(:object) { described_class.new }
- let(:string) { 'foo' }
-
- specify do
- expect { subject }.to change { object.content }.from('').to('foo')
- end
-
- it 'should not prefix with indentation' do
- object.append_without_prefix('foo')
- object.nl
- object.indent
- object.append_without_prefix('bar')
- object.append_without_prefix('baz')
- expect(object.content).to eql("foo\nbarbaz")
- end
-
- it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/capture_content_spec.rb b/spec/unit/unparser/buffer/capture_content_spec.rb
deleted file mode 100644
index 8937e6d..0000000
--- a/spec/unit/unparser/buffer/capture_content_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#capture_content' do
-
- let(:object) { described_class.new }
-
- it 'should capture only the content appended within the block' do
- object.append('foo')
- object.nl
- object.indent
- captured = object.capture_content do
- object.append('bar')
- object.nl
- end
- expect(captured).to eql(" bar\n")
- end
-end
diff --git a/spec/unit/unparser/buffer/content_spec.rb b/spec/unit/unparser/buffer/content_spec.rb
deleted file mode 100644
index 067d926..0000000
--- a/spec/unit/unparser/buffer/content_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#content' do
- subject { object.content }
-
- let(:object) { described_class.new }
-
- shared_examples_for 'buffer content' do
- it 'contains expected content' do
- should eql(expected_content)
- end
-
- it { should be_frozen }
-
- it 'returns fresh string copies' do
- first = object.content
- second = object.content
- expect(first).to eql(second)
- expect(first).not_to be(second)
- end
- end
-
- context 'with empty buffer' do
- let(:expected_content) { '' }
-
- it_should_behave_like 'buffer content'
- end
-
- context 'with filled buffer' do
- before do
- object.append('foo')
- end
-
- let(:expected_content) { 'foo' }
-
- it_behaves_like 'buffer content'
- end
-end
diff --git a/spec/unit/unparser/buffer/fresh_line_spec.rb b/spec/unit/unparser/buffer/fresh_line_spec.rb
deleted file mode 100644
index dc01499..0000000
--- a/spec/unit/unparser/buffer/fresh_line_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#fresh_line?' do
- let(:object) { described_class.new }
-
- it 'should return true while buffer is empty' do
- expect(object.fresh_line?).to eql(true)
- end
-
- it 'should return false after content has been appended' do
- object.append('foo')
- expect(object.fresh_line?).to eql(false)
- end
-
- it 'should return true after a nl has been appended' do
- object.append('foo')
- object.nl
- expect(object.fresh_line?).to eql(true)
- end
-end
diff --git a/spec/unit/unparser/buffer/indent_spec.rb b/spec/unit/unparser/buffer/indent_spec.rb
deleted file mode 100644
index e38084b..0000000
--- a/spec/unit/unparser/buffer/indent_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#indent' do
- let(:object) { described_class.new }
-
- subject { object.indent }
-
- it 'should indent with two spaces' do
- object.append('foo')
- object.nl
- object.indent
- object.append('bar')
- object.nl
- object.indent
- object.append('baz')
- expect(object.content).to eql("foo\n bar\n baz")
- end
-
- it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/nl_spec.rb b/spec/unit/unparser/buffer/nl_spec.rb
deleted file mode 100644
index 1917c04..0000000
--- a/spec/unit/unparser/buffer/nl_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#nl' do
- let(:object) { described_class.new }
-
- subject { object.nl }
-
- it 'writes a newline' do
- object.append('foo')
- subject
- object.append('bar')
- expect(object.content).to eql("foo\nbar")
- end
-
- it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/unindent_spec.rb b/spec/unit/unparser/buffer/unindent_spec.rb
deleted file mode 100644
index cc8202e..0000000
--- a/spec/unit/unparser/buffer/unindent_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#unindent' do
- let(:object) { described_class.new }
-
- subject { object.unindent }
-
- it 'unindents two chars' do
- object.append('foo')
- object.nl
- object.indent
- object.append('bar')
- object.nl
- object.unindent
- object.append('baz')
- expect(object.content).to eql("foo\n bar\nbaz")
- end
-
- it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/comments/consume_spec.rb b/spec/unit/unparser/comments/consume_spec.rb
deleted file mode 100644
index 57b1702..0000000
--- a/spec/unit/unparser/comments/consume_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#consume' do
-
- let(:ast_and_comments) do
- Unparser.parse_with_comments(<<~'RUBY')
- def hi # EOL 1
- end # EOL 2
- RUBY
- end
- let(:ast) { ast_and_comments[0] }
- let(:comments) { ast_and_comments[1] }
- let(:object) { described_class.new(comments) }
-
- it 'should cause further EOL comments to be returned' do
- expect(object.take_eol_comments).to eql([])
- object.consume(ast, :name)
- expect(object.take_eol_comments).to eql([comments[0]])
- object.consume(ast, :end)
- expect(object.take_eol_comments).to eql([comments[1]])
- end
-end
diff --git a/spec/unit/unparser/comments/take_all_spec.rb b/spec/unit/unparser/comments/take_all_spec.rb
deleted file mode 100644
index 594f672..0000000
--- a/spec/unit/unparser/comments/take_all_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#take_all' do
-
- let(:ast_and_comments) do
- Unparser.parse_with_comments(<<~'RUBY')
- def hi # EOL 1
- end # EOL 2
- RUBY
- end
- let(:ast) { ast_and_comments[0] }
- let(:comments) { ast_and_comments[1] }
- let(:object) { described_class.new(comments) }
-
- it 'should take all comments' do
- expect(object.take_all).to eql(comments)
- expect(object.take_all).to eql([])
- end
-end
diff --git a/spec/unit/unparser/comments/take_before_spec.rb b/spec/unit/unparser/comments/take_before_spec.rb
deleted file mode 100644
index 288cccd..0000000
--- a/spec/unit/unparser/comments/take_before_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#take_before' do
-
- let(:ast) { ast_and_comments[0] }
- let(:comments) { ast_and_comments[1] }
- let(:object) { described_class.new(comments) }
-
- context 'usual case' do
-
- let(:ast_and_comments) do
- Unparser.parse_with_comments(<<~'RUBY')
- def hi # EOL 1
- # comment
- end # EOL 2
- RUBY
- end
-
- it 'should return no comments if none are before the node' do
- expect(object.take_before(ast, :expression)).to eql([])
- end
-
- it 'should return only the comments that are before the specified part of the node' do
- expect(object.take_before(ast, :end)).to eql(comments.first(2))
- expect(object.take_all).to eql([comments[2]])
- end
- end
-
- context 'when node does not respond to source part' do
-
- let(:ast_and_comments) do
- Unparser.parse_with_comments(<<~'RUBY')
- expression ? :foo : :bar # EOL 1
- # EOL 2
- RUBY
- end
-
- it 'should return no comments if none are before the node' do
- expect(object.take_before(ast, :expression)).to eql([])
- end
-
- it 'should return only the comments that are before the specified part of the node' do
- expect(object.take_before(ast, :end)).to eql([])
- end
- end
-end
diff --git a/spec/unit/unparser/comments/take_eol_comments_spec.rb b/spec/unit/unparser/comments/take_eol_comments_spec.rb
deleted file mode 100644
index d2ff392..0000000
--- a/spec/unit/unparser/comments/take_eol_comments_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#take_eol_comments' do
-
- let(:ast_and_comments) do
- Unparser.parse_with_comments(<<~'RUBY')
- def hi # EOL 1
- =begin
- doc comment
- =end
- end # EOL 2
- RUBY
- end
- let(:ast) { ast_and_comments[0] }
- let(:comments) { ast_and_comments[1] }
- let(:object) { described_class.new(comments) }
-
- it 'should return no comments if nothing has been consumed' do
- expect(object.take_eol_comments).to eql([])
- end
-
- it 'should return comments once their line has been consumed' do
- object.consume(ast, :name)
- expect(object.take_eol_comments).to eql([comments[0]])
- end
-
- it 'should leave doc comments to be taken later' do
- object.consume(ast)
- expect(object.take_eol_comments).to eql([comments[0], comments[2]])
- expect(object.take_all).to eql([comments[1]])
- end
-end
diff --git a/spec/unit/unparser/emitter/class_methods/handle_spec.rb b/spec/unit/unparser/emitter/class_methods/handle_spec.rb
deleted file mode 100644
index 026697f..0000000
--- a/spec/unit/unparser/emitter/class_methods/handle_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Emitter, '.handle', mutant_expression: 'Unparser::Emitter*' do
- subject { class_under_test.class_eval { handle :foo } }
-
- let(:class_under_test) do
- Class.new(described_class)
- end
-
- before do
- stub_const('Unparser::Emitter::REGISTRY', {})
- end
-
- it 'should register emitter' do
- expect { subject }.to change { Unparser::Emitter::REGISTRY }.from({}).to(foo: class_under_test)
- end
-end
diff --git a/spec/unit/unparser_spec.rb b/spec/unit/unparser_spec.rb
deleted file mode 100644
index d92a185..0000000
--- a/spec/unit/unparser_spec.rb
+++ /dev/null
@@ -1,1849 +0,0 @@
-require 'spec_helper'
-
-describe Unparser, mutant_expression: 'Unparser::Emitter*' do
- describe '.buffer' do
- let(:source) { 'a + b' }
-
- def apply
- described_class.buffer(source)
- end
-
- it 'returns parser buffer with expected name' do
- expect(apply.name).to eql('(string)')
- end
-
- it 'returns parser buffer with pre-filled source' do
- expect(apply.source).to eql(source)
- end
- end
-
- describe '.parser' do
- let(:invalid_source_buffer) { Unparser.buffer('a +') }
-
- def apply
- described_class.parser
- end
-
- context 'failure' do
- def apply
- super.tap do |parser|
- parser.diagnostics.consumer = ->(_) {}
- end
- end
-
- it 'returns a parser that fails with syntax error' do
- expect { apply.parse(invalid_source_buffer) }
- .to raise_error(Parser::SyntaxError)
- end
- end
-
- context 'warnings' do
- before do
- allow(Kernel).to receive(:warn)
- end
-
- it 'returns a parser that warns on diagnostics' do
- expect { apply.parse(invalid_source_buffer) }
- .to raise_error(Parser::SyntaxError)
-
- expect(Kernel).to have_received(:warn)
- .with([
- "(string):1:4: error: unexpected token $end",
- "(string):1: a +", "(string):1: "
- ])
- end
- end
- end
-
- describe '.parse' do
- def apply
- described_class.parse('self[1]=2')
- end
-
- it 'returns expected AST' do
- expect(apply).to eql(s(:indexasgn, s(:self), s(:int, 1), s(:int, 2)))
- end
- end
-
- describe '.unparse' do
- let(:builder_options) { {} }
-
- def parser
- Unparser.parser.tap do |parser|
- builder_options.each do |name, value|
- parser.builder.public_send(:"#{name}=", value)
- end
- end
- end
-
- def buffer(input)
- Unparser.buffer(input)
- end
-
- def parse_with_comments(string)
- parser.parse_with_comments(buffer(string))
- end
-
- def self.with_builder_options(options, &block)
- context "with #{options}" do
- let(:builder_options) { options }
-
- class_eval(&block)
- end
- end
-
- def assert_round_trip(string, parser)
- ast, comments = parse_with_comments(string)
- generated = Unparser.unparse(ast, comments)
- expect(generated).to eql(string.chomp)
- generated_ast, _comments = parse_with_comments(generated)
- expect(ast == generated_ast).to be(true)
- end
-
- def assert_generates_from_string(parser, string, expected)
- ast_with_comments = parse_with_comments(string)
- assert_generates_from_ast(parser, ast_with_comments, expected.chomp)
- end
-
- def assert_generates_from_ast(parser, ast_with_comments, expected)
- generated = Unparser.unparse(*ast_with_comments)
- expect(generated).to eql(expected)
- ast, comments = parse_with_comments(generated)
- expect(Unparser.unparse(ast, comments)).to eql(expected)
- end
-
- def self.assert_unterminated(expression)
- assert_source(expression)
- assert_source("(#{expression}).foo")
- end
-
- def self.assert_terminated(expression)
- assert_source(expression)
- assert_source("foo(#{expression})")
- assert_source("#{expression}.foo")
- end
-
- def self.assert_generates(input, expected)
- it "should generate #{input} as #{expected}" do
- if input.is_a?(String)
- assert_generates_from_string(parser, input, expected)
- else
- assert_generates_from_ast(parser, [input, []], expected)
- end
- end
- end
-
- def self.assert_round_trip(input)
- it "should round trip #{input}" do
- assert_round_trip(input, parser)
- end
- end
-
- def self.assert_source(input)
- assert_round_trip(input)
- end
-
- context 'kwargs' do
- assert_source <<~RUBY
- def foo(bar:, baz:)
- end
- RUBY
-
- assert_source <<~RUBY
- foo(**bar)
- RUBY
-
- assert_source <<~RUBY
- def foo(bar:, baz: "value")
- end
- RUBY
- end
-
- context 'literal' do
- context 'int' do
- assert_generates '-0', '0'
- assert_source '++1'
- assert_terminated '1'
- assert_unterminated '-1'
- assert_generates '0x1', '1'
- assert_generates '1_000', '1000'
- assert_generates '1e10', '10000000000.0'
- assert_generates '10e10000000000', 'Float::INFINITY'
- assert_generates '-10e10000000000', '-Float::INFINITY'
- end
-
- context 'rational' do
- assert_terminated '1r'
- assert_generates '1.0r', '1r'
- assert_generates '-0r', '0r'
-
- assert_terminated '1.5r'
- assert_terminated '1.3r'
- end
-
- context 'complex' do
- %w(
- 5i
- -5i
- 0.6i
- -0.6i
- 1000000000000000000000000000000i
- 1ri
- ).each do |expression|
- assert_terminated(expression)
- end
- end
-
- context 'string' do
- assert_generates '?c', '"c"'
- assert_generates '"foo" "bar"', '"#{"foo"}#{"bar"}"'
- assert_generates '"foo" "bar #{baz}"', '"#{"foo"}#{"#{"bar "}#{baz}"}"'
- assert_generates '%Q(foo"#{@bar})', '"#{"foo\\""}#{@bar}"'
- assert_generates '"foo#{1}bar"', '"#{"foo"}#{1}#{"bar"}"'
- assert_generates '"\\\\#{}"', '"#{"\\\\"}#{}"'
- assert_generates '"#{}\#{}"', '"#{}#{"\#{}"}"'
- assert_generates '"\#{}#{}"', '"#{"\#{}"}#{}"'
- assert_terminated '"\""'
- assert_terminated '"foo bar"'
- assert_terminated '"foo\nbar"'
- # Within indentation
- assert_generates <<~'RUBY', <<~'RUBY'
- if foo
- "
- #{foo}
- "
- end
- RUBY
- if foo
- "#{"\n"}#{" "}#{foo}#{"\n"}#{" "}"
- end
- RUBY
- end
-
- context 'execute string' do
- assert_generates '`foo`', '`#{"foo"}`'
- assert_generates '`foo#{@bar}`', '`#{"foo"}#{@bar}`'
- assert_generates '%x(\))', '`#{")"}`'
- assert_generates '%x(`)', '`#{"`"}`'
- assert_generates '`"`', '`#{"\\""}`'
- end
-
- context 'symbol' do
- assert_generates s(:sym, :foo), ':foo'
- assert_generates s(:sym, :"A B"), ':"A B"'
- assert_terminated ':foo'
- assert_terminated ':"A B"'
- assert_terminated ':"A\"B"'
- assert_terminated ':""'
- end
-
- context 'regexp' do
- assert_terminated '/foo/'
- assert_terminated %q(/[^-+',.\/:@[:alnum:]\[\]]+/)
- assert_terminated '/foo#{@bar}/'
- assert_terminated '/foo#{@bar}/imx'
- assert_terminated '/#{"\u0000"}/'
- assert_terminated "/\n/"
- assert_terminated '/\n/'
- assert_terminated "/\n/x"
- # Within indentation
- assert_source <<~RUBY
- if foo
- /
- /
- end
- RUBY
- assert_generates '%r(/)', '/\//'
- assert_generates '%r(\))', '/\)/'
- assert_generates '%r(#{@bar}baz)', '/#{@bar}baz/'
- assert_terminated '/\/\//x'
- end
-
- context 'dynamic symbol' do
- assert_generates ':"foo#{bar}baz"', ':"#{"foo"}#{bar}#{"baz"}"'
- assert_source ':"#{"foo"}"'
- end
-
- context 'irange' do
- assert_unterminated '1..'
- assert_unterminated '1..2'
- assert_unterminated '(0.0 / 0.0)..1'
- assert_unterminated '1..(0.0 / 0.0)'
- assert_unterminated '(0.0 / 0.0)..100'
- end
-
- context 'erange' do
- assert_unterminated '1...'
- assert_unterminated '1...2'
- end
-
- context 'float' do
- assert_source '-0.1'
- assert_terminated '0.1'
- assert_terminated '0.1'
- assert_generates '10.2e10000000000', 'Float::INFINITY'
- assert_generates '-10.2e10000000000', '-Float::INFINITY'
- assert_generates s(:float, -0.1), '-0.1'
- assert_generates s(:float, 0.1), '0.1'
- end
-
- context 'array' do
- assert_terminated '[1, 2]'
- assert_terminated '[1, (), n2]'
- assert_terminated '[1]'
- assert_terminated '[]'
- assert_terminated '[1, *@foo]'
- assert_terminated '[*@foo, 1]'
- assert_terminated '[*@foo, *@baz]'
- assert_generates '%w(foo bar)', '["foo", "bar"]'
- end
-
- context 'hash' do
- assert_terminated '{}'
- assert_source '{ () => () }'
- assert_source '{ 1 => 2 }'
- assert_source '{ 1 => 2, 3 => 4 }'
-
- # special case for 2.1.3
- assert_source "{ foo: (if true\nend) }"
-
- context 'with symbol keys' do
- assert_source '{ a: (1 rescue foo), b: 2 }'
- assert_source '{ a: 1, b: 2 }'
- assert_source '{ a: :a }'
- assert_source '{ :"a b" => 1 }'
- assert_source '{ :-@ => 1 }'
- end
- end
- end
-
- context 'access' do
- %w(@a @@a $a $1 $` CONST SCOPED::CONST ::TOPLEVEL ::TOPLEVEL::CONST).each do |expression|
- assert_terminated(expression)
- end
- end
-
- context 'control keywords' do
- %w(retry redo).each do |expression|
- assert_terminated(expression)
- end
- end
-
- context 'singletons' do
- %w(self true false nil).each do |expression|
- assert_terminated(expression)
- end
- end
-
- context 'magic keywords' do
- assert_source '__ENCODING__'
-
- # These two assertions don't actually need to be wrapped in this block since `true` is the default,
- # but it is helpful to contrast with the assertions farther down.
- with_builder_options(emit_file_line_as_literals: true) do
- assert_generates '__FILE__', '"(string)"'
- assert_generates '__LINE__', '1'
- end
-
- with_builder_options(emit_file_line_as_literals: false) do
- assert_source '__FILE__'
- assert_source '__LINE__'
- end
- end
-
- context 'assignment' do
- context 'single' do
- assert_unterminated 'a = 1'
- assert_unterminated '@a = 1'
- assert_unterminated '@@a = 1'
- assert_unterminated '$a = 1'
- assert_unterminated 'CONST = 1'
- assert_unterminated 'Name::Spaced::CONST = 1'
- assert_unterminated '::Foo = ::Bar'
- end
-
- context 'lvar assigned from method with same name' do
- assert_unterminated 'foo = foo()'
- end
-
- context 'lvar introduction from condition' do
- assert_source 'foo = bar while foo'
- assert_source 'foo = bar until foo'
- assert_source <<~'RUBY'
- foo = exp
- while foo
- foo = bar
- end
- RUBY
-
- # Ugly I know. But its correct :D
- #
- # if foo { |pair| }
- # pair = :foo
- # foo
- # end
- assert_source <<~'RUBY'
- if foo do |pair|
- pair
- end
- pair = :foo
- foo
- end
- RUBY
-
- assert_source <<~'RUBY'
- while foo
- foo = bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- each do |bar|
- while foo
- foo = bar
- end
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo
- foo = bar while foo != baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- each do |baz|
- while foo
- foo = bar
- end
- end
- RUBY
-
- assert_source <<~'RUBY'
- each do |foo|
- while foo
- foo = bar
- end
- end
- RUBY
- end
-
- context 'multiple' do
- assert_source 'a, * = [1, 2]'
- assert_source 'a, *foo = [1, 2]'
- assert_source '*a = []'
- assert_source '*foo = [1, 2]'
- assert_source 'a, = foo'
- assert_unterminated 'a, b = [1, 2]'
- assert_unterminated '@a, @b = [1, 2]'
- assert_unterminated 'a.foo, a.bar = [1, 2]'
- assert_unterminated 'a[0], a[1] = [1, 2]'
- assert_unterminated 'a[*foo], a[1] = [1, 2]'
- assert_unterminated '@@a, @@b = [1, 2]'
- assert_unterminated '$a, $b = [1, 2]'
- assert_unterminated 'a, b = foo'
- assert_unterminated 'a, (b, c) = [1, [2, 3]]'
- assert_unterminated 'a = (b, c = 1)'
- assert_unterminated '(a,), b = 1'
- end
- end
-
- %w(next return break).each do |keyword|
-
- context keyword do
- assert_terminated keyword.to_s
- assert_unterminated "#{keyword} 1"
- assert_unterminated "#{keyword} 2, 3"
- assert_unterminated "#{keyword} *nil"
- assert_unterminated "#{keyword} *foo, bar"
-
- assert_generates <<~RUBY, <<~RUBY
- foo do |bar|
- bar =~ // || #{keyword}
- baz
- end
- RUBY
- foo do |bar|
- (bar =~ //) || #{keyword}
- baz
- end
- RUBY
-
- assert_generates <<~RUBY, <<~RUBY
- #{keyword}(a ? b : c)
- RUBY
- #{keyword} (if a
- b
- else
- c
- end)
- RUBY
- end
- end
-
- context 'conditional send (csend)' do
- assert_terminated 'a&.b'
- assert_terminated 'a&.b(c)'
- end
-
- context 'send' do
- assert_terminated 'foo'
- assert_terminated 'self.foo'
- assert_terminated 'a.foo'
- assert_terminated 'A.foo'
- assert_terminated 'foo[]'
- assert_terminated 'foo[1]'
- assert_terminated 'foo[*baz]'
- assert_terminated 'foo(1)'
- assert_terminated 'foo(bar)'
- assert_terminated 'foo(&block)'
- assert_terminated 'foo(&(foo || bar))'
- assert_terminated 'foo(*arguments)'
- assert_terminated 'foo(*arguments)'
-
- assert_source <<~'RUBY'
- foo do
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo do |a|
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo do |a, |
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo do |a, b|
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo(1) do
- nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo do |a, *b|
- nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo do |a, *|
- nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo do
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar(*args)
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |(a, b), c|
- d
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |*a; b|
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |a; b|
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |; a, b|
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |*|
- d
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |(*)|
- d
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |((*))|
- d
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |(a, (*))|
- d
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do |(a, b)|
- d
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo.bar do
- end.baz
- RUBY
-
- assert_source <<~'RUBY'
- FOO()
- RUBY
-
- assert_terminated '(1..2).max'
- assert_terminated '1..2.max'
- assert_unterminated 'a || return'
- assert_unterminated 'foo << (bar * baz)'
-
- assert_source <<~'RUBY'
- foo ||= (a, _ = b)
- RUBY
-
- assert_source <<~'RUBY'
- begin
- rescue
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- case (def foo
- end
- :bar)
- when bar
- end.baz
- RUBY
-
- assert_source <<~'RUBY'
- case foo
- when bar
- end.baz
- RUBY
-
- assert_source <<~'RUBY'
- class << self
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- def self.foo
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- def foo
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- until foo
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- while foo
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- loop do
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- class Foo
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- module Foo
- end.bar
- RUBY
-
- assert_source <<~'RUBY'
- if foo
- end.baz
- RUBY
-
- assert_source <<~'RUBY'
- local = 1
- local.bar
- RUBY
-
- assert_terminated 'foo.bar(*args)'
- assert_terminated 'foo.bar(*arga, foo, *argb)'
- assert_terminated 'foo.bar(*args, foo)'
- assert_terminated 'foo.bar(foo, *args)'
- assert_terminated 'foo.bar(foo, *args, &block)'
- assert_source <<~'RUBY'
- foo(bar, *args)
- RUBY
-
- assert_terminated 'foo(*args, &block)'
- assert_terminated 'foo.bar(&baz)'
- assert_terminated 'foo.bar(:baz, &baz)'
- assert_terminated 'foo.bar=:baz'
-
- assert_unterminated 'self.foo=:bar'
-
- assert_terminated 'foo.bar(baz: boz)'
- assert_terminated 'foo.bar(foo, "baz" => boz)'
- assert_terminated 'foo.bar({ foo: boz }, boz)'
- assert_terminated 'foo.bar(foo, {})'
- end
-
- context 'begin; end' do
- assert_generates s(:begin), ''
-
- assert_source <<~'RUBY'
- begin
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo
- bar
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- bar
- end.blah
- RUBY
- end
-
- context 'begin / rescue / ensure' do
- assert_source <<~'RUBY'
- begin
- foo
- ensure
- bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- rescue
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- begin
- foo
- bar
- rescue
- end
- rescue
- baz
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- raise(Exception) rescue foo = bar
- rescue Exception
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- bar
- rescue
- baz
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- rescue Exception
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- rescue => bar
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- rescue Exception, Other => bar
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- class << self
- undef :bar rescue nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- module Foo
- undef :bar rescue nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- class Foo
- undef :bar rescue nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- rescue Exception => e
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- ensure
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- rescue
- ensure
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- rescue Exception => bar
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- bar
- rescue SomeError, *bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- bar
- rescue SomeError, *bar => exception
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- bar
- rescue *bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- bar
- rescue LoadError
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- bar
- rescue
- else
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- bar
- rescue *bar => exception
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- rescue Exception => e
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- ensure
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- rescue
- ensure
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- foo
- rescue Exception => bar
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- bar
- rescue SomeError, *bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- bar
- rescue SomeError, *bar => exception
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- bar
- rescue *bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- bar
- rescue LoadError
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- bar
- rescue
- else
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- m do
- bar
- rescue *bar => exception
- baz
- end
- RUBY
-
- assert_source 'foo rescue bar'
- assert_source 'foo rescue return bar'
- assert_source 'x = (foo rescue return bar)'
-
- %w(while until if).each do |keyword|
- assert_source <<~RUBY
- #{keyword} (
- foo rescue false
- )
- end
- RUBY
-
- assert_generates <<~RUBY, <<~GENERATED
- foo rescue false #{keyword} true
- RUBY
- #{keyword} true
- foo rescue false
- end
- GENERATED
- end
-
- assert_generates <<~'RUBY', <<~GENERATED
- case (foo rescue false)
- when true
- end
- RUBY
- case (
- foo rescue false
- )
- when true
- end
- GENERATED
- end
-
- context 'super' do
- assert_source 'super'
-
- assert_source 'super()'
- assert_source 'super(a)'
- assert_source 'super(a, b)'
- assert_source 'super(&block)'
- assert_source 'super(a, &block)'
-
- assert_source <<~'RUBY'
- super(a do
- foo
- end)
- RUBY
-
- assert_source <<~'RUBY'
- super do
- foo
- end
- RUBY
-
- assert_source <<~'RUBY'
- super(a) do
- foo
- end
- RUBY
-
- assert_source <<~'RUBY'
- super() do
- foo
- end
- RUBY
-
- assert_source <<~'RUBY'
- super(a, b) do
- foo
- end
- RUBY
-
- end
-
- context 'undef' do
- assert_source 'undef :foo'
- assert_source 'undef :foo, :bar'
- end
-
- context 'BEGIN' do
- assert_source <<~'RUBY'
- BEGIN {
- foo
- }
- RUBY
- end
-
- context 'END' do
- assert_source <<~'RUBY'
- END {
- foo
- }
- RUBY
- end
-
- context 'alias' do
- assert_source <<~'RUBY'
- alias $foo $bar
- RUBY
-
- assert_source <<~'RUBY'
- alias :foo :bar
- RUBY
- end
-
- context 'yield' do
- context 'without arguments' do
- assert_source 'yield'
- end
-
- context 'with argument' do
- assert_source 'yield(a)'
- end
-
- context 'with arguments' do
- assert_source 'yield(a, b)'
- end
- end
-
- context 'if statement' do
- assert_source <<~'RUBY'
- if /foo/
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- if 3
- 9
- end
- RUBY
-
- assert_source <<~'RUBY'
- if 4
- 5
- else
- 6
- end
- RUBY
-
- assert_source <<~'RUBY'
- unless 3
- nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- unless 3
- 9
- end
- RUBY
-
- assert_source <<~'RUBY'
- if foo
- end
- RUBY
-
- assert_source <<~'RUBY'
- foo = bar if foo
- RUBY
-
- assert_source <<~'RUBY'
- foo = bar unless foo
- RUBY
-
- assert_source <<~'RUBY'
- def foo(*foo)
- unless foo
- foo = bar
- end
- end
- RUBY
-
- assert_source <<~'RUBY'
- each do |foo|
- unless foo
- foo = bar
- end
- end
- RUBY
- end
-
- context 'def' do
- context 'on instance' do
-
- assert_source <<~'RUBY'
- def foo
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo
- foo
- rescue
- bar
- ensure
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- ensure
- bar rescue nil
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo
- bar
- ensure
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def self.foo
- bar
- rescue
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo
- bar
- rescue
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar, baz)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar = ())
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar = (baz
- nil))
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar = true)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar, baz = true)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar: 1)
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar: bar)
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar: bar())
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(*)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(*bar)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar, *baz)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(baz = true, *bor)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(baz = true, *bor, &block)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar, baz = true, *bor)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(&block)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo(bar, &block)
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo
- bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def (foo do |bar|
- end).bar
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def (foo(1)).bar
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def (Foo::Bar.baz).bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def (Foo::Bar).bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def Foo.bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def foo.bar
- baz
- end
- RUBY
- end
-
- context 'on singleton' do
- assert_source <<~'RUBY'
- def self.foo
- end
- RUBY
-
- assert_source <<~'RUBY'
- def self.foo
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- def self.foo
- bar
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- def Foo.bar
- bar
- end
- RUBY
-
- end
-
- context 'class' do
- assert_source <<~'RUBY'
- class TestClass
- end
- RUBY
-
- assert_source <<~'RUBY'
- class << some_object
- end
- RUBY
-
- assert_source <<~'RUBY'
- class << some_object
- the_body
- end
- RUBY
-
- assert_source <<~'RUBY'
- class SomeNameSpace::TestClass
- end
- RUBY
-
- assert_source <<~'RUBY'
- class Some::Name::Space::TestClass
- end
- RUBY
-
- assert_source <<~'RUBY'
- class TestClass < Object
- end
- RUBY
-
- assert_source <<~'RUBY'
- class TestClass < SomeNameSpace::Object
- end
- RUBY
-
- assert_source <<~'RUBY'
- class TestClass
- def foo
- :bar
- end
- end
- RUBY
-
- assert_source <<~'RUBY'
- class ::TestClass
- end
- RUBY
- end
-
- context 'module' do
-
- assert_source <<~'RUBY'
- module TestModule
- end
- RUBY
-
- assert_source <<~'RUBY'
- module SomeNameSpace::TestModule
- end
- RUBY
-
- assert_source <<~'RUBY'
- module Some::Name::Space::TestModule
- end
- RUBY
-
- assert_source <<~'RUBY'
- module TestModule
- def foo
- :bar
- end
- end
- RUBY
-
- end
-
- context 'op assign' do
- %w(|= ||= &= &&= += -= *= /= **= %=).each do |op|
- assert_source "self.foo #{op} bar"
- assert_source "foo[key] #{op} bar"
- assert_source "a #{op} (true\nfalse)"
- end
- end
-
- context 'element assignment' do
- assert_source 'foo[index] = value'
- assert_source 'foo[*index] = value'
- assert_source 'foo[a, b] = value'
- assert_source 'foo[1..2] = value'
- assert_source 'foo.[]=()'
- assert_source 'foo.[]=true'
- assert_source 'foo.[]=(1, 2)'
- assert_source 'foo[] = 1'
- assert_unterminated 'foo[] = 1'
-
- %w(+ - * / % & | || &&).each do |operator|
- context "with #{operator}" do
- assert_source "foo[index] #{operator}= 2"
- assert_source "foo[] #{operator}= 2"
- end
- end
- end
-
- context 'defined?' do
- assert_source <<~'RUBY'
- defined?(@foo)
- RUBY
-
- assert_source <<~'RUBY'
- defined?(Foo)
- RUBY
-
- assert_source <<~'RUBY'
- defined?((a, b = [1, 2]))
- RUBY
- end
- end
-
- context 'lambda' do
- assert_source <<~'RUBY'
- lambda do
- end
- RUBY
-
- assert_source <<~'RUBY'
- lambda do |a, b|
- a
- end
- RUBY
-
- assert_source <<~'RUBY'
- ->() do
- end
- RUBY
-
- assert_source <<~'RUBY'
- ->(a) do
- end
- RUBY
-
- assert_source <<~'RUBY'
- ->(a, b) do
- end
- RUBY
- end
-
- context 'match operators' do
- assert_source '/bar/ =~ foo'
- assert_source '/bar/ =~ :foo'
- assert_source '(/bar/ =~ :foo).foo'
- assert_source 'foo =~ /bar/'
- assert_source 'foo(foo =~ /bar/)'
- assert_source '(foo =~ /bar/).foo'
- end
-
- context 'binary operator methods' do
- %w(+ - * / & | << >> == === != <= < <=> > >= =~ !~ ^ **).each do |operator|
- assert_source "(-1) #{operator} 2"
- assert_source "(-1.2) #{operator} 2"
- assert_source "left.#{operator}(*foo)"
- assert_source "left.#{operator}(a, b)"
- assert_source "self #{operator} b"
- assert_source "a #{operator} b"
- assert_source "(a #{operator} b).foo"
- end
-
- assert_source 'left / right'
- end
-
- context 'nested binary operators' do
- assert_source '(a + b) / (c - d)'
- assert_source '(a + b) / c.-(e, f)'
- assert_source '(a + b) / c.-(*f)'
- end
-
- context 'binary operator' do
- assert_source 'a || (return foo)'
- assert_source '(return foo) || a'
- assert_source 'a || (break foo)'
- assert_source '(break foo) || a'
- assert_source '(a || b).foo'
- assert_source 'a || (b || c)'
- end
-
- { or: :'||', and: :'&&' }.each do |word, symbol|
- assert_generates "a #{word} return foo", "a #{symbol} (return foo)"
- assert_generates "a #{word} break foo", "a #{symbol} (break foo)"
- assert_generates "a #{word} next foo", "a #{symbol} (next foo)"
- end
-
- context 'expansion of shortcuts' do
- assert_source 'a += 2'
- assert_source 'a -= 2'
- assert_source 'a **= 2'
- assert_source 'a *= 2'
- assert_source 'a /= 2'
- end
-
- context 'shortcuts' do
- assert_source 'a &&= b'
- assert_source 'a ||= 2'
- assert_source '(a ||= 2).bar'
- assert_source '(h ||= {})[k] = v'
- end
-
- context 'flip flops' do
- context 'inclusive' do
- assert_source <<~'RUBY'
- if ((i == 4)..(i == 4))
- foo
- end
- RUBY
- end
-
- context 'exclusive' do
- assert_source <<~'RUBY'
- if ((i == 4)...(i == 4))
- foo
- end
- RUBY
- end
- end
-
- context 'case statement' do
- assert_source <<~'RUBY'
- case
- when bar
- baz
- when baz
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- case foo
- when bar
- when baz
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- case foo
- when bar
- baz
- when baz
- bar
- end
- RUBY
-
- assert_source <<~'RUBY'
- case foo
- when bar, baz
- :other
- end
- RUBY
-
- assert_source <<~'RUBY'
- case foo
- when *bar
- :value
- end
- RUBY
-
- assert_source <<~'RUBY'
- case foo
- when bar
- baz
- else
- :foo
- end
- RUBY
- end
-
- context 'for' do
- assert_source <<~'RUBY'
- bar(for a in bar do
- baz
- end)
- RUBY
- assert_source <<~'RUBY'
- for a in bar do
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- for a, *b in bar do
- baz
- end
- RUBY
-
- assert_source <<~'RUBY'
- for a, b in bar do
- baz
- end
- RUBY
- end
-
- context 'unary operators' do
- assert_source '!1'
- assert_source '!(!1)'
- assert_source '!(!(foo || bar))'
- assert_source '!(!1).baz'
- assert_source '~a'
- assert_source '-a'
- assert_source '+a'
- assert_source '-(-a).foo'
- end
-
- context 'loop' do
- assert_source <<~'RUBY'
- loop do
- foo
- end
- RUBY
- end
-
- context 'post conditions' do
- assert_source <<~'RUBY'
- x = (begin
- foo
- end while baz)
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- end while baz
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- bar
- end until baz
- RUBY
-
- assert_source <<~'RUBY'
- begin
- foo
- bar
- end while baz
- RUBY
- end
-
- context 'while' do
- assert_source <<~'RUBY'
- while false
- end
- RUBY
-
- assert_source <<~'RUBY'
- while false
- 3
- end
- RUBY
-
- assert_source <<~'RUBY'
- while (foo do
- end)
- :body
- end
- RUBY
- end
-
- context 'until' do
- assert_source <<~'RUBY'
- until false
- end
- RUBY
-
- assert_source <<~'RUBY'
- until false
- 3
- end
- RUBY
-
- assert_source <<~'RUBY'
- until (foo do
- end)
- :body
- end
- RUBY
- end
-
- assert_source <<~'RUBY'
- # comment before
- a_line_of_code
- RUBY
-
- assert_source <<~'RUBY'
- a_line_of_code # comment after
- RUBY
-
- assert_source <<~'RUBY'
- nested do # first
- # second
- something # comment
- # another
- end
- # last
- RUBY
-
- assert_generates <<~'RUBY', <<~'RUBY'
- foo if bar
- # comment
- RUBY
- if bar
- foo
- end
- # comment
- RUBY
-
- assert_source <<~'RUBY'
- def noop
- # do nothing
- end
- RUBY
-
- assert_source <<~'RUBY'
- =begin
- block comment
- =end
- nested do
- =begin
- another block comment
- =end
- something
- =begin
- last block comment
- =end
- end
- RUBY
-
- assert_generates(<<~'RUBY', <<~'RUBY')
- 1 + # first
- 2 # second
- RUBY
- 1 + 2 # first # second
- RUBY
-
- assert_generates(<<~'RUBY', <<~'RUBY')
- 1 +
- # first
- 2 # second
- RUBY
- 1 + 2 # first # second
- RUBY
-
- assert_generates(<<~'RUBY', <<~'RUBY')
- 1 +
- =begin
- block comment
- =end
- 2
- RUBY
- 1 + 2
- =begin
- block comment
- =end
- RUBY
- end
-end
diff --git a/unparser.gemspec b/unparser.gemspec
index 3df65e1..e3b3902 100644
--- a/unparser.gemspec
+++ b/unparser.gemspec
@@ -1,32 +1,52 @@
-Gem::Specification.new do |gem|
- gem.name = 'unparser'
- gem.version = '0.4.7'
+#########################################################
+# This file has been automatically generated by gem2tgz #
+#########################################################
+# -*- encoding: utf-8 -*-
+# stub: unparser 0.6.8 ruby lib
- gem.authors = ['Markus Schirp']
- gem.email = 'mbj@schirp-dso.com'
- gem.summary = 'Generate equivalent source for parser gem AST nodes'
+Gem::Specification.new do |s|
+ s.name = "unparser".freeze
+ s.version = "0.6.8"
- gem.description = gem.summary
- gem.homepage = 'http://github.com/mbj/unparser'
- gem.license = 'MIT'
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
+ s.metadata = { "bug_tracker_uri" => "https://github.com/mbj/unparser/issues", "changelog_uri" => "https://github.com/mbj/unparser/blob/master/Changelog.md", "funding_uri" => "https://github.com/sponsors/mbj", "rubygems_mfa_required" => "true" } if s.respond_to? :metadata=
+ s.require_paths = ["lib".freeze]
+ s.authors = ["Markus Schirp".freeze]
+ s.date = "2023-06-15"
+ s.description = "Generate equivalent source for parser gem AST nodes".freeze
+ s.email = "mbj@schirp-dso.com".freeze
+ s.executables = ["unparser".freeze]
+ s.extra_rdoc_files = ["README.md".freeze]
+ s.files = ["README.md".freeze, "bin/unparser".freeze, "lib/unparser.rb".freeze, "lib/unparser/abstract_type.rb".freeze, "lib/unparser/adamantium.rb".freeze, "lib/unparser/adamantium/method_builder.rb".freeze, "lib/unparser/anima.rb".freeze, "lib/unparser/anima/attribute.rb".freeze, "lib/unparser/anima/error.rb".freeze, "lib/unparser/ast.rb".freeze, "lib/unparser/ast/local_variable_scope.rb".freeze, "lib/unparser/buffer.rb".freeze, "lib/unparser/cli.rb".freeze, "lib/unparser/color.rb".freeze, "lib/unparser/comments.rb".freeze, "lib/unparser/concord.rb".freeze, "lib/unparser/constants.rb".freeze, "lib/unparser/diff.rb".freeze, "lib/unparser/dsl.rb".freeze, "lib/unparser/either.rb".freeze, "lib/unparser/emitter.rb".freeze, "lib/unparser/emitter/alias.rb".freeze, "lib/unparser/emitter/args.rb".freeze, "lib/unparser/emitter/argument.rb".freeze, "lib/unparser/emitter/array.rb".freeze, "lib/unparser/emitter/array_pattern.rb".freeze, "lib/unparser/emitter/assignment.rb".freeze, "lib/unparser/emitter/begin.rb".freeze, "lib/unparser/emitter/binary.rb".freeze, "lib/unparser/emitter/block.rb".freeze, "lib/unparser/emitter/case.rb".freeze, "lib/unparser/emitter/case_guard.rb".freeze, "lib/unparser/emitter/case_match.rb".freeze, "lib/unparser/emitter/cbase.rb".freeze, "lib/unparser/emitter/class.rb".freeze, "lib/unparser/emitter/const_pattern.rb".freeze, "lib/unparser/emitter/def.rb".freeze, "lib/unparser/emitter/defined.rb".freeze, "lib/unparser/emitter/dstr.rb".freeze, "lib/unparser/emitter/dsym.rb".freeze, "lib/unparser/emitter/find_pattern.rb".freeze, "lib/unparser/emitter/flipflop.rb".freeze, "lib/unparser/emitter/float.rb".freeze, "lib/unparser/emitter/flow_modifier.rb".freeze, "lib/unparser/emitter/for.rb".freeze, "lib/unparser/emitter/hash.rb".freeze, "lib/unparser/emitter/hash_pattern.rb".freeze, "lib/unparser/emitter/hookexe.rb".freeze, "lib/unparser/emitter/if.rb".freeze, "lib/unparser/emitter/in_match.rb".freeze, "lib/unparser/emitter/in_pattern.rb".freeze, "lib/unparser/emitter/index.rb".freeze, "lib/unparser/emitter/kwargs.rb".freeze, "lib/unparser/emitter/kwbegin.rb".freeze, "lib/unparser/emitter/lambda.rb".freeze, "lib/unparser/emitter/masgn.rb".freeze, "lib/unparser/emitter/match.rb".freeze, "lib/unparser/emitter/match_alt.rb".freeze, "lib/unparser/emitter/match_as.rb".freeze, "lib/unparser/emitter/match_pattern.rb".freeze, "lib/unparser/emitter/match_pattern_p.rb".freeze, "lib/unparser/emitter/match_rest.rb".freeze, "lib/unparser/emitter/match_var.rb".freeze, "lib/unparser/emitter/mlhs.rb".freeze, "lib/unparser/emitter/module.rb".freeze, "lib/unparser/emitter/op_assign.rb".freeze, "lib/unparser/emitter/pair.rb".freeze, "lib/unparser/emitter/pin.rb".freeze, "lib/unparser/emitter/primitive.rb".freeze, "lib/unparser/emitter/range.rb".freeze, "lib/unparser/emitter/regexp.rb".freeze, "lib/unparser/emitter/repetition.rb".freeze, "lib/unparser/emitter/rescue.rb".freeze, "lib/unparser/emitter/root.rb".freeze, "lib/unparser/emitter/send.rb".freeze, "lib/unparser/emitter/simple.rb".freeze, "lib/unparser/emitter/splat.rb".freeze, "lib/unparser/emitter/super.rb".freeze, "lib/unparser/emitter/undef.rb".freeze, "lib/unparser/emitter/variable.rb".freeze, "lib/unparser/emitter/xstr.rb".freeze, "lib/unparser/emitter/yield.rb".freeze, "lib/unparser/equalizer.rb".freeze, "lib/unparser/finalize.rb".freeze, "lib/unparser/generation.rb".freeze, "lib/unparser/node_details.rb".freeze, "lib/unparser/node_details/send.rb".freeze, "lib/unparser/node_helpers.rb".freeze, "lib/unparser/validation.rb".freeze, "lib/unparser/writer.rb".freeze, "lib/unparser/writer/binary.rb".freeze, "lib/unparser/writer/dynamic_string.rb".freeze, "lib/unparser/writer/resbody.rb".freeze, "lib/unparser/writer/rescue.rb".freeze, "lib/unparser/writer/send.rb".freeze, "lib/unparser/writer/send/attribute_assignment.rb".freeze, "lib/unparser/writer/send/binary.rb".freeze, "lib/unparser/writer/send/conditional.rb".freeze, "lib/unparser/writer/send/regular.rb".freeze, "lib/unparser/writer/send/unary.rb".freeze]
+ s.homepage = "http://github.com/mbj/unparser".freeze
+ s.licenses = ["MIT".freeze]
+ s.required_ruby_version = Gem::Requirement.new(">= 2.7".freeze)
+ s.rubygems_version = "3.2.5".freeze
+ s.summary = "Generate equivalent source for parser gem AST nodes".freeze
- gem.files = `git ls-files`.split("\n")
- gem.test_files = `git ls-files -- {spec,features}/*`.split("\n")
- gem.require_paths = %w[lib]
- gem.extra_rdoc_files = %w[README.md]
- gem.executables = %w[unparser]
+ if s.respond_to? :specification_version then
+ s.specification_version = 4
+ end
- gem.add_dependency('abstract_type', '~> 0.0.7')
- gem.add_dependency('adamantium', '~> 0.2.0')
- gem.add_dependency('equalizer', '~> 0.0.9')
- gem.add_dependency('diff-lcs', '~> 1.3')
- gem.add_dependency('concord', '~> 0.1.5')
- gem.add_dependency('parser', '>= 2.6.5')
- gem.add_dependency('procto', '~> 0.0.2')
-
- gem.add_development_dependency('anima', '~> 0.3.1')
- gem.add_development_dependency('devtools', '~> 0.1.23')
- gem.add_development_dependency('morpher', '~> 0.2.6')
- gem.add_development_dependency('mutant', '~> 0.9.4')
- gem.add_development_dependency('mutant-rspec', '~> 0.9.4')
+ if s.respond_to? :add_runtime_dependency then
+ s.add_runtime_dependency(%q<diff-lcs>.freeze, ["~> 1.3"])
+ s.add_development_dependency(%q<mutant>.freeze, ["~> 0.11.18"])
+ s.add_development_dependency(%q<mutant-rspec>.freeze, ["~> 0.11.18"])
+ s.add_runtime_dependency(%q<parser>.freeze, [">= 3.2.0"])
+ s.add_development_dependency(%q<rspec>.freeze, ["~> 3.9"])
+ s.add_development_dependency(%q<rspec-core>.freeze, ["~> 3.9"])
+ s.add_development_dependency(%q<rspec-its>.freeze, ["~> 1.3.0"])
+ s.add_development_dependency(%q<rubocop>.freeze, ["~> 1.7"])
+ s.add_development_dependency(%q<rubocop-packaging>.freeze, ["~> 0.5"])
+ else
+ s.add_dependency(%q<diff-lcs>.freeze, ["~> 1.3"])
+ s.add_dependency(%q<mutant>.freeze, ["~> 0.11.18"])
+ s.add_dependency(%q<mutant-rspec>.freeze, ["~> 0.11.18"])
+ s.add_dependency(%q<parser>.freeze, [">= 3.2.0"])
+ s.add_dependency(%q<rspec>.freeze, ["~> 3.9"])
+ s.add_dependency(%q<rspec-core>.freeze, ["~> 3.9"])
+ s.add_dependency(%q<rspec-its>.freeze, ["~> 1.3.0"])
+ s.add_dependency(%q<rubocop>.freeze, ["~> 1.7"])
+ s.add_dependency(%q<rubocop-packaging>.freeze, ["~> 0.5"])
+ end
end
More details
Historical runs
- unsatisfied-apt-dependencies: Unsatisfied APT dependencies: ruby-whitequark-parser:amd64 (>= 3.2.0)
- unsatisfied-apt-dependencies: Unsatisfied APT dependencies: ruby-whitequark-parser:amd64 (>= 3.2.0)
- success: Merged new upstream version 0.6.5
- unsatisfied-apt-dependencies: Unsatisfied APT dependencies: ruby-whitequark-parser:amd64 (>= 3.1.0)