class HashRewriter

Constants

OUTSIDE_PARAMS_KEYS

Attributes

hash_node[R]
original_indent[R]

Public Class Methods

new(content:, hash_node:, original_indent:, options:) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 8
def initialize(content:, hash_node:, original_indent:, options:)
  @options = options
  @content = content
  @hash_node = hash_node
  @original_indent = original_indent
  @textifier = NodeTextifier.new(@content)
  partition_params(@hash_node)
end

Public Instance Methods

has_keys_outside_params?() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 76
def has_keys_outside_params?
  @pairs_that_belong_outside_params.length > 0
end
rewritten_params_hash() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 17
def rewritten_params_hash
  return if @pairs_that_belong_in_params.length == 0

  rewritten_hashes = []

  warn_if_inconsistent_indentation

  if multiline? && should_wrap_rewritten_hash_in_curly_braces?
    params_hash = restring_hash(
      @pairs_that_belong_in_params,
      joiner: ",\n"
    )

    other_hash = restring_hash(
      @pairs_that_belong_outside_params,
      joiner: ",\n"
    )

    optional_comma = has_trailing_comma?(hash_node) ? ',' : ''
    new_wrapped_hash_content = wrap_and_indent(
      "{",
      "}",
      [
        wrap_and_indent(
          "params: {",
          "}#{optional_comma}",
          params_hash,
          @options.indent
        ),
        "#{other_hash}"
      ].join("\n"),
      @options.indent
    )
    return add_indent(new_wrapped_hash_content, original_indent, skip_first_line: true)
  elsif multiline? && should_try_to_rewrite_multiline_hash?
    params_hash = appropriately_indented_params_hash(
      pairs: @pairs_that_belong_in_params
    )

    rewritten_hashes << "params: #{params_hash}"
  else
    curly_sep = determine_curly_sep(hash_node)
    rewritten_hashes << "params: {#{curly_sep}#{restring_hash(@pairs_that_belong_in_params)}#{curly_sep}}"
  end

  if has_keys_outside_params?
    rewritten_hashes << restring_hash(
      @pairs_that_belong_outside_params,
      joiner: first_joiner_between_pairs
    )
  end

  rewritten_hashes.join(first_joiner_between_pairs)
end
should_rewrite_hash?() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 72
def should_rewrite_hash?
  @pairs_that_belong_in_params.length > 0
end
should_try_to_rewrite_multiline_hash?() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 84
def should_try_to_rewrite_multiline_hash?
  return false unless multiline?
  return true unless first_joiner_between_pairs
  first_joiner_between_pairs =~ /\n/
