/* Determining the results of applying fix-it hints.
   Copyright (C) 2016-2017 Free Software Foundation, Inc.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.

GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "line-map.h"
#include "edit-context.h"
#include "pretty-print.h"
#include "diagnostic-color.h"
#include "selftest.h"

/* This file implements a way to track the effect of fix-its,
   via a class edit_context; the other classes are support classes for
   edit_context.

   A complication here is that fix-its are expressed relative to coordinates
   in the file when it was parsed, before any changes have been made, and
   so if there's more that one fix-it to be applied, we have to adjust
   later fix-its to allow for the changes made by earlier ones.  This
   is done by the various "get_effective_column" methods.

   The "filename" params are required to outlive the edit_context (no
   copy of the underlying str is taken, just the ptr).  */

/* Forward decls.  class edit_context is declared within edit-context.h.
   The other types are declared here.  */
class edit_context;
class edited_file;
class edited_line;
class line_event;
  class insert_event;
  class replace_event;

/* A struct to hold the params of a print_diff call.  */

struct diff
{
  diff (pretty_printer *pp, bool show_filenames)
  : m_pp (pp), m_show_filenames (show_filenames) {}

  pretty_printer *m_pp;
  bool m_show_filenames;
};

/* The state of one named file within an edit_context: the filename,
   and the lines that have been edited so far.  */

class edited_file
{
 public:
  edited_file (const char *filename);
  static void delete_cb (edited_file *file);

  const char *get_filename () const { return m_filename; }
  char *get_content ();

  bool apply_insert (int line, int column, const char *str, int len);
  bool apply_replace (int line, int start_column,
		      int finish_column,
		      const char *replacement_str,
		      int replacement_len);
  int get_effective_column (int line, int column);

  static int call_print_diff (const char *, edited_file *file,
			      void *user_data)
  {
    diff *d = (diff *)user_data;
    file->print_diff (d->m_pp, d->m_show_filenames);
    return 0;
  }

 private:
  bool print_content (pretty_printer *pp);
  void print_diff (pretty_printer *pp, bool show_filenames);
  void print_diff_hunk (pretty_printer *pp, int start_of_hunk,
			int end_of_hunk);
  void print_diff_line (pretty_printer *pp, char prefix_char,
			const char *line, int line_size);
  edited_line *get_line (int line);
  edited_line *get_or_insert_line (int line);
  int get_num_lines (bool *missing_trailing_newline);

  const char *m_filename;
  typed_splay_tree<int, edited_line *> m_edited_lines;
  int m_num_lines;
};

/* The state of one edited line within an edited_file.
   As well as the current content of the line, it contains a record of
   the changes, so that further changes can be applied in the correct
   place.  */

class edited_line
{
 public:
  edited_line (const char *filename, int line_num);
  ~edited_line ();
  static void delete_cb (edited_line *el);

  int get_line_num () const { return m_line_num; }
  const char *get_content () const { return m_content; }
  int get_len () const { return m_len; }

  int get_effective_column (int orig_column) const;
  bool apply_insert (int column, const char *str, int len);
  bool apply_replace (int start_column,
		      int finish_column,
		      const char *replacement_str,
		      int replacement_len);

 private:
  void ensure_capacity (int len);
  void ensure_terminated ();
  void print_content (pretty_printer *pp) const;

  int m_line_num;
  char *m_content;
  int m_len;
  int m_alloc_sz;
  auto_vec <line_event *> m_line_events;
};

/* Abstract base class for representing events that have occurred
   on one line of one file.  */

class line_event
{
 public:
  virtual ~line_event () {}
  virtual int get_effective_column (int orig_column) const = 0;
};

/* Concrete subclass of line_event: an insertion of some text
   at some column on the line.

   Subsequent events will need their columns adjusting if they're
   are on this line and their column is >= the insertion point.  */

class insert_event : public line_event
{
 public:
  insert_event (int column, int len) : m_column (column), m_len (len) {}
  int get_effective_column (int orig_column) const FINAL OVERRIDE
  {
    if (orig_column >= m_column)
      return orig_column + m_len;
    else
      return orig_column;
  }

 private:
  int m_column;
  int m_len;
};

/* Concrete subclass of line_event: the replacement of some text
   betweeen some columns on the line.

   Subsequent events will need their columns adjusting if they're
   are on this line and their column is >= the finish point.  */

class replace_event : public line_event
{
 public:
  replace_event (int start, int finish, int len) : m_start (start),
    m_finish (finish), m_delta (len - (finish + 1 - start)) {}

  int get_effective_column (int orig_column) const FINAL OVERRIDE
  {
    if (orig_column >= m_start)
      return orig_column += m_delta;
    else
      return orig_column;
  }

 private:
  int m_start;
  int m_finish;
  int m_delta;
};

/* Implementation of class edit_context.  */

/* edit_context's ctor.  */

edit_context::edit_context ()
: m_valid (true),
  m_files (strcmp, NULL, edited_file::delete_cb)
{}

/* Add any fixits within RICHLOC to this context, recording the
   changes that they make.  */

void
edit_context::add_fixits (rich_location *richloc)
{
  if (!m_valid)
    return;
  if (richloc->seen_impossible_fixit_p ())
    {
      m_valid = false;
      return;
    }
  for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
    {
      const fixit_hint *hint = richloc->get_fixit_hint (i);
      switch (hint->get_kind ())
	{
	case fixit_hint::INSERT:
	  if (!apply_insert ((const fixit_insert *)hint))
	    {
	      /* Failure.  */
	      m_valid = false;
	      return;
	    }
	  break;
	case fixit_hint::REPLACE:
	  if (!apply_replace ((const fixit_replace *)hint))
	    {
	      /* Failure.  */
	      m_valid = false;
	      return;
	    }
	  break;
	default:
	  gcc_unreachable ();
	}
    }
}

