2017-10-21 Paul Thomas <pault@gcc.gnu.org>
[official-gcc.git] / gcc / edit-context.c
blob6f35ca291f1c2fe181dcec64ea172fc11c6c2d52
1 /* Determining the results of applying fix-it hints.
2 Copyright (C) 2016-2017 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 int len;
426 const char *line
427 = location_get_source_line (m_filename, line_num, &len);
428 if (!line)
429 return false;
430 for (int i = 0; i < len; i++)
431 pp_character (pp, line[i]);
433 if (line_num < line_count)
434 pp_character (pp, '\n');
437 if (!missing_trailing_newline)
438 pp_character (pp, '\n');
440 return true;
443 /* Print a unified diff to PP, showing any changes that have occurred
444 to this file. */
446 void
447 edited_file::print_diff (pretty_printer *pp, bool show_filenames)
449 if (show_filenames)
451 pp_string (pp, colorize_start (pp_show_color (pp), "diff-filename"));
452 pp_printf (pp, "--- %s\n", m_filename);
453 pp_printf (pp, "+++ %s\n", m_filename);
454 pp_string (pp, colorize_stop (pp_show_color (pp)));
457 edited_line *el = m_edited_lines.min ();
459 bool missing_trailing_newline;
460 int line_count = get_num_lines (&missing_trailing_newline);
462 const int context_lines = 3;
464 /* Track new line numbers minus old line numbers. */
466 int line_delta = 0;
468 while (el)
470 int start_of_hunk = el->get_line_num ();
471 start_of_hunk -= context_lines;
472 if (start_of_hunk < 1)
473 start_of_hunk = 1;
475 /* Locate end of hunk, merging in changed lines
476 that are sufficiently close. */
477 while (true)
479 edited_line *next_el
480 = m_edited_lines.successor (el->get_line_num ());
481 if (!next_el)
482 break;
484 int end_of_printed_hunk = el->get_line_num () + context_lines;
485 if (!el->actually_edited_p ())
486 end_of_printed_hunk--;
488 if (end_of_printed_hunk
489 >= next_el->get_line_num () - context_lines)
490 el = next_el;
491 else
492 break;
495 int end_of_hunk = el->get_line_num ();
496 end_of_hunk += context_lines;
497 if (!el->actually_edited_p ())
498 end_of_hunk--;
499 if (end_of_hunk > line_count)
500 end_of_hunk = line_count;
502 int new_start_of_hunk = start_of_hunk + line_delta;
503 line_delta += print_diff_hunk (pp, start_of_hunk, end_of_hunk,
504 new_start_of_hunk);
505 el = m_edited_lines.successor (el->get_line_num ());
509 /* Print one hunk within a unified diff to PP, covering the
510 given range of lines. OLD_START_OF_HUNK and OLD_END_OF_HUNK are
511 line numbers in the unedited version of the file.
512 NEW_START_OF_HUNK is a line number in the edited version of the file.
513 Return the change in the line count within the hunk. */
516 edited_file::print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
517 int old_end_of_hunk, int new_start_of_hunk)
519 int old_num_lines = old_end_of_hunk - old_start_of_hunk + 1;
520 int new_num_lines
521 = get_effective_line_count (old_start_of_hunk, old_end_of_hunk);
523 pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
524 pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", old_start_of_hunk, old_num_lines,
525 new_start_of_hunk, new_num_lines);
526 pp_string (pp, colorize_stop (pp_show_color (pp)));
528 int line_num = old_start_of_hunk;
529 while (line_num <= old_end_of_hunk)
531 edited_line *el = get_line (line_num);
532 if (el)
534 /* We have an edited line.
535 Consolidate into runs of changed lines. */
536 const int first_changed_line_in_run = line_num;
537 while (get_line (line_num))
538 line_num++;
539 const int last_changed_line_in_run = line_num - 1;
540 print_run_of_changed_lines (pp, first_changed_line_in_run,
541 last_changed_line_in_run);
543 else
545 /* Unchanged line. */
546 int line_len;
547 const char *old_line
548 = location_get_source_line (m_filename, line_num, &line_len);
549 print_diff_line (pp, ' ', old_line, line_len);
550 line_num++;
554 return new_num_lines - old_num_lines;
557 /* Subroutine of edited_file::print_diff_hunk: given a run of lines
558 from START_OF_RUN to END_OF_RUN that all have edited_line instances,
559 print the diff to PP. */
561 void
562 edited_file::print_run_of_changed_lines (pretty_printer *pp,
563 int start_of_run,
564 int end_of_run)
566 /* Show old version of lines. */
567 pp_string (pp, colorize_start (pp_show_color (pp),
568 "diff-delete"));
569 for (int line_num = start_of_run;
570 line_num <= end_of_run;
571 line_num++)
573 edited_line *el_in_run = get_line (line_num);
574 gcc_assert (el_in_run);
575 if (el_in_run->actually_edited_p ())
577 int line_len;
578 const char *old_line
579 = location_get_source_line (m_filename, line_num, &line_len);
580 print_diff_line (pp, '-', old_line, line_len);
583 pp_string (pp, colorize_stop (pp_show_color (pp)));
585 /* Show new version of lines. */
586 pp_string (pp, colorize_start (pp_show_color (pp),
587 "diff-insert"));
588 for (int line_num = start_of_run;
589 line_num <= end_of_run;
590 line_num++)
592 edited_line *el_in_run = get_line (line_num);
593 gcc_assert (el_in_run);
594 el_in_run->print_diff_lines (pp);
596 pp_string (pp, colorize_stop (pp_show_color (pp)));
599 /* Print one line within a diff, starting with PREFIX_CHAR,
600 followed by the LINE of content, of length LEN. LINE is
601 not necessarily 0-terminated. Print a trailing newline. */
603 static void
604 print_diff_line (pretty_printer *pp, char prefix_char,
605 const char *line, int len)
607 pp_character (pp, prefix_char);
608 for (int i = 0; i < len; i++)
609 pp_character (pp, line[i]);
610 pp_character (pp, '\n');
613 /* Determine the number of lines that will be present after
614 editing for the range of lines from OLD_START_OF_HUNK to
615 OLD_END_OF_HUNK inclusive. */
618 edited_file::get_effective_line_count (int old_start_of_hunk,
619 int old_end_of_hunk)
621 int line_count = 0;
622 for (int old_line_num = old_start_of_hunk; old_line_num <= old_end_of_hunk;
623 old_line_num++)
625 edited_line *el = get_line (old_line_num);
626 if (el)
627 line_count += el->get_effective_line_count ();
628 else
629 line_count++;
631 return line_count;
634 /* Get the state of LINE within the file, or NULL if it is untouched. */
636 edited_line *
637 edited_file::get_line (int line)
639 return m_edited_lines.lookup (line);
642 /* Get the state of LINE within the file, creating a state for it
643 if necessary. Return NULL if an error occurs. */
645 edited_line *
646 edited_file::get_or_insert_line (int line)
648 edited_line *el = get_line (line);
649 if (el)
650 return el;
651 el = new edited_line (m_filename, line);
652 if (el->get_content () == NULL)
654 delete el;
655 return NULL;
657 m_edited_lines.insert (line, el);
658 return el;
661 /* Get the total number of lines in m_content, writing
662 true to *MISSING_TRAILING_NEWLINE if the final line
663 if missing a newline, false otherwise. */
666 edited_file::get_num_lines (bool *missing_trailing_newline)
668 gcc_assert (missing_trailing_newline);
669 if (m_num_lines == -1)
671 m_num_lines = 0;
672 while (true)
674 int line_size;
675 const char *line
676 = location_get_source_line (m_filename, m_num_lines + 1,
677 &line_size);
678 if (line)
679 m_num_lines++;
680 else
681 break;
684 *missing_trailing_newline = location_missing_trailing_newline (m_filename);
685 return m_num_lines;
688 /* Implementation of class edited_line. */
690 /* edited_line's ctor. */
692 edited_line::edited_line (const char *filename, int line_num)
693 : m_line_num (line_num),
694 m_content (NULL), m_len (0), m_alloc_sz (0),
695 m_line_events (),
696 m_predecessors ()
698 const char *line = location_get_source_line (filename, line_num,
699 &m_len);
700 if (!line)
701 return;
702 ensure_capacity (m_len);
703 memcpy (m_content, line, m_len);
704 ensure_terminated ();
707 /* edited_line's dtor. */
709 edited_line::~edited_line ()
711 unsigned i;
712 added_line *pred;
714 free (m_content);
715 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
716 delete pred;
719 /* A callback for deleting edited_line *, for use as a
720 delete_value_fn for edited_file::m_edited_lines. */
722 void
723 edited_line::delete_cb (edited_line *el)
725 delete el;
728 /* Map a location before the edits to a column number after the edits,
729 within a specific line. */
732 edited_line::get_effective_column (int orig_column) const
734 int i;
735 line_event *event;
736 FOR_EACH_VEC_ELT (m_line_events, i, event)
737 orig_column = event->get_effective_column (orig_column);
738 return orig_column;
741 /* Attempt to replace columns START_COLUMN up to but not including
742 NEXT_COLUMN of the line with the string REPLACEMENT_STR of
743 length REPLACEMENT_LEN, updating the in-memory copy of the line,
744 and the record of edits to the line.
745 Return true if successful; false if an error occurred. */
747 bool
748 edited_line::apply_fixit (int start_column,
749 int next_column,
750 const char *replacement_str,
751 int replacement_len)
753 /* Handle newlines. They will only ever be at the end of the
754 replacement text, thanks to the filtering in rich_location. */
755 if (replacement_len > 1)
756 if (replacement_str[replacement_len - 1] == '\n')
758 /* Stash in m_predecessors, stripping off newline. */
759 m_predecessors.safe_push (new added_line (replacement_str,
760 replacement_len - 1));
761 return true;
764 start_column = get_effective_column (start_column);
765 next_column = get_effective_column (next_column);
767 int start_offset = start_column - 1;
768 int next_offset = next_column - 1;
770 gcc_assert (start_offset >= 0);
771 gcc_assert (next_offset >= 0);
773 if (start_column > next_column)
774 return false;
775 if (start_offset >= (m_len + 1))
776 return false;
777 if (next_offset >= (m_len + 1))
778 return false;
780 size_t victim_len = next_offset - start_offset;
782 /* Ensure buffer is big enough. */
783 size_t new_len = m_len + replacement_len - victim_len;
784 ensure_capacity (new_len);
786 char *suffix = m_content + next_offset;
787 gcc_assert (suffix <= m_content + m_len);
788 size_t len_suffix = (m_content + m_len) - suffix;
790 /* Move successor content into position. They overlap, so use memmove. */
791 memmove (m_content + start_offset + replacement_len,
792 suffix, len_suffix);
794 /* Replace target content. They don't overlap, so use memcpy. */
795 memcpy (m_content + start_offset,
796 replacement_str,
797 replacement_len);
799 m_len = new_len;
801 ensure_terminated ();
803 /* Record the replacement, so that future changes to the line can have
804 their column information adjusted accordingly. */
805 m_line_events.safe_push (line_event (start_column, next_column,
806 replacement_len));
807 return true;
810 /* Determine the number of lines that will be present after
811 editing for this line. Typically this is just 1, but
812 if newlines have been added before this line, they will
813 also be counted. */
816 edited_line::get_effective_line_count () const
818 return m_predecessors.length () + 1;
821 /* Subroutine of edited_file::print_content.
822 Print this line and any new lines added before it, to PP. */
824 void
825 edited_line::print_content (pretty_printer *pp) const
827 unsigned i;
828 added_line *pred;
829 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
831 pp_string (pp, pred->get_content ());
832 pp_newline (pp);
834 pp_string (pp, m_content);
837 /* Subroutine of edited_file::print_run_of_changed_lines for
838 printing diff hunks to PP.
839 Print the '+' line for this line, and any newlines added
840 before it.
841 Note that if this edited_line was actually edited, the '-'
842 line has already been printed. If it wasn't, then we merely
843 have a placeholder edited_line for adding newlines to, and
844 we need to print a ' ' line for the edited_line as we haven't
845 printed it yet. */
847 void
848 edited_line::print_diff_lines (pretty_printer *pp) const
850 unsigned i;
851 added_line *pred;
852 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
853 print_diff_line (pp, '+', pred->get_content (),
854 pred->get_len ());
855 if (actually_edited_p ())
856 print_diff_line (pp, '+', m_content, m_len);
857 else
858 print_diff_line (pp, ' ', m_content, m_len);
861 /* Ensure that the buffer for m_content is at least large enough to hold
862 a string of length LEN and its 0-terminator, doubling on repeated
863 allocations. */
865 void
866 edited_line::ensure_capacity (int len)
868 /* Allow 1 extra byte for 0-termination. */
869 if (m_alloc_sz < (len + 1))
871 size_t new_alloc_sz = (len + 1) * 2;
872 m_content = (char *)xrealloc (m_content, new_alloc_sz);
873 m_alloc_sz = new_alloc_sz;
877 /* Ensure that m_content is 0-terminated. */
879 void
880 edited_line::ensure_terminated ()
882 /* 0-terminate the buffer. */
883 gcc_assert (m_len < m_alloc_sz);
884 m_content[m_len] = '\0';
887 #if CHECKING_P
889 /* Selftests of code-editing. */
891 namespace selftest {
893 /* A wrapper class for ensuring that the underlying pointer is freed. */
895 template <typename POINTER_T>
896 class auto_free
898 public:
899 auto_free (POINTER_T p) : m_ptr (p) {}
900 ~auto_free () { free (m_ptr); }
902 operator POINTER_T () { return m_ptr; }
904 private:
905 POINTER_T m_ptr;
908 /* Verify that edit_context::get_content works for unedited files. */
910 static void
911 test_get_content ()
913 /* Test of empty file. */
915 const char *content = ("");
916 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
917 edit_context edit;
918 auto_free <char *> result = edit.get_content (tmp.get_filename ());
919 ASSERT_STREQ ("", result);
922 /* Test of simple content. */
924 const char *content = ("/* before */\n"
925 "foo = bar.field;\n"
926 "/* after */\n");
927 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
928 edit_context edit;
929 auto_free <char *> result = edit.get_content (tmp.get_filename ());
930 ASSERT_STREQ ("/* before */\n"
931 "foo = bar.field;\n"
932 "/* after */\n", result);
935 /* Test of omitting the trailing newline on the final line. */
937 const char *content = ("/* before */\n"
938 "foo = bar.field;\n"
939 "/* after */");
940 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
941 edit_context edit;
942 auto_free <char *> result = edit.get_content (tmp.get_filename ());
943 /* We should respect the omitted trailing newline. */
944 ASSERT_STREQ ("/* before */\n"
945 "foo = bar.field;\n"
946 "/* after */", result);
950 /* Test applying an "insert" fixit, using insert_before. */
952 static void
953 test_applying_fixits_insert_before (const line_table_case &case_)
955 /* Create a tempfile and write some text to it.
956 .........................0000000001111111.
957 .........................1234567890123456. */
958 const char *old_content = ("/* before */\n"
959 "foo = bar.field;\n"
960 "/* after */\n");
961 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
962 const char *filename = tmp.get_filename ();
963 line_table_test ltt (case_);
964 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
966 /* Add a comment in front of "bar.field". */
967 location_t start = linemap_position_for_column (line_table, 7);
968 rich_location richloc (line_table, start);
969 richloc.add_fixit_insert_before ("/* inserted */");
971 if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
972 return;
974 edit_context edit;
975 edit.add_fixits (&richloc);
976 auto_free <char *> new_content = edit.get_content (filename);
977 if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
978 ASSERT_STREQ ("/* before */\n"
979 "foo = /* inserted */bar.field;\n"
980 "/* after */\n", new_content);
982 /* Verify that locations on other lines aren't affected by the change. */
983 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
984 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
986 /* Verify locations on the line before the change. */
987 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
988 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
990 /* Verify locations on the line at and after the change. */
991 ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
992 ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));
994 /* Verify diff. */
995 auto_free <char *> diff = edit.generate_diff (false);
996 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
997 " /* before */\n"
998 "-foo = bar.field;\n"
999 "+foo = /* inserted */bar.field;\n"
1000 " /* after */\n", diff);
1003 /* Test applying an "insert" fixit, using insert_after, with
1004 a range of length > 1 (to ensure that the end-point of
1005 the input range is used). */
1007 static void
1008 test_applying_fixits_insert_after (const line_table_case &case_)
1010 /* Create a tempfile and write some text to it.
1011 .........................0000000001111111.
1012 .........................1234567890123456. */
1013 const char *old_content = ("/* before */\n"
1014 "foo = bar.field;\n"
1015 "/* after */\n");
1016 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1017 const char *filename = tmp.get_filename ();
1018 line_table_test ltt (case_);
1019 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1021 /* Add a comment after "field". */
1022 location_t start = linemap_position_for_column (line_table, 11);
1023 location_t finish = linemap_position_for_column (line_table, 15);
1024 location_t field = make_location (start, start, finish);
1025 rich_location richloc (line_table, field);
1026 richloc.add_fixit_insert_after ("/* inserted */");
1028 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1029 return;
1031 /* Verify that the text was inserted after the end of "field". */
1032 edit_context edit;
1033 edit.add_fixits (&richloc);
1034 auto_free <char *> new_content = edit.get_content (filename);
1035 ASSERT_STREQ ("/* before */\n"
1036 "foo = bar.field/* inserted */;\n"
1037 "/* after */\n", new_content);
1039 /* Verify diff. */
1040 auto_free <char *> diff = edit.generate_diff (false);
1041 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1042 " /* before */\n"
1043 "-foo = bar.field;\n"
1044 "+foo = bar.field/* inserted */;\n"
1045 " /* after */\n", diff);
1048 /* Test applying an "insert" fixit, using insert_after at the end of
1049 a line (contrast with test_applying_fixits_insert_after_failure
1050 below). */
1052 static void
1053 test_applying_fixits_insert_after_at_line_end (const line_table_case &case_)
1055 /* Create a tempfile and write some text to it.
1056 .........................0000000001111111.
1057 .........................1234567890123456. */
1058 const char *old_content = ("/* before */\n"
1059 "foo = bar.field;\n"
1060 "/* after */\n");
1061 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1062 const char *filename = tmp.get_filename ();
1063 line_table_test ltt (case_);
1064 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1066 /* Add a comment after the semicolon. */
1067 location_t loc = linemap_position_for_column (line_table, 16);
1068 rich_location richloc (line_table, loc);
1069 richloc.add_fixit_insert_after ("/* inserted */");
1071 if (loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1072 return;
1074 edit_context edit;
1075 edit.add_fixits (&richloc);
1076 auto_free <char *> new_content = edit.get_content (filename);
1077 ASSERT_STREQ ("/* before */\n"
1078 "foo = bar.field;/* inserted */\n"
1079 "/* after */\n", new_content);
1081 /* Verify diff. */
1082 auto_free <char *> diff = edit.generate_diff (false);
1083 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1084 " /* before */\n"
1085 "-foo = bar.field;\n"
1086 "+foo = bar.field;/* inserted */\n"
1087 " /* after */\n", diff);
1090 /* Test of a failed attempt to apply an "insert" fixit, using insert_after,
1091 due to the relevant linemap ending. Contrast with
1092 test_applying_fixits_insert_after_at_line_end above. */
1094 static void
1095 test_applying_fixits_insert_after_failure (const line_table_case &case_)
1097 /* Create a tempfile and write some text to it.
1098 .........................0000000001111111.
1099 .........................1234567890123456. */
1100 const char *old_content = ("/* before */\n"
1101 "foo = bar.field;\n"
1102 "/* after */\n");
1103 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1104 const char *filename = tmp.get_filename ();
1105 line_table_test ltt (case_);
1106 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1108 /* Add a comment after the semicolon. */
1109 location_t loc = linemap_position_for_column (line_table, 16);
1110 rich_location richloc (line_table, loc);
1112 /* We want a failure of linemap_position_for_loc_and_offset.
1113 We can do this by starting a new linemap at line 3, so that
1114 there is no appropriate location value for the insertion point
1115 within the linemap for line 2. */
1116 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1118 /* The failure fails to happen at the transition point from
1119 packed ranges to unpacked ranges (where there are some "spare"
1120 location_t values). Skip the test there. */
1121 if (loc >= LINE_MAP_MAX_LOCATION_WITH_PACKED_RANGES)
1122 return;
1124 /* Offsetting "loc" should now fail (by returning the input loc. */
1125 ASSERT_EQ (loc, linemap_position_for_loc_and_offset (line_table, loc, 1));
1127 /* Hence attempting to use add_fixit_insert_after at the end of the line
1128 should now fail. */
1129 richloc.add_fixit_insert_after ("/* inserted */");
1130 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1132 edit_context edit;
1133 edit.add_fixits (&richloc);
1134 ASSERT_FALSE (edit.valid_p ());
1135 ASSERT_EQ (NULL, edit.get_content (filename));
1136 ASSERT_EQ (NULL, edit.generate_diff (false));
1139 /* Test applying an "insert" fixit that adds a newline. */
1141 static void
1142 test_applying_fixits_insert_containing_newline (const line_table_case &case_)
1144 /* Create a tempfile and write some text to it.
1145 .........................0000000001111111.
1146 .........................1234567890123456. */
1147 const char *old_content = (" case 'a':\n" /* line 1. */
1148 " x = a;\n" /* line 2. */
1149 " case 'b':\n" /* line 3. */
1150 " x = b;\n");/* line 4. */
1152 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1153 const char *filename = tmp.get_filename ();
1154 line_table_test ltt (case_);
1155 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1157 /* Add a "break;" on a line by itself before line 3 i.e. before
1158 column 1 of line 3. */
1159 location_t case_start = linemap_position_for_column (line_table, 5);
1160 location_t case_finish = linemap_position_for_column (line_table, 13);
1161 location_t case_loc = make_location (case_start, case_start, case_finish);
1162 rich_location richloc (line_table, case_loc);
1163 location_t line_start = linemap_position_for_column (line_table, 1);
1164 richloc.add_fixit_insert_before (line_start, " break;\n");
1166 if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1167 return;
1169 edit_context edit;
1170 edit.add_fixits (&richloc);
1171 auto_free <char *> new_content = edit.get_content (filename);
1172 ASSERT_STREQ ((" case 'a':\n"
1173 " x = a;\n"
1174 " break;\n"
1175 " case 'b':\n"
1176 " x = b;\n"),
1177 new_content);
1179 /* Verify diff. */
1180 auto_free <char *> diff = edit.generate_diff (false);
1181 ASSERT_STREQ (("@@ -1,4 +1,5 @@\n"
1182 " case 'a':\n"
1183 " x = a;\n"
1184 "+ break;\n"
1185 " case 'b':\n"
1186 " x = b;\n"),
1187 diff);
1190 /* Test applying a "replace" fixit that grows the affected line. */
1192 static void
1193 test_applying_fixits_growing_replace (const line_table_case &case_)
1195 /* Create a tempfile and write some text to it.
1196 .........................0000000001111111.
1197 .........................1234567890123456. */
1198 const char *old_content = ("/* before */\n"
1199 "foo = bar.field;\n"
1200 "/* after */\n");
1201 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1202 const char *filename = tmp.get_filename ();
1203 line_table_test ltt (case_);
1204 linemap_add (line_table, LC_ENTER, false, filename, 2);
1206 /* Replace "field" with "m_field". */
1207 location_t start = linemap_position_for_column (line_table, 11);
1208 location_t finish = linemap_position_for_column (line_table, 15);
1209 location_t field = make_location (start, start, finish);
1210 rich_location richloc (line_table, field);
1211 richloc.add_fixit_replace ("m_field");
1213 edit_context edit;
1214 edit.add_fixits (&richloc);
1215 auto_free <char *> new_content = edit.get_content (filename);
1216 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1218 ASSERT_STREQ ("/* before */\n"
1219 "foo = bar.m_field;\n"
1220 "/* after */\n", new_content);
1222 /* Verify location of ";" after the change. */
1223 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 16));
1225 /* Verify diff. */
1226 auto_free <char *> diff = edit.generate_diff (false);
1227 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1228 " /* before */\n"
1229 "-foo = bar.field;\n"
1230 "+foo = bar.m_field;\n"
1231 " /* after */\n", diff);
1235 /* Test applying a "replace" fixit that shrinks the affected line. */
1237 static void
1238 test_applying_fixits_shrinking_replace (const line_table_case &case_)
1240 /* Create a tempfile and write some text to it.
1241 .........................000000000111111111.
1242 .........................123456789012345678. */
1243 const char *old_content = ("/* before */\n"
1244 "foo = bar.m_field;\n"
1245 "/* after */\n");
1246 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1247 const char *filename = tmp.get_filename ();
1248 line_table_test ltt (case_);
1249 linemap_add (line_table, LC_ENTER, false, filename, 2);
1251 /* Replace "field" with "m_field". */
1252 location_t start = linemap_position_for_column (line_table, 11);
1253 location_t finish = linemap_position_for_column (line_table, 17);
1254 location_t m_field = make_location (start, start, finish);
1255 rich_location richloc (line_table, m_field);
1256 richloc.add_fixit_replace ("field");
1258 edit_context edit;
1259 edit.add_fixits (&richloc);
1260 auto_free <char *> new_content = edit.get_content (filename);
1261 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1263 ASSERT_STREQ ("/* before */\n"
1264 "foo = bar.field;\n"
1265 "/* after */\n", new_content);
1267 /* Verify location of ";" after the change. */
1268 ASSERT_EQ (16, edit.get_effective_column (filename, 2, 18));
1270 /* Verify diff. */
1271 auto_free <char *> diff = edit.generate_diff (false);
1272 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1273 " /* before */\n"
1274 "-foo = bar.m_field;\n"
1275 "+foo = bar.field;\n"
1276 " /* after */\n", diff);
1280 /* Replacement fix-it hint containing a newline. */
1282 static void
1283 test_applying_fixits_replace_containing_newline (const line_table_case &case_)
1285 /* Create a tempfile and write some text to it.
1286 .........................0000000001111.
1287 .........................1234567890123. */
1288 const char *old_content = "foo = bar ();\n";
1290 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1291 const char *filename = tmp.get_filename ();
1292 line_table_test ltt (case_);
1293 linemap_add (line_table, LC_ENTER, false, filename, 1);
1295 /* Replace the " = " with "\n = ", as if we were reformatting an
1296 overly long line. */
1297 location_t start = linemap_position_for_column (line_table, 4);
1298 location_t finish = linemap_position_for_column (line_table, 6);
1299 location_t loc = linemap_position_for_column (line_table, 13);
1300 rich_location richloc (line_table, loc);
1301 source_range range = source_range::from_locations (start, finish);
1302 richloc.add_fixit_replace (range, "\n = ");
1304 /* Newlines are only supported within fix-it hints that
1305 are at the start of lines (for entirely new lines), hence
1306 this fix-it should not be displayed. */
1307 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1309 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1310 return;
1312 edit_context edit;
1313 edit.add_fixits (&richloc);
1314 auto_free <char *> new_content = edit.get_content (filename);
1315 //ASSERT_STREQ ("foo\n = bar ();\n", new_content);
1318 /* Test applying a "remove" fixit. */
1320 static void
1321 test_applying_fixits_remove (const line_table_case &case_)
1323 /* Create a tempfile and write some text to it.
1324 .........................000000000111111111.
1325 .........................123456789012345678. */
1326 const char *old_content = ("/* before */\n"
1327 "foo = bar.m_field;\n"
1328 "/* after */\n");
1329 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1330 const char *filename = tmp.get_filename ();
1331 line_table_test ltt (case_);
1332 linemap_add (line_table, LC_ENTER, false, filename, 2);
1334 /* Remove ".m_field". */
1335 location_t start = linemap_position_for_column (line_table, 10);
1336 location_t finish = linemap_position_for_column (line_table, 17);
1337 rich_location richloc (line_table, start);
1338 source_range range;
1339 range.m_start = start;
1340 range.m_finish = finish;
1341 richloc.add_fixit_remove (range);
1343 edit_context edit;
1344 edit.add_fixits (&richloc);
1345 auto_free <char *> new_content = edit.get_content (filename);
1346 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1348 ASSERT_STREQ ("/* before */\n"
1349 "foo = bar;\n"
1350 "/* after */\n", new_content);
1352 /* Verify location of ";" after the change. */
1353 ASSERT_EQ (10, edit.get_effective_column (filename, 2, 18));
1355 /* Verify diff. */
1356 auto_free <char *> diff = edit.generate_diff (false);
1357 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1358 " /* before */\n"
1359 "-foo = bar.m_field;\n"
1360 "+foo = bar;\n"
1361 " /* after */\n", diff);
1365 /* Test applying multiple fixits to one line. */
1367 static void
1368 test_applying_fixits_multiple (const line_table_case &case_)
1370 /* Create a tempfile and write some text to it.
1371 .........................00000000011111111.
1372 .........................12345678901234567. */
1373 const char *old_content = ("/* before */\n"
1374 "foo = bar.field;\n"
1375 "/* after */\n");
1376 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1377 const char *filename = tmp.get_filename ();
1378 line_table_test ltt (case_);
1379 linemap_add (line_table, LC_ENTER, false, filename, 2);
1381 location_t c7 = linemap_position_for_column (line_table, 7);
1382 location_t c9 = linemap_position_for_column (line_table, 9);
1383 location_t c11 = linemap_position_for_column (line_table, 11);
1384 location_t c15 = linemap_position_for_column (line_table, 15);
1385 location_t c17 = linemap_position_for_column (line_table, 17);
1387 if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1388 return;
1390 /* Add a comment in front of "bar.field". */
1391 rich_location insert_a (line_table, c7);
1392 insert_a.add_fixit_insert_before (c7, "/* alpha */");
1394 /* Add a comment after "bar.field;". */
1395 rich_location insert_b (line_table, c17);
1396 insert_b.add_fixit_insert_before (c17, "/* beta */");
1398 /* Replace "bar" with "pub". */
1399 rich_location replace_a (line_table, c7);
1400 replace_a.add_fixit_replace (source_range::from_locations (c7, c9),
1401 "pub");
1403 /* Replace "field" with "meadow". */
1404 rich_location replace_b (line_table, c7);
1405 replace_b.add_fixit_replace (source_range::from_locations (c11, c15),
1406 "meadow");
1408 edit_context edit;
1409 edit.add_fixits (&insert_a);
1410 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
1411 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
1412 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
1413 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
1414 ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
1415 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
1417 edit.add_fixits (&insert_b);
1418 edit.add_fixits (&replace_a);
1419 edit.add_fixits (&replace_b);
1421 if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1423 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1424 ASSERT_STREQ ("/* before */\n"
1425 "foo = /* alpha */pub.meadow;/* beta */\n"
1426 "/* after */\n",
1427 new_content);
1429 /* Verify diff. */
1430 auto_free <char *> diff = edit.generate_diff (false);
1431 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1432 " /* before */\n"
1433 "-foo = bar.field;\n"
1434 "+foo = /* alpha */pub.meadow;/* beta */\n"
1435 " /* after */\n", diff);
1439 /* Subroutine of test_applying_fixits_multiple_lines.
1440 Add the text "CHANGED: " to the front of the given line. */
1442 static location_t
1443 change_line (edit_context &edit, int line_num)
1445 const line_map_ordinary *ord_map
1446 = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1447 const int column = 1;
1448 location_t loc =
1449 linemap_position_for_line_and_column (line_table, ord_map,
1450 line_num, column);
1452 expanded_location exploc = expand_location (loc);
1453 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1455 ASSERT_EQ (line_num, exploc.line);
1456 ASSERT_EQ (column, exploc.column);
1459 rich_location insert (line_table, loc);
1460 insert.add_fixit_insert_before ("CHANGED: ");
1461 edit.add_fixits (&insert);
1462 return loc;
1465 /* Subroutine of test_applying_fixits_multiple_lines.
1466 Add the text "INSERTED\n" in front of the given line. */
1468 static location_t
1469 insert_line (edit_context &edit, int line_num)
1471 const line_map_ordinary *ord_map
1472 = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1473 const int column = 1;
1474 location_t loc =
1475 linemap_position_for_line_and_column (line_table, ord_map,
1476 line_num, column);
1478 expanded_location exploc = expand_location (loc);
1479 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1481 ASSERT_EQ (line_num, exploc.line);
1482 ASSERT_EQ (column, exploc.column);
1485 rich_location insert (line_table, loc);
1486 insert.add_fixit_insert_before ("INSERTED\n");
1487 edit.add_fixits (&insert);
1488 return loc;
1491 /* Test of editing multiple lines within a long file,
1492 to ensure that diffs are generated as expected. */
1494 static void
1495 test_applying_fixits_multiple_lines (const line_table_case &case_)
1497 /* Create a tempfile and write many lines of text to it. */
1498 named_temp_file tmp (".txt");
1499 const char *filename = tmp.get_filename ();
1500 FILE *f = fopen (filename, "w");
1501 ASSERT_NE (f, NULL);
1502 for (int i = 1; i <= 1000; i++)
1503 fprintf (f, "line %i\n", i);
1504 fclose (f);
1506 line_table_test ltt (case_);
1507 linemap_add (line_table, LC_ENTER, false, filename, 1);
1508 linemap_position_for_column (line_table, 127);
1510 edit_context edit;
1512 /* A run of consecutive lines. */
1513 change_line (edit, 2);
1514 change_line (edit, 3);
1515 change_line (edit, 4);
1516 insert_line (edit, 5);
1518 /* A run of nearby lines, within the contextual limit. */
1519 change_line (edit, 150);
1520 change_line (edit, 151);
1521 location_t last_loc = change_line (edit, 153);
1523 if (last_loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1524 return;
1526 /* Verify diff. */
1527 auto_free <char *> diff = edit.generate_diff (false);
1528 ASSERT_STREQ ("@@ -1,7 +1,8 @@\n"
1529 " line 1\n"
1530 "-line 2\n"
1531 "-line 3\n"
1532 "-line 4\n"
1533 "+CHANGED: line 2\n"
1534 "+CHANGED: line 3\n"
1535 "+CHANGED: line 4\n"
1536 "+INSERTED\n"
1537 " line 5\n"
1538 " line 6\n"
1539 " line 7\n"
1540 "@@ -147,10 +148,10 @@\n"
1541 " line 147\n"
1542 " line 148\n"
1543 " line 149\n"
1544 "-line 150\n"
1545 "-line 151\n"
1546 "+CHANGED: line 150\n"
1547 "+CHANGED: line 151\n"
1548 " line 152\n"
1549 "-line 153\n"
1550 "+CHANGED: line 153\n"
1551 " line 154\n"
1552 " line 155\n"
1553 " line 156\n", diff);
1555 /* Ensure tmp stays alive until this point, so that the tempfile
1556 persists until after the generate_diff call. */
1557 tmp.get_filename ();
1560 /* Test of converting an initializer for a named field from
1561 the old GCC extension to C99 syntax.
1562 Exercises a shrinking replacement followed by a growing
1563 replacement on the same line. */
1565 static void
1566 test_applying_fixits_modernize_named_init (const line_table_case &case_)
1568 /* Create a tempfile and write some text to it.
1569 .........................00000000011111111.
1570 .........................12345678901234567. */
1571 const char *old_content = ("/* before */\n"
1572 "bar : 1,\n"
1573 "/* after */\n");
1574 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1575 const char *filename = tmp.get_filename ();
1576 line_table_test ltt (case_);
1577 linemap_add (line_table, LC_ENTER, false, filename, 2);
1579 location_t c1 = linemap_position_for_column (line_table, 1);
1580 location_t c3 = linemap_position_for_column (line_table, 3);
1581 location_t c8 = linemap_position_for_column (line_table, 8);
1583 if (c8 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1584 return;
1586 /* Replace "bar" with ".". */
1587 rich_location r1 (line_table, c8);
1588 r1.add_fixit_replace (source_range::from_locations (c1, c3),
1589 ".");
1591 /* Replace ":" with "bar =". */
1592 rich_location r2 (line_table, c8);
1593 r2.add_fixit_replace (source_range::from_locations (c8, c8),
1594 "bar =");
1596 /* The order should not matter. Do r1 then r2. */
1598 edit_context edit;
1599 edit.add_fixits (&r1);
1601 /* Verify state after first replacement. */
1603 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1604 /* We should now have:
1605 ............00000000011.
1606 ............12345678901. */
1607 ASSERT_STREQ ("/* before */\n"
1608 ". : 1,\n"
1609 "/* after */\n",
1610 new_content);
1611 /* Location of the "1". */
1612 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
1613 /* Location of the ",". */
1614 ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
1617 edit.add_fixits (&r2);
1619 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1620 /* Verify state after second replacement.
1621 ............00000000011111111.
1622 ............12345678901234567. */
1623 ASSERT_STREQ ("/* before */\n"
1624 ". bar = 1,\n"
1625 "/* after */\n",
1626 new_content);
1629 /* Try again, doing r2 then r1; the new_content should be the same. */
1631 edit_context edit;
1632 edit.add_fixits (&r2);
1633 edit.add_fixits (&r1);
1634 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1635 /*.............00000000011111111.
1636 .............12345678901234567. */
1637 ASSERT_STREQ ("/* before */\n"
1638 ". bar = 1,\n"
1639 "/* after */\n",
1640 new_content);
1644 /* Test of a fixit affecting a file that can't be read. */
1646 static void
1647 test_applying_fixits_unreadable_file ()
1649 const char *filename = "this-does-not-exist.txt";
1650 line_table_test ltt ();
1651 linemap_add (line_table, LC_ENTER, false, filename, 1);
1653 location_t loc = linemap_position_for_column (line_table, 1);
1655 rich_location insert (line_table, loc);
1656 insert.add_fixit_insert_before ("change 1");
1657 insert.add_fixit_insert_before ("change 2");
1659 edit_context edit;
1660 /* Attempting to add the fixits affecting the unreadable file
1661 should transition the edit from valid to invalid. */
1662 ASSERT_TRUE (edit.valid_p ());
1663 edit.add_fixits (&insert);
1664 ASSERT_FALSE (edit.valid_p ());
1665 ASSERT_EQ (NULL, edit.get_content (filename));
1666 ASSERT_EQ (NULL, edit.generate_diff (false));
1669 /* Verify that we gracefully handle an attempt to edit a line
1670 that's beyond the end of the file. */
1672 static void
1673 test_applying_fixits_line_out_of_range ()
1675 /* Create a tempfile and write some text to it.
1676 ........................00000000011111111.
1677 ........................12345678901234567. */
1678 const char *old_content = "One-liner file\n";
1679 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1680 const char *filename = tmp.get_filename ();
1681 line_table_test ltt ();
1682 linemap_add (line_table, LC_ENTER, false, filename, 2);
1684 /* Try to insert a string in line 2. */
1685 location_t loc = linemap_position_for_column (line_table, 1);
1687 rich_location insert (line_table, loc);
1688 insert.add_fixit_insert_before ("change");
1690 /* Verify that attempting the insertion puts an edit_context
1691 into an invalid state. */
1692 edit_context edit;
1693 ASSERT_TRUE (edit.valid_p ());
1694 edit.add_fixits (&insert);
1695 ASSERT_FALSE (edit.valid_p ());
1696 ASSERT_EQ (NULL, edit.get_content (filename));
1697 ASSERT_EQ (NULL, edit.generate_diff (false));
1700 /* Verify the boundary conditions of column values in fix-it
1701 hints applied to edit_context instances. */
1703 static void
1704 test_applying_fixits_column_validation (const line_table_case &case_)
1706 /* Create a tempfile and write some text to it.
1707 ........................00000000011111111.
1708 ........................12345678901234567. */
1709 const char *old_content = "One-liner file\n";
1710 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1711 const char *filename = tmp.get_filename ();
1712 line_table_test ltt (case_);
1713 linemap_add (line_table, LC_ENTER, false, filename, 1);
1715 location_t c11 = linemap_position_for_column (line_table, 11);
1716 location_t c14 = linemap_position_for_column (line_table, 14);
1717 location_t c15 = linemap_position_for_column (line_table, 15);
1718 location_t c16 = linemap_position_for_column (line_table, 16);
1720 /* Verify limits of valid columns in insertion fixits. */
1722 /* Verify inserting at the end of the line. */
1724 rich_location richloc (line_table, c11);
1725 richloc.add_fixit_insert_before (c15, " change");
1727 /* Col 15 is at the end of the line, so the insertion
1728 should succeed. */
1729 edit_context edit;
1730 edit.add_fixits (&richloc);
1731 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1732 if (c15 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1733 ASSERT_STREQ ("One-liner file change\n", new_content);
1734 else
1735 ASSERT_EQ (NULL, new_content);
1738 /* Verify inserting beyond the end of the line. */
1740 rich_location richloc (line_table, c11);
1741 richloc.add_fixit_insert_before (c16, " change");
1743 /* Col 16 is beyond the end of the line, so the insertion
1744 should fail gracefully. */
1745 edit_context edit;
1746 ASSERT_TRUE (edit.valid_p ());
1747 edit.add_fixits (&richloc);
1748 ASSERT_FALSE (edit.valid_p ());
1749 ASSERT_EQ (NULL, edit.get_content (filename));
1750 ASSERT_EQ (NULL, edit.generate_diff (false));
1753 /* Verify limits of valid columns in replacement fixits. */
1755 /* Verify replacing the end of the line. */
1757 rich_location richloc (line_table, c11);
1758 source_range range = source_range::from_locations (c11, c14);
1759 richloc.add_fixit_replace (range, "change");
1761 /* Col 14 is at the end of the line, so the replacement
1762 should succeed. */
1763 edit_context edit;
1764 edit.add_fixits (&richloc);
1765 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1766 if (c14 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1767 ASSERT_STREQ ("One-liner change\n", new_content);
1768 else
1769 ASSERT_EQ (NULL, new_content);
1772 /* Verify going beyond the end of the line. */
1774 rich_location richloc (line_table, c11);
1775 source_range range = source_range::from_locations (c11, c15);
1776 richloc.add_fixit_replace (range, "change");
1778 /* Col 15 is after the end of the line, so the replacement
1779 should fail; verify that the attempt fails gracefully. */
1780 edit_context edit;
1781 ASSERT_TRUE (edit.valid_p ());
1782 edit.add_fixits (&richloc);
1783 ASSERT_FALSE (edit.valid_p ());
1784 ASSERT_EQ (NULL, edit.get_content (filename));
1785 ASSERT_EQ (NULL, edit.generate_diff (false));
1789 /* Run all of the selftests within this file. */
1791 void
1792 edit_context_c_tests ()
1794 test_get_content ();
1795 for_each_line_table_case (test_applying_fixits_insert_before);
1796 for_each_line_table_case (test_applying_fixits_insert_after);
1797 for_each_line_table_case (test_applying_fixits_insert_after_at_line_end);
1798 for_each_line_table_case (test_applying_fixits_insert_after_failure);
1799 for_each_line_table_case (test_applying_fixits_insert_containing_newline);
1800 for_each_line_table_case (test_applying_fixits_growing_replace);
1801 for_each_line_table_case (test_applying_fixits_shrinking_replace);
1802 for_each_line_table_case (test_applying_fixits_replace_containing_newline);
1803 for_each_line_table_case (test_applying_fixits_remove);
1804 for_each_line_table_case (test_applying_fixits_multiple);
1805 for_each_line_table_case (test_applying_fixits_multiple_lines);
1806 for_each_line_table_case (test_applying_fixits_modernize_named_init);
1807 test_applying_fixits_unreadable_file ();
1808 test_applying_fixits_line_out_of_range ();
1809 for_each_line_table_case (test_applying_fixits_column_validation);
1812 } // namespace selftest
1814 #endif /* CHECKING_P */