[42/46] Add vec_info::replace_stmt
[official-gcc.git] / gcc / edit-context.c
blob3cdb88d565cebaf615929a1461e8e97a0e8cee23
1 /* Determining the results of applying fix-it hints.
2 Copyright (C) 2016-2018 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
9 version.
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
14 for more details.
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/>. */
20 #include "config.h"
21 #include "system.h"
22 #include "coretypes.h"
23 #include "line-map.h"
24 #include "edit-context.h"
25 #include "pretty-print.h"
26 #include "diagnostic-color.h"
27 #include "selftest.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
31 edit_context.
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. */
44 class edit_context;
45 class edited_file;
46 class edited_line;
47 class line_event;
49 /* A struct to hold the params of a print_diff call. */
51 struct diff
53 diff (pretty_printer *pp, bool show_filenames)
54 : m_pp (pp), m_show_filenames (show_filenames) {}
56 pretty_printer *m_pp;
57 bool m_show_filenames;
60 /* The state of one named file within an edit_context: the filename,
61 and the lines that have been edited so far. */
63 class edited_file
65 public:
66 edited_file (const char *filename);
67 static void delete_cb (edited_file *file);
69 const char *get_filename () const { return m_filename; }
70 char *get_content ();
72 bool apply_fixit (int line, int start_column,
73 int next_column,
74 const char *replacement_str,
75 int replacement_len);
76 int get_effective_column (int line, int column);
78 static int call_print_diff (const char *, edited_file *file,
79 void *user_data)
81 diff *d = (diff *)user_data;
82 file->print_diff (d->m_pp, d->m_show_filenames);
83 return 0;
86 private:
87 bool print_content (pretty_printer *pp);
88 void print_diff (pretty_printer *pp, bool show_filenames);
89 int print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
90 int old_end_of_hunk, int new_start_of_hunk);
91 edited_line *get_line (int line);
92 edited_line *get_or_insert_line (int line);
93 int get_num_lines (bool *missing_trailing_newline);
95 int get_effective_line_count (int old_start_of_hunk,
96 int old_end_of_hunk);
98 void print_run_of_changed_lines (pretty_printer *pp,
99 int start_of_run,
100 int end_of_run);
102 const char *m_filename;
103 typed_splay_tree<int, edited_line *> m_edited_lines;
104 int m_num_lines;
107 /* A line added before an edited_line. */
109 class added_line
111 public:
112 added_line (const char *content, int len)
113 : m_content (xstrndup (content, len)), m_len (len) {}
114 ~added_line () { free (m_content); }
116 const char *get_content () const { return m_content; }
117 int get_len () const { return m_len; }
119 private:
120 char *m_content;
121 int m_len;
124 /* The state of one edited line within an edited_file.
125 As well as the current content of the line, it contains a record of
126 the changes, so that further changes can be applied in the correct
127 place.
129 When handling fix-it hints containing newlines, new lines are added
130 as added_line predecessors to an edited_line. Hence it's possible
131 for an "edited_line" to not actually have been changed, but to merely
132 be a placeholder for the lines added before it. This can be tested
133 for with actuall_edited_p, and has a slight effect on how diff hunks
134 are generated. */
136 class edited_line
138 public:
139 edited_line (const char *filename, int line_num);
140 ~edited_line ();
141 static void delete_cb (edited_line *el);
143 int get_line_num () const { return m_line_num; }
144 const char *get_content () const { return m_content; }
145 int get_len () const { return m_len; }
147 int get_effective_column (int orig_column) const;
148 bool apply_fixit (int start_column,
149 int next_column,
150 const char *replacement_str,
151 int replacement_len);
153 int get_effective_line_count () const;
155 /* Has the content of this line actually changed, or are we merely
156 recording predecessor added_lines? */
157 bool actually_edited_p () const { return m_line_events.length () > 0; }
159 void print_content (pretty_printer *pp) const;
160 void print_diff_lines (pretty_printer *pp) const;
162 private:
163 void ensure_capacity (int len);
164 void ensure_terminated ();
166 int m_line_num;
167 char *m_content;
168 int m_len;
169 int m_alloc_sz;
170 auto_vec <line_event> m_line_events;
171 auto_vec <added_line *> m_predecessors;
174 /* Class for representing edit events that have occurred on one line of
175 one file: the replacement of some text betweeen some columns
176 on the line.
178 Subsequent events will need their columns adjusting if they're
179 are on this line and their column is >= the start point. */
181 class line_event
183 public:
184 line_event (int start, int next, int len) : m_start (start),
185 m_next (next), m_delta (len - (next - start)) {}
187 int get_effective_column (int orig_column) const
189 if (orig_column >= m_start)
190 return orig_column += m_delta;
191 else
192 return orig_column;
195 private:
196 int m_start;
197 int m_next;
198 int m_delta;
201 /* Forward decls. */
203 static void
204 print_diff_line (pretty_printer *pp, char prefix_char,
205 const char *line, int line_size);
207 /* Implementation of class edit_context. */
209 /* edit_context's ctor. */
211 edit_context::edit_context ()
212 : m_valid (true),
213 m_files (strcmp, NULL, edited_file::delete_cb)
216 /* Add any fixits within RICHLOC to this context, recording the
217 changes that they make. */
219 void
220 edit_context::add_fixits (rich_location *richloc)
222 if (!m_valid)
223 return;
224 if (richloc->seen_impossible_fixit_p ())
226 m_valid = false;
227 return;
229 for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
231 const fixit_hint *hint = richloc->get_fixit_hint (i);
232 if (!apply_fixit (hint))
233 m_valid = false;
237 /* Get the content of the given file, with fix-its applied.
238 If any errors occurred in this edit_context, return NULL.
239 The ptr should be freed by the caller. */
241 char *
242 edit_context::get_content (const char *filename)
244 if (!m_valid)
245 return NULL;
246 edited_file &file = get_or_insert_file (filename);
247 return file.get_content ();
250 /* Map a location before the edits to a column number after the edits.
251 This method is for the selftests. */
254 edit_context::get_effective_column (const char *filename, int line,
255 int column)
257 edited_file *file = get_file (filename);
258 if (!file)
259 return column;
260 return file->get_effective_column (line, column);
263 /* Generate a unified diff. The resulting string should be freed by the
264 caller. Primarily for selftests.
265 If any errors occurred in this edit_context, return NULL. */
267 char *
268 edit_context::generate_diff (bool show_filenames)
270 if (!m_valid)
271 return NULL;
273 pretty_printer pp;
274 print_diff (&pp, show_filenames);
275 return xstrdup (pp_formatted_text (&pp));
278 /* Print a unified diff to PP, showing the changes made within the
279 context. */
281 void
282 edit_context::print_diff (pretty_printer *pp, bool show_filenames)
284 if (!m_valid)
285 return;
286 diff d (pp, show_filenames);
287 m_files.foreach (edited_file::call_print_diff, &d);
290 /* Attempt to apply the given fixit. Return true if it can be
291 applied, or false otherwise. */
293 bool
294 edit_context::apply_fixit (const fixit_hint *hint)
296 expanded_location start = expand_location (hint->get_start_loc ());
297 expanded_location next_loc = expand_location (hint->get_next_loc ());
298 if (start.file != next_loc.file)
299 return false;
300 if (start.line != next_loc.line)
301 return false;
302 if (start.column == 0)
303 return false;
304 if (next_loc.column == 0)
305 return false;
307 edited_file &file = get_or_insert_file (start.file);
308 if (!m_valid)
309 return false;
310 return file.apply_fixit (start.line, start.column, next_loc.column,
311 hint->get_string (),
312 hint->get_length ());
315 /* Locate the edited_file * for FILENAME, if any
316 Return NULL if there isn't one. */
318 edited_file *
319 edit_context::get_file (const char *filename)
321 gcc_assert (filename);
322 return m_files.lookup (filename);
325 /* Locate the edited_file for FILENAME, adding one if there isn't one. */
327 edited_file &
328 edit_context::get_or_insert_file (const char *filename)
330 gcc_assert (filename);
332 edited_file *file = get_file (filename);
333 if (file)
334 return *file;
336 /* Not found. */
337 file = new edited_file (filename);
338 m_files.insert (filename, file);
339 return *file;
342 /* Implementation of class edited_file. */
344 /* Callback for m_edited_lines, for comparing line numbers. */
346 static int line_comparator (int a, int b)
348 return a - b;
351 /* edited_file's constructor. */
353 edited_file::edited_file (const char *filename)
354 : m_filename (filename),
355 m_edited_lines (line_comparator, NULL, edited_line::delete_cb),
356 m_num_lines (-1)
360 /* A callback for deleting edited_file *, for use as a
361 delete_value_fn for edit_context::m_files. */
363 void
364 edited_file::delete_cb (edited_file *file)
366 delete file;
369 /* Get the content of the file, with fix-its applied.
370 The ptr should be freed by the caller. */
372 char *
373 edited_file::get_content ()
375 pretty_printer pp;
376 if (!print_content (&pp))
377 return NULL;
378 return xstrdup (pp_formatted_text (&pp));
381 /* Attempt to replace columns START_COLUMN up to but not including NEXT_COLUMN
382 of LINE with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
383 updating the in-memory copy of the line, and the record of edits to
384 the line. */
386 bool
387 edited_file::apply_fixit (int line, int start_column, int next_column,
388 const char *replacement_str,
389 int replacement_len)
391 edited_line *el = get_or_insert_line (line);
392 if (!el)
393 return false;
394 return el->apply_fixit (start_column, next_column, replacement_str,
395 replacement_len);
398 /* Given line LINE, map from COLUMN in the input file to its current
399 column after edits have been applied. */
402 edited_file::get_effective_column (int line, int column)
404 const edited_line *el = get_line (line);
405 if (!el)
406 return column;
407 return el->get_effective_column (column);
410 /* Attempt to print the content of the file to PP, with edits applied.
411 Return true if successful, false otherwise. */
413 bool
414 edited_file::print_content (pretty_printer *pp)
416 bool missing_trailing_newline;
417 int line_count = get_num_lines (&missing_trailing_newline);
418 for (int line_num = 1; line_num <= line_count; line_num++)
420 edited_line *el = get_line (line_num);
421 if (el)
422 el->print_content (pp);
423 else
425 char_span line = location_get_source_line (m_filename, line_num);
426 if (!line)
427 return false;
428 for (size_t i = 0; i < line.length (); i++)
429 pp_character (pp, line[i]);
431 if (line_num < line_count)
432 pp_character (pp, '\n');
435 if (!missing_trailing_newline)
436 pp_character (pp, '\n');
438 return true;
441 /* Print a unified diff to PP, showing any changes that have occurred
442 to this file. */
444 void
445 edited_file::print_diff (pretty_printer *pp, bool show_filenames)
447 if (show_filenames)
449 pp_string (pp, colorize_start (pp_show_color (pp), "diff-filename"));
450 pp_printf (pp, "--- %s\n", m_filename);
451 pp_printf (pp, "+++ %s\n", m_filename);
452 pp_string (pp, colorize_stop (pp_show_color (pp)));
455 edited_line *el = m_edited_lines.min ();
457 bool missing_trailing_newline;
458 int line_count = get_num_lines (&missing_trailing_newline);
460 const int context_lines = 3;
462 /* Track new line numbers minus old line numbers. */
464 int line_delta = 0;
466 while (el)
468 int start_of_hunk = el->get_line_num ();
469 start_of_hunk -= context_lines;
470 if (start_of_hunk < 1)
471 start_of_hunk = 1;
473 /* Locate end of hunk, merging in changed lines
474 that are sufficiently close. */
475 while (true)
477 edited_line *next_el
478 = m_edited_lines.successor (el->get_line_num ());
479 if (!next_el)
480 break;
482 int end_of_printed_hunk = el->get_line_num () + context_lines;
483 if (!el->actually_edited_p ())
484 end_of_printed_hunk--;
486 if (end_of_printed_hunk
487 >= next_el->get_line_num () - context_lines)
488 el = next_el;
489 else
490 break;
493 int end_of_hunk = el->get_line_num ();
494 end_of_hunk += context_lines;
495 if (!el->actually_edited_p ())
496 end_of_hunk--;
497 if (end_of_hunk > line_count)
498 end_of_hunk = line_count;
500 int new_start_of_hunk = start_of_hunk + line_delta;
501 line_delta += print_diff_hunk (pp, start_of_hunk, end_of_hunk,
502 new_start_of_hunk);
503 el = m_edited_lines.successor (el->get_line_num ());
507 /* Print one hunk within a unified diff to PP, covering the
508 given range of lines. OLD_START_OF_HUNK and OLD_END_OF_HUNK are
509 line numbers in the unedited version of the file.
510 NEW_START_OF_HUNK is a line number in the edited version of the file.
511 Return the change in the line count within the hunk. */
514 edited_file::print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
515 int old_end_of_hunk, int new_start_of_hunk)
517 int old_num_lines = old_end_of_hunk - old_start_of_hunk + 1;
518 int new_num_lines
519 = get_effective_line_count (old_start_of_hunk, old_end_of_hunk);
521 pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
522 pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", old_start_of_hunk, old_num_lines,
523 new_start_of_hunk, new_num_lines);
524 pp_string (pp, colorize_stop (pp_show_color (pp)));
526 int line_num = old_start_of_hunk;
527 while (line_num <= old_end_of_hunk)
529 edited_line *el = get_line (line_num);
530 if (el)
532 /* We have an edited line.
533 Consolidate into runs of changed lines. */
534 const int first_changed_line_in_run = line_num;
535 while (get_line (line_num))
536 line_num++;
537 const int last_changed_line_in_run = line_num - 1;
538 print_run_of_changed_lines (pp, first_changed_line_in_run,
539 last_changed_line_in_run);
541 else
543 /* Unchanged line. */
544 char_span old_line = location_get_source_line (m_filename, line_num);
545 print_diff_line (pp, ' ', old_line.get_buffer (), old_line.length ());
546 line_num++;
550 return new_num_lines - old_num_lines;
553 /* Subroutine of edited_file::print_diff_hunk: given a run of lines
554 from START_OF_RUN to END_OF_RUN that all have edited_line instances,
555 print the diff to PP. */
557 void
558 edited_file::print_run_of_changed_lines (pretty_printer *pp,
559 int start_of_run,
560 int end_of_run)
562 /* Show old version of lines. */
563 pp_string (pp, colorize_start (pp_show_color (pp),
564 "diff-delete"));
565 for (int line_num = start_of_run;
566 line_num <= end_of_run;
567 line_num++)
569 edited_line *el_in_run = get_line (line_num);
570 gcc_assert (el_in_run);
571 if (el_in_run->actually_edited_p ())
573 char_span old_line = location_get_source_line (m_filename, line_num);
574 print_diff_line (pp, '-', old_line.get_buffer (),
575 old_line.length ());
578 pp_string (pp, colorize_stop (pp_show_color (pp)));
580 /* Show new version of lines. */
581 pp_string (pp, colorize_start (pp_show_color (pp),
582 "diff-insert"));
583 for (int line_num = start_of_run;
584 line_num <= end_of_run;
585 line_num++)
587 edited_line *el_in_run = get_line (line_num);
588 gcc_assert (el_in_run);
589 el_in_run->print_diff_lines (pp);
591 pp_string (pp, colorize_stop (pp_show_color (pp)));
594 /* Print one line within a diff, starting with PREFIX_CHAR,
595 followed by the LINE of content, of length LEN. LINE is
596 not necessarily 0-terminated. Print a trailing newline. */
598 static void
599 print_diff_line (pretty_printer *pp, char prefix_char,
600 const char *line, int len)
602 pp_character (pp, prefix_char);
603 for (int i = 0; i < len; i++)
604 pp_character (pp, line[i]);
605 pp_character (pp, '\n');
608 /* Determine the number of lines that will be present after
609 editing for the range of lines from OLD_START_OF_HUNK to
610 OLD_END_OF_HUNK inclusive. */
613 edited_file::get_effective_line_count (int old_start_of_hunk,
614 int old_end_of_hunk)
616 int line_count = 0;
617 for (int old_line_num = old_start_of_hunk; old_line_num <= old_end_of_hunk;
618 old_line_num++)
620 edited_line *el = get_line (old_line_num);
621 if (el)
622 line_count += el->get_effective_line_count ();
623 else
624 line_count++;
626 return line_count;
629 /* Get the state of LINE within the file, or NULL if it is untouched. */
631 edited_line *
632 edited_file::get_line (int line)
634 return m_edited_lines.lookup (line);
637 /* Get the state of LINE within the file, creating a state for it
638 if necessary. Return NULL if an error occurs. */
640 edited_line *
641 edited_file::get_or_insert_line (int line)
643 edited_line *el = get_line (line);
644 if (el)
645 return el;
646 el = new edited_line (m_filename, line);
647 if (el->get_content () == NULL)
649 delete el;
650 return NULL;
652 m_edited_lines.insert (line, el);
653 return el;
656 /* Get the total number of lines in m_content, writing
657 true to *MISSING_TRAILING_NEWLINE if the final line
658 if missing a newline, false otherwise. */
661 edited_file::get_num_lines (bool *missing_trailing_newline)
663 gcc_assert (missing_trailing_newline);
664 if (m_num_lines == -1)
666 m_num_lines = 0;
667 while (true)
669 char_span line
670 = location_get_source_line (m_filename, m_num_lines + 1);
671 if (line)
672 m_num_lines++;
673 else
674 break;
677 *missing_trailing_newline = location_missing_trailing_newline (m_filename);
678 return m_num_lines;
681 /* Implementation of class edited_line. */
683 /* edited_line's ctor. */
685 edited_line::edited_line (const char *filename, int line_num)
686 : m_line_num (line_num),
687 m_content (NULL), m_len (0), m_alloc_sz (0),
688 m_line_events (),
689 m_predecessors ()
691 char_span line = location_get_source_line (filename, line_num);
692 if (!line)
693 return;
694 m_len = line.length ();
695 ensure_capacity (m_len);
696 memcpy (m_content, line.get_buffer (), m_len);
697 ensure_terminated ();
700 /* edited_line's dtor. */
702 edited_line::~edited_line ()
704 unsigned i;
705 added_line *pred;
707 free (m_content);
708 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
709 delete pred;
712 /* A callback for deleting edited_line *, for use as a
713 delete_value_fn for edited_file::m_edited_lines. */
715 void
716 edited_line::delete_cb (edited_line *el)
718 delete el;
721 /* Map a location before the edits to a column number after the edits,
722 within a specific line. */
725 edited_line::get_effective_column (int orig_column) const
727 int i;
728 line_event *event;
729 FOR_EACH_VEC_ELT (m_line_events, i, event)
730 orig_column = event->get_effective_column (orig_column);
731 return orig_column;
734 /* Attempt to replace columns START_COLUMN up to but not including
735 NEXT_COLUMN of the line with the string REPLACEMENT_STR of
736 length REPLACEMENT_LEN, updating the in-memory copy of the line,
737 and the record of edits to the line.
738 Return true if successful; false if an error occurred. */
740 bool
741 edited_line::apply_fixit (int start_column,
742 int next_column,
743 const char *replacement_str,
744 int replacement_len)
746 /* Handle newlines. They will only ever be at the end of the
747 replacement text, thanks to the filtering in rich_location. */
748 if (replacement_len > 1)
749 if (replacement_str[replacement_len - 1] == '\n')
751 /* Stash in m_predecessors, stripping off newline. */
752 m_predecessors.safe_push (new added_line (replacement_str,
753 replacement_len - 1));
754 return true;
757 start_column = get_effective_column (start_column);
758 next_column = get_effective_column (next_column);
760 int start_offset = start_column - 1;
761 int next_offset = next_column - 1;
763 gcc_assert (start_offset >= 0);
764 gcc_assert (next_offset >= 0);
766 if (start_column > next_column)
767 return false;
768 if (start_offset >= (m_len + 1))
769 return false;
770 if (next_offset >= (m_len + 1))
771 return false;
773 size_t victim_len = next_offset - start_offset;
775 /* Ensure buffer is big enough. */
776 size_t new_len = m_len + replacement_len - victim_len;
777 ensure_capacity (new_len);
779 char *suffix = m_content + next_offset;
780 gcc_assert (suffix <= m_content + m_len);
781 size_t len_suffix = (m_content + m_len) - suffix;
783 /* Move successor content into position. They overlap, so use memmove. */
784 memmove (m_content + start_offset + replacement_len,
785 suffix, len_suffix);
787 /* Replace target content. They don't overlap, so use memcpy. */
788 memcpy (m_content + start_offset,
789 replacement_str,
790 replacement_len);
792 m_len = new_len;
794 ensure_terminated ();
796 /* Record the replacement, so that future changes to the line can have
797 their column information adjusted accordingly. */
798 m_line_events.safe_push (line_event (start_column, next_column,
799 replacement_len));
800 return true;
803 /* Determine the number of lines that will be present after
804 editing for this line. Typically this is just 1, but
805 if newlines have been added before this line, they will
806 also be counted. */
809 edited_line::get_effective_line_count () const
811 return m_predecessors.length () + 1;
814 /* Subroutine of edited_file::print_content.
815 Print this line and any new lines added before it, to PP. */
817 void
818 edited_line::print_content (pretty_printer *pp) const
820 unsigned i;
821 added_line *pred;
822 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
824 pp_string (pp, pred->get_content ());
825 pp_newline (pp);
827 pp_string (pp, m_content);
830 /* Subroutine of edited_file::print_run_of_changed_lines for
831 printing diff hunks to PP.
832 Print the '+' line for this line, and any newlines added
833 before it.
834 Note that if this edited_line was actually edited, the '-'
835 line has already been printed. If it wasn't, then we merely
836 have a placeholder edited_line for adding newlines to, and
837 we need to print a ' ' line for the edited_line as we haven't
838 printed it yet. */
840 void
841 edited_line::print_diff_lines (pretty_printer *pp) const
843 unsigned i;
844 added_line *pred;
845 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
846 print_diff_line (pp, '+', pred->get_content (),
847 pred->get_len ());
848 if (actually_edited_p ())
849 print_diff_line (pp, '+', m_content, m_len);
850 else
851 print_diff_line (pp, ' ', m_content, m_len);
854 /* Ensure that the buffer for m_content is at least large enough to hold
855 a string of length LEN and its 0-terminator, doubling on repeated
856 allocations. */
858 void
859 edited_line::ensure_capacity (int len)
861 /* Allow 1 extra byte for 0-termination. */
862 if (m_alloc_sz < (len + 1))
864 size_t new_alloc_sz = (len + 1) * 2;
865 m_content = (char *)xrealloc (m_content, new_alloc_sz);
866 m_alloc_sz = new_alloc_sz;
870 /* Ensure that m_content is 0-terminated. */
872 void
873 edited_line::ensure_terminated ()
875 /* 0-terminate the buffer. */
876 gcc_assert (m_len < m_alloc_sz);
877 m_content[m_len] = '\0';
880 #if CHECKING_P
882 /* Selftests of code-editing. */
884 namespace selftest {
886 /* A wrapper class for ensuring that the underlying pointer is freed. */
888 template <typename POINTER_T>
889 class auto_free
891 public:
892 auto_free (POINTER_T p) : m_ptr (p) {}
893 ~auto_free () { free (m_ptr); }
895 operator POINTER_T () { return m_ptr; }
897 private:
898 POINTER_T m_ptr;
901 /* Verify that edit_context::get_content works for unedited files. */
903 static void
904 test_get_content ()
906 /* Test of empty file. */
908 const char *content = ("");
909 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
910 edit_context edit;
911 auto_free <char *> result = edit.get_content (tmp.get_filename ());
912 ASSERT_STREQ ("", result);
915 /* Test of simple content. */
917 const char *content = ("/* before */\n"
918 "foo = bar.field;\n"
919 "/* after */\n");
920 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
921 edit_context edit;
922 auto_free <char *> result = edit.get_content (tmp.get_filename ());
923 ASSERT_STREQ ("/* before */\n"
924 "foo = bar.field;\n"
925 "/* after */\n", result);
928 /* Test of omitting the trailing newline on the final line. */
930 const char *content = ("/* before */\n"
931 "foo = bar.field;\n"
932 "/* after */");
933 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
934 edit_context edit;
935 auto_free <char *> result = edit.get_content (tmp.get_filename ());
936 /* We should respect the omitted trailing newline. */
937 ASSERT_STREQ ("/* before */\n"
938 "foo = bar.field;\n"
939 "/* after */", result);
943 /* Test applying an "insert" fixit, using insert_before. */
945 static void
946 test_applying_fixits_insert_before (const line_table_case &case_)
948 /* Create a tempfile and write some text to it.
949 .........................0000000001111111.
950 .........................1234567890123456. */
951 const char *old_content = ("/* before */\n"
952 "foo = bar.field;\n"
953 "/* after */\n");
954 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
955 const char *filename = tmp.get_filename ();
956 line_table_test ltt (case_);
957 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
959 /* Add a comment in front of "bar.field". */
960 location_t start = linemap_position_for_column (line_table, 7);
961 rich_location richloc (line_table, start);
962 richloc.add_fixit_insert_before ("/* inserted */");
964 if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
965 return;
967 edit_context edit;
968 edit.add_fixits (&richloc);
969 auto_free <char *> new_content = edit.get_content (filename);
970 if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
971 ASSERT_STREQ ("/* before */\n"
972 "foo = /* inserted */bar.field;\n"
973 "/* after */\n", new_content);
975 /* Verify that locations on other lines aren't affected by the change. */
976 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
977 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
979 /* Verify locations on the line before the change. */
980 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
981 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
983 /* Verify locations on the line at and after the change. */
984 ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
985 ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));
987 /* Verify diff. */
988 auto_free <char *> diff = edit.generate_diff (false);
989 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
990 " /* before */\n"
991 "-foo = bar.field;\n"
992 "+foo = /* inserted */bar.field;\n"
993 " /* after */\n", diff);
996 /* Test applying an "insert" fixit, using insert_after, with
997 a range of length > 1 (to ensure that the end-point of
998 the input range is used). */
1000 static void
1001 test_applying_fixits_insert_after (const line_table_case &case_)
1003 /* Create a tempfile and write some text to it.
1004 .........................0000000001111111.
1005 .........................1234567890123456. */
1006 const char *old_content = ("/* before */\n"
1007 "foo = bar.field;\n"
1008 "/* after */\n");
1009 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1010 const char *filename = tmp.get_filename ();
1011 line_table_test ltt (case_);
1012 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1014 /* Add a comment after "field". */
1015 location_t start = linemap_position_for_column (line_table, 11);
1016 location_t finish = linemap_position_for_column (line_table, 15);
1017 location_t field = make_location (start, start, finish);
1018 rich_location richloc (line_table, field);
1019 richloc.add_fixit_insert_after ("/* inserted */");
1021 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1022 return;
1024 /* Verify that the text was inserted after the end of "field". */
1025 edit_context edit;
1026 edit.add_fixits (&richloc);
1027 auto_free <char *> new_content = edit.get_content (filename);
1028 ASSERT_STREQ ("/* before */\n"
1029 "foo = bar.field/* inserted */;\n"
1030 "/* after */\n", new_content);
1032 /* Verify diff. */
1033 auto_free <char *> diff = edit.generate_diff (false);
1034 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1035 " /* before */\n"
1036 "-foo = bar.field;\n"
1037 "+foo = bar.field/* inserted */;\n"
1038 " /* after */\n", diff);
1041 /* Test applying an "insert" fixit, using insert_after at the end of
1042 a line (contrast with test_applying_fixits_insert_after_failure
1043 below). */
1045 static void
1046 test_applying_fixits_insert_after_at_line_end (const line_table_case &case_)
1048 /* Create a tempfile and write some text to it.
1049 .........................0000000001111111.
1050 .........................1234567890123456. */
1051 const char *old_content = ("/* before */\n"
1052 "foo = bar.field;\n"
1053 "/* after */\n");
1054 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1055 const char *filename = tmp.get_filename ();
1056 line_table_test ltt (case_);
1057 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1059 /* Add a comment after the semicolon. */
1060 location_t loc = linemap_position_for_column (line_table, 16);
1061 rich_location richloc (line_table, loc);
1062 richloc.add_fixit_insert_after ("/* inserted */");
1064 if (loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1065 return;
1067 edit_context edit;
1068 edit.add_fixits (&richloc);
1069 auto_free <char *> new_content = edit.get_content (filename);
1070 ASSERT_STREQ ("/* before */\n"
1071 "foo = bar.field;/* inserted */\n"
1072 "/* after */\n", new_content);
1074 /* Verify diff. */
1075 auto_free <char *> diff = edit.generate_diff (false);
1076 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1077 " /* before */\n"
1078 "-foo = bar.field;\n"
1079 "+foo = bar.field;/* inserted */\n"
1080 " /* after */\n", diff);
1083 /* Test of a failed attempt to apply an "insert" fixit, using insert_after,
1084 due to the relevant linemap ending. Contrast with
1085 test_applying_fixits_insert_after_at_line_end above. */
1087 static void
1088 test_applying_fixits_insert_after_failure (const line_table_case &case_)
1090 /* Create a tempfile and write some text to it.
1091 .........................0000000001111111.
1092 .........................1234567890123456. */
1093 const char *old_content = ("/* before */\n"
1094 "foo = bar.field;\n"
1095 "/* after */\n");
1096 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1097 const char *filename = tmp.get_filename ();
1098 line_table_test ltt (case_);
1099 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1101 /* Add a comment after the semicolon. */
1102 location_t loc = linemap_position_for_column (line_table, 16);
1103 rich_location richloc (line_table, loc);
1105 /* We want a failure of linemap_position_for_loc_and_offset.
1106 We can do this by starting a new linemap at line 3, so that
1107 there is no appropriate location value for the insertion point
1108 within the linemap for line 2. */
1109 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1111 /* The failure fails to happen at the transition point from
1112 packed ranges to unpacked ranges (where there are some "spare"
1113 location_t values). Skip the test there. */
1114 if (loc >= LINE_MAP_MAX_LOCATION_WITH_PACKED_RANGES)
1115 return;
1117 /* Offsetting "loc" should now fail (by returning the input loc. */
1118 ASSERT_EQ (loc, linemap_position_for_loc_and_offset (line_table, loc, 1));
1120 /* Hence attempting to use add_fixit_insert_after at the end of the line
1121 should now fail. */
1122 richloc.add_fixit_insert_after ("/* inserted */");
1123 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1125 edit_context edit;
1126 edit.add_fixits (&richloc);
1127 ASSERT_FALSE (edit.valid_p ());
1128 ASSERT_EQ (NULL, edit.get_content (filename));
1129 ASSERT_EQ (NULL, edit.generate_diff (false));
1132 /* Test applying an "insert" fixit that adds a newline. */
1134 static void
1135 test_applying_fixits_insert_containing_newline (const line_table_case &case_)
1137 /* Create a tempfile and write some text to it.
1138 .........................0000000001111111.
1139 .........................1234567890123456. */
1140 const char *old_content = (" case 'a':\n" /* line 1. */
1141 " x = a;\n" /* line 2. */
1142 " case 'b':\n" /* line 3. */
1143 " x = b;\n");/* line 4. */
1145 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1146 const char *filename = tmp.get_filename ();
1147 line_table_test ltt (case_);
1148 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1150 /* Add a "break;" on a line by itself before line 3 i.e. before
1151 column 1 of line 3. */
1152 location_t case_start = linemap_position_for_column (line_table, 5);
1153 location_t case_finish = linemap_position_for_column (line_table, 13);
1154 location_t case_loc = make_location (case_start, case_start, case_finish);
1155 rich_location richloc (line_table, case_loc);
1156 location_t line_start = linemap_position_for_column (line_table, 1);
1157 richloc.add_fixit_insert_before (line_start, " break;\n");
1159 if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1160 return;
1162 edit_context edit;
1163 edit.add_fixits (&richloc);
1164 auto_free <char *> new_content = edit.get_content (filename);
1165 ASSERT_STREQ ((" case 'a':\n"
1166 " x = a;\n"
1167 " break;\n"
1168 " case 'b':\n"
1169 " x = b;\n"),
1170 new_content);
1172 /* Verify diff. */
1173 auto_free <char *> diff = edit.generate_diff (false);
1174 ASSERT_STREQ (("@@ -1,4 +1,5 @@\n"
1175 " case 'a':\n"
1176 " x = a;\n"
1177 "+ break;\n"
1178 " case 'b':\n"
1179 " x = b;\n"),
1180 diff);
1183 /* Test applying a "replace" fixit that grows the affected line. */
1185 static void
1186 test_applying_fixits_growing_replace (const line_table_case &case_)
1188 /* Create a tempfile and write some text to it.
1189 .........................0000000001111111.
1190 .........................1234567890123456. */
1191 const char *old_content = ("/* before */\n"
1192 "foo = bar.field;\n"
1193 "/* after */\n");
1194 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1195 const char *filename = tmp.get_filename ();
1196 line_table_test ltt (case_);
1197 linemap_add (line_table, LC_ENTER, false, filename, 2);
1199 /* Replace "field" with "m_field". */
1200 location_t start = linemap_position_for_column (line_table, 11);
1201 location_t finish = linemap_position_for_column (line_table, 15);
1202 location_t field = make_location (start, start, finish);
1203 rich_location richloc (line_table, field);
1204 richloc.add_fixit_replace ("m_field");
1206 edit_context edit;
1207 edit.add_fixits (&richloc);
1208 auto_free <char *> new_content = edit.get_content (filename);
1209 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1211 ASSERT_STREQ ("/* before */\n"
1212 "foo = bar.m_field;\n"
1213 "/* after */\n", new_content);
1215 /* Verify location of ";" after the change. */
1216 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 16));
1218 /* Verify diff. */
1219 auto_free <char *> diff = edit.generate_diff (false);
1220 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1221 " /* before */\n"
1222 "-foo = bar.field;\n"
1223 "+foo = bar.m_field;\n"
1224 " /* after */\n", diff);
1228 /* Test applying a "replace" fixit that shrinks the affected line. */
1230 static void
1231 test_applying_fixits_shrinking_replace (const line_table_case &case_)
1233 /* Create a tempfile and write some text to it.
1234 .........................000000000111111111.
1235 .........................123456789012345678. */
1236 const char *old_content = ("/* before */\n"
1237 "foo = bar.m_field;\n"
1238 "/* after */\n");
1239 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1240 const char *filename = tmp.get_filename ();
1241 line_table_test ltt (case_);
1242 linemap_add (line_table, LC_ENTER, false, filename, 2);
1244 /* Replace "field" with "m_field". */
1245 location_t start = linemap_position_for_column (line_table, 11);
1246 location_t finish = linemap_position_for_column (line_table, 17);
1247 location_t m_field = make_location (start, start, finish);
1248 rich_location richloc (line_table, m_field);
1249 richloc.add_fixit_replace ("field");
1251 edit_context edit;
1252 edit.add_fixits (&richloc);
1253 auto_free <char *> new_content = edit.get_content (filename);
1254 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1256 ASSERT_STREQ ("/* before */\n"
1257 "foo = bar.field;\n"
1258 "/* after */\n", new_content);
1260 /* Verify location of ";" after the change. */
1261 ASSERT_EQ (16, edit.get_effective_column (filename, 2, 18));
1263 /* Verify diff. */
1264 auto_free <char *> diff = edit.generate_diff (false);
1265 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1266 " /* before */\n"
1267 "-foo = bar.m_field;\n"
1268 "+foo = bar.field;\n"
1269 " /* after */\n", diff);
1273 /* Replacement fix-it hint containing a newline. */
1275 static void
1276 test_applying_fixits_replace_containing_newline (const line_table_case &case_)
1278 /* Create a tempfile and write some text to it.
1279 .........................0000000001111.
1280 .........................1234567890123. */
1281 const char *old_content = "foo = bar ();\n";
1283 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1284 const char *filename = tmp.get_filename ();
1285 line_table_test ltt (case_);
1286 linemap_add (line_table, LC_ENTER, false, filename, 1);
1288 /* Replace the " = " with "\n = ", as if we were reformatting an
1289 overly long line. */
1290 location_t start = linemap_position_for_column (line_table, 4);
1291 location_t finish = linemap_position_for_column (line_table, 6);
1292 location_t loc = linemap_position_for_column (line_table, 13);
1293 rich_location richloc (line_table, loc);
1294 source_range range = source_range::from_locations (start, finish);
1295 richloc.add_fixit_replace (range, "\n = ");
1297 /* Newlines are only supported within fix-it hints that
1298 are at the start of lines (for entirely new lines), hence
1299 this fix-it should not be displayed. */
1300 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1302 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1303 return;
1305 edit_context edit;
1306 edit.add_fixits (&richloc);
1307 auto_free <char *> new_content = edit.get_content (filename);
1308 //ASSERT_STREQ ("foo\n = bar ();\n", new_content);
1311 /* Test applying a "remove" fixit. */
1313 static void
1314 test_applying_fixits_remove (const line_table_case &case_)
1316 /* Create a tempfile and write some text to it.
1317 .........................000000000111111111.
1318 .........................123456789012345678. */
1319 const char *old_content = ("/* before */\n"
1320 "foo = bar.m_field;\n"
1321 "/* after */\n");
1322 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1323 const char *filename = tmp.get_filename ();
1324 line_table_test ltt (case_);
1325 linemap_add (line_table, LC_ENTER, false, filename, 2);
1327 /* Remove ".m_field". */
1328 location_t start = linemap_position_for_column (line_table, 10);
1329 location_t finish = linemap_position_for_column (line_table, 17);
1330 rich_location richloc (line_table, start);
1331 source_range range;
1332 range.m_start = start;
1333 range.m_finish = finish;
1334 richloc.add_fixit_remove (range);
1336 edit_context edit;
1337 edit.add_fixits (&richloc);
1338 auto_free <char *> new_content = edit.get_content (filename);
1339 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1341 ASSERT_STREQ ("/* before */\n"
1342 "foo = bar;\n"
1343 "/* after */\n", new_content);
1345 /* Verify location of ";" after the change. */
1346 ASSERT_EQ (10, edit.get_effective_column (filename, 2, 18));
1348 /* Verify diff. */
1349 auto_free <char *> diff = edit.generate_diff (false);
1350 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1351 " /* before */\n"
1352 "-foo = bar.m_field;\n"
1353 "+foo = bar;\n"
1354 " /* after */\n", diff);
1358 /* Test applying multiple fixits to one line. */
1360 static void
1361 test_applying_fixits_multiple (const line_table_case &case_)
1363 /* Create a tempfile and write some text to it.
1364 .........................00000000011111111.
1365 .........................12345678901234567. */
1366 const char *old_content = ("/* before */\n"
1367 "foo = bar.field;\n"
1368 "/* after */\n");
1369 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1370 const char *filename = tmp.get_filename ();
1371 line_table_test ltt (case_);
1372 linemap_add (line_table, LC_ENTER, false, filename, 2);
1374 location_t c7 = linemap_position_for_column (line_table, 7);
1375 location_t c9 = linemap_position_for_column (line_table, 9);
1376 location_t c11 = linemap_position_for_column (line_table, 11);
1377 location_t c15 = linemap_position_for_column (line_table, 15);
1378 location_t c17 = linemap_position_for_column (line_table, 17);
1380 if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1381 return;
1383 /* Add a comment in front of "bar.field". */
1384 rich_location insert_a (line_table, c7);
1385 insert_a.add_fixit_insert_before (c7, "/* alpha */");
1387 /* Add a comment after "bar.field;". */
1388 rich_location insert_b (line_table, c17);
1389 insert_b.add_fixit_insert_before (c17, "/* beta */");
1391 /* Replace "bar" with "pub". */
1392 rich_location replace_a (line_table, c7);
1393 replace_a.add_fixit_replace (source_range::from_locations (c7, c9),
1394 "pub");
1396 /* Replace "field" with "meadow". */
1397 rich_location replace_b (line_table, c7);
1398 replace_b.add_fixit_replace (source_range::from_locations (c11, c15),
1399 "meadow");
1401 edit_context edit;
1402 edit.add_fixits (&insert_a);
1403 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
1404 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
1405 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
1406 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
1407 ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
1408 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
1410 edit.add_fixits (&insert_b);
1411 edit.add_fixits (&replace_a);
1412 edit.add_fixits (&replace_b);
1414 if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1416 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1417 ASSERT_STREQ ("/* before */\n"
1418 "foo = /* alpha */pub.meadow;/* beta */\n"
1419 "/* after */\n",
1420 new_content);
1422 /* Verify diff. */
1423 auto_free <char *> diff = edit.generate_diff (false);
1424 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1425 " /* before */\n"
1426 "-foo = bar.field;\n"
1427 "+foo = /* alpha */pub.meadow;/* beta */\n"
1428 " /* after */\n", diff);
1432 /* Subroutine of test_applying_fixits_multiple_lines.
1433 Add the text "CHANGED: " to the front of the given line. */
1435 static location_t
1436 change_line (edit_context &edit, int line_num)
1438 const line_map_ordinary *ord_map
1439 = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1440 const int column = 1;
1441 location_t loc =
1442 linemap_position_for_line_and_column (line_table, ord_map,
1443 line_num, column);
1445 expanded_location exploc = expand_location (loc);
1446 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1448 ASSERT_EQ (line_num, exploc.line);
1449 ASSERT_EQ (column, exploc.column);
1452 rich_location insert (line_table, loc);
1453 insert.add_fixit_insert_before ("CHANGED: ");
1454 edit.add_fixits (&insert);
1455 return loc;
1458 /* Subroutine of test_applying_fixits_multiple_lines.
1459 Add the text "INSERTED\n" in front of the given line. */
1461 static location_t
1462 insert_line (edit_context &edit, int line_num)
1464 const line_map_ordinary *ord_map
1465 = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1466 const int column = 1;
1467 location_t loc =
1468 linemap_position_for_line_and_column (line_table, ord_map,
1469 line_num, column);
1471 expanded_location exploc = expand_location (loc);
1472 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1474 ASSERT_EQ (line_num, exploc.line);
1475 ASSERT_EQ (column, exploc.column);
1478 rich_location insert (line_table, loc);
1479 insert.add_fixit_insert_before ("INSERTED\n");
1480 edit.add_fixits (&insert);
1481 return loc;
1484 /* Test of editing multiple lines within a long file,
1485 to ensure that diffs are generated as expected. */
1487 static void
1488 test_applying_fixits_multiple_lines (const line_table_case &case_)
1490 /* Create a tempfile and write many lines of text to it. */
1491 named_temp_file tmp (".txt");
1492 const char *filename = tmp.get_filename ();
1493 FILE *f = fopen (filename, "w");
1494 ASSERT_NE (f, NULL);
1495 for (int i = 1; i <= 1000; i++)
1496 fprintf (f, "line %i\n", i);
1497 fclose (f);
1499 line_table_test ltt (case_);
1500 linemap_add (line_table, LC_ENTER, false, filename, 1);
1501 linemap_position_for_column (line_table, 127);
1503 edit_context edit;
1505 /* A run of consecutive lines. */
1506 change_line (edit, 2);
1507 change_line (edit, 3);
1508 change_line (edit, 4);
1509 insert_line (edit, 5);
1511 /* A run of nearby lines, within the contextual limit. */
1512 change_line (edit, 150);
1513 change_line (edit, 151);
1514 location_t last_loc = change_line (edit, 153);
1516 if (last_loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1517 return;
1519 /* Verify diff. */
1520 auto_free <char *> diff = edit.generate_diff (false);
1521 ASSERT_STREQ ("@@ -1,7 +1,8 @@\n"
1522 " line 1\n"
1523 "-line 2\n"
1524 "-line 3\n"
1525 "-line 4\n"
1526 "+CHANGED: line 2\n"
1527 "+CHANGED: line 3\n"
1528 "+CHANGED: line 4\n"
1529 "+INSERTED\n"
1530 " line 5\n"
1531 " line 6\n"
1532 " line 7\n"
1533 "@@ -147,10 +148,10 @@\n"
1534 " line 147\n"
1535 " line 148\n"
1536 " line 149\n"
1537 "-line 150\n"
1538 "-line 151\n"
1539 "+CHANGED: line 150\n"
1540 "+CHANGED: line 151\n"
1541 " line 152\n"
1542 "-line 153\n"
1543 "+CHANGED: line 153\n"
1544 " line 154\n"
1545 " line 155\n"
1546 " line 156\n", diff);
1548 /* Ensure tmp stays alive until this point, so that the tempfile
1549 persists until after the generate_diff call. */
1550 tmp.get_filename ();
1553 /* Test of converting an initializer for a named field from
1554 the old GCC extension to C99 syntax.
1555 Exercises a shrinking replacement followed by a growing
1556 replacement on the same line. */
1558 static void
1559 test_applying_fixits_modernize_named_init (const line_table_case &case_)
1561 /* Create a tempfile and write some text to it.
1562 .........................00000000011111111.
1563 .........................12345678901234567. */
1564 const char *old_content = ("/* before */\n"
1565 "bar : 1,\n"
1566 "/* after */\n");
1567 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1568 const char *filename = tmp.get_filename ();
1569 line_table_test ltt (case_);
1570 linemap_add (line_table, LC_ENTER, false, filename, 2);
1572 location_t c1 = linemap_position_for_column (line_table, 1);
1573 location_t c3 = linemap_position_for_column (line_table, 3);
1574 location_t c8 = linemap_position_for_column (line_table, 8);
1576 if (c8 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1577 return;
1579 /* Replace "bar" with ".". */
1580 rich_location r1 (line_table, c8);
1581 r1.add_fixit_replace (source_range::from_locations (c1, c3),
1582 ".");
1584 /* Replace ":" with "bar =". */
1585 rich_location r2 (line_table, c8);
1586 r2.add_fixit_replace (source_range::from_locations (c8, c8),
1587 "bar =");
1589 /* The order should not matter. Do r1 then r2. */
1591 edit_context edit;
1592 edit.add_fixits (&r1);
1594 /* Verify state after first replacement. */
1596 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1597 /* We should now have:
1598 ............00000000011.
1599 ............12345678901. */
1600 ASSERT_STREQ ("/* before */\n"
1601 ". : 1,\n"
1602 "/* after */\n",
1603 new_content);
1604 /* Location of the "1". */
1605 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
1606 /* Location of the ",". */
1607 ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
1610 edit.add_fixits (&r2);
1612 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1613 /* Verify state after second replacement.
1614 ............00000000011111111.
1615 ............12345678901234567. */
1616 ASSERT_STREQ ("/* before */\n"
1617 ". bar = 1,\n"
1618 "/* after */\n",
1619 new_content);
1622 /* Try again, doing r2 then r1; the new_content should be the same. */
1624 edit_context edit;
1625 edit.add_fixits (&r2);
1626 edit.add_fixits (&r1);
1627 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1628 /*.............00000000011111111.
1629 .............12345678901234567. */
1630 ASSERT_STREQ ("/* before */\n"
1631 ". bar = 1,\n"
1632 "/* after */\n",
1633 new_content);
1637 /* Test of a fixit affecting a file that can't be read. */
1639 static void
1640 test_applying_fixits_unreadable_file ()
1642 const char *filename = "this-does-not-exist.txt";
1643 line_table_test ltt ();
1644 linemap_add (line_table, LC_ENTER, false, filename, 1);
1646 location_t loc = linemap_position_for_column (line_table, 1);
1648 rich_location insert (line_table, loc);
1649 insert.add_fixit_insert_before ("change 1");
1650 insert.add_fixit_insert_before ("change 2");
1652 edit_context edit;
1653 /* Attempting to add the fixits affecting the unreadable file
1654 should transition the edit from valid to invalid. */
1655 ASSERT_TRUE (edit.valid_p ());
1656 edit.add_fixits (&insert);
1657 ASSERT_FALSE (edit.valid_p ());
1658 ASSERT_EQ (NULL, edit.get_content (filename));
1659 ASSERT_EQ (NULL, edit.generate_diff (false));
1662 /* Verify that we gracefully handle an attempt to edit a line
1663 that's beyond the end of the file. */
1665 static void
1666 test_applying_fixits_line_out_of_range ()
1668 /* Create a tempfile and write some text to it.
1669 ........................00000000011111111.
1670 ........................12345678901234567. */
1671 const char *old_content = "One-liner file\n";
1672 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1673 const char *filename = tmp.get_filename ();
1674 line_table_test ltt ();
1675 linemap_add (line_table, LC_ENTER, false, filename, 2);
1677 /* Try to insert a string in line 2. */
1678 location_t loc = linemap_position_for_column (line_table, 1);
1680 rich_location insert (line_table, loc);
1681 insert.add_fixit_insert_before ("change");
1683 /* Verify that attempting the insertion puts an edit_context
1684 into an invalid state. */
1685 edit_context edit;
1686 ASSERT_TRUE (edit.valid_p ());
1687 edit.add_fixits (&insert);
1688 ASSERT_FALSE (edit.valid_p ());
1689 ASSERT_EQ (NULL, edit.get_content (filename));
1690 ASSERT_EQ (NULL, edit.generate_diff (false));
1693 /* Verify the boundary conditions of column values in fix-it
1694 hints applied to edit_context instances. */
1696 static void
1697 test_applying_fixits_column_validation (const line_table_case &case_)
1699 /* Create a tempfile and write some text to it.
1700 ........................00000000011111111.
1701 ........................12345678901234567. */
1702 const char *old_content = "One-liner file\n";
1703 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1704 const char *filename = tmp.get_filename ();
1705 line_table_test ltt (case_);
1706 linemap_add (line_table, LC_ENTER, false, filename, 1);
1708 location_t c11 = linemap_position_for_column (line_table, 11);
1709 location_t c14 = linemap_position_for_column (line_table, 14);
1710 location_t c15 = linemap_position_for_column (line_table, 15);
1711 location_t c16 = linemap_position_for_column (line_table, 16);
1713 /* Verify limits of valid columns in insertion fixits. */
1715 /* Verify inserting at the end of the line. */
1717 rich_location richloc (line_table, c11);
1718 richloc.add_fixit_insert_before (c15, " change");
1720 /* Col 15 is at the end of the line, so the insertion
1721 should succeed. */
1722 edit_context edit;
1723 edit.add_fixits (&richloc);
1724 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1725 if (c15 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1726 ASSERT_STREQ ("One-liner file change\n", new_content);
1727 else
1728 ASSERT_EQ (NULL, new_content);
1731 /* Verify inserting beyond the end of the line. */
1733 rich_location richloc (line_table, c11);
1734 richloc.add_fixit_insert_before (c16, " change");
1736 /* Col 16 is beyond the end of the line, so the insertion
1737 should fail gracefully. */
1738 edit_context edit;
1739 ASSERT_TRUE (edit.valid_p ());
1740 edit.add_fixits (&richloc);
1741 ASSERT_FALSE (edit.valid_p ());
1742 ASSERT_EQ (NULL, edit.get_content (filename));
1743 ASSERT_EQ (NULL, edit.generate_diff (false));
1746 /* Verify limits of valid columns in replacement fixits. */
1748 /* Verify replacing the end of the line. */
1750 rich_location richloc (line_table, c11);
1751 source_range range = source_range::from_locations (c11, c14);
1752 richloc.add_fixit_replace (range, "change");
1754 /* Col 14 is at the end of the line, so the replacement
1755 should succeed. */
1756 edit_context edit;
1757 edit.add_fixits (&richloc);
1758 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1759 if (c14 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1760 ASSERT_STREQ ("One-liner change\n", new_content);
1761 else
1762 ASSERT_EQ (NULL, new_content);
1765 /* Verify going beyond the end of the line. */
1767 rich_location richloc (line_table, c11);
1768 source_range range = source_range::from_locations (c11, c15);
1769 richloc.add_fixit_replace (range, "change");
1771 /* Col 15 is after the end of the line, so the replacement
1772 should fail; verify that the attempt fails gracefully. */
1773 edit_context edit;
1774 ASSERT_TRUE (edit.valid_p ());
1775 edit.add_fixits (&richloc);
1776 ASSERT_FALSE (edit.valid_p ());
1777 ASSERT_EQ (NULL, edit.get_content (filename));
1778 ASSERT_EQ (NULL, edit.generate_diff (false));
1782 /* Run all of the selftests within this file. */
1784 void
1785 edit_context_c_tests ()
1787 test_get_content ();
1788 for_each_line_table_case (test_applying_fixits_insert_before);
1789 for_each_line_table_case (test_applying_fixits_insert_after);
1790 for_each_line_table_case (test_applying_fixits_insert_after_at_line_end);
1791 for_each_line_table_case (test_applying_fixits_insert_after_failure);
1792 for_each_line_table_case (test_applying_fixits_insert_containing_newline);
1793 for_each_line_table_case (test_applying_fixits_growing_replace);
1794 for_each_line_table_case (test_applying_fixits_shrinking_replace);
1795 for_each_line_table_case (test_applying_fixits_replace_containing_newline);
1796 for_each_line_table_case (test_applying_fixits_remove);
1797 for_each_line_table_case (test_applying_fixits_multiple);
1798 for_each_line_table_case (test_applying_fixits_multiple_lines);
1799 for_each_line_table_case (test_applying_fixits_modernize_named_init);
1800 test_applying_fixits_unreadable_file ();
1801 test_applying_fixits_line_out_of_range ();
1802 for_each_line_table_case (test_applying_fixits_column_validation);
1805 } // namespace selftest
1807 #endif /* CHECKING_P */