/* Get the content of the given file, with fix-its applied.
   If any errors occurred in this edit_context, return NULL.
   The ptr should be freed by the caller.  */

char *
edit_context::get_content (const char *filename)
{
  if (!m_valid)
    return NULL;
  edited_file &file = get_or_insert_file (filename);
  return file.get_content ();
}

/* Map a location before the edits to a column number after the edits.
   This method is for the selftests.  */

int
edit_context::get_effective_column (const char *filename, int line,
				    int column)
{
  edited_file *file = get_file (filename);
  if (!file)
    return column;
  return file->get_effective_column (line, column);
}

/* Generate a unified diff.  The resulting string should be freed by the
   caller.  Primarily for selftests.
   If any errors occurred in this edit_context, return NULL.  */

char *
edit_context::generate_diff (bool show_filenames)
{
  if (!m_valid)
    return NULL;

  pretty_printer pp;
  print_diff (&pp, show_filenames);
  return xstrdup (pp_formatted_text (&pp));
}

/* Print a unified diff to PP, showing the changes made within the
   context.  */

void
edit_context::print_diff (pretty_printer *pp, bool show_filenames)
{
  if (!m_valid)
    return;
  diff d (pp, show_filenames);
  m_files.foreach (edited_file::call_print_diff, &d);
}

/* Attempt to apply the given fixit.  Return true if it can be
   applied, or false otherwise.  */

bool
edit_context::apply_insert (const fixit_insert *insert)
{
  expanded_location exploc = expand_location (insert->get_location ());

  if (exploc.column == 0)
    return false;

  edited_file &file = get_or_insert_file (exploc.file);
  if (!m_valid)
    return false;
  return file.apply_insert (exploc.line, exploc.column, insert->get_string (),
			    insert->get_length ());
}

/* Attempt to apply the given fixit.  Return true if it can be
   applied, or false otherwise.  */

bool
edit_context::apply_replace (const fixit_replace *replace)
{
  source_range range = replace->get_range ();

  expanded_location start = expand_location (range.m_start);
  expanded_location finish = expand_location (range.m_finish);
  if (start.file != finish.file)
    return false;
  if (start.line != finish.line)
    return false;
  if (start.column == 0)
    return false;
  if (finish.column == 0)
    return false;

  edited_file &file = get_or_insert_file (start.file);
  if (!m_valid)
    return false;
  return file.apply_replace (start.line, start.column, finish.column,
			     replace->get_string (),
			     replace->get_length ());
}

/* Locate the edited_file * for FILENAME, if any
   Return NULL if there isn't one.  */

edited_file *
edit_context::get_file (const char *filename)
{
  gcc_assert (filename);
  return m_files.lookup (filename);
}

/* Locate the edited_file for FILENAME, adding one if there isn't one.  */

edited_file &
edit_context::get_or_insert_file (const char *filename)
{
  gcc_assert (filename);

  edited_file *file = get_file (filename);
  if (file)
    return *file;

  /* Not found.  */
  file = new edited_file (filename);
  m_files.insert (filename, file);
  return *file;
}

/* Implementation of class edited_file.  */

/* Callback for m_edited_lines, for comparing line numbers.  */

static int line_comparator (int a, int b)
{
  return a - b;
}

/* edited_file's constructor.  */

edited_file::edited_file (const char *filename)
: m_filename (filename),
  m_edited_lines (line_comparator, NULL, edited_line::delete_cb),
  m_num_lines (-1)
{
}

/* A callback for deleting edited_file *, for use as a
   delete_value_fn for edit_context::m_files.  */

void
edited_file::delete_cb (edited_file *file)
{
  delete file;
}

/* Get the content of the file, with fix-its applied.
   The ptr should be freed by the caller.  */

char *
edited_file::get_content ()
{
  pretty_printer pp;
  if (!print_content (&pp))
    return NULL;
  return xstrdup (pp_formatted_text (&pp));
}

/* Attempt to insert the string INSERT_STR with length INSERT_LEN
   at LINE and COLUMN, updating the in-memory copy of the line, and
   the record of edits to the line.  */

bool
edited_file::apply_insert (int line, int column,
			   const char *insert_str,
			   int insert_len)
{
  edited_line *el = get_or_insert_line (line);
  if (!el)
    return false;
  return el->apply_insert (column, insert_str, insert_len);
}

/* Attempt to replace columns START_COLUMN through FINISH_COLUMN of LINE
   with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
   updating the in-memory copy of the line, and the record of edits to
   the line.  */

bool
edited_file::apply_replace (int line, int start_column,
			    int finish_column,
			    const char *replacement_str,
			    int replacement_len)
{
  edited_line *el = get_or_insert_line (line);
  if (!el)
    return false;
  return el->apply_replace (start_column, finish_column, replacement_str,
			    replacement_len);
}

/* Given line LINE, map from COLUMN in the input file to its current
   column after edits have been applied.  */

int
edited_file::get_effective_column (int line, int column)
{
  const edited_line *el = get_line (line);
  if (!el)
    return column;
  return el->get_effective_column (column);
}

/* Attempt to print the content of the file to PP, with edits applied.
   Return true if successful, false otherwise.  */