end
should_wrap_rewritten_hash_in_curly_braces?() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 80
def should_wrap_rewritten_hash_in_curly_braces?
  multiline? && has_keys_outside_params? && @textifier.node_to_string(hash_node) =~ /^{\n/
end

Private Instance Methods

add_indent(str, indent, skip_first_line: false) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 237
def add_indent(str, indent, skip_first_line: false)
  str.split("\n").each_with_index.map do |line, index|
    if index.zero? && skip_first_line
      line
    else
      indent + line
    end
  end.join("\n")
end
add_indent_and_curly_braces(str, indent) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 229
def add_indent_and_curly_braces(str, indent)
  "{\n#{add_indent(str, indent)}\n}"
end
appropriately_indented_params_hash(pairs:) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 188
def appropriately_indented_params_hash(pairs:)
  outer_indent = existing_indent(hash_node)

  restrung_hash = restring_hash(
    pairs,
    indent: outer_indent,
    joiner: ",\n"
  )

  if should_indent_restrung_content?(hash_node)
    restrung_hash = add_indent(restrung_hash, @options.indent)
  end

  middle_indent = indent_of_first_value_if_multiline(hash_node)
  if middle_indent
    restrung_hash = original_indent + add_indent(restrung_hash, middle_indent)
  end
  final_brace_indent = if middle_indent
                         original_indent
                       else
                         indent_after_last_pair(hash_node) || outer_indent
                       end
  "{\n#{restrung_hash}\n#{final_brace_indent}}"
end
determine_curly_sep(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 213
def determine_curly_sep(hash_node)
  return ' ' if @options.hash_spacing == true
  return '' if @options.hash_spacing == false

  no_space_after_curly?(hash_node) ? '' : ' '
end
existing_indent(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 147
def existing_indent(hash_node)
  text_before_hash = @textifier.text_before_node(hash_node)
  whitespace_indent = extract_indent(text_before_hash)
  return whitespace_indent if whitespace_indent

  return indent_before_first_pair(hash_node) if indent_before_first_pair(hash_node)

  joiner = first_joiner_between_pairs
  extract_indent(joiner) || ''
end
extract_indent(str) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 247
def extract_indent(str)
  return unless str

  match = str.match("\n(\s*)")
  match[1] if match
end
first_joiner_between_pairs() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 173
def first_joiner_between_pairs
  texts_between_pairs[0]
end
has_inconsistent_indentation?() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 177
def has_inconsistent_indentation?
  texts_between_pairs.uniq.length > 1
end
has_trailing_comma?(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 107
def has_trailing_comma?(hash_node)
  @textifier.text_after_last_pair(hash_node) =~ /,/
end
indent_after_last_pair(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 117
def indent_after_last_pair(hash_node)
  return nil unless hash_node.children.length > 0

  extract_indent(@textifier.text_after_last_pair(hash_node))
end
indent_before_first_pair(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 111
def indent_before_first_pair(hash_node)
  return nil unless hash_node.children.length > 0

  extract_indent(@textifier.text_before_first_pair(hash_node))
end
indent_of_first_value_if_multiline(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 127
def indent_of_first_value_if_multiline(hash_node)
  return nil if hash_node.children.length == 0
  return nil unless hash_node.children[0].pair_type?

  first_value = hash_node.children[0].children[1]
  return nil unless first_value.hash_type? || first_value.array_type?
  value_str_lines = @textifier.node_to_string(first_value).split("\n")
  return nil if value_str_lines.length == 1
  return nil unless value_str_lines[0].match(/[\s\[{]/)

  value_str_lines[1].match(/^(\s*)/)[1].sub(original_indent, '')
end
log(str) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 254
def log(str)
  return if @options.quiet?

  puts str
end
multiline?() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 123
def multiline?
  @textifier.node_to_string(hash_node).include?("\n")
end
no_space_after_curly?(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 158
def no_space_after_curly?(hash_node)
  hash_node.parent.loc.expression.source.match(/{\S/)
end
partition_params(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 92
def partition_params(hash_node)
  @pairs_that_belong_in_params = []
  @pairs_that_belong_outside_params = []

  hash_node.children.each do |pair|
    key = pair.children[0].children[0]

    if OUTSIDE_PARAMS_KEYS.include?(key)
      @pairs_that_belong_outside_params << pair
    else
      @pairs_that_belong_in_params << pair
    end
  end
end
restring_hash(pairs, joiner: ", ", indent: '') click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 220
def restring_hash(pairs, joiner: ", ", indent: '')
  hash_string = pairs.map { |pair| "#{indent}#{pair.loc.expression.source}" }.join(joiner)
  if has_trailing_comma?(hash_node)
    hash_string + ','
  else
    hash_string
  end
end
should_indent_restrung_content?(hash_node) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 140
def should_indent_restrung_content?(hash_node)
  return nil if indent_before_first_pair(hash_node)

  joiner = first_joiner_between_pairs
  joiner && joiner.include?("\n")
end
texts_between_pairs() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 162
def texts_between_pairs
  return @texts_between if @texts_between

  @texts_between = []
  hash_node.children[0..-2].each_with_index do |pair, index|
    next_pair = hash_node.children[index + 1]
    @texts_between << @textifier.text_between_siblings(pair, next_pair)
  end
  @texts_between
end
warn_if_inconsistent_indentation() click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 181
def warn_if_inconsistent_indentation
  return unless has_inconsistent_indentation?

  log "Inconsistent whitespace between hash pairs, using the first separator (#{texts_between_pairs[0].inspect})."
  log "Seen when processing this expression: \n```\n#{hash_node.loc.expression.source}\n```\n\n"
end
wrap_and_indent(start_string, end_string, inner_string, indent) click to toggle source
# File lib/rails5/spec_converter/hash_rewriter.rb, line 233
def wrap_and_indent(start_string, end_string, inner_string, indent)
  "#{start_string}\n#{add_indent(inner_string, indent)}\n#{end_string}"
end