Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect argument formatting for HEREDOC #50

Closed
ianks opened this issue Feb 8, 2019 · 3 comments
Closed

Incorrect argument formatting for HEREDOC #50

ianks opened this issue Feb 8, 2019 · 3 comments
Labels
enhancement New feature or request

Comments

@ianks
Copy link
Contributor

ianks commented Feb 8, 2019

I have an easily reproducible error case for you, hope you dont mind 馃槃

With this input:

require 'set'

module Delegation
  class DelegationError < NoMethodError; end

  RUBY_RESERVED_KEYWORDS = %w[alias and BEGIN begin break case class def defined? do
                              else elsif END end ensure false for if in module next nil not or redo rescue retry
                              return self super then true undef unless until when while yield].freeze
  DELEGATION_RESERVED_KEYWORDS = %w[_ arg args block].freeze
  DELEGATION_RESERVED_METHOD_NAMES = Set.new(
    RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
  ).freeze

  def delegate_missing_to(target)
    target = target.to_s
    target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)

    module_eval <<-RUBY, __FILE__, __LINE__ + 1
      def respond_to_missing?(name, include_private = false)
        # It may look like an oversight, but we deliberately do not pass
        # +include_private+, because they do not get delegated.

        #{target}.respond_to?(name) || super
      end

      def method_missing(method, *args, &block)
        if #{target}.respond_to?(method)
          #{target}.public_send(method, *args, &block)
        else
          begin
            super
          rescue NoMethodError
            if #{target}.nil?
              raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
            else
              raise
            end
          end
        end
      end
    RUBY
  end
end

prettier-ruby creates syntactically incorrect output:

# frozen_string_literal: true

require 'set'

module Delegation
  class DelegationError < NoMethodError; end

  RUBY_RESERVED_KEYWORDS = %w[
    alias
    and
    BEGIN
    begin
    break
    case
    class
    def
    defined?
    do
    else
    elsif
    END
    end
    ensure
    false
    for
    if
    in
    module
    next
    nil
    not
    or
    redo
    rescue
    retry
    return
    self
    super
    then
    true
    undef
    unless
    until
    when
    while
    yield
  ]
    .freeze
  DELEGATION_RESERVED_KEYWORDS = %w[_ arg args block].freeze
  DELEGATION_RESERVED_METHOD_NAMES =
    Set.new(RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS).freeze

  def delegate_missing_to(target)
    target = target.to_s
    if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
      target = "self.#{target}"
    end

    module_eval <<-RUBY
                      def respond_to_missing?(name, include_private = false)
        # It may look like an oversight, but we deliberately do not pass
        # +include_private+, because they do not get delegated.

        #{target}.respond_to?(name) || super
      end

      def method_missing(method, *args, &block)
        if #{target}.respond_to?(method)
          #{target}.public_send(method, *args, &block)
        else
          begin
            super
          rescue NoMethodError
            if #{target}.nil?
              raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
            else
              raise
            end
          end
        end
      end
    RUBY,
                __FILE__,
                __LINE__ + 1
  end
end

@ianks ianks changed the title code within module_eval breaks things Incorrect argument formatting for HEREDOC Feb 8, 2019
@somebody32
Copy link

also interesting how .freeze was positioned for RUBY_RESERVED_KEYWORDS array, not sure if that is intentional

@kddnewton
Copy link
Member

Thanks for the report! Yeah definitely need to fix this up. I'll take a look next chance I get.

@AlanFoster
Copy link
Contributor

AlanFoster commented Mar 1, 2019

Just consolidating some concise examples created from the above snippet/issues that could be used as test cases:

(PARSER_EVENTS - events).each do |event|
    module_eval(<<-End, __FILE__, __LINE__ + 1)
      def on_#{event}(*args)
         true
      End
end
module_eval <<-RUBY, __FILE__, __LINE__ + 1
  def respond_to_missing?(name, include_private = false)
     false
  end
RUBY
class_eval <<-RUBY, __FILE__, __LINE__ + 1
    def #{key}; _get(#{key.inspect}); end
RUBY
Person.where(<<~WHERE, arg1)
  some sql
WHERE

I've never seen the syntax for multiple heredocs as arguments before, but here we are:

combined = concatenate(<<~EOF1, <<~EOF2)
  first string
EOF1
  second string
EOF2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants