| /* Determining the results of applying fix-it hints. |
| Copyright (C) 2016-2021 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; |
| |
| /* A struct to hold the params of a print_diff call. */ |
| |
| class diff |
| { |
| public: |
| 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_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, |
| 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); |
| 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. |
| |
| 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 |
| { |
| 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_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 (); |
| |
| int m_line_num; |
| char *m_content; |
| int m_len; |
| int m_alloc_sz; |
| auto_vec <line_event> m_line_events; |
| auto_vec <added_line *> m_predecessors; |
| }; |
| |
| /* 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 start point. */ |
| |
| class line_event |
| { |
| public: |
| line_event (int start, int next, int len) : m_start (start), |
| m_delta (len - (next - start)) {} |
| |
| int get_effective_column (int orig_column) const |
| { |
| if (orig_column >= m_start) |
| return orig_column += m_delta; |
| else |
| return orig_column; |
| } |
| |
| private: |
| int m_start; |
| 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. */ |
| |
| 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); |
| if (!apply_fixit (hint)) |
| m_valid = false; |
| } |
| } |
| |
| /* 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_fixit (const fixit_hint *hint) |
| { |
| 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 != next_loc.line) |
| return false; |
| if (start.column == 0) |
| return false; |
| if (next_loc.column == 0) |
| return false; |
| |
| edited_file &file = get_or_insert_file (start.file); |
| if (!m_valid) |
| return false; |
| 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 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 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_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_fixit (start_column, next_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) |
| el->print_content (pp); |
| else |
| { |
| char_span line = location_get_source_line (m_filename, line_num); |
| if (!line) |
| return false; |
| for (size_t i = 0; i < line.length (); 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")); |
| /* Avoid -Wformat-diag in non-diagnostic output. */ |
| pp_string (pp, "--- "); |
| pp_string (pp, m_filename); |
| pp_newline (pp); |
| pp_string (pp, "+++ "); |
| pp_string (pp, m_filename); |
| pp_newline (pp); |
| 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; |
| |
| /* Track new line numbers minus old line numbers. */ |
| |
| int line_delta = 0; |
| |
| 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; |
| |
| 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 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 ()); |
| } |
| } |
| |
| /* 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); |
| |
| pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk")); |
| pp_printf (pp, "%s -%i,%i +%i,%i %s", |
| "@@", old_start_of_hunk, old_num_lines, |
| new_start_of_hunk, new_num_lines, "@@\n"); |
| 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) |
| { |
| /* 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++; |
| } |
| } |
| |
| 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. */ |
| |
| 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) |
| { |
| char_span line |
| = location_get_source_line (m_filename, m_num_lines + 1); |
| 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 (), |
| m_predecessors () |
| { |
| 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.get_buffer (), m_len); |
| ensure_terminated (); |
| } |
| |
| /* edited_line's dtor. */ |
| |
| edited_line::~edited_line () |
| { |
| unsigned i; |
| added_line *pred; |
| |
| free (m_content); |
| FOR_EACH_VEC_ELT (m_predecessors, i, pred) |
| delete pred; |
| } |
| |
| /* 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 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_fixit (int start_column, |
| int next_column, |
| const char *replacement_str, |
| int replacement_len) |
| { |
| /* 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; |
| } |
| |
| start_column = get_effective_column (start_column); |
| next_column = get_effective_column (next_column); |
| |
| int start_offset = start_column - 1; |
| int next_offset = next_column - 1; |
| |
| gcc_assert (start_offset >= 0); |
| gcc_assert (next_offset >= 0); |
| |
| if (start_column > next_column) |
| return false; |
| if (start_offset >= (m_len + 1)) |
| return false; |
| if (next_offset >= (m_len + 1)) |
| return false; |
| |
| 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 + next_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 + 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 (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. */ |
| |
| 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 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 |
| 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); |
| } |
| } |
| |
| /* 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 |
| 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; |
| } |
| |
| /* 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; |
| } |
| |
| /* 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); |
| insert_line (edit, 5); |
| |
| /* 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,8 @@\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" |
| "+INSERTED\n" |
| " line 5\n" |
| " line 6\n" |
| " line 7\n" |
| "@@ -147,10 +148,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_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); |
| 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 */ |