class GraphQL::Upgrader::ResolveProcToMethodTransform

Public Instance Methods

apply(input_text) click to toggle source
# File lib/graphql/upgrader/member.rb, line 492
def apply(input_text)
  if input_text =~ /resolve\(? ?->/
    # - Find the proc literal
    # - Get the three argument names (obj, arg, ctx)
    # - Get the proc body
    # - Find and replace:
    #  - The ctx argument becomes `context`
    #  - The obj argument becomes `object`
    # - Args is trickier:
    #   - If it's not used, remove it
    #   - If it's used, abandon ship and make it `**args`
    #   - Convert string args access to symbol access, since it's a Ruby **splat
    #   - Convert camelized arg names to underscored arg names
    #   - (It would be nice to correctly become Ruby kwargs, but that might be too hard)
    #   - Add a `# TODO` comment to the method source?
    # - Rebuild the method:
    #   - use the field name as the method name
    #   - handle args as described above
    #   - put the modified proc body as the method body

    input_text.match(/(?<field_type>input_field|field|connection|argument) :(?<name>[a-zA-Z_0-9_]*)/)
    field_name = $~[:name]
    processor = apply_processor(input_text, ResolveProcProcessor.new)
    proc_body = input_text[processor.proc_start..processor.proc_end]
    obj_arg_name, args_arg_name, ctx_arg_name = processor.proc_arg_names
    # This is not good, it will hit false positives
    # Should use AST to make this substitution
    if obj_arg_name != "_"
      proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1object\2')
    end
    if ctx_arg_name != "_"
      proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1context\2')
    end

    method_def_indent = " " * (processor.resolve_indent - 2)
    # Turn the proc body into a method body
    method_body = reindent_lines(proc_body, from_indent: processor.resolve_indent + 2, to_indent: processor.resolve_indent)
    # Add `def... end`
    method_def = if input_text.include?("argument ")
      # This field has arguments
      "def #{field_name}(**#{args_arg_name})"
    else
      # No field arguments, so, no method arguments
      "def #{field_name}"
    end
    # Wrap the body in def ... end
    method_body = "\n#{method_def_indent}#{method_def}\n#{method_body}\n#{method_def_indent}end\n"
    # Update Argument access to be underscore and symbols
    # Update `args[...]` and `args.key?`
    method_body = method_body.gsub(/#{args_arg_name}(?<method_begin>\.key\?\(?|\[)["':](?<arg_name>[a-zA-Z0-9_]+)["']?(?<method_end>\]|\))?/) do
      method_begin = $~[:method_begin]
      arg_name = underscorize($~[:arg_name])
      method_end = $~[:method_end]
      "#{args_arg_name}#{method_begin}:#{arg_name}#{method_end}"
    end

    # Replace the resolve proc with the method
    input_text[processor.resolve_start..processor.resolve_end] = ""
    # The replacement above might have left some preceeding whitespace,
    # so remove it by deleting all whitespace chars before `resolve`:
    preceeding_whitespace = processor.resolve_start - 1
    while input_text[preceeding_whitespace] == " " && preceeding_whitespace > 0
      input_text[preceeding_whitespace] = ""
      preceeding_whitespace -= 1
    end
    input_text += method_body
    input_text
  else
    # No resolve proc
    input_text
  end
end