bool
edited_file::print_content (pretty_printer *pp)
{
  bool missing_trailing_newline;
  int line_count = get_num_lines (&missing_trailing_newline);
  for (int line_num = 1; line_num <= line_count; line_num++)
    {
      edited_line *el = get_line (line_num);
      if (el)
	pp_string (pp, el->get_content ());
      else
	{
	  int len;
	  const char *line
	    = location_get_source_line (m_filename, line_num, &len);
	  if (!line)
	    return false;
	  for (int i = 0; i < len; i++)
	    pp_character (pp, line[i]);
	}
      if (line_num < line_count)
	pp_character (pp, '\n');
    }

  if (!missing_trailing_newline)
    pp_character (pp, '\n');

  return true;
}

/* Print a unified diff to PP, showing any changes that have occurred
   to this file.  */

void
edited_file::print_diff (pretty_printer *pp, bool show_filenames)
{
  if (show_filenames)
    {
      pp_string (pp, colorize_start (pp_show_color (pp), "diff-filename"));
      pp_printf (pp, "--- %s\n", m_filename);
      pp_printf (pp, "+++ %s\n", m_filename);
      pp_string (pp, colorize_stop (pp_show_color (pp)));
    }

  edited_line *el = m_edited_lines.min ();

  bool missing_trailing_newline;
  int line_count = get_num_lines (&missing_trailing_newline);

  const int context_lines = 3;

  while (el)
    {
      int start_of_hunk = el->get_line_num ();
      start_of_hunk -= context_lines;
      if (start_of_hunk < 1)
	start_of_hunk = 1;

      /* Locate end of hunk, merging in changed lines
	 that are sufficiently close.  */
      while (true)
	{
	  edited_line *next_el
	    = m_edited_lines.successor (el->get_line_num ());
	  if (!next_el)
	    break;
	  if (el->get_line_num () + context_lines
	      >= next_el->get_line_num () - context_lines)
	    el = next_el;
	  else
	    break;
	}
      int end_of_hunk = el->get_line_num ();
      end_of_hunk += context_lines;
      if (end_of_hunk > line_count)
	end_of_hunk = line_count;

      print_diff_hunk (pp, start_of_hunk, end_of_hunk);

      el = m_edited_lines.successor (el->get_line_num ());
    }
}

/* Print one hunk within a unified diff to PP, covering the
   given range of lines.  */

void
edited_file::print_diff_hunk (pretty_printer *pp, int start_of_hunk,
			      int end_of_hunk)
{
  int num_lines = end_of_hunk - start_of_hunk + 1;

  pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
  pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", start_of_hunk, num_lines,
	     start_of_hunk, num_lines);
  pp_string (pp, colorize_stop (pp_show_color (pp)));

  int line_num = start_of_hunk;
  while (line_num <= end_of_hunk)
    {
      edited_line *el = get_line (line_num);
      if (el)
	{
	  /* We have an edited line.
	     Consolidate into runs of changed lines.  */
	  const int first_changed_line_in_run = line_num;
	  while (get_line (line_num))
	    line_num++;
	  const int last_changed_line_in_run = line_num - 1;

	  /* Show old version of lines.  */
	  pp_string (pp, colorize_start (pp_show_color (pp),
					 "diff-delete"));
	  for (line_num = first_changed_line_in_run;
	       line_num <= last_changed_line_in_run;
	       line_num++)
	    {
	      int line_len;
	      const char *old_line
		= location_get_source_line (m_filename, line_num, &line_len);
	      print_diff_line (pp, '-', old_line, line_len);
	    }
	  pp_string (pp, colorize_stop (pp_show_color (pp)));

	  /* Show new version of lines.  */
	  pp_string (pp, colorize_start (pp_show_color (pp),
					 "diff-insert"));
	  for (line_num = first_changed_line_in_run;
	       line_num <= last_changed_line_in_run;
	       line_num++)
	    {
	      edited_line *el_in_run = get_line (line_num);
	      gcc_assert (el_in_run);
	      print_diff_line (pp, '+', el_in_run->get_content (),
			       el_in_run->get_len ());
	    }
	  pp_string (pp, colorize_stop (pp_show_color (pp)));
	}
      else
	{
	  /* Unchanged line.  */
	  int line_len;
	  const char *old_line
	    = location_get_source_line (m_filename, line_num, &line_len);
	  print_diff_line (pp, ' ', old_line, line_len);
	  line_num++;
	}
    }
}

/* Print one line within a diff, starting with PREFIX_CHAR,
   followed by the LINE of content, of length LEN.  LINE is
   not necessarily 0-terminated.  Print a trailing newline.  */

void
edited_file::print_diff_line (pretty_printer *pp, char prefix_char,
			      const char *line, int len)
{
  pp_character (pp, prefix_char);
  for (int i = 0; i < len; i++)
    pp_character (pp, line[i]);
  pp_character (pp, '\n');
}

/* Get the state of LINE within the file, or NULL if it is untouched.  */

edited_line *
edited_file::get_line (int line)
{
  return m_edited_lines.lookup (line);
}

/* Get the state of LINE within the file, creating a state for it
   if necessary.  Return NULL if an error occurs.  */

edited_line *
edited_file::get_or_insert_line (int line)
{
  edited_line *el = get_line (line);
  if (el)
    return el;
  el = new edited_line (m_filename, line);
  if (el->get_content () == NULL)
    {
      delete el;
      return NULL;
    }
  m_edited_lines.insert (line, el);
  return el;
}

/* Get the total number of lines in m_content, writing
   true to *MISSING_TRAILING_NEWLINE if the final line
   if missing a newline, false otherwise.  */

