/* Determining the results of applying fix-it hints.
- Copyright (C) 2016 Free Software Foundation, Inc.
+ Copyright (C) 2016-2019 Free Software Foundation, Inc.
This file is part of GCC.
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. */
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);
+ bool apply_fixit (int line, int start_column,
+ int next_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,
private:
bool print_content (pretty_printer *pp);
void print_diff (pretty_printer *pp, bool show_filenames);
+ int print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
+ int old_end_of_hunk, int new_start_of_hunk);
edited_line *get_line (int line);
edited_line *get_or_insert_line (int line);
int get_num_lines (bool *missing_trailing_newline);
+ int get_effective_line_count (int old_start_of_hunk,
+ int old_end_of_hunk);
+
+ void print_run_of_changed_lines (pretty_printer *pp,
+ int start_of_run,
+ int end_of_run);
+
const char *m_filename;
typed_splay_tree<int, edited_line *> m_edited_lines;
int m_num_lines;
};
+/* A line added before an edited_line. */
+
+class added_line
+{
+ public:
+ added_line (const char *content, int len)
+ : m_content (xstrndup (content, len)), m_len (len) {}
+ ~added_line () { free (m_content); }
+
+ const char *get_content () const { return m_content; }
+ int get_len () const { return m_len; }
+
+ private:
+ char *m_content;
+ int m_len;
+};
+
/* 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. */
+ place.
+
+ When handling fix-it hints containing newlines, new lines are added
+ as added_line predecessors to an edited_line. Hence it's possible
+ for an "edited_line" to not actually have been changed, but to merely
+ be a placeholder for the lines added before it. This can be tested
+ for with actuall_edited_p, and has a slight effect on how diff hunks
+ are generated. */
class edited_line
{
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);
+ bool apply_fixit (int start_column,
+ int next_column,
+ const char *replacement_str,
+ int replacement_len);
+
+ int get_effective_line_count () const;
+
+ /* Has the content of this line actually changed, or are we merely
+ recording predecessor added_lines? */
+ bool actually_edited_p () const { return m_line_events.length () > 0; }
+
+ void print_content (pretty_printer *pp) const;
+ void print_diff_lines (pretty_printer *pp) const;
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;
+ auto_vec <line_event> m_line_events;
+ auto_vec <added_line *> m_predecessors;
};
-/* Concrete subclass of line_event: the replacement of some text
- betweeen some columns on the line.
+/* Class for representing edit events that have occurred on one line of
+ one file: 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. */
+ are on this line and their column is >= the start point. */
-class replace_event : public line_event
+class line_event
{
public:
- replace_event (int start, int finish, int len) : m_start (start),
- m_finish (finish), m_delta (len - (finish + 1 - start)) {}
+ line_event (int start, int next, int len) : m_start (start),
+ m_delta (len - (next - start)) {}
- int get_effective_column (int orig_column) const FINAL OVERRIDE
+ int get_effective_column (int orig_column) const
{
if (orig_column >= m_start)
return orig_column += m_delta;
private:
int m_start;
- int m_finish;
int m_delta;
};
+/* Forward decls. */
+
+static void
+print_diff_line (pretty_printer *pp, char prefix_char,
+ const char *line, int line_size);
+
/* Implementation of class edit_context. */
/* edit_context's ctor. */
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 ();
- }
+ if (!apply_fixit (hint))
+ m_valid = false;
}
}
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)
+edit_context::apply_fixit (const fixit_hint *hint)
{
- 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)
+ expanded_location start = expand_location (hint->get_start_loc ());
+ expanded_location next_loc = expand_location (hint->get_next_loc ());
+ if (start.file != next_loc.file)
return false;
- if (start.line != finish.line)
+ if (start.line != next_loc.line)
return false;
if (start.column == 0)
return false;
- if (finish.column == 0)
+ if (next_loc.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 ());
+ return file.apply_fixit (start.line, start.column, next_loc.column,
+ hint->get_string (),
+ hint->get_length ());
}
/* Locate the edited_file * for FILENAME, if any
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,
+/* Attempt to replace columns START_COLUMN up to but not including NEXT_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_file::apply_fixit (int line, int start_column, int next_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);
+ return el->apply_fixit (start_column, next_column, replacement_str,
+ replacement_len);
}
/* Given line LINE, map from COLUMN in the input file to its current
{
edited_line *el = get_line (line_num);
if (el)
- pp_string (pp, el->get_content ());
+ el->print_content (pp);
else
{
- int len;
- const char *line
- = location_get_source_line (m_filename, line_num, &len);
+ char_span line = location_get_source_line (m_filename, line_num);
if (!line)
return false;
- for (int i = 0; i < len; i++)
+ for (size_t i = 0; i < line.length (); i++)
pp_character (pp, line[i]);
}
if (line_num < line_count)
const int context_lines = 3;
+ /* Track new line numbers minus old line numbers. */
+
+ int line_delta = 0;
+
while (el)
{
int start_of_hunk = el->get_line_num ();
= m_edited_lines.successor (el->get_line_num ());
if (!next_el)
break;
- if (el->get_line_num () + context_lines
+
+ int end_of_printed_hunk = el->get_line_num () + context_lines;
+ if (!el->actually_edited_p ())
+ end_of_printed_hunk--;
+
+ if (end_of_printed_hunk
>= 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 (!el->actually_edited_p ())
+ end_of_hunk--;
if (end_of_hunk > line_count)
end_of_hunk = line_count;
- int num_lines = end_of_hunk - start_of_hunk + 1;
+ int new_start_of_hunk = start_of_hunk + line_delta;
+ line_delta += print_diff_hunk (pp, start_of_hunk, end_of_hunk,
+ new_start_of_hunk);
+ el = m_edited_lines.successor (el->get_line_num ());
+ }
+}
- 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)));
+/* Print one hunk within a unified diff to PP, covering the
+ given range of lines. OLD_START_OF_HUNK and OLD_END_OF_HUNK are
+ line numbers in the unedited version of the file.
+ NEW_START_OF_HUNK is a line number in the edited version of the file.
+ Return the change in the line count within the hunk. */
+
+int
+edited_file::print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
+ int old_end_of_hunk, int new_start_of_hunk)
+{
+ int old_num_lines = old_end_of_hunk - old_start_of_hunk + 1;
+ int new_num_lines
+ = get_effective_line_count (old_start_of_hunk, old_end_of_hunk);
- int line_num = start_of_hunk;
- while (line_num <= end_of_hunk)
+ pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
+ pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", old_start_of_hunk, old_num_lines,
+ new_start_of_hunk, new_num_lines);
+ pp_string (pp, colorize_stop (pp_show_color (pp)));
+
+ int line_num = old_start_of_hunk;
+ while (line_num <= old_end_of_hunk)
+ {
+ edited_line *el = get_line (line_num);
+ if (el)
{
- 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;
-
- pp_string (pp, colorize_start (pp_show_color (pp),
- "diff-delete"));
-
- /* Show old version of lines. */
- for (line_num = first_changed_line_in_run;
- line_num <= last_changed_line_in_run;
- line_num++)
- {
- int line_size;
- const char *old_line
- = location_get_source_line (m_filename, line_num,
- &line_size);
- pp_character (pp, '-');
- for (int i = 0; i < line_size; i++)
- pp_character (pp, old_line[i]);
- pp_character (pp, '\n');
- }
-
- pp_string (pp, colorize_stop (pp_show_color (pp)));
-
- pp_string (pp, colorize_start (pp_show_color (pp),
- "diff-insert"));
-
- /* Show new version of lines. */
- 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);
- pp_character (pp, '+');
- pp_string (pp, el_in_run->get_content ());
- pp_character (pp, '\n');
- }
-
- pp_string (pp, colorize_stop (pp_show_color (pp)));
- }
- else
- {
- /* Unchanged line. */
- int line_size;
- const char *old_line
- = location_get_source_line (m_filename, line_num,
- &line_size);
- pp_character (pp, ' ');
- for (int i = 0; i < line_size; i++)
- pp_character (pp, old_line[i]);
- pp_character (pp, '\n');
- line_num++;
- }
+ /* 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;
+ print_run_of_changed_lines (pp, first_changed_line_in_run,
+ last_changed_line_in_run);
}
+ else
+ {
+ /* Unchanged line. */
+ char_span old_line = location_get_source_line (m_filename, line_num);
+ print_diff_line (pp, ' ', old_line.get_buffer (), old_line.length ());
+ line_num++;
+ }
+ }
- el = m_edited_lines.successor (el->get_line_num ());
+ return new_num_lines - old_num_lines;
+}
+
+/* Subroutine of edited_file::print_diff_hunk: given a run of lines
+ from START_OF_RUN to END_OF_RUN that all have edited_line instances,
+ print the diff to PP. */
+
+void
+edited_file::print_run_of_changed_lines (pretty_printer *pp,
+ int start_of_run,
+ int end_of_run)
+{
+ /* Show old version of lines. */
+ pp_string (pp, colorize_start (pp_show_color (pp),
+ "diff-delete"));
+ for (int line_num = start_of_run;
+ line_num <= end_of_run;
+ line_num++)
+ {
+ edited_line *el_in_run = get_line (line_num);
+ gcc_assert (el_in_run);
+ if (el_in_run->actually_edited_p ())
+ {
+ char_span old_line = location_get_source_line (m_filename, line_num);
+ print_diff_line (pp, '-', old_line.get_buffer (),
+ old_line.length ());
+ }
+ }
+ 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 (int line_num = start_of_run;
+ line_num <= end_of_run;
+ line_num++)
+ {
+ edited_line *el_in_run = get_line (line_num);
+ gcc_assert (el_in_run);
+ el_in_run->print_diff_lines (pp);
}
+ pp_string (pp, colorize_stop (pp_show_color (pp)));
+}
+
+/* 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. */
+
+static void
+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');
+}
+
+/* Determine the number of lines that will be present after
+ editing for the range of lines from OLD_START_OF_HUNK to
+ OLD_END_OF_HUNK inclusive. */
+
+int
+edited_file::get_effective_line_count (int old_start_of_hunk,
+ int old_end_of_hunk)
+{
+ int line_count = 0;
+ for (int old_line_num = old_start_of_hunk; old_line_num <= old_end_of_hunk;
+ old_line_num++)
+ {
+ edited_line *el = get_line (old_line_num);
+ if (el)
+ line_count += el->get_effective_line_count ();
+ else
+ line_count++;
+ }
+ return line_count;
}
/* Get the state of LINE within the file, or NULL if it is untouched. */
m_num_lines = 0;
while (true)
{
- int line_size;
- const char *line
- = location_get_source_line (m_filename, m_num_lines + 1,
- &line_size);
+ char_span line
+ = location_get_source_line (m_filename, m_num_lines + 1);
if (line)
m_num_lines++;
else
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 ()
+ m_line_events (),
+ m_predecessors ()
{
- const char *line = location_get_source_line (filename, line_num,
- &m_len);
+ char_span line = location_get_source_line (filename, line_num);
if (!line)
return;
+ m_len = line.length ();
ensure_capacity (m_len);
- memcpy (m_content, line, m_len);
+ memcpy (m_content, line.get_buffer (), m_len);
ensure_terminated ();
}
edited_line::~edited_line ()
{
- free (m_content);
+ unsigned i;
+ added_line *pred;
- int i;
- line_event *event;
- FOR_EACH_VEC_ELT (m_line_events, i, event)
- delete event;
+ free (m_content);
+ FOR_EACH_VEC_ELT (m_predecessors, i, pred)
+ delete pred;
}
/* A callback for deleting edited_line *, for use as a
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.
+/* Attempt to replace columns START_COLUMN up to but not including
+ NEXT_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_insert (int column, const char *insert_str,
- int insert_len)
+edited_line::apply_fixit (int start_column,
+ int next_column,
+ const char *replacement_str,
+ int replacement_len)
{
- column = get_effective_column (column);
+ /* Handle newlines. They will only ever be at the end of the
+ replacement text, thanks to the filtering in rich_location. */
+ if (replacement_len > 1)
+ if (replacement_str[replacement_len - 1] == '\n')
+ {
+ /* Stash in m_predecessors, stripping off newline. */
+ m_predecessors.safe_push (new added_line (replacement_str,
+ replacement_len - 1));
+ return true;
+ }
- 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);
+ next_column = get_effective_column (next_column);
int start_offset = start_column - 1;
- int end_offset = finish_column - 1;
+ int next_offset = next_column - 1;
gcc_assert (start_offset >= 0);
- gcc_assert (end_offset >= 0);
+ gcc_assert (next_offset >= 0);
- if (start_column > finish_column)
+ if (start_column > next_column)
return false;
- if (start_offset >= m_len)
+ if (start_offset >= (m_len + 1))
return false;
- if (end_offset >= m_len)
+ if (next_offset >= (m_len + 1))
return false;
- size_t victim_len = end_offset - start_offset + 1;
+ size_t victim_len = next_offset - start_offset;
/* 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;
+ char *suffix = m_content + next_offset;
gcc_assert (suffix <= m_content + m_len);
size_t len_suffix = (m_content + m_len) - suffix;
/* 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));
+ m_line_events.safe_push (line_event (start_column, next_column,
+ replacement_len));
return true;
}
+/* Determine the number of lines that will be present after
+ editing for this line. Typically this is just 1, but
+ if newlines have been added before this line, they will
+ also be counted. */
+
+int
+edited_line::get_effective_line_count () const
+{
+ return m_predecessors.length () + 1;
+}
+
+/* Subroutine of edited_file::print_content.
+ Print this line and any new lines added before it, to PP. */
+
+void
+edited_line::print_content (pretty_printer *pp) const
+{
+ unsigned i;
+ added_line *pred;
+ FOR_EACH_VEC_ELT (m_predecessors, i, pred)
+ {
+ pp_string (pp, pred->get_content ());
+ pp_newline (pp);
+ }
+ pp_string (pp, m_content);
+}
+
+/* Subroutine of edited_file::print_run_of_changed_lines for
+ printing diff hunks to PP.
+ Print the '+' line for this line, and any newlines added
+ before it.
+ Note that if this edited_line was actually edited, the '-'
+ line has already been printed. If it wasn't, then we merely
+ have a placeholder edited_line for adding newlines to, and
+ we need to print a ' ' line for the edited_line as we haven't
+ printed it yet. */
+
+void
+edited_line::print_diff_lines (pretty_printer *pp) const
+{
+ unsigned i;
+ added_line *pred;
+ FOR_EACH_VEC_ELT (m_predecessors, i, pred)
+ print_diff_line (pp, '+', pred->get_content (),
+ pred->get_len ());
+ if (actually_edited_p ())
+ print_diff_line (pp, '+', m_content, m_len);
+ else
+ print_diff_line (pp, ' ', m_content, m_len);
+}
+
/* 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. */
}
}
-/* Test applying an "insert" fixit. */
+/* Test applying an "insert" fixit, using insert_before. */
static void
-test_applying_fixits_insert (const line_table_case &case_)
+test_applying_fixits_insert_before (const line_table_case &case_)
{
/* Create a tempfile and write some text to it.
.........................0000000001111111.
/* 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 ("/* inserted */");
+ richloc.add_fixit_insert_before ("/* inserted */");
if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
return;
" /* 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 an "insert" fixit that adds a newline. */
+
+static void
+test_applying_fixits_insert_containing_newline (const line_table_case &case_)
+{
+ /* Create a tempfile and write some text to it.
+ .........................0000000001111111.
+ .........................1234567890123456. */
+ const char *old_content = (" case 'a':\n" /* line 1. */
+ " x = a;\n" /* line 2. */
+ " case 'b':\n" /* line 3. */
+ " x = b;\n");/* line 4. */
+
+ 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 (), 3);
+
+ /* Add a "break;" on a line by itself before line 3 i.e. before
+ column 1 of line 3. */
+ location_t case_start = linemap_position_for_column (line_table, 5);
+ location_t case_finish = linemap_position_for_column (line_table, 13);
+ location_t case_loc = make_location (case_start, case_start, case_finish);
+ rich_location richloc (line_table, case_loc);
+ location_t line_start = linemap_position_for_column (line_table, 1);
+ richloc.add_fixit_insert_before (line_start, " break;\n");
+
+ if (case_finish > 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 ((" case 'a':\n"
+ " x = a;\n"
+ " break;\n"
+ " case 'b':\n"
+ " x = b;\n"),
+ new_content);
+
+ /* Verify diff. */
+ auto_free <char *> diff = edit.generate_diff (false);
+ ASSERT_STREQ (("@@ -1,4 +1,5 @@\n"
+ " case 'a':\n"
+ " x = a;\n"
+ "+ break;\n"
+ " case 'b':\n"
+ " x = b;\n"),
+ diff);
+}
+
/* Test applying a "replace" fixit that grows the affected line. */
static void
}
}
+/* Replacement fix-it hint containing a newline. */
+
+static void
+test_applying_fixits_replace_containing_newline (const line_table_case &case_)
+{
+ /* Create a tempfile and write some text to it.
+ .........................0000000001111.
+ .........................1234567890123. */
+ const char *old_content = "foo = bar ();\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, 1);
+
+ /* Replace the " = " with "\n = ", as if we were reformatting an
+ overly long line. */
+ location_t start = linemap_position_for_column (line_table, 4);
+ location_t finish = linemap_position_for_column (line_table, 6);
+ location_t loc = linemap_position_for_column (line_table, 13);
+ rich_location richloc (line_table, loc);
+ source_range range = source_range::from_locations (start, finish);
+ richloc.add_fixit_replace (range, "\n = ");
+
+ /* Newlines are only supported within fix-it hints that
+ are at the start of lines (for entirely new lines), hence
+ this fix-it should not be displayed. */
+ ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
+
+ if (finish > 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 ("foo\n = bar ();\n", new_content);
+}
+
/* Test applying a "remove" fixit. */
static void
/* Add a comment in front of "bar.field". */
rich_location insert_a (line_table, c7);
- insert_a.add_fixit_insert (c7, "/* alpha */");
+ 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 (c17, "/* beta */");
+ insert_b.add_fixit_insert_before (c17, "/* beta */");
/* Replace "bar" with "pub". */
rich_location replace_a (line_table, c7);
}
rich_location insert (line_table, loc);
- insert.add_fixit_insert ("CHANGED: ");
+ insert.add_fixit_insert_before ("CHANGED: ");
+ edit.add_fixits (&insert);
+ return loc;
+}
+
+/* Subroutine of test_applying_fixits_multiple_lines.
+ Add the text "INSERTED\n" in front of the given line. */
+
+static location_t
+insert_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 ("INSERTED\n");
edit.add_fixits (&insert);
return loc;
}
change_line (edit, 2);
change_line (edit, 3);
change_line (edit, 4);
+ insert_line (edit, 5);
/* A run of nearby lines, within the contextual limit. */
change_line (edit, 150);
/* Verify diff. */
auto_free <char *> diff = edit.generate_diff (false);
- ASSERT_STREQ ("@@ -1,7 +1,7 @@\n"
+ ASSERT_STREQ ("@@ -1,7 +1,8 @@\n"
" line 1\n"
"-line 2\n"
"-line 3\n"
"+CHANGED: line 2\n"
"+CHANGED: line 3\n"
"+CHANGED: line 4\n"
+ "+INSERTED\n"
" line 5\n"
" line 6\n"
" line 7\n"
- "@@ -147,10 +147,10 @@\n"
+ "@@ -147,10 +148,10 @@\n"
" line 147\n"
" line 148\n"
" line 149\n"
location_t loc = linemap_position_for_column (line_table, 1);
rich_location insert (line_table, loc);
- insert.add_fixit_insert ("change 1");
- insert.add_fixit_insert ("change 2");
+ 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
location_t loc = linemap_position_for_column (line_table, 1);
rich_location insert (line_table, loc);
- insert.add_fixit_insert ("change");
+ insert.add_fixit_insert_before ("change");
/* Verify that attempting the insertion puts an edit_context
into an invalid state. */
/* Verify inserting at the end of the line. */
{
rich_location richloc (line_table, c11);
- richloc.add_fixit_insert (c15, " change");
+ richloc.add_fixit_insert_before (c15, " change");
/* Col 15 is at the end of the line, so the insertion
should succeed. */
/* Verify inserting beyond the end of the line. */
{
rich_location richloc (line_table, c11);
- richloc.add_fixit_insert (c16, " change");
+ richloc.add_fixit_insert_before (c16, " change");
/* Col 16 is beyond the end of the line, so the insertion
should fail gracefully. */
edit_context_c_tests ()
{
test_get_content ();
- for_each_line_table_case (test_applying_fixits_insert);
+ 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_insert_containing_newline);
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_replace_containing_newline);
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);