diff options
author | David Malcolm <dmalcolm@redhat.com> | 2017-05-02 19:03:56 +0000 |
---|---|---|
committer | David Malcolm <dmalcolm@gcc.gnu.org> | 2017-05-02 19:03:56 +0000 |
commit | ad53f12355a61ad5811d59977c963a16b6c5be8d (patch) | |
tree | fbb33622c1d2ca1c3acfafe4aab4578465d8f35d /gcc/edit-context.c | |
parent | 19576ba7408ccd45fea5f1ad2171ab2b8403f59e (diff) | |
download | gcc-ad53f12355a61ad5811d59977c963a16b6c5be8d.zip gcc-ad53f12355a61ad5811d59977c963a16b6c5be8d.tar.gz gcc-ad53f12355a61ad5811d59977c963a16b6c5be8d.tar.bz2 |
Support fix-it hints that add new lines
Previously fix-it hints couldn't contain newlines. This is
due to the need to print something user-readable for them
within diagnostic-show-locus, and for handling them within
edit-context for printing diffs and regenerating content.
This patch enables limited support for fix-it hints with newlines,
for suggesting adding new lines.
Such a fix-it hint must have exactly one newline character, at the
end of the content. It must be an insertion at the beginning of
a line (so that e.g. fix-its that split a pre-existing line are
still rejected).
They are printed by diagnostic-show-locus with a '+' in the
left-hand margin, like this:
test.c:42:4: note: suggest adding 'break;' here
+ break;
case 'b':
^~~~~~~~~
and the printer injects "spans" if the insertion location is not
near the primary range of the diagnostic e.g.:
test.c:4:2: note: unrecognized 'putchar'; suggest including '<stdio.h>'
test.c:1:1:
+#include <stdio.h>
test.c:4:2:
putchar (ch);
^~~~~~~
gcc/ChangeLog:
* diagnostic-show-locus.c
(layout::should_print_annotation_line_p): Make private.
(layout::print_annotation_line): Make private.
(layout::annotation_line_showed_range_p): Make private.
(layout::show_ruler): Make private.
(layout::print_source_line): Make private. Pass in line and
line_width, rather than calling location_get_source_line. Drop
returned value.
(layout::print_leading_fixits): New method.
(layout::print_any_fixits): Rename to...
(layout::print_trailing_fixits): ...this, and make private.
Don't print newline fixits.
(diagnostic_show_locus): Move logic for printing one row into...
(layout::print_line): ...this new function. Move the
location_get_source_line call and error-handling from
print_source_line to here. Call print_leading_fixits, and rename
print_any_fixits to print_trailing_fixits.
(selftest::test_fixit_insert_containing_newline): Update now that
newlines are partially supported.
(selftest::test_fixit_insert_containing_newline_2): New test.
(selftest::test_fixit_replace_containing_newline): Update comments.
(selftest::diagnostic_show_locus_c_tests): Call the new test.
* edit-context.c (class added_line): New class.
(class edited_line): Describe newline handling in comment.
(edited_line::actually_edited_p): New method.
(edited_line::print_content): Delete redundant decl.
(edited_line::m_predecessors): New field.
(edited_file::print_content): Call edited_line::print_content.
(edited_file::print_diff): Update to support newlines.
(edited_file::print_diff_hunk): Likewise.
(edited_file::print_run_of_changed_lines): New function.
(edited_file::print_diff_line): Convert to...
(print_diff_line): ...this.
(edited_file::get_effective_line_count): New function.
(edited_line::edited_line): Initialize new field m_predecessors.
(edited_line::~edited_line): Clean up m_predecessors.
(edited_line::apply_fixit): Handle newlines.
(edited_line::get_effective_line_count): New function.
(edited_line::print_content): New function.
(edited_line::print_diff_lines): New function.
(selftest::test_applying_fixits_insert_containing_newline): New
test.
(selftest::test_applying_fixits_replace_containing_newline): New
test.
(selftest::insert_line): New function.
(selftest::test_applying_fixits_multiple_lines): Add example of
inserting a line.
(selftest::edit_context_c_tests): Call the new tests.
gcc/testsuite/ChangeLog:
* gcc.dg/plugin/diagnostic-test-show-locus-bw.c
(test_fixit_insert_newline): New function.
* gcc.dg/plugin/diagnostic-test-show-locus-color.c
(test_fixit_insert_newline): New function.
* gcc.dg/plugin/diagnostic-test-show-locus-generate-patch.c
(test_fixit_insert_newline): New function.
* gcc.dg/plugin/diagnostic-test-show-locus-parseable-fixits.c
(test_fixit_insert_newline): New function.
* gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
(test_show_locus): Handle test_fixit_insert_newline.
libcpp/ChangeLog:
* include/line-map.h (class rich_location): Update description of
newline handling.
(class fixit_hint): Likewise.
(fixit_hint::ends_with_newline_p): New decl.
* line-map.c (rich_location::maybe_add_fixit): Support newlines
in fix-it hints that are insertions of single lines at the start
of a line. Don't consolidate into such fix-it hints.
(fixit_hint::ends_with_newline_p): New method.
From-SVN: r247522
Diffstat (limited to 'gcc/edit-context.c')
-rw-r--r-- | gcc/edit-context.c | 394 |
1 files changed, 341 insertions, 53 deletions
diff --git a/gcc/edit-context.c b/gcc/edit-context.c index bea8a8a..6f35ca2 100644 --- a/gcc/edit-context.c +++ b/gcc/edit-context.c @@ -86,23 +86,52 @@ class edited_file private: bool print_content (pretty_printer *pp); void print_diff (pretty_printer *pp, bool show_filenames); - void print_diff_hunk (pretty_printer *pp, int start_of_hunk, - int end_of_hunk); - void print_diff_line (pretty_printer *pp, char prefix_char, - const char *line, int line_size); + 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 { @@ -121,16 +150,25 @@ class edited_line 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; + auto_vec <added_line *> m_predecessors; }; /* Class for representing edit events that have occurred on one line of @@ -160,6 +198,12 @@ class line_event 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. */ @@ -375,7 +419,7 @@ edited_file::print_content (pretty_printer *pp) { edited_line *el = get_line (line_num); if (el) - pp_string (pp, el->get_content ()); + el->print_content (pp); else { int len; @@ -417,6 +461,10 @@ edited_file::print_diff (pretty_printer *pp, bool show_filenames) 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 (); @@ -432,39 +480,53 @@ edited_file::print_diff (pretty_printer *pp, bool show_filenames) = 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; - print_diff_hunk (pp, start_of_hunk, end_of_hunk); - + 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. */ + 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. */ -void -edited_file::print_diff_hunk (pretty_printer *pp, int start_of_hunk, - int end_of_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 num_lines = end_of_hunk - start_of_hunk + 1; + 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, "@@ -%i,%i +%i,%i @@\n", start_of_hunk, num_lines, - start_of_hunk, num_lines); + 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 = start_of_hunk; - while (line_num <= end_of_hunk) + int line_num = old_start_of_hunk; + while (line_num <= old_end_of_hunk) { edited_line *el = get_line (line_num); if (el) @@ -475,34 +537,8 @@ edited_file::print_diff_hunk (pretty_printer *pp, int start_of_hunk, while (get_line (line_num)) line_num++; const int last_changed_line_in_run = line_num - 1; - - /* Show old version of lines. */ - pp_string (pp, colorize_start (pp_show_color (pp), - "diff-delete")); - for (line_num = first_changed_line_in_run; - line_num <= last_changed_line_in_run; - line_num++) - { - int line_len; - const char *old_line - = location_get_source_line (m_filename, line_num, &line_len); - print_diff_line (pp, '-', old_line, line_len); - } - pp_string (pp, colorize_stop (pp_show_color (pp))); - - /* Show new version of lines. */ - pp_string (pp, colorize_start (pp_show_color (pp), - "diff-insert")); - for (line_num = first_changed_line_in_run; - line_num <= last_changed_line_in_run; - line_num++) - { - edited_line *el_in_run = get_line (line_num); - gcc_assert (el_in_run); - print_diff_line (pp, '+', el_in_run->get_content (), - el_in_run->get_len ()); - } - pp_string (pp, colorize_stop (pp_show_color (pp))); + print_run_of_changed_lines (pp, first_changed_line_in_run, + last_changed_line_in_run); } else { @@ -514,15 +550,59 @@ edited_file::print_diff_hunk (pretty_printer *pp, int start_of_hunk, 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 ()) + { + int line_len; + const char *old_line + = location_get_source_line (m_filename, line_num, &line_len); + print_diff_line (pp, '-', old_line, line_len); + } + } + pp_string (pp, colorize_stop (pp_show_color (pp))); + + /* Show new version of lines. */ + pp_string (pp, colorize_start (pp_show_color (pp), + "diff-insert")); + for (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. */ -void -edited_file::print_diff_line (pretty_printer *pp, char prefix_char, - const char *line, int len) +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++) @@ -530,6 +610,27 @@ edited_file::print_diff_line (pretty_printer *pp, char prefix_char, 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 * @@ -591,7 +692,8 @@ edited_file::get_num_lines (bool *missing_trailing_newline) 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); @@ -606,7 +708,12 @@ edited_line::edited_line (const char *filename, int line_num) 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 @@ -643,6 +750,17 @@ edited_line::apply_fixit (int start_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); @@ -689,6 +807,57 @@ edited_line::apply_fixit (int start_column, 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. */ @@ -967,6 +1136,57 @@ test_applying_fixits_insert_after_failure (const line_table_case &case_) 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 @@ -1057,6 +1277,44 @@ test_applying_fixits_shrinking_replace (const line_table_case &case_) } } +/* 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 @@ -1204,6 +1462,32 @@ change_line (edit_context &edit, int line_num) 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. */ @@ -1229,6 +1513,7 @@ test_applying_fixits_multiple_lines (const line_table_case &case_) 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); @@ -1240,7 +1525,7 @@ test_applying_fixits_multiple_lines (const line_table_case &case_) /* 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" @@ -1248,10 +1533,11 @@ test_applying_fixits_multiple_lines (const line_table_case &case_) "+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" @@ -1510,8 +1796,10 @@ edit_context_c_tests () 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); |