int
edited_file::get_num_lines (bool *missing_trailing_newline)
{
  gcc_assert (missing_trailing_newline);
  if (m_num_lines == -1)
    {
      m_num_lines = 0;
      while (true)
	{
	  int line_size;
	  const char *line
	    = location_get_source_line (m_filename, m_num_lines + 1,
					&line_size);
	  if (line)
	    m_num_lines++;
	  else
	    break;
	}
    }
  *missing_trailing_newline = location_missing_trailing_newline (m_filename);
  return m_num_lines;
}

/* Implementation of class edited_line.  */

/* edited_line's ctor.  */

edited_line::edited_line (const char *filename, int line_num)
: m_line_num (line_num),
  m_content (NULL), m_len (0), m_alloc_sz (0),
  m_line_events ()
{
  const char *line = location_get_source_line (filename, line_num,
					       &m_len);
  if (!line)
    return;
  ensure_capacity (m_len);
  memcpy (m_content, line, m_len);
  ensure_terminated ();
}

/* edited_line's dtor.  */

edited_line::~edited_line ()
{
  free (m_content);

  int i;
  line_event *event;
  FOR_EACH_VEC_ELT (m_line_events, i, event)
    delete event;
}

/* A callback for deleting edited_line *, for use as a
   delete_value_fn for edited_file::m_edited_lines.  */

void
edited_line::delete_cb (edited_line *el)
{
  delete el;
}

/* Map a location before the edits to a column number after the edits,
   within a specific line.  */

int
edited_line::get_effective_column (int orig_column) const
{
  int i;
  line_event *event;
  FOR_EACH_VEC_ELT (m_line_events, i, event)
    orig_column = event->get_effective_column (orig_column);
  return orig_column;
}

/* Attempt to insert the string INSERT_STR with length INSERT_LEN at COLUMN
   of this line, updating the in-memory copy of the line, and the record
   of edits to it.
   Return true if successful; false if an error occurred.  */

bool
edited_line::apply_insert (int column, const char *insert_str,
			   int insert_len)
{
  column = get_effective_column (column);

  int start_offset = column - 1;
  gcc_assert (start_offset >= 0);
  if (start_offset > m_len)
    return false;

  /* Ensure buffer is big enough.  */
  size_t new_len = m_len + insert_len;
  ensure_capacity (new_len);

  char *suffix = m_content + start_offset;
  gcc_assert (suffix <= m_content + m_len);
  size_t len_suffix = (m_content + m_len) - suffix;

  /* Move successor content into position.  They overlap, so use memmove.  */
  memmove (m_content + start_offset + insert_len,
	   suffix, len_suffix);

  /* Replace target content.  They don't overlap, so use memcpy.  */
  memcpy (m_content + start_offset,
	  insert_str,
	  insert_len);

  m_len = new_len;

  ensure_terminated ();

  /* Record the insertion, so that future changes to the line can have
     their column information adjusted accordingly.  */
  m_line_events.safe_push (new insert_event (column, insert_len));

  return true;
}

/* Attempt to replace columns START_COLUMN through FINISH_COLUMN of the line
   with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
   updating the in-memory copy of the line, and the record of edits to
   the line.
   Return true if successful; false if an error occurred.  */

bool
edited_line::apply_replace (int start_column,
			    int finish_column,
			    const char *replacement_str,
			    int replacement_len)
{
  start_column = get_effective_column (start_column);
  finish_column = get_effective_column (finish_column);

  int start_offset = start_column - 1;
  int end_offset = finish_column - 1;

  gcc_assert (start_offset >= 0);
  gcc_assert (end_offset >= 0);

  if (start_column > finish_column)
    return false;
  if (start_offset >= m_len)
    return false;
  if (end_offset >= m_len)
    return false;

  size_t victim_len = end_offset - start_offset + 1;

  /* Ensure buffer is big enough.  */
  size_t new_len = m_len + replacement_len - victim_len;
  ensure_capacity (new_len);

  char *suffix = m_content + end_offset + 1;
  gcc_assert (suffix <= m_content + m_len);
  size_t len_suffix = (m_content + m_len) - suffix;

  /* Move successor content into position.  They overlap, so use memmove.  */
  memmove (m_content + start_offset + replacement_len,
	   suffix, len_suffix);

  /* Replace target content.  They don't overlap, so use memcpy.  */
  memcpy (m_content + start_offset,
	  replacement_str,
	  replacement_len);

  m_len = new_len;

  ensure_terminated ();

  /* Record the replacement, so that future changes to the line can have
     their column information adjusted accordingly.  */
  m_line_events.safe_push (new replace_event (start_column, finish_column,
					      replacement_len));
  return true;
}

/* Ensure that the buffer for m_content is at least large enough to hold
   a string of length LEN and its 0-terminator, doubling on repeated
   allocations.  */

void
edited_line::ensure_capacity (int len)
{
  /* Allow 1 extra byte for 0-termination.  */
  if (m_alloc_sz < (len + 1))
    {
      size_t new_alloc_sz = (len + 1) * 2;
      m_content = (char *)xrealloc (m_content, new_alloc_sz);
      m_alloc_sz = new_alloc_sz;
    }
}

/* Ensure that m_content is 0-terminated.  */

void
edited_line::ensure_terminated ()
{
  /* 0-terminate the buffer.  */
  gcc_assert (m_len < m_alloc_sz);
  m_content[m_len] = '\0';
}

#if CHECKING_P

/* Selftests of code-editing.  */

namespace selftest {

/* A wrapper class for ensuring that the underlying pointer is freed.  */

template <typename POINTER_T>
class auto_free
{
 public:
  auto_free (POINTER_T p) : m_ptr (p) {}
  ~auto_free () { free (m_ptr); }

