Class: GraphQL::Upgrader::ResolveProcToMethodTransform

Inherits:
Transform
  • Object
show all
Defined in:
lib/graphql/upgrader/member.rb

Defined Under Namespace

Classes: ResolveProcProcessor

Instance Method Summary collapse

Methods inherited from Transform

#apply_processor, #normalize_type_expression, #reindent_lines, #trim_lines, #underscorize

Instance Method Details

#apply(input_text) ⇒ Object



488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/graphql/upgrader/member.rb', line 488

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