unparser-0.4.7/0000755000175000017500000000000013715561413014607 5ustar debbiecocoadebbiecocoaunparser-0.4.7/Gemfile.lock0000644000175000017500000001062213715561413017032 0ustar debbiecocoadebbiecocoaPATH 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 unparser-0.4.7/README.md0000644000175000017500000001010313715561413016061 0ustar debbiecocoadebbiecocoaunparser ======== [![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) [![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). 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 It serves well for [mutant](https://github.com/mbj/mutant) mutators and the in-memory vendoring for self hosting, and other tooling. Public API: ----------- While unparser is in the `0.x` versions its public API can change any moment. I recommend to use `~> 0.x.y` style version constraints that should give the best mileage. Usage ----- ```ruby require 'parser/current' require 'unparser' ast = Unparser.parse('your(ruby(code))') Unparser.unparse(ast) # => 'your(ruby(code))' ``` To preserve the comments from the source: ```ruby require 'parser/current' require 'unparser' ast, comments = Unparser.parse_with_comments('your(ruby(code)) # with comments') Unparser.unparse(ast, comments) # => 'your(ruby(code)) # with comments' ``` Passing in manually constructed AST: ```ruby require 'parser/current' require 'unparser' module YourHelper def s(type, *children) Parser::AST::Node.new(type, children) end end include YourHelper node = s(:def, :foo, s(:args, s(:arg, :x) ), s(:send, s(:lvar, :x), :+, s(:int, 3) ) ) Unparser.unparse(node) # => "def foo(x)\n x + 3\nend" ``` Note: DO NOT attempt to pass in nodes generated via `AST::Sexp#s`, these ones return API incompatible `AST::Node` instances, unparser needs `Parser::AST::Node` instances. Equivalent vs identical: ```ruby require 'unparser' node = Unparser.parse(<<~'RUBY') %w[foo bar] RUBY generated = Unparser.unparse(node) # ["foo", "bar"], NOT %w[foo bar] ! 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. Testing: -------- Unparser currently successfully round trips almost all ruby code around. Using MRI-2.5.x. If there is a non round trippable example that is NOT subjected to known [Limitations](#limitations). please report a bug. On CI unparser is currently tested against rubyspec with minor [excludes](https://github.com/mbj/unparser/blob/master/spec/integrations.yml). Limitations: ------------ Source parsed with magic encoding headers other than UTF-8 and that have literal strings. where parts can be represented in UTF-8 will fail to get reproduced. A fix is possible as with latest updates the parser gem carries the information. Example: Original-Source: ```ruby # -*- encoding: binary -*- "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF" ``` Original-AST: ``` (str "\x98v\xAB\xCDE2\xEF\x01\x01#Eg\x89\xAB\xCD\xEF") ``` Generated-Source: ```ruby "\x98v\xAB\xCDE2\xEF\x01\x01#Eg\x89\xAB\xCD\xEF" ``` Generated-AST: ``` (str "\x98v\xAB\xCDE2\xEF\u0001\u0001#Eg\x89\xAB\xCD\xEF") ``` Diff: ``` @@ -1,2 +1,2 @@ -(str "\x98v\xAB\xCDE2\xEF\x01\x01#Eg\x89\xAB\xCD\xEF") +(str "\x98v\xAB\xCDE2\xEF\u0001\u0001#Eg\x89\xAB\xCD\xEF") ``` Installation ------------ Install the gem `unparser` via your prefered method. People ------ Various people contributed to this repository. See [Contributors](https://github.com/mbj/unparser/graphs/contributors). Contributing ------------- * Fork the project. * 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 (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. License ------- See LICENSE file. unparser-0.4.7/.gitignore0000644000175000017500000000040513715561413016576 0ustar debbiecocoadebbiecocoa## 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 unparser-0.4.7/config/0000755000175000017500000000000013715561413016054 5ustar debbiecocoadebbiecocoaunparser-0.4.7/config/reek.yml0000644000175000017500000000335513715561413017533 0ustar debbiecocoadebbiecocoa--- 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: [] unparser-0.4.7/config/devtools.yml0000644000175000017500000000003313715561413020432 0ustar debbiecocoadebbiecocoa--- unit_test_timeout: 1.0 unparser-0.4.7/config/yardstick.yml0000644000175000017500000000002313715561413020567 0ustar debbiecocoadebbiecocoa--- threshold: 100 unparser-0.4.7/config/flog.yml0000644000175000017500000000002413715561413017522 0ustar debbiecocoadebbiecocoa--- threshold: 21.3 unparser-0.4.7/config/flay.yml0000644000175000017500000000004313715561413017527 0ustar debbiecocoadebbiecocoa--- threshold: 13 total_score: 638 unparser-0.4.7/config/mutant.yml0000644000175000017500000000007413715561413020110 0ustar debbiecocoadebbiecocoa--- includes: - lib integration: rspec requires: - unparser unparser-0.4.7/config/rubocop.yml0000644000175000017500000000453513715561413020257 0ustar debbiecocoadebbiecocoainherit_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 unparser-0.4.7/.rspec0000644000175000017500000000006413715561413015724 0ustar debbiecocoadebbiecocoa--color --format progress --warnings --order random unparser-0.4.7/unparser.gemspec0000644000175000017500000000234513715561413020017 0ustar debbiecocoadebbiecocoaGem::Specification.new do |gem| gem.name = 'unparser' gem.version = '0.4.7' gem.authors = ['Markus Schirp'] gem.email = 'mbj@schirp-dso.com' gem.summary = 'Generate equivalent source for parser gem AST nodes' gem.description = gem.summary gem.homepage = 'http://github.com/mbj/unparser' gem.license = 'MIT' 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] 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') end unparser-0.4.7/LICENSE0000644000175000017500000000204113715561413015611 0ustar debbiecocoadebbiecocoaCopyright (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. unparser-0.4.7/.rubocop.yml0000644000175000017500000000023613715561413017062 0ustar debbiecocoadebbiecocoaAllCops: Include: - 'Gemfile' Exclude: - 'Gemfile.devtools' - 'vendor/**/*' - 'benchmarks/**/*' - 'tmp/**/*' TargetRubyVersion: 2.5 unparser-0.4.7/bin/0000755000175000017500000000000013715561413015357 5ustar debbiecocoadebbiecocoaunparser-0.4.7/bin/unparser0000755000175000017500000000023213715561413017141 0ustar debbiecocoadebbiecocoa#!/usr/bin/env ruby # frozen_string_literal: true trap('INT') do |status| exit! 128 + status end require 'unparser/cli' exit Unparser::CLI.run(ARGV) unparser-0.4.7/.circleci/0000755000175000017500000000000013715561413016442 5ustar debbiecocoadebbiecocoaunparser-0.4.7/.circleci/config.yml0000644000175000017500000000252113715561413020432 0ustar debbiecocoadebbiecocoadefaults: &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 unparser-0.4.7/lib/0000755000175000017500000000000013715561413015355 5ustar debbiecocoadebbiecocoaunparser-0.4.7/lib/unparser.rb0000644000175000017500000001034013715561413017537 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true require 'set' require 'abstract_type' require 'procto' require 'concord' require 'parser/current' # Library namespace module Unparser # Unparser specific AST builder defaulting to modern AST format class Builder < Parser::Builders::Default modernize end EMPTY_STRING = ''.freeze EMPTY_ARRAY = [].freeze private_constant(*constants(false)) # Unparse an AST (and, optionally, comments) into a string # # @param [Parser::AST::Node, nil] node # @param [Array] comment_array # # @return [String] # # @api private # 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 end # Parse string into AST # # @param [String] source # # @return [Parser::AST::Node] def self.parse(source) parser.parse(buffer(source)) end # Parse string into AST, with comments # # @param [String] source # # @return [Parser::AST::Node] def self.parse_with_comments(source) parser.parse_with_comments(buffer(source)) end # Parser instance that produces AST unparser understands # # @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 end end # Unparser require 'unparser/buffer' require 'unparser/node_helpers' require 'unparser/preprocessor' require 'unparser/comments' require 'unparser/constants' 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/argument' require 'unparser/emitter/begin' require 'unparser/emitter/flow_modifier' require 'unparser/emitter/undef' require 'unparser/emitter/def' require 'unparser/emitter/class' require 'unparser/emitter/module' require 'unparser/emitter/op_assign' require 'unparser/emitter/defined' 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/index' require 'unparser/emitter/lambda' # make it easy for zombie require 'unparser/finalize' unparser-0.4.7/lib/unparser/0000755000175000017500000000000013715561413017214 5ustar debbiecocoadebbiecocoaunparser-0.4.7/lib/unparser/emitter.rb0000644000175000017500000002136613715561413021222 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser # 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 # Registry for node emitters REGISTRY = {} # rubocop:disable MutableConstant NOINDENT = %i[rescue ensure].to_set.freeze module Unterminated def terminated? false end end module Terminated def terminated? true end end module LocalVariableRoot # Return local variable root # # @return [Parser::AST::Node] # # @api private # def local_variable_scope AST::LocalVariableScope.new(node) end def self.included(descendant) descendant.class_eval do 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] # # @api private # abstract_method :local_variables # Return node type # # @return [Symbol] # # @api private # def node_type node.type end # Register emitter for type # # @param [Symbol] types # # @return [undefined] # # @api private # def self.handle(*types) types.each do |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? dispatch comments.consume(node) emit_eof_comments if parent.is_a?(Root) self end memoize :write_to_buffer # Return emitter # # @return [Emitter] # # @api private # def self.emitter(node, parent) 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] nodes # # @return [undefined] # # @api private # def delimited_plain(nodes) delimited(nodes, &method(:visit_plain)) end # Emit delimited body # # @param [Enumerable] 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] # # @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) } end end # Return parent type # # @return [Symbol] # if parent is present # # @return [nil] # otherwise # # @api private # def parent_type parent.node_type end # Delegate to emitter # # @param [Class:Emitter] emitter # # @return [undefined] # # @api private # # rubocop:disable MethodCallWithoutArgsParentheses def run(emitter, node = node()) emitter.new(node, self).write_to_buffer end # rubocop:enable MethodCallWithoutArgsParentheses end # Emitter end # Unparser unparser-0.4.7/lib/unparser/cli/0000755000175000017500000000000013715561413017763 5ustar debbiecocoadebbiecocoaunparser-0.4.7/lib/unparser/cli/source.rb0000644000175000017500000001315113715561413021611 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/cli/color.rb0000644000175000017500000000147613715561413021436 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser # Class to colorize strings class Color include Adamantium::Flat, Concord.new(:code) # Format text with color # # @param [String] text # # @return [String] # # @api private # def format(text) "\e[#{code}m#{text}\e[0m" end NONE = Class.new(self) do # Format null color # # @param [String] text # # @return [String] # the argument string # # @api private # def format(text) text end private # Initialize null color # # @return [undefined] # # @api private # def initialize; end end.new RED = Color.new(31) GREEN = Color.new(32) BLUE = Color.new(34) end # Color end # Unparser unparser-0.4.7/lib/unparser/cli/differ.rb0000644000175000017500000000557113715561413021557 0ustar debbiecocoadebbiecocoa# 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] # # @api private # def self.lines(source) source.lines.map(&:chomp) end private_class_method :lines # Return hunks # # @return [Array] # # @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] # # @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] # # @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 unparser-0.4.7/lib/unparser/emitter/0000755000175000017500000000000013715561413020665 5ustar debbiecocoadebbiecocoaunparser-0.4.7/lib/unparser/emitter/empty.rb0000644000175000017500000000047113715561413022352 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/lambda.rb0000644000175000017500000000056713715561413022442 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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 end # Lambda end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/repetition.rb0000644000175000017500000000403113715561413023372 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # 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 }.freeze handle(*MAP.keys) # Perform dispatch # # @return [undefined] # # @api private # def dispatch visit(body) write(WS, MAP.fetch(node.type), WS) visit(condition) end end # Base class for while and until emitters class Repetition < self include Terminated MAP = { while: K_WHILE, until: K_UNTIL }.freeze handle(*MAP.keys) children :condition, :body private # Perform dispatch # # @return [undefined] # # @api private # def dispatch if postcontrol? emit_postcontrol else emit_normal 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) end # Emit keyword # # @return [undefined] # # @api private # def emit_keyword write(MAP.fetch(node.type), WS) end # Emit embedded # # @return [undefned] # # @api private # def emit_normal emit_keyword conditional_parentheses(condition.type.equal?(:block)) do visit(condition) end emit_body k_end end # Emit postcontrol # # @return [undefined] # # @api private # def emit_postcontrol visit_plain(body) ws emit_keyword visit_plain(condition) end end # Repetition end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/binary.rb0000644000175000017500000000104513715561413022476 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Base class for binary emitters class Binary < self include Unterminated children :left, :right MAP = { or: T_OR, and: T_AND }.freeze handle(*MAP.keys) private # Perform dispatch # # @return [undefined] # # @api private # def dispatch visit(left) write(WS, MAP.fetch(node.type), WS) visit(right) end end # Binary end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/rescue.rb0000644000175000017500000000416113715561413022502 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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) 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 unparser-0.4.7/lib/unparser/emitter/super.rb0000644000175000017500000000132613715561413022352 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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) parentheses do delimited(children) end end end # Super end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/literal.rb0000644000175000017500000000031413715561413022644 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Namespace class for literal emiters class Literal < self include Terminated end # Literal end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/for.rb0000644000175000017500000000131013715561413021773 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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) emit_condition emit_body k_end end # Emit assignment # # @return [undefined] # # @api private # def emit_condition visit_plain(condition) write(WS, K_IN, WS) visit(assignment) write(WS, K_DO) end end # For end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/case.rb0000644000175000017500000000322313715561413022125 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for case nodes class Case < self include Terminated handle :case children :condition define_group :whens, 1..-2 private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(K_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) 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) 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) emit_captures body = children.last emit_body(body) end # Emit captures # # @return [undefined] # # @api private # def emit_captures delimited(captures) end end # When end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/meta.rb0000644000175000017500000000046313715561413022143 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/send/0000755000175000017500000000000013715561413021616 5ustar debbiecocoadebbiecocoaunparser-0.4.7/lib/unparser/emitter/send/attribute_assignment.rb0000644000175000017500000000211513715561413026375 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter class Send # Emitter 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) if arguments.one? visit(first_argument) else parentheses { delimited(arguments) } end end private # Emit receiver # # @return [Parser::AST::Node] # # @api private # def emit_receiver visit(receiver) write(T_DOT) end # Emit attribute # # @return [undefined] # # @api private # def emit_attribute write(non_assignment_selector) end end # AttributeAssignment end # Send end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/send/binary.rb0000644000175000017500000000174213715561413023433 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/send/conditional.rb0000644000175000017500000000134413715561413024450 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/send/regular.rb0000644000175000017500000000134213715561413023604 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/send/unary.rb0000644000175000017500000000124313715561413023301 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter class Send # Emitter for unary sends class Unary < self include Unterminated private MAP = IceNine.deep_freeze( '-@': '-', '+@': '+' ) # Perform dispatch # # @return [undefined] # # @api private # def dispatch name = selector write(MAP.fetch(name, name).to_s) if receiver.type.equal?(:int) && selector.equal?(:'+@') write('+') end visit(receiver) end end # Unary end # Send end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/argument.rb0000644000175000017500000001145213715561413023037 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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] # # @api private # def normal_arguments children.reject(&SHADOWARGS) end memoize :normal_arguments # Return shadow args # # @return [Enumerable] # # @api private # def shadowargs children.select(&SHADOWARGS) end memoize :shadowargs end # Arguments # Emitter for block and kwrestarg arguments class Morearg < self include Terminated MAP = { blockarg: T_AMP, kwrestarg: T_DSPLAT }.freeze handle :blockarg handle :kwrestarg children :name private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(MAP.fetch(node_type), name.to_s) end end # Blockarg # 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) 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) visit(value) end end # KeywordOptional # 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) 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) 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 end # Argument # 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 delimited(children) end else delimited(children) end end def needs_parens? children.length > 1 || children.any? do |node| PARENS.include?(node.type) end end end # 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) end end # BlockPass end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/index.rb0000644000175000017500000000477113715561413022332 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for send to index references # # ignore :reek:RepeatedConditional class Index < self # Perform dispatch # # @return [undefined] # # @api 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) end end end # Reference # Emitter for assign to index nodes class Assign < self 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 private # 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 end # The indices # # @return [Array] # def indices if emit_value? children[VALUE_RANGE] else children.drop(1) end end # Test if value should be emitted # # @return [Boolean] # # @api private # def emit_value? !mlhs? && !no_value_parent? end # Test for no value parent # # @return [Boolean] # # @api private # def no_value_parent? NO_VALUE_PARENT.include?(parent_type) end end # Assign end # Index end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/flow_modifier.rb0000644000175000017500000000273013715561413024041 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter control flow modifiers class FlowModifier < self MAP = { return: K_RETURN, next: K_NEXT, break: K_BREAK }.freeze handle(*MAP.keys) def terminated? children.empty? 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 else emit_arguments 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 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 unparser-0.4.7/lib/unparser/emitter/alias.rb0000644000175000017500000000070013715561413022300 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for alias nodes class Alias < self handle :alias children :target, :source private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(K_ALIAS, WS) visit(target) write(WS) visit(source) end end # Alias end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/root.rb0000644000175000017500000000040213715561413022171 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Root emitter a special case class Root < self include Concord::Public.new(:node, :buffer, :comments) include LocalVariableRoot end # Root end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/flipflop.rb0000644000175000017500000000106713715561413023031 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for flip flops class FlipFlop < self include Unterminated MAP = IceNine.deep_freeze( iflipflop: '..', eflipflop: '...' ).freeze handle(*MAP.keys) children :left, :right private # Perform dispatch # # @return [undefined] # # @api private # def dispatch visit(left) write(MAP.fetch(node.type)) visit(right) end end # FlipFLop end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/defined.rb0000644000175000017500000000072713715561413022616 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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 end end # Defined end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/variable.rb0000644000175000017500000000254613715561413023006 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # 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 end # Access # 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) end end # Emitter for nth_ref nodes (regexp captures) class NthRef < self include Terminated PREFIX = '$'.freeze handle :nth_ref children :name private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(PREFIX) write(name.to_s) end end # NthRef end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/begin.rb0000644000175000017500000000343113715561413022277 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for begin nodes class Begin < self 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 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 # 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 end end # Explicit end # Begin end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/hookexe.rb0000644000175000017500000000107013715561413022652 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Base class for pre and postexe emitters class Hookexe < self MAP = { preexe: K_PREEXE, postexe: K_POSTEXE }.freeze handle(*MAP.keys) children :body private # Perfrom dispatch # # @return [undefined] # # @api private # def dispatch write(MAP.fetch(node.type), WS) parentheses(*BRACKETS_CURLY) do emit_body end end end # Hookexe end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/block.rb0000644000175000017500000000231113715561413022301 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Block emitter # # ignore :reek:RepeatedConditional class Block < self include Terminated handle :block 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 end # Emit target # # @return [undefined] # # @api private # def emit_target visit(target) if stabby_lambda? parentheses { visit(arguments) } end end # Test if we are emitting a stabby lambda # # @return [Boolean] # # @api private # def stabby_lambda? target.type.equal?(:lambda) end # Emit arguments # # @return [undefined] # # @api private # def emit_block_arguments return if arguments.children.empty? ws visit_parentheses(arguments, T_PIPE, T_PIPE) end end # Block end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/assignment.rb0000644000175000017500000000564513715561413023374 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Base class for assignment emitters class Assignment < self 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 # Emit right # # @return [undefined] # # @api private # def emit_right return unless right write(WS, T_ASN, WS) visit(right) 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 end # Constant end # Single # Multiple assignment class Multiple < self include Unterminated handle :masgn private # Emit left # # @return [undefined] # # @api private # def emit_left visit_plain(first_child) end # Emit right # # @return [undefined] # # @api private # def emit_right write(WS, T_ASN, WS) visit(children.last) end end # Multiple # Emitter for multiple assignment left hand side class MLHS < Emitter include Unterminated handle :mlhs 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) end end # MLHS end # Assignment end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/def.rb0000644000175000017500000000430213715561413021747 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for def node class Def < self include LocalVariableRoot, Terminated 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) emit_name comments.consume(node, :name) emit_arguments emit_body k_end end # Emit arguments # # @return [undefined] # # @api private # def emit_arguments return if arguments.children.empty? visit_parentheses(arguments) 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 end # Instance # Emitter for defines on singleton class Singleton < self handle :defs children :subject, :name, :arguments, :body 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) end # Test if subject needs parentheses # # @return [Boolean] # # @api private # def subject_without_parens? case subject.type when :self true when :const !subject.children.first when :send receiver, _selector, *arguments = *subject !receiver && arguments.empty? else false end end end # Singleton end # Def end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/redo.rb0000644000175000017500000000056113715561413022145 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/retry.rb0000644000175000017500000000056613715561413022366 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/splat.rb0000644000175000017500000000134113715561413022334 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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) visit(subject) end end # Emitter for splats class Splat < self include Terminated handle :splat children :subject private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(T_SPLAT) visit(subject) if subject end end end end # Unparser unparser-0.4.7/lib/unparser/emitter/resbody.rb0000644000175000017500000000254513715561413022667 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/cbase.rb0000644000175000017500000000061213715561413022266 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for toplevel constant reference nodes class CBase < self include Terminated handle :cbase private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(T_DCL) end end # CBase end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/literal/0000755000175000017500000000000013715561413022321 5ustar debbiecocoadebbiecocoaunparser-0.4.7/lib/unparser/emitter/literal/singleton.rb0000644000175000017500000000071613715561413024654 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/literal/hash.rb0000644000175000017500000000601613715561413023574 0ustar debbiecocoadebbiecocoa# 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] # # @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 unparser-0.4.7/lib/unparser/emitter/literal/execute_string.rb0000644000175000017500000000132713715561413025701 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/literal/regexp.rb0000644000175000017500000000441313715561413024142 0ustar debbiecocoadebbiecocoa# 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] # # @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] # # @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 unparser-0.4.7/lib/unparser/emitter/literal/dynamic_body.rb0000644000175000017500000000517113715561413025313 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/literal/array.rb0000644000175000017500000000073413715561413023770 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/literal/primitive.rb0000644000175000017500000000570413715561413024664 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/literal/range.rb0000644000175000017500000000126313715561413023744 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/literal/dynamic.rb0000644000175000017500000000177713715561413024306 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/yield.rb0000644000175000017500000000073313715561413022323 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for yield node class Yield < self include Terminated handle :yield private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(K_YIELD) return if children.empty? parentheses do delimited(children) end end end # Yield end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/ensure.rb0000644000175000017500000000107713715561413022520 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/lib/unparser/emitter/if.rb0000644000175000017500000000467313715561413021622 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter if nodes # # ignore :reek:RepeatedConditional class If < self handle :if children :condition, :if_branch, :else_branch def terminated? !postcondition? end private # Perform dispatch # # @return [undefined] # # @api private # def dispatch if postcondition? emit_postcondition else emit_normal 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) end # Emit in postcondition style # # @return [undefined] # # @api private # def emit_postcondition visit_plain(if_branch || else_branch) write(WS, keyword, WS) emit_condition end # Emit in normal style # # @return [undefined] # # @api private # def emit_normal write(keyword, WS) 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 end # Emit condition # # @return [undefined] # # @api private # def emit_condition if condition.type.equal?(:match_current_line) visit_plain(condition) else visit(condition) end end # Emit if branch # # @return [undefined] # # @api private # def emit_if_branch if if_branch visit_indented(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) end end # If end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/module.rb0000644000175000017500000000074313715561413022503 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for module nodes class Module < self include LocalVariableRoot, Terminated handle :module children :name, :body private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(K_MODULE, WS) visit(name) emit_body k_end end end # Module end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/match.rb0000644000175000017500000000172413715561413022312 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # 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 children :regexp, :lvasgn private # Perform dispatch # # @return [undefined] # # @api private # def dispatch visit(regexp) write(WS, OPERATOR, WS) visit(lvasgn) end end # Lvasgn # Emitter for match current line class CurrentLine < self handle :match_current_line children :regexp # Perform dispatch # # @return [undefined] # # @api private # def dispatch visit(regexp) end end # CurrentLine end # Match end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/op_assign.rb0000644000175000017500000000214213715561413023173 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Base class for and and or op-assign class BinaryAssign < self include Unterminated children :target, :expression MAP = IceNine.deep_freeze( and_asgn: '&&=', or_asgn: '||=' ) handle(*MAP.keys) private # Perform dispatch # # @return [undefined] # # @api private # def dispatch visit(target) write(WS, MAP.fetch(node.type), WS) visit(expression) end end # BinaryAssign # Emitter for op assign class OpAssign < self include Unterminated handle :op_asgn private # Perform dispatch # # @return [undefined] # # @api private # def dispatch visit(first_child) emit_operator visit(children[2]) end # Emit operator # # @return [undefined] # # @api private # def emit_operator write(WS, children[1].to_s, T_ASN, WS) end end # OpAssign end # Emitte end # Unparser unparser-0.4.7/lib/unparser/emitter/send.rb0000644000175000017500000001132413715561413022144 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for send # ignore :reek:TooManyMethods class Send < self handle :send ASSIGN_SUFFIX = '='.freeze INDEX_ASSIGN = :'[]=' INDEX_REFERENCE = :'[]' NON_ASSIGN_RANGE = (0..-2).freeze children :receiver, :selector def terminated? effective_emitter.terminated? 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] # # @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] end # Test if selector parses as constant # # @return [Boolean] # # @api private # def parses_as_constant? Unparser.parse(selector.to_s).type.equal?(:const) end end # Send end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/class.rb0000644000175000017500000000214213715561413022316 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser class Emitter # Emitter for class nodes class Class < self include LocalVariableRoot, Terminated handle :class children :name, :superclass, :body private # Perform dispatch # # @return [undefined] # # @api private # def dispatch write(K_CLASS, WS) visit(name) emit_superclass emit_body k_end end # Emit superclass # # @return [undefined] # # @api private # def emit_superclass return unless superclass write(WS, T_LT, WS) visit(superclass) end end # Class # 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) visit(object) emit_body k_end end end # SClass end # Emitter end # Unparser unparser-0.4.7/lib/unparser/emitter/undef.rb0000644000175000017500000000063013715561413022312 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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) delimited(children) end end # Undef end # Emitter end # Unparser unparser-0.4.7/lib/unparser/finalize.rb0000644000175000017500000000010213715561413021333 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true Unparser::Emitter::REGISTRY.freeze unparser-0.4.7/lib/unparser/ast/0000755000175000017500000000000013715561413020003 5ustar debbiecocoadebbiecocoaunparser-0.4.7/lib/unparser/ast/local_variable_scope.rb0000644000175000017500000001200113715561413024452 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser module AST # Calculated local variable scope for a given node class LocalVariableScope include Enumerable, Adamantium, Concord.new(:node) # Initialize object # # @param [Parser::AST::Node] node # # @return [undefined] # # @api private # def initialize(node) items = [] LocalVariableScopeEnumerator.each(node) do |*scope| items << scope end @items = items super(node) end # Test if local variable was first at given assignment # # @param [Parser::AST::Node] node # # @return [Boolean] # # @api private # def first_assignment?(node) name = node.children.first match(node) do |current, before| current.include?(name) && !before.include?(name) end end # Test if local variable is defined for given node # # @param [Parser::AST::Node] node # @param [Symbol] name # # @return [Boolean] # # @api private # def local_variable_defined_for_node?(node, name) match(node) do |current| current.include?(name) end end # Test if local variables where first assigned in body and read by conditional # # @param [Parser::AST::Node] body # @param [Parser::AST::Node] condition # # @api private # def first_assignment_in_body_and_used_in_condition?(body, condition) condition_reads = AST.local_variable_reads(condition) candidates = AST.local_variable_assignments(body).select do |node| name = node.children.first condition_reads.include?(name) end candidates.any? do |node| first_assignment?(node) end 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 # Local variable scope enumerator class LocalVariableScopeEnumerator include Enumerable # Initialize object # # @return [undefined] # # @api private # def initialize @stack = [Set.new] end # Enumerate each node with its local variable scope # # @param [Parser::AST::Node] node # # @return [self] # # @api private # def self.each(node, &block) new.each(node, &block) self end # Enumerate local variable scope scope # # @return [self] # if block given # # @return [Enumerator>>] # otherwise # # @api private # def each(node, &block) visit(node, &block) end private # Return current set of local variables # # @return [Set] # # @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) yield node, current.dup, before node.children.each do |child| visit(child, &block) if child.is_a?(Parser::AST::Node) end 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 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 end # LocalVariableScopeEnumerator end # AST end # Unparser unparser-0.4.7/lib/unparser/ast.rb0000644000175000017500000001027113715561413020331 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser # Namespace for AST processing tools # :reek:TooManyConstants module AST FIRST_CHILD = ->(node) { node.children.first }.freeze TAUTOLOGY = ->(_node) { true }.freeze RESET_NODES = %i[module class sclass def defs].freeze INHERIT_NODES = [:block].freeze CLOSE_NODES = (RESET_NODES + INHERIT_NODES).freeze # Nodes that assign a local variable ASSIGN_NODES = %i[ arg kwarg kwoptarg lvasgn optarg procarg0 restarg ].to_set.freeze # Test for local variable inherited scope reset # # @param [Parser::AST::Node] node # # @return [Boolean] # # @api private # def self.not_close_scope?(node) !CLOSE_NODES.include?(node.type) end # Test for local variable scope reset # # @param [Parser::AST::Node] node # # @return [Boolean] # # @api private # def self.not_reset_scope?(node) !RESET_NODES.include?(node.type) end # Return local variables that get assigned in scope # # @param [Parser::AST::Node] node # # @return [Set] # # @api private # def self.local_variable_assignments(node) Enumerator.new( node, method(:not_reset_scope?) ).types(ASSIGN_NODES) end # Return local variables read # # @param [Parser::AST::Node] node # # @return [Set] # # @api private # def self.local_variable_reads(node) Enumerator.new( node, method(:not_close_scope?) ).type(:lvar).map(&FIRST_CHILD).to_set end # AST enumerator class Enumerator include Adamantium::Flat, Concord.new(:node, :controller), Enumerable # Return new instance # # @param [Parser::AST::Node] node # @param [#call(node)] controller # # @return [Enumerator] # # @api private # def self.new(node, controller = TAUTOLOGY) super end # Return each node # # @return [Enumerator] # if no block given # # @return [self] # otherwise # # @api private # def each(&block) Walker.call(node, controller, &block) end # Return nodes selected by types # # @param [Enumerable] types # # @return [Enumerable] # # @api private # def types(types) select { |node| types.include?(node.type) } end # Return nodes selected by type # # @param [Symbol] type # # @return [Enumerable] # # @api private # def type(type) select { |node| node.type.equal?(type) } end # Return frozne set of objects # # @param [Enumerable] enumerable # # @return [Set] # # @api private # def self.set(enumerable) enumerable.to_set.freeze end private_class_method :set # Return nodes of type # # @param [Parser::AST::Node] node # @param [Symbol] type # # @return [Enumerable] names # # @return [undefined] # # @api private # def define_remaining_children(names) range = names.length..-1 define_method(:remaining_children) do children[range] end 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) end 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] end private(name) memoize(name) end # Create name helpers # # @return [undefined] # # @api private # def children(*names) define_remaining_children(names) names.each_with_index do |name, index| define_child(name, index) end end end # DSL end # Unparser unparser-0.4.7/lib/unparser/cli.rb0000644000175000017500000000661613715561413020321 0ustar debbiecocoadebbiecocoa# 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 # Run CLI # # @param [Array] arguments # # @return [Fixnum] # the exit status # # @api private # def self.run(*arguments) new(*arguments).exit_status end # Initialize object # # @param [Array] arguments # # @return [undefined] # # @api private # # ignore :reek:TooManyStatements def initialize(arguments) @sources = [] @ignore = Set.new @success = true @fail_fast = false @verbose = false opts = OptionParser.new do |builder| add_options(builder) end opts.parse!(arguments).each do |name| @sources.concat(sources(name)) end end # Add options # # @param [OptionParser] builder # # @return [undefined] # # @api private # # ignore :reek:TooManyStatements 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) end builder.on('--start-with FILE') do |file| @start_with = sources(file).first end builder.on('-v', '--verbose') do @verbose = true end builder.on('--ignore FILE') do |file| @ignore.merge(sources(file)) end builder.on('--fail-fast') do @fail_fast = true end end # Return exit status # # @return [Fixnum] # # @api private # def exit_status effective_sources.each do |source| next if @ignore.include?(source) process_source(source) break if @fail_fast && !@success end @success ? EXIT_SUCCESS : EXIT_FAILURE end 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}" else puts source.report puts "Error: #{source.identification}" @success = false end end # Return effective sources # # @return [Enumerable] # # @api private # def effective_sources if @start_with reject = true @sources.reject do |source| if reject && source.eql?(@start_with) reject = false end reject end else @sources end end # Return sources for file name # # @param [String] file_name # # @return [Enumerable] # # @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)) end end # CLI end # Unparser unparser-0.4.7/lib/unparser/comments.rb0000644000175000017500000000653713715561413021401 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true module Unparser # Holds the comments that remain to be emitted # # ignore :reek:RepeatedConditional class Comments # Proxy to singleton # # NOTICE: # Delegating to stateless helpers is a pattern I saw many times in our code. # Maybe we should make another helper module? include SingletonDelegator.new(:source_range) ? # # @return [undefined] # # @api private # def source_range(*arguments) self.class.source_range(*arguments) end # Initialize object # # @param [Array] comments # # @return [undefined] # # @api private # def initialize(comments) @comments = comments.dup @last_range_consumed = nil end # Consume part or all of the node # # @param [Parser::AST::Node] node # @param [Symbol] source_part # # @return [undefined] # # @api private # def consume(node, source_part = :expression) range = source_range(node, source_part) @last_range_consumed = range if range end # Take end-of-line comments # # @return [Array] # # @api private # def take_eol_comments return EMPTY_ARRAY unless @last_range_consumed comments = take_up_to_line(@last_range_consumed.end.line) unshift_documents(comments) end # Take all remaining comments # # @return [Array] # # @api private # def take_all take_while { true } end # Take comments appear in the source before the specified part of the node # # @param [Parser::AST::Node] node # @param [Symbol] source_part # # @return [Array] # # @api private # def take_before(node, source_part) range = source_range(node, source_part) if range take_while { |comment| comment.location.expression.end_pos <= range.begin_pos } else EMPTY_ARRAY end end # Return source location part # # FIXME: This method should not be needed. It does to much inline signalling. # # @param [Parser::AST::Node] node # @param [Symbol] part # # @return [Parser::Source::Range] # if present # # @return [nil] # otherwise # # @api private # # :reek:ManualDispatch # def self.source_range(node, part) location = node.location location.public_send(part) if location.respond_to?(part) end 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) } other_comments end end # Comments end # Unparser unparser-0.4.7/lib/unparser/constants.rb0000644000175000017500000000522413715561413021560 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true 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] # # @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[ ! ~ -@ +@ ] # All binary operators of the ruby language BINARY_OPERATORS = symbol_set %w[ + - * / & | && || << >> == === != <= < <=> > >= =~ !~ ^ ** % ] 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 = '"' # Keywords K_DO = 'do' K_DEF = 'def' K_END = 'end' K_BEGIN = 'begin' K_CASE = 'case' K_CLASS = 'class' K_SELF = 'self' K_ENSURE = 'ensure' K_DEFINE = 'define' K_MODULE = 'module' K_RESCUE = 'rescue' K_RETURN = 'return' K_UNDEF = 'undef' K_DEFINED = 'defined?' K_PREEXE = 'BEGIN' K_POSTEXE = 'END' K_SUPER = 'super' K_BREAK = 'break' K_RETRY = 'retry' K_REDO = 'redo' K_NEXT = 'next' K_FALSE = 'false' K_TRUE = 'true' K_IF = 'if' K_AND = 'and' K_ALIAS = 'alias' K_ELSE = 'else' K_ELSIF = 'elsif' K_FOR = 'for' K_NIL = 'nil' K_NOT = 'not' K_IN = 'in' K_OR = 'or' K_UNLESS = 'unless' K_WHEN = 'when' K_WHILE = 'while' K_UNTIL = 'until' K_YIELD = 'yield' K_ENCODING = '__ENCODING__' K_EEND = '__END__' 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_') keywords << value.to_sym end.to_set.freeze end # Constants end # Unparser unparser-0.4.7/lib/unparser/preprocessor.rb0000644000175000017500000000630013715561413022266 0ustar debbiecocoadebbiecocoa# 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] # # @api private # def children node.children end # Return visited children # # @return [Array] # # @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 unparser-0.4.7/Gemfile0000644000175000017500000000025213715561413016101 0ustar debbiecocoadebbiecocoa# frozen_string_literal: true source 'https://rubygems.org' gemspec source 'https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev' do gem 'mutant-license' end unparser-0.4.7/Rakefile0000644000175000017500000000075013715561413016256 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/Changelog.md0000644000175000017500000000603713715561413017026 0ustar debbiecocoadebbiecocoa# 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 unparser-0.4.7/spec/0000755000175000017500000000000013715561413015541 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/unit/0000755000175000017500000000000013715561413016520 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/unit/unparser/0000755000175000017500000000000013715561413020357 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/unit/unparser/emitter/0000755000175000017500000000000013715561413022030 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/unit/unparser/emitter/class_methods/0000755000175000017500000000000013715561413024660 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/unit/unparser/emitter/class_methods/handle_spec.rb0000644000175000017500000000067313715561413027460 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/comments/0000755000175000017500000000000013715561413022204 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/unit/unparser/comments/take_all_spec.rb0000644000175000017500000000071113715561413025316 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/comments/consume_spec.rb0000644000175000017500000000115713715561413025220 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/comments/take_before_spec.rb0000644000175000017500000000236413715561413026016 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/comments/take_eol_comments_spec.rb0000644000175000017500000000160113715561413027231 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/0000755000175000017500000000000013715561413021630 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/unit/unparser/buffer/nl_spec.rb0000644000175000017500000000047113715561413023602 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/unindent_spec.rb0000644000175000017500000000063613715561413025020 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/capture_content_spec.rb0000644000175000017500000000057313715561413026371 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/append_spec.rb0000644000175000017500000000107513715561413024441 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/indent_spec.rb0000644000175000017500000000064613715561413024456 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/content_spec.rb0000644000175000017500000000137213715561413024644 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/fresh_line_spec.rb0000644000175000017500000000076413715561413025314 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser/buffer/append_without_prefix_spec.rb0000644000175000017500000000111213715561413027571 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/unit/unparser_spec.rb0000644000175000017500000011063013715561413021717 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/spec_helper.rb0000644000175000017500000000056213715561413020362 0ustar debbiecocoadebbiecocoarequire '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 unparser-0.4.7/spec/integrations.yml0000644000175000017500000000663213715561413021001 0ustar debbiecocoadebbiecocoa--- - 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 unparser-0.4.7/spec/integration/0000755000175000017500000000000013715561413020064 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/integration/unparser/0000755000175000017500000000000013715561413021723 5ustar debbiecocoadebbiecocoaunparser-0.4.7/spec/integration/unparser/corpus_spec.rb0000644000175000017500000000515613715561413024604 0ustar debbiecocoadebbiecocoarequire '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] 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