  operator POINTER_T () { return m_ptr; }

 private:
  POINTER_T m_ptr;
};

/* Verify that edit_context::get_content works for unedited files.  */

static void
test_get_content ()
{
  /* Test of empty file.  */
  {
    const char *content = ("");
    temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
    edit_context edit;
    auto_free <char *> result = edit.get_content (tmp.get_filename ());
    ASSERT_STREQ ("", result);
  }

  /* Test of simple content.  */
  {
    const char *content = ("/* before */\n"
			   "foo = bar.field;\n"
			   "/* after */\n");
    temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
    edit_context edit;
    auto_free <char *> result = edit.get_content (tmp.get_filename ());
    ASSERT_STREQ ("/* before */\n"
		  "foo = bar.field;\n"
		  "/* after */\n", result);
  }

  /* Test of omitting the trailing newline on the final line.  */
  {
    const char *content = ("/* before */\n"
			   "foo = bar.field;\n"
			   "/* after */");
    temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
    edit_context edit;
    auto_free <char *> result = edit.get_content (tmp.get_filename ());
    /* We should respect the omitted trailing newline.  */
    ASSERT_STREQ ("/* before */\n"
		  "foo = bar.field;\n"
		  "/* after */", result);
  }
}

/* Test applying an "insert" fixit, using insert_before.  */

static void
test_applying_fixits_insert_before (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................0000000001111111.
     .........................1234567890123456.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);

  /* Add a comment in front of "bar.field".  */
  location_t start = linemap_position_for_column (line_table, 7);
  rich_location richloc (line_table, start);
  richloc.add_fixit_insert_before ("/* inserted */");

  if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  edit_context edit;
  edit.add_fixits (&richloc);
  auto_free <char *> new_content = edit.get_content (filename);
  if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
    ASSERT_STREQ ("/* before */\n"
		  "foo = /* inserted */bar.field;\n"
		  "/* after */\n", new_content);

  /* Verify that locations on other lines aren't affected by the change.  */
  ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
  ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));

  /* Verify locations on the line before the change.  */
  ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
  ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));

  /* Verify locations on the line at and after the change.  */
  ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
  ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));

  /* Verify diff.  */
  auto_free <char *> diff = edit.generate_diff (false);
  ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
		" /* before */\n"
		"-foo = bar.field;\n"
		"+foo = /* inserted */bar.field;\n"
		" /* after */\n", diff);
}

/* Test applying an "insert" fixit, using insert_after, with
   a range of length > 1 (to ensure that the end-point of
   the input range is used).  */

static void
test_applying_fixits_insert_after (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................0000000001111111.
     .........................1234567890123456.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);

  /* Add a comment after "field".  */
  location_t start = linemap_position_for_column (line_table, 11);
  location_t finish = linemap_position_for_column (line_table, 15);
  location_t field = make_location (start, start, finish);
  rich_location richloc (line_table, field);
  richloc.add_fixit_insert_after ("/* inserted */");

  if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  /* Verify that the text was inserted after the end of "field". */
  edit_context edit;
  edit.add_fixits (&richloc);
  auto_free <char *> new_content = edit.get_content (filename);
  ASSERT_STREQ ("/* before */\n"
		"foo = bar.field/* inserted */;\n"
		"/* after */\n", new_content);

  /* Verify diff.  */
  auto_free <char *> diff = edit.generate_diff (false);
  ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
		" /* before */\n"
		"-foo = bar.field;\n"
		"+foo = bar.field/* inserted */;\n"
		" /* after */\n", diff);
}

/* Test applying an "insert" fixit, using insert_after at the end of
   a line (contrast with test_applying_fixits_insert_after_failure
   below).  */

static void
test_applying_fixits_insert_after_at_line_end (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................0000000001111111.
     .........................1234567890123456.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);

  /* Add a comment after the semicolon.  */
  location_t loc = linemap_position_for_column (line_table, 16);
  rich_location richloc (line_table, loc);
  richloc.add_fixit_insert_after ("/* inserted */");

  if (loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  edit_context edit;
  edit.add_fixits (&richloc);
  auto_free <char *> new_content = edit.get_content (filename);
  ASSERT_STREQ ("/* before */\n"
		"foo = bar.field;/* inserted */\n"
		"/* after */\n", new_content);

  /* Verify diff.  */
  auto_free <char *> diff = edit.generate_diff (false);
  ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
		" /* before */\n"
		"-foo = bar.field;\n"
		"+foo = bar.field;/* inserted */\n"
		" /* after */\n", diff);
}

/* Test of a failed attempt to apply an "insert" fixit, using insert_after,
   due to the relevant linemap ending.  Contrast with
   test_applying_fixits_insert_after_at_line_end above.  */

static void
test_applying_fixits_insert_after_failure (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................0000000001111111.
     .........................1234567890123456.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);

  /* Add a comment after the semicolon.  */
  location_t loc = linemap_position_for_column (line_table, 16);
  rich_location richloc (line_table, loc);

  /* We want a failure of linemap_position_for_loc_and_offset.
     We can do this by starting a new linemap at line 3, so that
     there is no appropriate location value for the insertion point
     within the linemap for line 2.  */
  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);

  /* The failure fails to happen at the transition point from
     packed ranges to unpacked ranges (where there are some "spare"
     location_t values).  Skip the test there.  */
  if (loc >= LINE_MAP_MAX_LOCATION_WITH_PACKED_RANGES)
    return;

  /* Offsetting "loc" should now fail (by returning the input loc. */
  ASSERT_EQ (loc, linemap_position_for_loc_and_offset (line_table, loc, 1));

  /* Hence attempting to use add_fixit_insert_after at the end of the line
     should now fail.  */
  richloc.add_fixit_insert_after ("/* inserted */");
  ASSERT_TRUE (richloc.seen_impossible_fixit_p ());

  edit_context edit;
  edit.add_fixits (&richloc);
  ASSERT_FALSE (edit.valid_p ());
  ASSERT_EQ (NULL, edit.get_content (filename));
  ASSERT_EQ (NULL, edit.generate_diff (false));
}

