1 /* Determining the results of applying fix-it hints.
2 Copyright (C) 2016 Free Software Foundation, Inc.
4 This file is part of GCC.
6 GCC is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 3, or (at your option) any later
11 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 You should have received a copy of the GNU General Public License
17 along with GCC; see the file COPYING3. If not see
18 <http://www.gnu.org/licenses/>. */
22 #include "coretypes.h"
24 #include "edit-context.h"
25 #include "pretty-print.h"
26 #include "diagnostic-color.h"
29 /* This file implements a way to track the effect of fix-its,
30 via a class edit_context; the other classes are support classes for
33 A complication here is that fix-its are expressed relative to coordinates
34 in the file when it was parsed, before any changes have been made, and
35 so if there's more that one fix-it to be applied, we have to adjust
36 later fix-its to allow for the changes made by earlier ones. This
37 is done by the various "get_effective_column" methods.
39 The "filename" params are required to outlive the edit_context (no
40 copy of the underlying str is taken, just the ptr). */
42 /* Forward decls. class edit_context is declared within edit-context.h.
43 The other types are declared here. */
51 /* A struct to hold the params of a print_diff call. */
55 diff (pretty_printer
*pp
, bool show_filenames
)
56 : m_pp (pp
), m_show_filenames (show_filenames
) {}
59 bool m_show_filenames
;
62 /* The state of one named file within an edit_context: the filename,
63 and the lines that have been edited so far. */
68 edited_file (const char *filename
);
69 static void delete_cb (edited_file
*file
);
71 const char *get_filename () const { return m_filename
; }
74 bool apply_insert (int line
, int column
, const char *str
, int len
);
75 bool apply_replace (int line
, int start_column
,
77 const char *replacement_str
,
79 int get_effective_column (int line
, int column
);
81 static int call_print_diff (const char *, edited_file
*file
,
84 diff
*d
= (diff
*)user_data
;
85 file
->print_diff (d
->m_pp
, d
->m_show_filenames
);
90 bool print_content (pretty_printer
*pp
);
91 void print_diff (pretty_printer
*pp
, bool show_filenames
);
92 void print_diff_hunk (pretty_printer
*pp
, int start_of_hunk
,
94 void print_diff_line (pretty_printer
*pp
, char prefix_char
,
95 const char *line
, int line_size
);
96 edited_line
*get_line (int line
);
97 edited_line
*get_or_insert_line (int line
);
98 int get_num_lines (bool *missing_trailing_newline
);
100 const char *m_filename
;
101 typed_splay_tree
<int, edited_line
*> m_edited_lines
;
105 /* The state of one edited line within an edited_file.
106 As well as the current content of the line, it contains a record of
107 the changes, so that further changes can be applied in the correct
113 edited_line (const char *filename
, int line_num
);
115 static void delete_cb (edited_line
*el
);
117 int get_line_num () const { return m_line_num
; }
118 const char *get_content () const { return m_content
; }
119 int get_len () const { return m_len
; }
121 int get_effective_column (int orig_column
) const;
122 bool apply_insert (int column
, const char *str
, int len
);
123 bool apply_replace (int start_column
,
125 const char *replacement_str
,
126 int replacement_len
);
129 void ensure_capacity (int len
);
130 void ensure_terminated ();
131 void print_content (pretty_printer
*pp
) const;
137 auto_vec
<line_event
*> m_line_events
;
140 /* Abstract base class for representing events that have occurred
141 on one line of one file. */
146 virtual ~line_event () {}
147 virtual int get_effective_column (int orig_column
) const = 0;
150 /* Concrete subclass of line_event: an insertion of some text
151 at some column on the line.
153 Subsequent events will need their columns adjusting if they're
154 are on this line and their column is >= the insertion point. */
156 class insert_event
: public line_event
159 insert_event (int column
, int len
) : m_column (column
), m_len (len
) {}
160 int get_effective_column (int orig_column
) const FINAL OVERRIDE
162 if (orig_column
>= m_column
)
163 return orig_column
+ m_len
;
173 /* Concrete subclass of line_event: the replacement of some text
174 betweeen some columns on the line.
176 Subsequent events will need their columns adjusting if they're
177 are on this line and their column is >= the finish point. */
179 class replace_event
: public line_event
182 replace_event (int start
, int finish
, int len
) : m_start (start
),
183 m_finish (finish
), m_delta (len
- (finish
+ 1 - start
)) {}
185 int get_effective_column (int orig_column
) const FINAL OVERRIDE
187 if (orig_column
>= m_start
)
188 return orig_column
+= m_delta
;
199 /* Implementation of class edit_context. */
201 /* edit_context's ctor. */
203 edit_context::edit_context ()
205 m_files (strcmp
, NULL
, edited_file::delete_cb
)
208 /* Add any fixits within RICHLOC to this context, recording the
209 changes that they make. */
212 edit_context::add_fixits (rich_location
*richloc
)
216 if (richloc
->seen_impossible_fixit_p ())
221 for (unsigned i
= 0; i
< richloc
->get_num_fixit_hints (); i
++)
223 const fixit_hint
*hint
= richloc
->get_fixit_hint (i
);
224 switch (hint
->get_kind ())
226 case fixit_hint::INSERT
:
227 if (!apply_insert ((const fixit_insert
*)hint
))
234 case fixit_hint::REPLACE
:
235 if (!apply_replace ((const fixit_replace
*)hint
))
248 /* Get the content of the given file, with fix-its applied.
249 If any errors occurred in this edit_context, return NULL.
250 The ptr should be freed by the caller. */
253 edit_context::get_content (const char *filename
)
257 edited_file
&file
= get_or_insert_file (filename
);
258 return file
.get_content ();
261 /* Map a location before the edits to a column number after the edits.
262 This method is for the selftests. */
265 edit_context::get_effective_column (const char *filename
, int line
,
268 edited_file
*file
= get_file (filename
);
271 return file
->get_effective_column (line
, column
);
274 /* Generate a unified diff. The resulting string should be freed by the
275 caller. Primarily for selftests.
276 If any errors occurred in this edit_context, return NULL. */
279 edit_context::generate_diff (bool show_filenames
)
285 print_diff (&pp
, show_filenames
);
286 return xstrdup (pp_formatted_text (&pp
));
289 /* Print a unified diff to PP, showing the changes made within the
293 edit_context::print_diff (pretty_printer
*pp
, bool show_filenames
)
297 diff
d (pp
, show_filenames
);
298 m_files
.foreach (edited_file::call_print_diff
, &d
);
301 /* Attempt to apply the given fixit. Return true if it can be
302 applied, or false otherwise. */
305 edit_context::apply_insert (const fixit_insert
*insert
)
307 expanded_location exploc
= expand_location (insert
->get_location ());
309 if (exploc
.column
== 0)
312 edited_file
&file
= get_or_insert_file (exploc
.file
);
315 return file
.apply_insert (exploc
.line
, exploc
.column
, insert
->get_string (),
316 insert
->get_length ());
319 /* Attempt to apply the given fixit. Return true if it can be
320 applied, or false otherwise. */
323 edit_context::apply_replace (const fixit_replace
*replace
)
325 source_range range
= replace
->get_range ();
327 expanded_location start
= expand_location (range
.m_start
);
328 expanded_location finish
= expand_location (range
.m_finish
);
329 if (start
.file
!= finish
.file
)
331 if (start
.line
!= finish
.line
)
333 if (start
.column
== 0)
335 if (finish
.column
== 0)
338 edited_file
&file
= get_or_insert_file (start
.file
);
341 return file
.apply_replace (start
.line
, start
.column
, finish
.column
,
342 replace
->get_string (),
343 replace
->get_length ());
346 /* Locate the edited_file * for FILENAME, if any
347 Return NULL if there isn't one. */
350 edit_context::get_file (const char *filename
)
352 gcc_assert (filename
);
353 return m_files
.lookup (filename
);
356 /* Locate the edited_file for FILENAME, adding one if there isn't one. */
359 edit_context::get_or_insert_file (const char *filename
)
361 gcc_assert (filename
);
363 edited_file
*file
= get_file (filename
);
368 file
= new edited_file (filename
);
369 m_files
.insert (filename
, file
);
373 /* Implementation of class edited_file. */
375 /* Callback for m_edited_lines, for comparing line numbers. */
377 static int line_comparator (int a
, int b
)
382 /* edited_file's constructor. */
384 edited_file::edited_file (const char *filename
)
385 : m_filename (filename
),
386 m_edited_lines (line_comparator
, NULL
, edited_line::delete_cb
),
391 /* A callback for deleting edited_file *, for use as a
392 delete_value_fn for edit_context::m_files. */
395 edited_file::delete_cb (edited_file
*file
)
400 /* Get the content of the file, with fix-its applied.
401 The ptr should be freed by the caller. */
404 edited_file::get_content ()
407 if (!print_content (&pp
))
409 return xstrdup (pp_formatted_text (&pp
));
412 /* Attempt to insert the string INSERT_STR with length INSERT_LEN
413 at LINE and COLUMN, updating the in-memory copy of the line, and
414 the record of edits to the line. */
417 edited_file::apply_insert (int line
, int column
,
418 const char *insert_str
,
421 edited_line
*el
= get_or_insert_line (line
);
424 return el
->apply_insert (column
, insert_str
, insert_len
);
427 /* Attempt to replace columns START_COLUMN through FINISH_COLUMN of LINE
428 with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
429 updating the in-memory copy of the line, and the record of edits to
433 edited_file::apply_replace (int line
, int start_column
,
435 const char *replacement_str
,
438 edited_line
*el
= get_or_insert_line (line
);
441 return el
->apply_replace (start_column
, finish_column
, replacement_str
,
445 /* Given line LINE, map from COLUMN in the input file to its current
446 column after edits have been applied. */
449 edited_file::get_effective_column (int line
, int column
)
451 const edited_line
*el
= get_line (line
);
454 return el
->get_effective_column (column
);
457 /* Attempt to print the content of the file to PP, with edits applied.
458 Return true if successful, false otherwise. */
461 edited_file::print_content (pretty_printer
*pp
)
463 bool missing_trailing_newline
;
464 int line_count
= get_num_lines (&missing_trailing_newline
);
465 for (int line_num
= 1; line_num
<= line_count
; line_num
++)
467 edited_line
*el
= get_line (line_num
);
469 pp_string (pp
, el
->get_content ());
474 = location_get_source_line (m_filename
, line_num
, &len
);
477 for (int i
= 0; i
< len
; i
++)
478 pp_character (pp
, line
[i
]);
480 if (line_num
< line_count
)
481 pp_character (pp
, '\n');
484 if (!missing_trailing_newline
)
485 pp_character (pp
, '\n');
490 /* Print a unified diff to PP, showing any changes that have occurred
494 edited_file::print_diff (pretty_printer
*pp
, bool show_filenames
)
498 pp_string (pp
, colorize_start (pp_show_color (pp
), "diff-filename"));
499 pp_printf (pp
, "--- %s\n", m_filename
);
500 pp_printf (pp
, "+++ %s\n", m_filename
);
501 pp_string (pp
, colorize_stop (pp_show_color (pp
)));
504 edited_line
*el
= m_edited_lines
.min ();
506 bool missing_trailing_newline
;
507 int line_count
= get_num_lines (&missing_trailing_newline
);
509 const int context_lines
= 3;
513 int start_of_hunk
= el
->get_line_num ();
514 start_of_hunk
-= context_lines
;
515 if (start_of_hunk
< 1)
518 /* Locate end of hunk, merging in changed lines
519 that are sufficiently close. */
523 = m_edited_lines
.successor (el
->get_line_num ());
526 if (el
->get_line_num () + context_lines
527 >= next_el
->get_line_num () - context_lines
)
532 int end_of_hunk
= el
->get_line_num ();
533 end_of_hunk
+= context_lines
;
534 if (end_of_hunk
> line_count
)
535 end_of_hunk
= line_count
;
537 print_diff_hunk (pp
, start_of_hunk
, end_of_hunk
);
539 el
= m_edited_lines
.successor (el
->get_line_num ());
543 /* Print one hunk within a unified diff to PP, covering the
544 given range of lines. */
547 edited_file::print_diff_hunk (pretty_printer
*pp
, int start_of_hunk
,
550 int num_lines
= end_of_hunk
- start_of_hunk
+ 1;
552 pp_string (pp
, colorize_start (pp_show_color (pp
), "diff-hunk"));
553 pp_printf (pp
, "@@ -%i,%i +%i,%i @@\n", start_of_hunk
, num_lines
,
554 start_of_hunk
, num_lines
);
555 pp_string (pp
, colorize_stop (pp_show_color (pp
)));
557 int line_num
= start_of_hunk
;
558 while (line_num
<= end_of_hunk
)
560 edited_line
*el
= get_line (line_num
);
563 /* We have an edited line.
564 Consolidate into runs of changed lines. */
565 const int first_changed_line_in_run
= line_num
;
566 while (get_line (line_num
))
568 const int last_changed_line_in_run
= line_num
- 1;
570 /* Show old version of lines. */
571 pp_string (pp
, colorize_start (pp_show_color (pp
),
573 for (line_num
= first_changed_line_in_run
;
574 line_num
<= last_changed_line_in_run
;
579 = location_get_source_line (m_filename
, line_num
, &line_len
);
580 print_diff_line (pp
, '-', old_line
, line_len
);
582 pp_string (pp
, colorize_stop (pp_show_color (pp
)));
584 /* Show new version of lines. */
585 pp_string (pp
, colorize_start (pp_show_color (pp
),
587 for (line_num
= first_changed_line_in_run
;
588 line_num
<= last_changed_line_in_run
;
591 edited_line
*el_in_run
= get_line (line_num
);
592 gcc_assert (el_in_run
);
593 print_diff_line (pp
, '+', el_in_run
->get_content (),
594 el_in_run
->get_len ());
596 pp_string (pp
, colorize_stop (pp_show_color (pp
)));
600 /* Unchanged line. */
603 = location_get_source_line (m_filename
, line_num
, &line_len
);
604 print_diff_line (pp
, ' ', old_line
, line_len
);
610 /* Print one line within a diff, starting with PREFIX_CHAR,
611 followed by the LINE of content, of length LEN. LINE is
612 not necessarily 0-terminated. Print a trailing newline. */
615 edited_file::print_diff_line (pretty_printer
*pp
, char prefix_char
,
616 const char *line
, int len
)
618 pp_character (pp
, prefix_char
);
619 for (int i
= 0; i
< len
; i
++)
620 pp_character (pp
, line
[i
]);
621 pp_character (pp
, '\n');
624 /* Get the state of LINE within the file, or NULL if it is untouched. */
627 edited_file::get_line (int line
)
629 return m_edited_lines
.lookup (line
);
632 /* Get the state of LINE within the file, creating a state for it
633 if necessary. Return NULL if an error occurs. */
636 edited_file::get_or_insert_line (int line
)
638 edited_line
*el
= get_line (line
);
641 el
= new edited_line (m_filename
, line
);
642 if (el
->get_content () == NULL
)
647 m_edited_lines
.insert (line
, el
);
651 /* Get the total number of lines in m_content, writing
652 true to *MISSING_TRAILING_NEWLINE if the final line
653 if missing a newline, false otherwise. */
656 edited_file::get_num_lines (bool *missing_trailing_newline
)
658 gcc_assert (missing_trailing_newline
);
659 if (m_num_lines
== -1)
666 = location_get_source_line (m_filename
, m_num_lines
+ 1,
674 *missing_trailing_newline
= location_missing_trailing_newline (m_filename
);
678 /* Implementation of class edited_line. */
680 /* edited_line's ctor. */
682 edited_line::edited_line (const char *filename
, int line_num
)
683 : m_line_num (line_num
),
684 m_content (NULL
), m_len (0), m_alloc_sz (0),
687 const char *line
= location_get_source_line (filename
, line_num
,
691 ensure_capacity (m_len
);
692 memcpy (m_content
, line
, m_len
);
693 ensure_terminated ();
696 /* edited_line's dtor. */
698 edited_line::~edited_line ()
704 FOR_EACH_VEC_ELT (m_line_events
, i
, event
)
708 /* A callback for deleting edited_line *, for use as a
709 delete_value_fn for edited_file::m_edited_lines. */
712 edited_line::delete_cb (edited_line
*el
)
717 /* Map a location before the edits to a column number after the edits,
718 within a specific line. */
721 edited_line::get_effective_column (int orig_column
) const
725 FOR_EACH_VEC_ELT (m_line_events
, i
, event
)
726 orig_column
= event
->get_effective_column (orig_column
);
730 /* Attempt to insert the string INSERT_STR with length INSERT_LEN at COLUMN
731 of this line, updating the in-memory copy of the line, and the record
733 Return true if successful; false if an error occurred. */
736 edited_line::apply_insert (int column
, const char *insert_str
,
739 column
= get_effective_column (column
);
741 int start_offset
= column
- 1;
742 gcc_assert (start_offset
>= 0);
743 if (start_offset
> m_len
)
746 /* Ensure buffer is big enough. */
747 size_t new_len
= m_len
+ insert_len
;
748 ensure_capacity (new_len
);
750 char *suffix
= m_content
+ start_offset
;
751 gcc_assert (suffix
<= m_content
+ m_len
);
752 size_t len_suffix
= (m_content
+ m_len
) - suffix
;
754 /* Move successor content into position. They overlap, so use memmove. */
755 memmove (m_content
+ start_offset
+ insert_len
,
758 /* Replace target content. They don't overlap, so use memcpy. */
759 memcpy (m_content
+ start_offset
,
765 ensure_terminated ();
767 /* Record the insertion, so that future changes to the line can have
768 their column information adjusted accordingly. */
769 m_line_events
.safe_push (new insert_event (column
, insert_len
));
774 /* Attempt to replace columns START_COLUMN through FINISH_COLUMN of the line
775 with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
776 updating the in-memory copy of the line, and the record of edits to
778 Return true if successful; false if an error occurred. */
781 edited_line::apply_replace (int start_column
,
783 const char *replacement_str
,
786 start_column
= get_effective_column (start_column
);
787 finish_column
= get_effective_column (finish_column
);
789 int start_offset
= start_column
- 1;
790 int end_offset
= finish_column
- 1;
792 gcc_assert (start_offset
>= 0);
793 gcc_assert (end_offset
>= 0);
795 if (start_column
> finish_column
)
797 if (start_offset
>= m_len
)
799 if (end_offset
>= m_len
)
802 size_t victim_len
= end_offset
- start_offset
+ 1;
804 /* Ensure buffer is big enough. */
805 size_t new_len
= m_len
+ replacement_len
- victim_len
;
806 ensure_capacity (new_len
);
808 char *suffix
= m_content
+ end_offset
+ 1;
809 gcc_assert (suffix
<= m_content
+ m_len
);
810 size_t len_suffix
= (m_content
+ m_len
) - suffix
;
812 /* Move successor content into position. They overlap, so use memmove. */
813 memmove (m_content
+ start_offset
+ replacement_len
,
816 /* Replace target content. They don't overlap, so use memcpy. */
817 memcpy (m_content
+ start_offset
,
823 ensure_terminated ();
825 /* Record the replacement, so that future changes to the line can have
826 their column information adjusted accordingly. */
827 m_line_events
.safe_push (new replace_event (start_column
, finish_column
,
832 /* Ensure that the buffer for m_content is at least large enough to hold
833 a string of length LEN and its 0-terminator, doubling on repeated
837 edited_line::ensure_capacity (int len
)
839 /* Allow 1 extra byte for 0-termination. */
840 if (m_alloc_sz
< (len
+ 1))
842 size_t new_alloc_sz
= (len
+ 1) * 2;
843 m_content
= (char *)xrealloc (m_content
, new_alloc_sz
);
844 m_alloc_sz
= new_alloc_sz
;
848 /* Ensure that m_content is 0-terminated. */
851 edited_line::ensure_terminated ()
853 /* 0-terminate the buffer. */
854 gcc_assert (m_len
< m_alloc_sz
);
855 m_content
[m_len
] = '\0';
860 /* Selftests of code-editing. */
864 /* A wrapper class for ensuring that the underlying pointer is freed. */
866 template <typename POINTER_T
>
870 auto_free (POINTER_T p
) : m_ptr (p
) {}
871 ~auto_free () { free (m_ptr
); }
873 operator POINTER_T () { return m_ptr
; }
879 /* Verify that edit_context::get_content works for unedited files. */
884 /* Test of empty file. */
886 const char *content
= ("");
887 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", content
);
889 auto_free
<char *> result
= edit
.get_content (tmp
.get_filename ());
890 ASSERT_STREQ ("", result
);
893 /* Test of simple content. */
895 const char *content
= ("/* before */\n"
898 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", content
);
900 auto_free
<char *> result
= edit
.get_content (tmp
.get_filename ());
901 ASSERT_STREQ ("/* before */\n"
903 "/* after */\n", result
);
906 /* Test of omitting the trailing newline on the final line. */
908 const char *content
= ("/* before */\n"
911 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", content
);
913 auto_free
<char *> result
= edit
.get_content (tmp
.get_filename ());
914 /* We should respect the omitted trailing newline. */
915 ASSERT_STREQ ("/* before */\n"
917 "/* after */", result
);
921 /* Test applying an "insert" fixit, using insert_before. */
924 test_applying_fixits_insert_before (const line_table_case
&case_
)
926 /* Create a tempfile and write some text to it.
927 .........................0000000001111111.
928 .........................1234567890123456. */
929 const char *old_content
= ("/* before */\n"
932 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
933 const char *filename
= tmp
.get_filename ();
934 line_table_test
ltt (case_
);
935 linemap_add (line_table
, LC_ENTER
, false, tmp
.get_filename (), 2);
937 /* Add a comment in front of "bar.field". */
938 location_t start
= linemap_position_for_column (line_table
, 7);
939 rich_location
richloc (line_table
, start
);
940 richloc
.add_fixit_insert_before ("/* inserted */");
942 if (start
> LINE_MAP_MAX_LOCATION_WITH_COLS
)
946 edit
.add_fixits (&richloc
);
947 auto_free
<char *> new_content
= edit
.get_content (filename
);
948 if (start
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
949 ASSERT_STREQ ("/* before */\n"
950 "foo = /* inserted */bar.field;\n"
951 "/* after */\n", new_content
);
953 /* Verify that locations on other lines aren't affected by the change. */
954 ASSERT_EQ (100, edit
.get_effective_column (filename
, 1, 100));
955 ASSERT_EQ (100, edit
.get_effective_column (filename
, 3, 100));
957 /* Verify locations on the line before the change. */
958 ASSERT_EQ (1, edit
.get_effective_column (filename
, 2, 1));
959 ASSERT_EQ (6, edit
.get_effective_column (filename
, 2, 6));
961 /* Verify locations on the line at and after the change. */
962 ASSERT_EQ (21, edit
.get_effective_column (filename
, 2, 7));
963 ASSERT_EQ (22, edit
.get_effective_column (filename
, 2, 8));
966 auto_free
<char *> diff
= edit
.generate_diff (false);
967 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
969 "-foo = bar.field;\n"
970 "+foo = /* inserted */bar.field;\n"
971 " /* after */\n", diff
);
974 /* Test applying an "insert" fixit, using insert_after, with
975 a range of length > 1 (to ensure that the end-point of
976 the input range is used). */
979 test_applying_fixits_insert_after (const line_table_case
&case_
)
981 /* Create a tempfile and write some text to it.
982 .........................0000000001111111.
983 .........................1234567890123456. */
984 const char *old_content
= ("/* before */\n"
987 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
988 const char *filename
= tmp
.get_filename ();
989 line_table_test
ltt (case_
);
990 linemap_add (line_table
, LC_ENTER
, false, tmp
.get_filename (), 2);
992 /* Add a comment after "field". */
993 location_t start
= linemap_position_for_column (line_table
, 11);
994 location_t finish
= linemap_position_for_column (line_table
, 15);
995 location_t field
= make_location (start
, start
, finish
);
996 rich_location
richloc (line_table
, field
);
997 richloc
.add_fixit_insert_after ("/* inserted */");
999 if (finish
> LINE_MAP_MAX_LOCATION_WITH_COLS
)
1002 /* Verify that the text was inserted after the end of "field". */
1004 edit
.add_fixits (&richloc
);
1005 auto_free
<char *> new_content
= edit
.get_content (filename
);
1006 ASSERT_STREQ ("/* before */\n"
1007 "foo = bar.field/* inserted */;\n"
1008 "/* after */\n", new_content
);
1011 auto_free
<char *> diff
= edit
.generate_diff (false);
1012 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1014 "-foo = bar.field;\n"
1015 "+foo = bar.field/* inserted */;\n"
1016 " /* after */\n", diff
);
1019 /* Test applying an "insert" fixit, using insert_after at the end of
1020 a line (contrast with test_applying_fixits_insert_after_failure
1024 test_applying_fixits_insert_after_at_line_end (const line_table_case
&case_
)
1026 /* Create a tempfile and write some text to it.
1027 .........................0000000001111111.
1028 .........................1234567890123456. */
1029 const char *old_content
= ("/* before */\n"
1030 "foo = bar.field;\n"
1032 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
1033 const char *filename
= tmp
.get_filename ();
1034 line_table_test
ltt (case_
);
1035 linemap_add (line_table
, LC_ENTER
, false, tmp
.get_filename (), 2);
1037 /* Add a comment after the semicolon. */
1038 location_t loc
= linemap_position_for_column (line_table
, 16);
1039 rich_location
richloc (line_table
, loc
);
1040 richloc
.add_fixit_insert_after ("/* inserted */");
1042 if (loc
> LINE_MAP_MAX_LOCATION_WITH_COLS
)
1046 edit
.add_fixits (&richloc
);
1047 auto_free
<char *> new_content
= edit
.get_content (filename
);
1048 ASSERT_STREQ ("/* before */\n"
1049 "foo = bar.field;/* inserted */\n"
1050 "/* after */\n", new_content
);
1053 auto_free
<char *> diff
= edit
.generate_diff (false);
1054 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1056 "-foo = bar.field;\n"
1057 "+foo = bar.field;/* inserted */\n"
1058 " /* after */\n", diff
);
1061 /* Test of a failed attempt to apply an "insert" fixit, using insert_after,
1062 due to the relevant linemap ending. Contrast with
1063 test_applying_fixits_insert_after_at_line_end above. */
1066 test_applying_fixits_insert_after_failure (const line_table_case
&case_
)
1068 /* Create a tempfile and write some text to it.
1069 .........................0000000001111111.
1070 .........................1234567890123456. */
1071 const char *old_content
= ("/* before */\n"
1072 "foo = bar.field;\n"
1074 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
1075 const char *filename
= tmp
.get_filename ();
1076 line_table_test
ltt (case_
);
1077 linemap_add (line_table
, LC_ENTER
, false, tmp
.get_filename (), 2);
1079 /* Add a comment after the semicolon. */
1080 location_t loc
= linemap_position_for_column (line_table
, 16);
1081 rich_location
richloc (line_table
, loc
);
1083 /* We want a failure of linemap_position_for_loc_and_offset.
1084 We can do this by starting a new linemap at line 3, so that
1085 there is no appropriate location value for the insertion point
1086 within the linemap for line 2. */
1087 linemap_add (line_table
, LC_ENTER
, false, tmp
.get_filename (), 3);
1089 /* The failure fails to happen at the transition point from
1090 packed ranges to unpacked ranges (where there are some "spare"
1091 location_t values). Skip the test there. */
1092 if (loc
>= LINE_MAP_MAX_LOCATION_WITH_PACKED_RANGES
)
1095 /* Offsetting "loc" should now fail (by returning the input loc. */
1096 ASSERT_EQ (loc
, linemap_position_for_loc_and_offset (line_table
, loc
, 1));
1098 /* Hence attempting to use add_fixit_insert_after at the end of the line
1100 richloc
.add_fixit_insert_after ("/* inserted */");
1101 ASSERT_TRUE (richloc
.seen_impossible_fixit_p ());
1104 edit
.add_fixits (&richloc
);
1105 ASSERT_FALSE (edit
.valid_p ());
1106 ASSERT_EQ (NULL
, edit
.get_content (filename
));
1107 ASSERT_EQ (NULL
, edit
.generate_diff (false));
1110 /* Test applying a "replace" fixit that grows the affected line. */
1113 test_applying_fixits_growing_replace (const line_table_case
&case_
)
1115 /* Create a tempfile and write some text to it.
1116 .........................0000000001111111.
1117 .........................1234567890123456. */
1118 const char *old_content
= ("/* before */\n"
1119 "foo = bar.field;\n"
1121 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
1122 const char *filename
= tmp
.get_filename ();
1123 line_table_test
ltt (case_
);
1124 linemap_add (line_table
, LC_ENTER
, false, filename
, 2);
1126 /* Replace "field" with "m_field". */
1127 location_t start
= linemap_position_for_column (line_table
, 11);
1128 location_t finish
= linemap_position_for_column (line_table
, 15);
1129 location_t field
= make_location (start
, start
, finish
);
1130 rich_location
richloc (line_table
, field
);
1131 richloc
.add_fixit_replace ("m_field");
1134 edit
.add_fixits (&richloc
);
1135 auto_free
<char *> new_content
= edit
.get_content (filename
);
1136 if (finish
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
1138 ASSERT_STREQ ("/* before */\n"
1139 "foo = bar.m_field;\n"
1140 "/* after */\n", new_content
);
1142 /* Verify location of ";" after the change. */
1143 ASSERT_EQ (18, edit
.get_effective_column (filename
, 2, 16));
1146 auto_free
<char *> diff
= edit
.generate_diff (false);
1147 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1149 "-foo = bar.field;\n"
1150 "+foo = bar.m_field;\n"
1151 " /* after */\n", diff
);
1155 /* Test applying a "replace" fixit that shrinks the affected line. */
1158 test_applying_fixits_shrinking_replace (const line_table_case
&case_
)
1160 /* Create a tempfile and write some text to it.
1161 .........................000000000111111111.
1162 .........................123456789012345678. */
1163 const char *old_content
= ("/* before */\n"
1164 "foo = bar.m_field;\n"
1166 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
1167 const char *filename
= tmp
.get_filename ();
1168 line_table_test
ltt (case_
);
1169 linemap_add (line_table
, LC_ENTER
, false, filename
, 2);
1171 /* Replace "field" with "m_field". */
1172 location_t start
= linemap_position_for_column (line_table
, 11);
1173 location_t finish
= linemap_position_for_column (line_table
, 17);
1174 location_t m_field
= make_location (start
, start
, finish
);
1175 rich_location
richloc (line_table
, m_field
);
1176 richloc
.add_fixit_replace ("field");
1179 edit
.add_fixits (&richloc
);
1180 auto_free
<char *> new_content
= edit
.get_content (filename
);
1181 if (finish
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
1183 ASSERT_STREQ ("/* before */\n"
1184 "foo = bar.field;\n"
1185 "/* after */\n", new_content
);
1187 /* Verify location of ";" after the change. */
1188 ASSERT_EQ (16, edit
.get_effective_column (filename
, 2, 18));
1191 auto_free
<char *> diff
= edit
.generate_diff (false);
1192 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1194 "-foo = bar.m_field;\n"
1195 "+foo = bar.field;\n"
1196 " /* after */\n", diff
);
1200 /* Test applying a "remove" fixit. */
1203 test_applying_fixits_remove (const line_table_case
&case_
)
1205 /* Create a tempfile and write some text to it.
1206 .........................000000000111111111.
1207 .........................123456789012345678. */
1208 const char *old_content
= ("/* before */\n"
1209 "foo = bar.m_field;\n"
1211 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
1212 const char *filename
= tmp
.get_filename ();
1213 line_table_test
ltt (case_
);
1214 linemap_add (line_table
, LC_ENTER
, false, filename
, 2);
1216 /* Remove ".m_field". */
1217 location_t start
= linemap_position_for_column (line_table
, 10);
1218 location_t finish
= linemap_position_for_column (line_table
, 17);
1219 rich_location
richloc (line_table
, start
);
1221 range
.m_start
= start
;
1222 range
.m_finish
= finish
;
1223 richloc
.add_fixit_remove (range
);
1226 edit
.add_fixits (&richloc
);
1227 auto_free
<char *> new_content
= edit
.get_content (filename
);
1228 if (finish
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
1230 ASSERT_STREQ ("/* before */\n"
1232 "/* after */\n", new_content
);
1234 /* Verify location of ";" after the change. */
1235 ASSERT_EQ (10, edit
.get_effective_column (filename
, 2, 18));
1238 auto_free
<char *> diff
= edit
.generate_diff (false);
1239 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1241 "-foo = bar.m_field;\n"
1243 " /* after */\n", diff
);
1247 /* Test applying multiple fixits to one line. */
1250 test_applying_fixits_multiple (const line_table_case
&case_
)
1252 /* Create a tempfile and write some text to it.
1253 .........................00000000011111111.
1254 .........................12345678901234567. */
1255 const char *old_content
= ("/* before */\n"
1256 "foo = bar.field;\n"
1258 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
1259 const char *filename
= tmp
.get_filename ();
1260 line_table_test
ltt (case_
);
1261 linemap_add (line_table
, LC_ENTER
, false, filename
, 2);
1263 location_t c7
= linemap_position_for_column (line_table
, 7);
1264 location_t c9
= linemap_position_for_column (line_table
, 9);
1265 location_t c11
= linemap_position_for_column (line_table
, 11);
1266 location_t c15
= linemap_position_for_column (line_table
, 15);
1267 location_t c17
= linemap_position_for_column (line_table
, 17);
1269 if (c17
> LINE_MAP_MAX_LOCATION_WITH_COLS
)
1272 /* Add a comment in front of "bar.field". */
1273 rich_location
insert_a (line_table
, c7
);
1274 insert_a
.add_fixit_insert_before (c7
, "/* alpha */");
1276 /* Add a comment after "bar.field;". */
1277 rich_location
insert_b (line_table
, c17
);
1278 insert_b
.add_fixit_insert_before (c17
, "/* beta */");
1280 /* Replace "bar" with "pub". */
1281 rich_location
replace_a (line_table
, c7
);
1282 replace_a
.add_fixit_replace (source_range::from_locations (c7
, c9
),
1285 /* Replace "field" with "meadow". */
1286 rich_location
replace_b (line_table
, c7
);
1287 replace_b
.add_fixit_replace (source_range::from_locations (c11
, c15
),
1291 edit
.add_fixits (&insert_a
);
1292 ASSERT_EQ (100, edit
.get_effective_column (filename
, 1, 100));
1293 ASSERT_EQ (1, edit
.get_effective_column (filename
, 2, 1));
1294 ASSERT_EQ (6, edit
.get_effective_column (filename
, 2, 6));
1295 ASSERT_EQ (18, edit
.get_effective_column (filename
, 2, 7));
1296 ASSERT_EQ (27, edit
.get_effective_column (filename
, 2, 16));
1297 ASSERT_EQ (100, edit
.get_effective_column (filename
, 3, 100));
1299 edit
.add_fixits (&insert_b
);
1300 edit
.add_fixits (&replace_a
);
1301 edit
.add_fixits (&replace_b
);
1303 if (c17
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
1305 auto_free
<char *> new_content
= edit
.get_content (tmp
.get_filename ());
1306 ASSERT_STREQ ("/* before */\n"
1307 "foo = /* alpha */pub.meadow;/* beta */\n"
1312 auto_free
<char *> diff
= edit
.generate_diff (false);
1313 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1315 "-foo = bar.field;\n"
1316 "+foo = /* alpha */pub.meadow;/* beta */\n"
1317 " /* after */\n", diff
);
1321 /* Subroutine of test_applying_fixits_multiple_lines.
1322 Add the text "CHANGED: " to the front of the given line. */
1325 change_line (edit_context
&edit
, int line_num
)
1327 const line_map_ordinary
*ord_map
1328 = LINEMAPS_LAST_ORDINARY_MAP (line_table
);
1329 const int column
= 1;
1331 linemap_position_for_line_and_column (line_table
, ord_map
,
1334 expanded_location exploc
= expand_location (loc
);
1335 if (loc
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
1337 ASSERT_EQ (line_num
, exploc
.line
);
1338 ASSERT_EQ (column
, exploc
.column
);
1341 rich_location
insert (line_table
, loc
);
1342 insert
.add_fixit_insert_before ("CHANGED: ");
1343 edit
.add_fixits (&insert
);
1347 /* Test of editing multiple lines within a long file,
1348 to ensure that diffs are generated as expected. */
1351 test_applying_fixits_multiple_lines (const line_table_case
&case_
)
1353 /* Create a tempfile and write many lines of text to it. */
1354 named_temp_file
tmp (".txt");
1355 const char *filename
= tmp
.get_filename ();
1356 FILE *f
= fopen (filename
, "w");
1357 ASSERT_NE (f
, NULL
);
1358 for (int i
= 1; i
<= 1000; i
++)
1359 fprintf (f
, "line %i\n", i
);
1362 line_table_test
ltt (case_
);
1363 linemap_add (line_table
, LC_ENTER
, false, filename
, 1);
1364 linemap_position_for_column (line_table
, 127);
1368 /* A run of consecutive lines. */
1369 change_line (edit
, 2);
1370 change_line (edit
, 3);
1371 change_line (edit
, 4);
1373 /* A run of nearby lines, within the contextual limit. */
1374 change_line (edit
, 150);
1375 change_line (edit
, 151);
1376 location_t last_loc
= change_line (edit
, 153);
1378 if (last_loc
> LINE_MAP_MAX_LOCATION_WITH_COLS
)
1382 auto_free
<char *> diff
= edit
.generate_diff (false);
1383 ASSERT_STREQ ("@@ -1,7 +1,7 @@\n"
1388 "+CHANGED: line 2\n"
1389 "+CHANGED: line 3\n"
1390 "+CHANGED: line 4\n"
1394 "@@ -147,10 +147,10 @@\n"
1400 "+CHANGED: line 150\n"
1401 "+CHANGED: line 151\n"
1404 "+CHANGED: line 153\n"
1407 " line 156\n", diff
);
1409 /* Ensure tmp stays alive until this point, so that the tempfile
1410 persists until after the generate_diff call. */
1411 tmp
.get_filename ();
1414 /* Test of converting an initializer for a named field from
1415 the old GCC extension to C99 syntax.
1416 Exercises a shrinking replacement followed by a growing
1417 replacement on the same line. */
1420 test_applying_fixits_modernize_named_init (const line_table_case
&case_
)
1422 /* Create a tempfile and write some text to it.
1423 .........................00000000011111111.
1424 .........................12345678901234567. */
1425 const char *old_content
= ("/* before */\n"
1428 temp_source_file
tmp (SELFTEST_LOCATION
, ".c", old_content
);
1429 const char *filename
= tmp
.get_filename ();
1430 line_table_test
ltt (case_
);
1431 linemap_add (line_table
, LC_ENTER
, false, filename
, 2);
1433 location_t c1
= linemap_position_for_column (line_table
, 1);
1434 location_t c3
= linemap_position_for_column (line_table
, 3);
1435 location_t c8
= linemap_position_for_column (line_table
, 8);
1437 if (c8
> LINE_MAP_MAX_LOCATION_WITH_COLS
)
1440 /* Replace "bar" with ".". */
1441 rich_location
r1 (line_table
, c8
);
1442 r1
.add_fixit_replace (source_range::from_locations (c1
, c3
),
1445 /* Replace ":" with "bar =". */
1446 rich_location
r2 (line_table
, c8
);
1447 r2
.add_fixit_replace (source_range::from_locations (c8
, c8
),
1450 /* The order should not matter. Do r1 then r2. */
1453 edit
.add_fixits (&r1
);
1455 /* Verify state after first replacement. */
1457 auto_free
<char *> new_content
= edit
.get_content (tmp
.get_filename ());
1458 /* We should now have:
1459 ............00000000011.
1460 ............12345678901. */
1461 ASSERT_STREQ ("/* before */\n"
1465 /* Location of the "1". */
1466 ASSERT_EQ (6, edit
.get_effective_column (filename
, 2, 8));
1467 /* Location of the ",". */
1468 ASSERT_EQ (9, edit
.get_effective_column (filename
, 2, 11));
1471 edit
.add_fixits (&r2
);
1473 auto_free
<char *> new_content
= edit
.get_content (tmp
.get_filename ());
1474 /* Verify state after second replacement.
1475 ............00000000011111111.
1476 ............12345678901234567. */
1477 ASSERT_STREQ ("/* before */\n"
1483 /* Try again, doing r2 then r1; the new_content should be the same. */
1486 edit
.add_fixits (&r2
);
1487 edit
.add_fixits (&r1
);
1488 auto_free
<char *> new_content
= edit
.get_content (tmp
.get_filename ());
1489 /*.............00000000011111111.
1490 .............12345678901234567. */
1491 ASSERT_STREQ ("/* before */\n"
1498 /* Test of a fixit affecting a file that can't be read. */
1501 test_applying_fixits_unreadable_file ()
1503 const char *filename
= "this-does-not-exist.txt";
1504 line_table_test
ltt ();
1505 linemap_add (line_table
, LC_ENTER
, false, filename
, 1);
1507 location_t loc
= linemap_position_for_column (line_table
, 1);
1509 rich_location
insert (line_table
, loc
);
1510 insert
.add_fixit_insert_before ("change 1");
1511 insert
.add_fixit_insert_before ("change 2");
1514 /* Attempting to add the fixits affecting the unreadable file
1515 should transition the edit from valid to invalid. */
1516 ASSERT_TRUE (edit
.valid_p ());
1517 edit
.add_fixits (&insert
);
1518 ASSERT_FALSE (edit
.valid_p ());
1519 ASSERT_EQ (NULL
, edit
.get_content (filename
));
1520 ASSERT_EQ (NULL
, edit
.generate_diff (false));
1523 /* Verify that we gracefully handle an attempt to edit a line
1524 that's beyond the end of the file. */
1527 test_applying_fixits_line_out_of_range ()
1529 /* Create a tempfile and write some text to it.
1530 ........................00000000011111111.
1531 ........................12345678901234567. */
1532 const char *old_content
= "One-liner file\n";
1533 temp_source_file
tmp (SELFTEST_LOCATION
, ".txt", old_content
);
1534 const char *filename
= tmp
.get_filename ();
1535 line_table_test
ltt ();
1536 linemap_add (line_table
, LC_ENTER
, false, filename
, 2);
1538 /* Try to insert a string in line 2. */
1539 location_t loc
= linemap_position_for_column (line_table
, 1);
1541 rich_location
insert (line_table
, loc
);
1542 insert
.add_fixit_insert_before ("change");
1544 /* Verify that attempting the insertion puts an edit_context
1545 into an invalid state. */
1547 ASSERT_TRUE (edit
.valid_p ());
1548 edit
.add_fixits (&insert
);
1549 ASSERT_FALSE (edit
.valid_p ());
1550 ASSERT_EQ (NULL
, edit
.get_content (filename
));
1551 ASSERT_EQ (NULL
, edit
.generate_diff (false));
1554 /* Verify the boundary conditions of column values in fix-it
1555 hints applied to edit_context instances. */
1558 test_applying_fixits_column_validation (const line_table_case
&case_
)
1560 /* Create a tempfile and write some text to it.
1561 ........................00000000011111111.
1562 ........................12345678901234567. */
1563 const char *old_content
= "One-liner file\n";
1564 temp_source_file
tmp (SELFTEST_LOCATION
, ".txt", old_content
);
1565 const char *filename
= tmp
.get_filename ();
1566 line_table_test
ltt (case_
);
1567 linemap_add (line_table
, LC_ENTER
, false, filename
, 1);
1569 location_t c11
= linemap_position_for_column (line_table
, 11);
1570 location_t c14
= linemap_position_for_column (line_table
, 14);
1571 location_t c15
= linemap_position_for_column (line_table
, 15);
1572 location_t c16
= linemap_position_for_column (line_table
, 16);
1574 /* Verify limits of valid columns in insertion fixits. */
1576 /* Verify inserting at the end of the line. */
1578 rich_location
richloc (line_table
, c11
);
1579 richloc
.add_fixit_insert_before (c15
, " change");
1581 /* Col 15 is at the end of the line, so the insertion
1584 edit
.add_fixits (&richloc
);
1585 auto_free
<char *> new_content
= edit
.get_content (tmp
.get_filename ());
1586 if (c15
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
1587 ASSERT_STREQ ("One-liner file change\n", new_content
);
1589 ASSERT_EQ (NULL
, new_content
);
1592 /* Verify inserting beyond the end of the line. */
1594 rich_location
richloc (line_table
, c11
);
1595 richloc
.add_fixit_insert_before (c16
, " change");
1597 /* Col 16 is beyond the end of the line, so the insertion
1598 should fail gracefully. */
1600 ASSERT_TRUE (edit
.valid_p ());
1601 edit
.add_fixits (&richloc
);
1602 ASSERT_FALSE (edit
.valid_p ());
1603 ASSERT_EQ (NULL
, edit
.get_content (filename
));
1604 ASSERT_EQ (NULL
, edit
.generate_diff (false));
1607 /* Verify limits of valid columns in replacement fixits. */
1609 /* Verify replacing the end of the line. */
1611 rich_location
richloc (line_table
, c11
);
1612 source_range range
= source_range::from_locations (c11
, c14
);
1613 richloc
.add_fixit_replace (range
, "change");
1615 /* Col 14 is at the end of the line, so the replacement
1618 edit
.add_fixits (&richloc
);
1619 auto_free
<char *> new_content
= edit
.get_content (tmp
.get_filename ());
1620 if (c14
<= LINE_MAP_MAX_LOCATION_WITH_COLS
)
1621 ASSERT_STREQ ("One-liner change\n", new_content
);
1623 ASSERT_EQ (NULL
, new_content
);
1626 /* Verify going beyond the end of the line. */
1628 rich_location
richloc (line_table
, c11
);
1629 source_range range
= source_range::from_locations (c11
, c15
);
1630 richloc
.add_fixit_replace (range
, "change");
1632 /* Col 15 is after the end of the line, so the replacement
1633 should fail; verify that the attempt fails gracefully. */
1635 ASSERT_TRUE (edit
.valid_p ());
1636 edit
.add_fixits (&richloc
);
1637 ASSERT_FALSE (edit
.valid_p ());
1638 ASSERT_EQ (NULL
, edit
.get_content (filename
));
1639 ASSERT_EQ (NULL
, edit
.generate_diff (false));
1643 /* Run all of the selftests within this file. */
1646 edit_context_c_tests ()
1648 test_get_content ();
1649 for_each_line_table_case (test_applying_fixits_insert_before
);
1650 for_each_line_table_case (test_applying_fixits_insert_after
);
1651 for_each_line_table_case (test_applying_fixits_insert_after_at_line_end
);
1652 for_each_line_table_case (test_applying_fixits_insert_after_failure
);
1653 for_each_line_table_case (test_applying_fixits_growing_replace
);
1654 for_each_line_table_case (test_applying_fixits_shrinking_replace
);
1655 for_each_line_table_case (test_applying_fixits_remove
);
1656 for_each_line_table_case (test_applying_fixits_multiple
);
1657 for_each_line_table_case (test_applying_fixits_multiple_lines
);
1658 for_each_line_table_case (test_applying_fixits_modernize_named_init
);
1659 test_applying_fixits_unreadable_file ();
1660 test_applying_fixits_line_out_of_range ();
1661 for_each_line_table_case (test_applying_fixits_column_validation
);
1664 } // namespace selftest
1666 #endif /* CHECKING_P */