/* Test applying a "replace" fixit that grows the affected line.  */

static void
test_applying_fixits_growing_replace (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................0000000001111111.
     .........................1234567890123456.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, filename, 2);

  /* Replace "field" with "m_field".  */
  location_t start = linemap_position_for_column (line_table, 11);
  location_t finish = linemap_position_for_column (line_table, 15);
  location_t field = make_location (start, start, finish);
  rich_location richloc (line_table, field);
  richloc.add_fixit_replace ("m_field");

  edit_context edit;
  edit.add_fixits (&richloc);
  auto_free <char *> new_content = edit.get_content (filename);
  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
    {
      ASSERT_STREQ ("/* before */\n"
		    "foo = bar.m_field;\n"
		    "/* after */\n", new_content);

      /* Verify location of ";" after the change.  */
      ASSERT_EQ (18, edit.get_effective_column (filename, 2, 16));

      /* Verify diff.  */
      auto_free <char *> diff = edit.generate_diff (false);
      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
		    " /* before */\n"
		    "-foo = bar.field;\n"
		    "+foo = bar.m_field;\n"
		    " /* after */\n", diff);
    }
}

/* Test applying a "replace" fixit that shrinks the affected line.  */

static void
test_applying_fixits_shrinking_replace (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................000000000111111111.
     .........................123456789012345678.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.m_field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, filename, 2);

  /* Replace "field" with "m_field".  */
  location_t start = linemap_position_for_column (line_table, 11);
  location_t finish = linemap_position_for_column (line_table, 17);
  location_t m_field = make_location (start, start, finish);
  rich_location richloc (line_table, m_field);
  richloc.add_fixit_replace ("field");

  edit_context edit;
  edit.add_fixits (&richloc);
  auto_free <char *> new_content = edit.get_content (filename);
  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
    {
      ASSERT_STREQ ("/* before */\n"
		    "foo = bar.field;\n"
		    "/* after */\n", new_content);

      /* Verify location of ";" after the change.  */
      ASSERT_EQ (16, edit.get_effective_column (filename, 2, 18));

      /* Verify diff.  */
      auto_free <char *> diff = edit.generate_diff (false);
      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
		    " /* before */\n"
		    "-foo = bar.m_field;\n"
		    "+foo = bar.field;\n"
		    " /* after */\n", diff);
    }
}

/* Test applying a "remove" fixit.  */

static void
test_applying_fixits_remove (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................000000000111111111.
     .........................123456789012345678.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.m_field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, filename, 2);

  /* Remove ".m_field".  */
  location_t start = linemap_position_for_column (line_table, 10);
  location_t finish = linemap_position_for_column (line_table, 17);
  rich_location richloc (line_table, start);
  source_range range;
  range.m_start = start;
  range.m_finish = finish;
  richloc.add_fixit_remove (range);

  edit_context edit;
  edit.add_fixits (&richloc);
  auto_free <char *> new_content = edit.get_content (filename);
  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
    {
      ASSERT_STREQ ("/* before */\n"
		    "foo = bar;\n"
		    "/* after */\n", new_content);

      /* Verify location of ";" after the change.  */
      ASSERT_EQ (10, edit.get_effective_column (filename, 2, 18));

      /* Verify diff.  */
      auto_free <char *> diff = edit.generate_diff (false);
      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
		    " /* before */\n"
		    "-foo = bar.m_field;\n"
		    "+foo = bar;\n"
		    " /* after */\n", diff);
    }
}

/* Test applying multiple fixits to one line.  */

static void
test_applying_fixits_multiple (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................00000000011111111.
     .........................12345678901234567.  */
  const char *old_content = ("/* before */\n"
			     "foo = bar.field;\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, filename, 2);

  location_t c7 = linemap_position_for_column (line_table, 7);
  location_t c9 = linemap_position_for_column (line_table, 9);
  location_t c11 = linemap_position_for_column (line_table, 11);
  location_t c15 = linemap_position_for_column (line_table, 15);
  location_t c17 = linemap_position_for_column (line_table, 17);

  if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  /* Add a comment in front of "bar.field".  */
  rich_location insert_a (line_table, c7);
  insert_a.add_fixit_insert_before (c7, "/* alpha */");

  /* Add a comment after "bar.field;".  */
  rich_location insert_b (line_table, c17);
  insert_b.add_fixit_insert_before (c17, "/* beta */");

  /* Replace "bar" with "pub".   */
  rich_location replace_a (line_table, c7);
  replace_a.add_fixit_replace (source_range::from_locations (c7, c9),
			       "pub");

  /* Replace "field" with "meadow".   */
  rich_location replace_b (line_table, c7);
  replace_b.add_fixit_replace (source_range::from_locations (c11, c15),
			       "meadow");

  edit_context edit;
  edit.add_fixits (&insert_a);
  ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
  ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
  ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
  ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
  ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
  ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));

  edit.add_fixits (&insert_b);
  edit.add_fixits (&replace_a);
  edit.add_fixits (&replace_b);

  if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
    {
      auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
      ASSERT_STREQ ("/* before */\n"
		     "foo = /* alpha */pub.meadow;/* beta */\n"
		     "/* after */\n",
		    new_content);

      /* Verify diff.  */
      auto_free <char *> diff = edit.generate_diff (false);
      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
		    " /* before */\n"
		    "-foo = bar.field;\n"
		    "+foo = /* alpha */pub.meadow;/* beta */\n"
		    " /* after */\n", diff);
    }
}

/* Subroutine of test_applying_fixits_multiple_lines.
   Add the text "CHANGED: " to the front of the given line.  */

static location_t
change_line (edit_context &edit, int line_num)
{
  const line_map_ordinary *ord_map
    = LINEMAPS_LAST_ORDINARY_MAP (line_table);
  const int column = 1;
  location_t loc =
    linemap_position_for_line_and_column (line_table, ord_map,
					  line_num, column);

  expanded_location exploc = expand_location (loc);
  if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
    {
      ASSERT_EQ (line_num, exploc.line);
      ASSERT_EQ (column, exploc.column);
    }

  rich_location insert (line_table, loc);
  insert.add_fixit_insert_before ("CHANGED: ");
  edit.add_fixits (&insert);
  return loc;
}

/* Test of editing multiple lines within a long file,
   to ensure that diffs are generated as expected.  */

static void
test_applying_fixits_multiple_lines (const line_table_case &case_)
{
  /* Create a tempfile and write many lines of text to it.  */
  named_temp_file tmp (".txt");
  const char *filename = tmp.get_filename ();
  FILE *f = fopen (filename, "w");
  ASSERT_NE (f, NULL);
  for (int i = 1; i <= 1000; i++)
    fprintf (f, "line %i\n", i);
  fclose (f);

  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, filename, 1);
  linemap_position_for_column (line_table, 127);

  edit_context edit;

  /* A run of consecutive lines.  */
  change_line (edit, 2);
  change_line (edit, 3);
  change_line (edit, 4);

  /* A run of nearby lines, within the contextual limit.  */
  change_line (edit, 150);
  change_line (edit, 151);
  location_t last_loc = change_line (edit, 153);

  if (last_loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  /* Verify diff.  */
  auto_free <char *> diff = edit.generate_diff (false);
  ASSERT_STREQ ("@@ -1,7 +1,7 @@\n"
		" line 1\n"
		"-line 2\n"
		"-line 3\n"
		"-line 4\n"
		"+CHANGED: line 2\n"
		"+CHANGED: line 3\n"
		"+CHANGED: line 4\n"
		" line 5\n"
		" line 6\n"
		" line 7\n"
		"@@ -147,10 +147,10 @@\n"
		" line 147\n"
		" line 148\n"
		" line 149\n"
		"-line 150\n"
		"-line 151\n"
		"+CHANGED: line 150\n"
		"+CHANGED: line 151\n"
		" line 152\n"
		"-line 153\n"
		"+CHANGED: line 153\n"
		" line 154\n"
		" line 155\n"
		" line 156\n", diff);

  /* Ensure tmp stays alive until this point, so that the tempfile
     persists until after the generate_diff call.  */
  tmp.get_filename ();
}

/* Test of converting an initializer for a named field from
   the old GCC extension to C99 syntax.
   Exercises a shrinking replacement followed by a growing
   replacement on the same line.  */

static void
test_applying_fixits_modernize_named_init (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     .........................00000000011111111.
     .........................12345678901234567.  */
  const char *old_content = ("/* before */\n"
			     "bar    : 1,\n"
			     "/* after */\n");
  temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, filename, 2);

  location_t c1 = linemap_position_for_column (line_table, 1);
  location_t c3 = linemap_position_for_column (line_table, 3);
  location_t c8 = linemap_position_for_column (line_table, 8);

  if (c8 > LINE_MAP_MAX_LOCATION_WITH_COLS)
    return;

  /* Replace "bar" with ".".  */
  rich_location r1 (line_table, c8);
  r1.add_fixit_replace (source_range::from_locations (c1, c3),
			".");

  /* Replace ":" with "bar =".   */
  rich_location r2 (line_table, c8);
  r2.add_fixit_replace (source_range::from_locations (c8, c8),
			"bar =");

  /* The order should not matter.  Do r1 then r2. */
  {
    edit_context edit;
    edit.add_fixits (&r1);

    /* Verify state after first replacement.  */
    {
      auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
      /* We should now have:
	 ............00000000011.
	 ............12345678901.  */
      ASSERT_STREQ ("/* before */\n"
		    ".    : 1,\n"
		    "/* after */\n",
		    new_content);
      /* Location of the "1".  */
      ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
      /* Location of the ",".  */
      ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
    }

    edit.add_fixits (&r2);

    auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
    /* Verify state after second replacement.
       ............00000000011111111.
       ............12345678901234567.  */
    ASSERT_STREQ ("/* before */\n"
		  ".    bar = 1,\n"
		  "/* after */\n",
		  new_content);
  }

  /* Try again, doing r2 then r1; the new_content should be the same.  */
  {
    edit_context edit;
    edit.add_fixits (&r2);
    edit.add_fixits (&r1);
    auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
    /*.............00000000011111111.
      .............12345678901234567.  */
    ASSERT_STREQ ("/* before */\n"
		  ".    bar = 1,\n"
		  "/* after */\n",
		  new_content);
  }
}

/* Test of a fixit affecting a file that can't be read.  */

static void
test_applying_fixits_unreadable_file ()
{
  const char *filename = "this-does-not-exist.txt";
  line_table_test ltt ();
  linemap_add (line_table, LC_ENTER, false, filename, 1);

  location_t loc = linemap_position_for_column (line_table, 1);

  rich_location insert (line_table, loc);
  insert.add_fixit_insert_before ("change 1");
  insert.add_fixit_insert_before ("change 2");

  edit_context edit;
  /* Attempting to add the fixits affecting the unreadable file
     should transition the edit from valid to invalid.  */
  ASSERT_TRUE (edit.valid_p ());
  edit.add_fixits (&insert);
  ASSERT_FALSE (edit.valid_p ());
  ASSERT_EQ (NULL, edit.get_content (filename));
  ASSERT_EQ (NULL, edit.generate_diff (false));
}

/* Verify that we gracefully handle an attempt to edit a line
   that's beyond the end of the file.  */

static void
test_applying_fixits_line_out_of_range ()
{
  /* Create a tempfile and write some text to it.
     ........................00000000011111111.
     ........................12345678901234567.  */
  const char *old_content = "One-liner file\n";
  temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt ();
  linemap_add (line_table, LC_ENTER, false, filename, 2);

  /* Try to insert a string in line 2.  */
  location_t loc = linemap_position_for_column (line_table, 1);

  rich_location insert (line_table, loc);
  insert.add_fixit_insert_before ("change");

  /* Verify that attempting the insertion puts an edit_context
     into an invalid state.  */
  edit_context edit;
  ASSERT_TRUE (edit.valid_p ());
  edit.add_fixits (&insert);
  ASSERT_FALSE (edit.valid_p ());
  ASSERT_EQ (NULL, edit.get_content (filename));
  ASSERT_EQ (NULL, edit.generate_diff (false));
}

/* Verify the boundary conditions of column values in fix-it
   hints applied to edit_context instances.  */

static void
test_applying_fixits_column_validation (const line_table_case &case_)
{
  /* Create a tempfile and write some text to it.
     ........................00000000011111111.
     ........................12345678901234567.  */
  const char *old_content = "One-liner file\n";
  temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
  const char *filename = tmp.get_filename ();
  line_table_test ltt (case_);
  linemap_add (line_table, LC_ENTER, false, filename, 1);

  location_t c11 = linemap_position_for_column (line_table, 11);
  location_t c14 = linemap_position_for_column (line_table, 14);
  location_t c15 = linemap_position_for_column (line_table, 15);
  location_t c16 = linemap_position_for_column (line_table, 16);

  /* Verify limits of valid columns in insertion fixits.  */

  /* Verify inserting at the end of the line.  */
  {
    rich_location richloc (line_table, c11);
    richloc.add_fixit_insert_before (c15, " change");

    /* Col 15 is at the end of the line, so the insertion
       should succeed.  */
    edit_context edit;
    edit.add_fixits (&richloc);
    auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
    if (c15 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
      ASSERT_STREQ ("One-liner file change\n", new_content);
    else
      ASSERT_EQ (NULL, new_content);
  }

  /* Verify inserting beyond the end of the line.  */
  {
    rich_location richloc (line_table, c11);
    richloc.add_fixit_insert_before (c16, " change");

    /* Col 16 is beyond the end of the line, so the insertion
       should fail gracefully.  */
    edit_context edit;
    ASSERT_TRUE (edit.valid_p ());
    edit.add_fixits (&richloc);
    ASSERT_FALSE (edit.valid_p ());
    ASSERT_EQ (NULL, edit.get_content (filename));
    ASSERT_EQ (NULL, edit.generate_diff (false));
  }

  /* Verify limits of valid columns in replacement fixits.  */

  /* Verify replacing the end of the line.  */
  {
    rich_location richloc (line_table, c11);
    source_range range = source_range::from_locations (c11, c14);
    richloc.add_fixit_replace (range, "change");

    /* Col 14 is at the end of the line, so the replacement
       should succeed.  */
    edit_context edit;
    edit.add_fixits (&richloc);
    auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
    if (c14 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
      ASSERT_STREQ ("One-liner change\n", new_content);
    else
      ASSERT_EQ (NULL, new_content);
  }

  /* Verify going beyond the end of the line.  */
  {
    rich_location richloc (line_table, c11);
    source_range range = source_range::from_locations (c11, c15);
    richloc.add_fixit_replace (range, "change");

    /* Col 15 is after the end of the line, so the replacement
       should fail; verify that the attempt fails gracefully.  */
    edit_context edit;
    ASSERT_TRUE (edit.valid_p ());
    edit.add_fixits (&richloc);
    ASSERT_FALSE (edit.valid_p ());
    ASSERT_EQ (NULL, edit.get_content (filename));
    ASSERT_EQ (NULL, edit.generate_diff (false));
  }
}

/* Run all of the selftests within this file.  */

void
edit_context_c_tests ()
{
  test_get_content ();
  for_each_line_table_case (test_applying_fixits_insert_before);
  for_each_line_table_case (test_applying_fixits_insert_after);
  for_each_line_table_case (test_applying_fixits_insert_after_at_line_end);
  for_each_line_table_case (test_applying_fixits_insert_after_failure);
  for_each_line_table_case (test_applying_fixits_growing_replace);
  for_each_line_table_case (test_applying_fixits_shrinking_replace);
  for_each_line_table_case (test_applying_fixits_remove);
  for_each_line_table_case (test_applying_fixits_multiple);
  for_each_line_table_case (test_applying_fixits_multiple_lines);
  for_each_line_table_case (test_applying_fixits_modernize_named_init);
  test_applying_fixits_unreadable_file ();
  test_applying_fixits_line_out_of_range ();
  for_each_line_table_case (test_applying_fixits_column_validation);
}

} // namespace selftest

#endif /* CHECKING_P */
