2018-10-26 Richard Biener <rguenther@suse.de>
[official-gcc.git] / gcc / edit-context.c
blob85afc4a2ec7d05425d346d301ef209e3624f7511
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_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_delta;
200 /* Forward decls. */
202 static void
203 print_diff_line (pretty_printer *pp, char prefix_char,
204 const char *line, int line_size);
206 /* Implementation of class edit_context. */
208 /* edit_context's ctor. */
210 edit_context::edit_context ()
211 : m_valid (true),
212 m_files (strcmp, NULL, edited_file::delete_cb)
215 /* Add any fixits within RICHLOC to this context, recording the
216 changes that they make. */
218 void
219 edit_context::add_fixits (rich_location *richloc)
221 if (!m_valid)
222 return;
223 if (richloc->seen_impossible_fixit_p ())
225 m_valid = false;
226 return;
228 for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
230 const fixit_hint *hint = richloc->get_fixit_hint (i);
231 if (!apply_fixit (hint))
232 m_valid = false;
236 /* Get the content of the given file, with fix-its applied.
237 If any errors occurred in this edit_context, return NULL.
238 The ptr should be freed by the caller. */
240 char *
241 edit_context::get_content (const char *filename)
243 if (!m_valid)
244 return NULL;
245 edited_file &file = get_or_insert_file (filename);
246 return file.get_content ();
249 /* Map a location before the edits to a column number after the edits.
250 This method is for the selftests. */
253 edit_context::get_effective_column (const char *filename, int line,
254 int column)
256 edited_file *file = get_file (filename);
257 if (!file)
258 return column;
259 return file->get_effective_column (line, column);
262 /* Generate a unified diff. The resulting string should be freed by the
263 caller. Primarily for selftests.
264 If any errors occurred in this edit_context, return NULL. */
266 char *
267 edit_context::generate_diff (bool show_filenames)
269 if (!m_valid)
270 return NULL;
272 pretty_printer pp;
273 print_diff (&pp, show_filenames);
274 return xstrdup (pp_formatted_text (&pp));
277 /* Print a unified diff to PP, showing the changes made within the
278 context. */
280 void
281 edit_context::print_diff (pretty_printer *pp, bool show_filenames)
283 if (!m_valid)
284 return;
285 diff d (pp, show_filenames);
286 m_files.foreach (edited_file::call_print_diff, &d);
289 /* Attempt to apply the given fixit. Return true if it can be
290 applied, or false otherwise. */
292 bool
293 edit_context::apply_fixit (const fixit_hint *hint)
295 expanded_location start = expand_location (hint->get_start_loc ());
296 expanded_location next_loc = expand_location (hint->get_next_loc ());
297 if (start.file != next_loc.file)
298 return false;
299 if (start.line != next_loc.line)
300 return false;
301 if (start.column == 0)
302 return false;
303 if (next_loc.column == 0)
304 return false;
306 edited_file &file = get_or_insert_file (start.file);
307 if (!m_valid)
308 return false;
309 return file.apply_fixit (start.line, start.column, next_loc.column,
310 hint->get_string (),
311 hint->get_length ());
314 /* Locate the edited_file * for FILENAME, if any
315 Return NULL if there isn't one. */
317 edited_file *
318 edit_context::get_file (const char *filename)
320 gcc_assert (filename);
321 return m_files.lookup (filename);
324 /* Locate the edited_file for FILENAME, adding one if there isn't one. */
326 edited_file &
327 edit_context::get_or_insert_file (const char *filename)
329 gcc_assert (filename);
331 edited_file *file = get_file (filename);
332 if (file)
333 return *file;
335 /* Not found. */
336 file = new edited_file (filename);
337 m_files.insert (filename, file);
338 return *file;
341 /* Implementation of class edited_file. */
343 /* Callback for m_edited_lines, for comparing line numbers. */
345 static int line_comparator (int a, int b)
347 return a - b;
350 /* edited_file's constructor. */
352 edited_file::edited_file (const char *filename)
353 : m_filename (filename),
354 m_edited_lines (line_comparator, NULL, edited_line::delete_cb),
355 m_num_lines (-1)
359 /* A callback for deleting edited_file *, for use as a
360 delete_value_fn for edit_context::m_files. */
362 void
363 edited_file::delete_cb (edited_file *file)
365 delete file;
368 /* Get the content of the file, with fix-its applied.
369 The ptr should be freed by the caller. */
371 char *
372 edited_file::get_content ()
374 pretty_printer pp;
375 if (!print_content (&pp))
376 return NULL;
377 return xstrdup (pp_formatted_text (&pp));
380 /* Attempt to replace columns START_COLUMN up to but not including NEXT_COLUMN
381 of LINE with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
382 updating the in-memory copy of the line, and the record of edits to
383 the line. */
385 bool
386 edited_file::apply_fixit (int line, int start_column, int next_column,
387 const char *replacement_str,
388 int replacement_len)
390 edited_line *el = get_or_insert_line (line);
391 if (!el)
392 return false;
393 return el->apply_fixit (start_column, next_column, replacement_str,
394 replacement_len);
397 /* Given line LINE, map from COLUMN in the input file to its current
398 column after edits have been applied. */
401 edited_file::get_effective_column (int line, int column)
403 const edited_line *el = get_line (line);
404 if (!el)
405 return column;
406 return el->get_effective_column (column);
409 /* Attempt to print the content of the file to PP, with edits applied.
410 Return true if successful, false otherwise. */
412 bool
413 edited_file::print_content (pretty_printer *pp)
415 bool missing_trailing_newline;
416 int line_count = get_num_lines (&missing_trailing_newline);
417 for (int line_num = 1; line_num <= line_count; line_num++)
419 edited_line *el = get_line (line_num);
420 if (el)
421 el->print_content (pp);
422 else
424 char_span line = location_get_source_line (m_filename, line_num);
425 if (!line)
426 return false;
427 for (size_t i = 0; i < line.length (); i++)
428 pp_character (pp, line[i]);
430 if (line_num < line_count)
431 pp_character (pp, '\n');
434 if (!missing_trailing_newline)
435 pp_character (pp, '\n');
437 return true;
440 /* Print a unified diff to PP, showing any changes that have occurred
441 to this file. */
443 void
444 edited_file::print_diff (pretty_printer *pp, bool show_filenames)
446 if (show_filenames)
448 pp_string (pp, colorize_start (pp_show_color (pp), "diff-filename"));
449 pp_printf (pp, "--- %s\n", m_filename);
450 pp_printf (pp, "+++ %s\n", m_filename);
451 pp_string (pp, colorize_stop (pp_show_color (pp)));
454 edited_line *el = m_edited_lines.min ();
456 bool missing_trailing_newline;
457 int line_count = get_num_lines (&missing_trailing_newline);
459 const int context_lines = 3;
461 /* Track new line numbers minus old line numbers. */
463 int line_delta = 0;
465 while (el)
467 int start_of_hunk = el->get_line_num ();
468 start_of_hunk -= context_lines;
469 if (start_of_hunk < 1)
470 start_of_hunk = 1;
472 /* Locate end of hunk, merging in changed lines
473 that are sufficiently close. */
474 while (true)
476 edited_line *next_el
477 = m_edited_lines.successor (el->get_line_num ());
478 if (!next_el)
479 break;
481 int end_of_printed_hunk = el->get_line_num () + context_lines;
482 if (!el->actually_edited_p ())
483 end_of_printed_hunk--;
485 if (end_of_printed_hunk
486 >= next_el->get_line_num () - context_lines)
487 el = next_el;
488 else
489 break;
492 int end_of_hunk = el->get_line_num ();
493 end_of_hunk += context_lines;
494 if (!el->actually_edited_p ())
495 end_of_hunk--;
496 if (end_of_hunk > line_count)
497 end_of_hunk = line_count;
499 int new_start_of_hunk = start_of_hunk + line_delta;
500 line_delta += print_diff_hunk (pp, start_of_hunk, end_of_hunk,
501 new_start_of_hunk);
502 el = m_edited_lines.successor (el->get_line_num ());
506 /* Print one hunk within a unified diff to PP, covering the
507 given range of lines. OLD_START_OF_HUNK and OLD_END_OF_HUNK are
508 line numbers in the unedited version of the file.
509 NEW_START_OF_HUNK is a line number in the edited version of the file.
510 Return the change in the line count within the hunk. */
513 edited_file::print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
514 int old_end_of_hunk, int new_start_of_hunk)
516 int old_num_lines = old_end_of_hunk - old_start_of_hunk + 1;
517 int new_num_lines
518 = get_effective_line_count (old_start_of_hunk, old_end_of_hunk);
520 pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
521 pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", old_start_of_hunk, old_num_lines,
522 new_start_of_hunk, new_num_lines);
523 pp_string (pp, colorize_stop (pp_show_color (pp)));
525 int line_num = old_start_of_hunk;
526 while (line_num <= old_end_of_hunk)
528 edited_line *el = get_line (line_num);
529 if (el)
531 /* We have an edited line.
532 Consolidate into runs of changed lines. */
533 const int first_changed_line_in_run = line_num;
534 while (get_line (line_num))
535 line_num++;
536 const int last_changed_line_in_run = line_num - 1;
537 print_run_of_changed_lines (pp, first_changed_line_in_run,
538 last_changed_line_in_run);
540 else
542 /* Unchanged line. */
543 char_span old_line = location_get_source_line (m_filename, line_num);
544 print_diff_line (pp, ' ', old_line.get_buffer (), old_line.length ());
545 line_num++;
549 return new_num_lines - old_num_lines;
552 /* Subroutine of edited_file::print_diff_hunk: given a run of lines
553 from START_OF_RUN to END_OF_RUN that all have edited_line instances,
554 print the diff to PP. */
556 void
557 edited_file::print_run_of_changed_lines (pretty_printer *pp,
558 int start_of_run,
559 int end_of_run)
561 /* Show old version of lines. */
562 pp_string (pp, colorize_start (pp_show_color (pp),
563 "diff-delete"));
564 for (int line_num = start_of_run;
565 line_num <= end_of_run;
566 line_num++)
568 edited_line *el_in_run = get_line (line_num);
569 gcc_assert (el_in_run);
570 if (el_in_run->actually_edited_p ())
572 char_span old_line = location_get_source_line (m_filename, line_num);
573 print_diff_line (pp, '-', old_line.get_buffer (),
574 old_line.length ());
577 pp_string (pp, colorize_stop (pp_show_color (pp)));
579 /* Show new version of lines. */
580 pp_string (pp, colorize_start (pp_show_color (pp),
581 "diff-insert"));
582 for (int line_num = start_of_run;
583 line_num <= end_of_run;
584 line_num++)
586 edited_line *el_in_run = get_line (line_num);
587 gcc_assert (el_in_run);
588 el_in_run->print_diff_lines (pp);
590 pp_string (pp, colorize_stop (pp_show_color (pp)));
593 /* Print one line within a diff, starting with PREFIX_CHAR,
594 followed by the LINE of content, of length LEN. LINE is
595 not necessarily 0-terminated. Print a trailing newline. */
597 static void
598 print_diff_line (pretty_printer *pp, char prefix_char,
599 const char *line, int len)
601 pp_character (pp, prefix_char);
602 for (int i = 0; i < len; i++)
603 pp_character (pp, line[i]);
604 pp_character (pp, '\n');
607 /* Determine the number of lines that will be present after
608 editing for the range of lines from OLD_START_OF_HUNK to
609 OLD_END_OF_HUNK inclusive. */
612 edited_file::get_effective_line_count (int old_start_of_hunk,
613 int old_end_of_hunk)
615 int line_count = 0;
616 for (int old_line_num = old_start_of_hunk; old_line_num <= old_end_of_hunk;
617 old_line_num++)
619 edited_line *el = get_line (old_line_num);
620 if (el)
621 line_count += el->get_effective_line_count ();
622 else
623 line_count++;
625 return line_count;
628 /* Get the state of LINE within the file, or NULL if it is untouched. */
630 edited_line *
631 edited_file::get_line (int line)
633 return m_edited_lines.lookup (line);
636 /* Get the state of LINE within the file, creating a state for it
637 if necessary. Return NULL if an error occurs. */
639 edited_line *
640 edited_file::get_or_insert_line (int line)
642 edited_line *el = get_line (line);
643 if (el)
644 return el;
645 el = new edited_line (m_filename, line);
646 if (el->get_content () == NULL)
648 delete el;
649 return NULL;
651 m_edited_lines.insert (line, el);
652 return el;
655 /* Get the total number of lines in m_content, writing
656 true to *MISSING_TRAILING_NEWLINE if the final line
657 if missing a newline, false otherwise. */
660 edited_file::get_num_lines (bool *missing_trailing_newline)
662 gcc_assert (missing_trailing_newline);
663 if (m_num_lines == -1)
665 m_num_lines = 0;
666 while (true)
668 char_span line
669 = location_get_source_line (m_filename, m_num_lines + 1);
670 if (line)
671 m_num_lines++;
672 else
673 break;
676 *missing_trailing_newline = location_missing_trailing_newline (m_filename);
677 return m_num_lines;
680 /* Implementation of class edited_line. */
682 /* edited_line's ctor. */
684 edited_line::edited_line (const char *filename, int line_num)
685 : m_line_num (line_num),
686 m_content (NULL), m_len (0), m_alloc_sz (0),
687 m_line_events (),
688 m_predecessors ()
690 char_span line = location_get_source_line (filename, line_num);
691 if (!line)
692 return;
693 m_len = line.length ();
694 ensure_capacity (m_len);
695 memcpy (m_content, line.get_buffer (), m_len);
696 ensure_terminated ();
699 /* edited_line's dtor. */
701 edited_line::~edited_line ()
703 unsigned i;
704 added_line *pred;
706 free (m_content);
707 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
708 delete pred;
711 /* A callback for deleting edited_line *, for use as a
712 delete_value_fn for edited_file::m_edited_lines. */
714 void
715 edited_line::delete_cb (edited_line *el)
717 delete el;
720 /* Map a location before the edits to a column number after the edits,
721 within a specific line. */
724 edited_line::get_effective_column (int orig_column) const
726 int i;
727 line_event *event;
728 FOR_EACH_VEC_ELT (m_line_events, i, event)
729 orig_column = event->get_effective_column (orig_column);
730 return orig_column;
733 /* Attempt to replace columns START_COLUMN up to but not including
734 NEXT_COLUMN of the line with the string REPLACEMENT_STR of
735 length REPLACEMENT_LEN, updating the in-memory copy of the line,
736 and the record of edits to the line.
737 Return true if successful; false if an error occurred. */
739 bool
740 edited_line::apply_fixit (int start_column,
741 int next_column,
742 const char *replacement_str,
743 int replacement_len)
745 /* Handle newlines. They will only ever be at the end of the
746 replacement text, thanks to the filtering in rich_location. */
747 if (replacement_len > 1)
748 if (replacement_str[replacement_len - 1] == '\n')
750 /* Stash in m_predecessors, stripping off newline. */
751 m_predecessors.safe_push (new added_line (replacement_str,
752 replacement_len - 1));
753 return true;
756 start_column = get_effective_column (start_column);
757 next_column = get_effective_column (next_column);
759 int start_offset = start_column - 1;
760 int next_offset = next_column - 1;
762 gcc_assert (start_offset >= 0);
763 gcc_assert (next_offset >= 0);
765 if (start_column > next_column)
766 return false;
767 if (start_offset >= (m_len + 1))
768 return false;
769 if (next_offset >= (m_len + 1))
770 return false;
772 size_t victim_len = next_offset - start_offset;
774 /* Ensure buffer is big enough. */
775 size_t new_len = m_len + replacement_len - victim_len;
776 ensure_capacity (new_len);
778 char *suffix = m_content + next_offset;
779 gcc_assert (suffix <= m_content + m_len);
780 size_t len_suffix = (m_content + m_len) - suffix;
782 /* Move successor content into position. They overlap, so use memmove. */
783 memmove (m_content + start_offset + replacement_len,
784 suffix, len_suffix);
786 /* Replace target content. They don't overlap, so use memcpy. */
787 memcpy (m_content + start_offset,
788 replacement_str,
789 replacement_len);
791 m_len = new_len;
793 ensure_terminated ();
795 /* Record the replacement, so that future changes to the line can have
796 their column information adjusted accordingly. */
797 m_line_events.safe_push (line_event (start_column, next_column,
798 replacement_len));
799 return true;
802 /* Determine the number of lines that will be present after
803 editing for this line. Typically this is just 1, but
804 if newlines have been added before this line, they will
805 also be counted. */
808 edited_line::get_effective_line_count () const
810 return m_predecessors.length () + 1;
813 /* Subroutine of edited_file::print_content.
814 Print this line and any new lines added before it, to PP. */
816 void
817 edited_line::print_content (pretty_printer *pp) const
819 unsigned i;
820 added_line *pred;
821 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
823 pp_string (pp, pred->get_content ());
824 pp_newline (pp);
826 pp_string (pp, m_content);
829 /* Subroutine of edited_file::print_run_of_changed_lines for
830 printing diff hunks to PP.
831 Print the '+' line for this line, and any newlines added
832 before it.
833 Note that if this edited_line was actually edited, the '-'
834 line has already been printed. If it wasn't, then we merely
835 have a placeholder edited_line for adding newlines to, and
836 we need to print a ' ' line for the edited_line as we haven't
837 printed it yet. */
839 void
840 edited_line::print_diff_lines (pretty_printer *pp) const
842 unsigned i;
843 added_line *pred;
844 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
845 print_diff_line (pp, '+', pred->get_content (),
846 pred->get_len ());
847 if (actually_edited_p ())
848 print_diff_line (pp, '+', m_content, m_len);
849 else
850 print_diff_line (pp, ' ', m_content, m_len);
853 /* Ensure that the buffer for m_content is at least large enough to hold
854 a string of length LEN and its 0-terminator, doubling on repeated
855 allocations. */
857 void
858 edited_line::ensure_capacity (int len)
860 /* Allow 1 extra byte for 0-termination. */
861 if (m_alloc_sz < (len + 1))
863 size_t new_alloc_sz = (len + 1) * 2;
864 m_content = (char *)xrealloc (m_content, new_alloc_sz);
865 m_alloc_sz = new_alloc_sz;
869 /* Ensure that m_content is 0-terminated. */
871 void
872 edited_line::ensure_terminated ()
874 /* 0-terminate the buffer. */
875 gcc_assert (m_len < m_alloc_sz);
876 m_content[m_len] = '\0';
879 #if CHECKING_P
881 /* Selftests of code-editing. */
883 namespace selftest {
885 /* A wrapper class for ensuring that the underlying pointer is freed. */
887 template <typename POINTER_T>
888 class auto_free
890 public:
891 auto_free (POINTER_T p) : m_ptr (p) {}
892 ~auto_free () { free (m_ptr); }
894 operator POINTER_T () { return m_ptr; }
896 private:
897 POINTER_T m_ptr;
900 /* Verify that edit_context::get_content works for unedited files. */
902 static void
903 test_get_content ()
905 /* Test of empty file. */
907 const char *content = ("");
908 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
909 edit_context edit;
910 auto_free <char *> result = edit.get_content (tmp.get_filename ());
911 ASSERT_STREQ ("", result);
914 /* Test of simple content. */
916 const char *content = ("/* before */\n"
917 "foo = bar.field;\n"
918 "/* after */\n");
919 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
920 edit_context edit;
921 auto_free <char *> result = edit.get_content (tmp.get_filename ());
922 ASSERT_STREQ ("/* before */\n"
923 "foo = bar.field;\n"
924 "/* after */\n", result);
927 /* Test of omitting the trailing newline on the final line. */
929 const char *content = ("/* before */\n"
930 "foo = bar.field;\n"
931 "/* after */");
932 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
933 edit_context edit;
934 auto_free <char *> result = edit.get_content (tmp.get_filename ());
935 /* We should respect the omitted trailing newline. */
936 ASSERT_STREQ ("/* before */\n"
937 "foo = bar.field;\n"
938 "/* after */", result);
942 /* Test applying an "insert" fixit, using insert_before. */
944 static void
945 test_applying_fixits_insert_before (const line_table_case &case_)
947 /* Create a tempfile and write some text to it.
948 .........................0000000001111111.
949 .........................1234567890123456. */
950 const char *old_content = ("/* before */\n"
951 "foo = bar.field;\n"
952 "/* after */\n");
953 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
954 const char *filename = tmp.get_filename ();
955 line_table_test ltt (case_);
956 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
958 /* Add a comment in front of "bar.field". */
959 location_t start = linemap_position_for_column (line_table, 7);
960 rich_location richloc (line_table, start);
961 richloc.add_fixit_insert_before ("/* inserted */");
963 if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
964 return;
966 edit_context edit;
967 edit.add_fixits (&richloc);
968 auto_free <char *> new_content = edit.get_content (filename);
969 if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
970 ASSERT_STREQ ("/* before */\n"
971 "foo = /* inserted */bar.field;\n"
972 "/* after */\n", new_content);
974 /* Verify that locations on other lines aren't affected by the change. */
975 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
976 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
978 /* Verify locations on the line before the change. */
979 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
980 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
982 /* Verify locations on the line at and after the change. */
983 ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
984 ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));
986 /* Verify diff. */
987 auto_free <char *> diff = edit.generate_diff (false);
988 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
989 " /* before */\n"
990 "-foo = bar.field;\n"
991 "+foo = /* inserted */bar.field;\n"
992 " /* after */\n", diff);
995 /* Test applying an "insert" fixit, using insert_after, with
996 a range of length > 1 (to ensure that the end-point of
997 the input range is used). */
999 static void
1000 test_applying_fixits_insert_after (const line_table_case &case_)
1002 /* Create a tempfile and write some text to it.
1003 .........................0000000001111111.
1004 .........................1234567890123456. */
1005 const char *old_content = ("/* before */\n"
1006 "foo = bar.field;\n"
1007 "/* after */\n");
1008 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1009 const char *filename = tmp.get_filename ();
1010 line_table_test ltt (case_);
1011 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1013 /* Add a comment after "field". */
1014 location_t start = linemap_position_for_column (line_table, 11);
1015 location_t finish = linemap_position_for_column (line_table, 15);
1016 location_t field = make_location (start, start, finish);
1017 rich_location richloc (line_table, field);
1018 richloc.add_fixit_insert_after ("/* inserted */");
1020 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1021 return;
1023 /* Verify that the text was inserted after the end of "field". */
1024 edit_context edit;
1025 edit.add_fixits (&richloc);
1026 auto_free <char *> new_content = edit.get_content (filename);
1027 ASSERT_STREQ ("/* before */\n"
1028 "foo = bar.field/* inserted */;\n"
1029 "/* after */\n", new_content);
1031 /* Verify diff. */
1032 auto_free <char *> diff = edit.generate_diff (false);
1033 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1034 " /* before */\n"
1035 "-foo = bar.field;\n"
1036 "+foo = bar.field/* inserted */;\n"
1037 " /* after */\n", diff);
1040 /* Test applying an "insert" fixit, using insert_after at the end of
1041 a line (contrast with test_applying_fixits_insert_after_failure
1042 below). */
1044 static void
1045 test_applying_fixits_insert_after_at_line_end (const line_table_case &case_)
1047 /* Create a tempfile and write some text to it.
1048 .........................0000000001111111.
1049 .........................1234567890123456. */
1050 const char *old_content = ("/* before */\n"
1051 "foo = bar.field;\n"
1052 "/* after */\n");
1053 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1054 const char *filename = tmp.get_filename ();
1055 line_table_test ltt (case_);
1056 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1058 /* Add a comment after the semicolon. */
1059 location_t loc = linemap_position_for_column (line_table, 16);
1060 rich_location richloc (line_table, loc);
1061 richloc.add_fixit_insert_after ("/* inserted */");
1063 if (loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1064 return;
1066 edit_context edit;
1067 edit.add_fixits (&richloc);
1068 auto_free <char *> new_content = edit.get_content (filename);
1069 ASSERT_STREQ ("/* before */\n"
1070 "foo = bar.field;/* inserted */\n"
1071 "/* after */\n", new_content);
1073 /* Verify diff. */
1074 auto_free <char *> diff = edit.generate_diff (false);
1075 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1076 " /* before */\n"
1077 "-foo = bar.field;\n"
1078 "+foo = bar.field;/* inserted */\n"
1079 " /* after */\n", diff);
1082 /* Test of a failed attempt to apply an "insert" fixit, using insert_after,
1083 due to the relevant linemap ending. Contrast with
1084 test_applying_fixits_insert_after_at_line_end above. */
1086 static void
1087 test_applying_fixits_insert_after_failure (const line_table_case &case_)
1089 /* Create a tempfile and write some text to it.
1090 .........................0000000001111111.
1091 .........................1234567890123456. */
1092 const char *old_content = ("/* before */\n"
1093 "foo = bar.field;\n"
1094 "/* after */\n");
1095 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1096 const char *filename = tmp.get_filename ();
1097 line_table_test ltt (case_);
1098 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
1100 /* Add a comment after the semicolon. */
1101 location_t loc = linemap_position_for_column (line_table, 16);
1102 rich_location richloc (line_table, loc);
1104 /* We want a failure of linemap_position_for_loc_and_offset.
1105 We can do this by starting a new linemap at line 3, so that
1106 there is no appropriate location value for the insertion point
1107 within the linemap for line 2. */
1108 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1110 /* The failure fails to happen at the transition point from
1111 packed ranges to unpacked ranges (where there are some "spare"
1112 location_t values). Skip the test there. */
1113 if (loc >= LINE_MAP_MAX_LOCATION_WITH_PACKED_RANGES)
1114 return;
1116 /* Offsetting "loc" should now fail (by returning the input loc. */
1117 ASSERT_EQ (loc, linemap_position_for_loc_and_offset (line_table, loc, 1));
1119 /* Hence attempting to use add_fixit_insert_after at the end of the line
1120 should now fail. */
1121 richloc.add_fixit_insert_after ("/* inserted */");
1122 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1124 edit_context edit;
1125 edit.add_fixits (&richloc);
1126 ASSERT_FALSE (edit.valid_p ());
1127 ASSERT_EQ (NULL, edit.get_content (filename));
1128 ASSERT_EQ (NULL, edit.generate_diff (false));
1131 /* Test applying an "insert" fixit that adds a newline. */
1133 static void
1134 test_applying_fixits_insert_containing_newline (const line_table_case &case_)
1136 /* Create a tempfile and write some text to it.
1137 .........................0000000001111111.
1138 .........................1234567890123456. */
1139 const char *old_content = (" case 'a':\n" /* line 1. */
1140 " x = a;\n" /* line 2. */
1141 " case 'b':\n" /* line 3. */
1142 " x = b;\n");/* line 4. */
1144 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1145 const char *filename = tmp.get_filename ();
1146 line_table_test ltt (case_);
1147 linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
1149 /* Add a "break;" on a line by itself before line 3 i.e. before
1150 column 1 of line 3. */
1151 location_t case_start = linemap_position_for_column (line_table, 5);
1152 location_t case_finish = linemap_position_for_column (line_table, 13);
1153 location_t case_loc = make_location (case_start, case_start, case_finish);
1154 rich_location richloc (line_table, case_loc);
1155 location_t line_start = linemap_position_for_column (line_table, 1);
1156 richloc.add_fixit_insert_before (line_start, " break;\n");
1158 if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1159 return;
1161 edit_context edit;
1162 edit.add_fixits (&richloc);
1163 auto_free <char *> new_content = edit.get_content (filename);
1164 ASSERT_STREQ ((" case 'a':\n"
1165 " x = a;\n"
1166 " break;\n"
1167 " case 'b':\n"
1168 " x = b;\n"),
1169 new_content);
1171 /* Verify diff. */
1172 auto_free <char *> diff = edit.generate_diff (false);
1173 ASSERT_STREQ (("@@ -1,4 +1,5 @@\n"
1174 " case 'a':\n"
1175 " x = a;\n"
1176 "+ break;\n"
1177 " case 'b':\n"
1178 " x = b;\n"),
1179 diff);
1182 /* Test applying a "replace" fixit that grows the affected line. */
1184 static void
1185 test_applying_fixits_growing_replace (const line_table_case &case_)
1187 /* Create a tempfile and write some text to it.
1188 .........................0000000001111111.
1189 .........................1234567890123456. */
1190 const char *old_content = ("/* before */\n"
1191 "foo = bar.field;\n"
1192 "/* after */\n");
1193 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1194 const char *filename = tmp.get_filename ();
1195 line_table_test ltt (case_);
1196 linemap_add (line_table, LC_ENTER, false, filename, 2);
1198 /* Replace "field" with "m_field". */
1199 location_t start = linemap_position_for_column (line_table, 11);
1200 location_t finish = linemap_position_for_column (line_table, 15);
1201 location_t field = make_location (start, start, finish);
1202 rich_location richloc (line_table, field);
1203 richloc.add_fixit_replace ("m_field");
1205 edit_context edit;
1206 edit.add_fixits (&richloc);
1207 auto_free <char *> new_content = edit.get_content (filename);
1208 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1210 ASSERT_STREQ ("/* before */\n"
1211 "foo = bar.m_field;\n"
1212 "/* after */\n", new_content);
1214 /* Verify location of ";" after the change. */
1215 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 16));
1217 /* Verify diff. */
1218 auto_free <char *> diff = edit.generate_diff (false);
1219 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1220 " /* before */\n"
1221 "-foo = bar.field;\n"
1222 "+foo = bar.m_field;\n"
1223 " /* after */\n", diff);
1227 /* Test applying a "replace" fixit that shrinks the affected line. */
1229 static void
1230 test_applying_fixits_shrinking_replace (const line_table_case &case_)
1232 /* Create a tempfile and write some text to it.
1233 .........................000000000111111111.
1234 .........................123456789012345678. */
1235 const char *old_content = ("/* before */\n"
1236 "foo = bar.m_field;\n"
1237 "/* after */\n");
1238 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1239 const char *filename = tmp.get_filename ();
1240 line_table_test ltt (case_);
1241 linemap_add (line_table, LC_ENTER, false, filename, 2);
1243 /* Replace "field" with "m_field". */
1244 location_t start = linemap_position_for_column (line_table, 11);
1245 location_t finish = linemap_position_for_column (line_table, 17);
1246 location_t m_field = make_location (start, start, finish);
1247 rich_location richloc (line_table, m_field);
1248 richloc.add_fixit_replace ("field");
1250 edit_context edit;
1251 edit.add_fixits (&richloc);
1252 auto_free <char *> new_content = edit.get_content (filename);
1253 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1255 ASSERT_STREQ ("/* before */\n"
1256 "foo = bar.field;\n"
1257 "/* after */\n", new_content);
1259 /* Verify location of ";" after the change. */
1260 ASSERT_EQ (16, edit.get_effective_column (filename, 2, 18));
1262 /* Verify diff. */
1263 auto_free <char *> diff = edit.generate_diff (false);
1264 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1265 " /* before */\n"
1266 "-foo = bar.m_field;\n"
1267 "+foo = bar.field;\n"
1268 " /* after */\n", diff);
1272 /* Replacement fix-it hint containing a newline. */
1274 static void
1275 test_applying_fixits_replace_containing_newline (const line_table_case &case_)
1277 /* Create a tempfile and write some text to it.
1278 .........................0000000001111.
1279 .........................1234567890123. */
1280 const char *old_content = "foo = bar ();\n";
1282 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1283 const char *filename = tmp.get_filename ();
1284 line_table_test ltt (case_);
1285 linemap_add (line_table, LC_ENTER, false, filename, 1);
1287 /* Replace the " = " with "\n = ", as if we were reformatting an
1288 overly long line. */
1289 location_t start = linemap_position_for_column (line_table, 4);
1290 location_t finish = linemap_position_for_column (line_table, 6);
1291 location_t loc = linemap_position_for_column (line_table, 13);
1292 rich_location richloc (line_table, loc);
1293 source_range range = source_range::from_locations (start, finish);
1294 richloc.add_fixit_replace (range, "\n = ");
1296 /* Newlines are only supported within fix-it hints that
1297 are at the start of lines (for entirely new lines), hence
1298 this fix-it should not be displayed. */
1299 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1301 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1302 return;
1304 edit_context edit;
1305 edit.add_fixits (&richloc);
1306 auto_free <char *> new_content = edit.get_content (filename);
1307 //ASSERT_STREQ ("foo\n = bar ();\n", new_content);
1310 /* Test applying a "remove" fixit. */
1312 static void
1313 test_applying_fixits_remove (const line_table_case &case_)
1315 /* Create a tempfile and write some text to it.
1316 .........................000000000111111111.
1317 .........................123456789012345678. */
1318 const char *old_content = ("/* before */\n"
1319 "foo = bar.m_field;\n"
1320 "/* after */\n");
1321 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1322 const char *filename = tmp.get_filename ();
1323 line_table_test ltt (case_);
1324 linemap_add (line_table, LC_ENTER, false, filename, 2);
1326 /* Remove ".m_field". */
1327 location_t start = linemap_position_for_column (line_table, 10);
1328 location_t finish = linemap_position_for_column (line_table, 17);
1329 rich_location richloc (line_table, start);
1330 source_range range;
1331 range.m_start = start;
1332 range.m_finish = finish;
1333 richloc.add_fixit_remove (range);
1335 edit_context edit;
1336 edit.add_fixits (&richloc);
1337 auto_free <char *> new_content = edit.get_content (filename);
1338 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1340 ASSERT_STREQ ("/* before */\n"
1341 "foo = bar;\n"
1342 "/* after */\n", new_content);
1344 /* Verify location of ";" after the change. */
1345 ASSERT_EQ (10, edit.get_effective_column (filename, 2, 18));
1347 /* Verify diff. */
1348 auto_free <char *> diff = edit.generate_diff (false);
1349 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1350 " /* before */\n"
1351 "-foo = bar.m_field;\n"
1352 "+foo = bar;\n"
1353 " /* after */\n", diff);
1357 /* Test applying multiple fixits to one line. */
1359 static void
1360 test_applying_fixits_multiple (const line_table_case &case_)
1362 /* Create a tempfile and write some text to it.
1363 .........................00000000011111111.
1364 .........................12345678901234567. */
1365 const char *old_content = ("/* before */\n"
1366 "foo = bar.field;\n"
1367 "/* after */\n");
1368 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1369 const char *filename = tmp.get_filename ();
1370 line_table_test ltt (case_);
1371 linemap_add (line_table, LC_ENTER, false, filename, 2);
1373 location_t c7 = linemap_position_for_column (line_table, 7);
1374 location_t c9 = linemap_position_for_column (line_table, 9);
1375 location_t c11 = linemap_position_for_column (line_table, 11);
1376 location_t c15 = linemap_position_for_column (line_table, 15);
1377 location_t c17 = linemap_position_for_column (line_table, 17);
1379 if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1380 return;
1382 /* Add a comment in front of "bar.field". */
1383 rich_location insert_a (line_table, c7);
1384 insert_a.add_fixit_insert_before (c7, "/* alpha */");
1386 /* Add a comment after "bar.field;". */
1387 rich_location insert_b (line_table, c17);
1388 insert_b.add_fixit_insert_before (c17, "/* beta */");
1390 /* Replace "bar" with "pub". */
1391 rich_location replace_a (line_table, c7);
1392 replace_a.add_fixit_replace (source_range::from_locations (c7, c9),
1393 "pub");
1395 /* Replace "field" with "meadow". */
1396 rich_location replace_b (line_table, c7);
1397 replace_b.add_fixit_replace (source_range::from_locations (c11, c15),
1398 "meadow");
1400 edit_context edit;
1401 edit.add_fixits (&insert_a);
1402 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
1403 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
1404 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
1405 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
1406 ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
1407 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
1409 edit.add_fixits (&insert_b);
1410 edit.add_fixits (&replace_a);
1411 edit.add_fixits (&replace_b);
1413 if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1415 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1416 ASSERT_STREQ ("/* before */\n"
1417 "foo = /* alpha */pub.meadow;/* beta */\n"
1418 "/* after */\n",
1419 new_content);
1421 /* Verify diff. */
1422 auto_free <char *> diff = edit.generate_diff (false);
1423 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1424 " /* before */\n"
1425 "-foo = bar.field;\n"
1426 "+foo = /* alpha */pub.meadow;/* beta */\n"
1427 " /* after */\n", diff);
1431 /* Subroutine of test_applying_fixits_multiple_lines.
1432 Add the text "CHANGED: " to the front of the given line. */
1434 static location_t
1435 change_line (edit_context &edit, int line_num)
1437 const line_map_ordinary *ord_map
1438 = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1439 const int column = 1;
1440 location_t loc =
1441 linemap_position_for_line_and_column (line_table, ord_map,
1442 line_num, column);
1444 expanded_location exploc = expand_location (loc);
1445 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1447 ASSERT_EQ (line_num, exploc.line);
1448 ASSERT_EQ (column, exploc.column);
1451 rich_location insert (line_table, loc);
1452 insert.add_fixit_insert_before ("CHANGED: ");
1453 edit.add_fixits (&insert);
1454 return loc;
1457 /* Subroutine of test_applying_fixits_multiple_lines.
1458 Add the text "INSERTED\n" in front of the given line. */
1460 static location_t
1461 insert_line (edit_context &edit, int line_num)
1463 const line_map_ordinary *ord_map
1464 = LINEMAPS_LAST_ORDINARY_MAP (line_table);
1465 const int column = 1;
1466 location_t loc =
1467 linemap_position_for_line_and_column (line_table, ord_map,
1468 line_num, column);
1470 expanded_location exploc = expand_location (loc);
1471 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1473 ASSERT_EQ (line_num, exploc.line);
1474 ASSERT_EQ (column, exploc.column);
1477 rich_location insert (line_table, loc);
1478 insert.add_fixit_insert_before ("INSERTED\n");
1479 edit.add_fixits (&insert);
1480 return loc;
1483 /* Test of editing multiple lines within a long file,
1484 to ensure that diffs are generated as expected. */
1486 static void
1487 test_applying_fixits_multiple_lines (const line_table_case &case_)
1489 /* Create a tempfile and write many lines of text to it. */
1490 named_temp_file tmp (".txt");
1491 const char *filename = tmp.get_filename ();
1492 FILE *f = fopen (filename, "w");
1493 ASSERT_NE (f, NULL);
1494 for (int i = 1; i <= 1000; i++)
1495 fprintf (f, "line %i\n", i);
1496 fclose (f);
1498 line_table_test ltt (case_);
1499 linemap_add (line_table, LC_ENTER, false, filename, 1);
1500 linemap_position_for_column (line_table, 127);
1502 edit_context edit;
1504 /* A run of consecutive lines. */
1505 change_line (edit, 2);
1506 change_line (edit, 3);
1507 change_line (edit, 4);
1508 insert_line (edit, 5);
1510 /* A run of nearby lines, within the contextual limit. */
1511 change_line (edit, 150);
1512 change_line (edit, 151);
1513 location_t last_loc = change_line (edit, 153);
1515 if (last_loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1516 return;
1518 /* Verify diff. */
1519 auto_free <char *> diff = edit.generate_diff (false);
1520 ASSERT_STREQ ("@@ -1,7 +1,8 @@\n"
1521 " line 1\n"
1522 "-line 2\n"
1523 "-line 3\n"
1524 "-line 4\n"
1525 "+CHANGED: line 2\n"
1526 "+CHANGED: line 3\n"
1527 "+CHANGED: line 4\n"
1528 "+INSERTED\n"
1529 " line 5\n"
1530 " line 6\n"
1531 " line 7\n"
1532 "@@ -147,10 +148,10 @@\n"
1533 " line 147\n"
1534 " line 148\n"
1535 " line 149\n"
1536 "-line 150\n"
1537 "-line 151\n"
1538 "+CHANGED: line 150\n"
1539 "+CHANGED: line 151\n"
1540 " line 152\n"
1541 "-line 153\n"
1542 "+CHANGED: line 153\n"
1543 " line 154\n"
1544 " line 155\n"
1545 " line 156\n", diff);
1547 /* Ensure tmp stays alive until this point, so that the tempfile
1548 persists until after the generate_diff call. */
1549 tmp.get_filename ();
1552 /* Test of converting an initializer for a named field from
1553 the old GCC extension to C99 syntax.
1554 Exercises a shrinking replacement followed by a growing
1555 replacement on the same line. */
1557 static void
1558 test_applying_fixits_modernize_named_init (const line_table_case &case_)
1560 /* Create a tempfile and write some text to it.
1561 .........................00000000011111111.
1562 .........................12345678901234567. */
1563 const char *old_content = ("/* before */\n"
1564 "bar : 1,\n"
1565 "/* after */\n");
1566 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1567 const char *filename = tmp.get_filename ();
1568 line_table_test ltt (case_);
1569 linemap_add (line_table, LC_ENTER, false, filename, 2);
1571 location_t c1 = linemap_position_for_column (line_table, 1);
1572 location_t c3 = linemap_position_for_column (line_table, 3);
1573 location_t c8 = linemap_position_for_column (line_table, 8);
1575 if (c8 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1576 return;
1578 /* Replace "bar" with ".". */
1579 rich_location r1 (line_table, c8);
1580 r1.add_fixit_replace (source_range::from_locations (c1, c3),
1581 ".");
1583 /* Replace ":" with "bar =". */
1584 rich_location r2 (line_table, c8);
1585 r2.add_fixit_replace (source_range::from_locations (c8, c8),
1586 "bar =");
1588 /* The order should not matter. Do r1 then r2. */
1590 edit_context edit;
1591 edit.add_fixits (&r1);
1593 /* Verify state after first replacement. */
1595 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1596 /* We should now have:
1597 ............00000000011.
1598 ............12345678901. */
1599 ASSERT_STREQ ("/* before */\n"
1600 ". : 1,\n"
1601 "/* after */\n",
1602 new_content);
1603 /* Location of the "1". */
1604 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
1605 /* Location of the ",". */
1606 ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
1609 edit.add_fixits (&r2);
1611 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1612 /* Verify state after second replacement.
1613 ............00000000011111111.
1614 ............12345678901234567. */
1615 ASSERT_STREQ ("/* before */\n"
1616 ". bar = 1,\n"
1617 "/* after */\n",
1618 new_content);
1621 /* Try again, doing r2 then r1; the new_content should be the same. */
1623 edit_context edit;
1624 edit.add_fixits (&r2);
1625 edit.add_fixits (&r1);
1626 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1627 /*.............00000000011111111.
1628 .............12345678901234567. */
1629 ASSERT_STREQ ("/* before */\n"
1630 ". bar = 1,\n"
1631 "/* after */\n",
1632 new_content);
1636 /* Test of a fixit affecting a file that can't be read. */
1638 static void
1639 test_applying_fixits_unreadable_file ()
1641 const char *filename = "this-does-not-exist.txt";
1642 line_table_test ltt ();
1643 linemap_add (line_table, LC_ENTER, false, filename, 1);
1645 location_t loc = linemap_position_for_column (line_table, 1);
1647 rich_location insert (line_table, loc);
1648 insert.add_fixit_insert_before ("change 1");
1649 insert.add_fixit_insert_before ("change 2");
1651 edit_context edit;
1652 /* Attempting to add the fixits affecting the unreadable file
1653 should transition the edit from valid to invalid. */
1654 ASSERT_TRUE (edit.valid_p ());
1655 edit.add_fixits (&insert);
1656 ASSERT_FALSE (edit.valid_p ());
1657 ASSERT_EQ (NULL, edit.get_content (filename));
1658 ASSERT_EQ (NULL, edit.generate_diff (false));
1661 /* Verify that we gracefully handle an attempt to edit a line
1662 that's beyond the end of the file. */
1664 static void
1665 test_applying_fixits_line_out_of_range ()
1667 /* Create a tempfile and write some text to it.
1668 ........................00000000011111111.
1669 ........................12345678901234567. */
1670 const char *old_content = "One-liner file\n";
1671 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1672 const char *filename = tmp.get_filename ();
1673 line_table_test ltt ();
1674 linemap_add (line_table, LC_ENTER, false, filename, 2);
1676 /* Try to insert a string in line 2. */
1677 location_t loc = linemap_position_for_column (line_table, 1);
1679 rich_location insert (line_table, loc);
1680 insert.add_fixit_insert_before ("change");
1682 /* Verify that attempting the insertion puts an edit_context
1683 into an invalid state. */
1684 edit_context edit;
1685 ASSERT_TRUE (edit.valid_p ());
1686 edit.add_fixits (&insert);
1687 ASSERT_FALSE (edit.valid_p ());
1688 ASSERT_EQ (NULL, edit.get_content (filename));
1689 ASSERT_EQ (NULL, edit.generate_diff (false));
1692 /* Verify the boundary conditions of column values in fix-it
1693 hints applied to edit_context instances. */
1695 static void
1696 test_applying_fixits_column_validation (const line_table_case &case_)
1698 /* Create a tempfile and write some text to it.
1699 ........................00000000011111111.
1700 ........................12345678901234567. */
1701 const char *old_content = "One-liner file\n";
1702 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1703 const char *filename = tmp.get_filename ();
1704 line_table_test ltt (case_);
1705 linemap_add (line_table, LC_ENTER, false, filename, 1);
1707 location_t c11 = linemap_position_for_column (line_table, 11);
1708 location_t c14 = linemap_position_for_column (line_table, 14);
1709 location_t c15 = linemap_position_for_column (line_table, 15);
1710 location_t c16 = linemap_position_for_column (line_table, 16);
1712 /* Verify limits of valid columns in insertion fixits. */
1714 /* Verify inserting at the end of the line. */
1716 rich_location richloc (line_table, c11);
1717 richloc.add_fixit_insert_before (c15, " change");
1719 /* Col 15 is at the end of the line, so the insertion
1720 should succeed. */
1721 edit_context edit;
1722 edit.add_fixits (&richloc);
1723 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1724 if (c15 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1725 ASSERT_STREQ ("One-liner file change\n", new_content);
1726 else
1727 ASSERT_EQ (NULL, new_content);
1730 /* Verify inserting beyond the end of the line. */
1732 rich_location richloc (line_table, c11);
1733 richloc.add_fixit_insert_before (c16, " change");
1735 /* Col 16 is beyond the end of the line, so the insertion
1736 should fail gracefully. */
1737 edit_context edit;
1738 ASSERT_TRUE (edit.valid_p ());
1739 edit.add_fixits (&richloc);
1740 ASSERT_FALSE (edit.valid_p ());
1741 ASSERT_EQ (NULL, edit.get_content (filename));
1742 ASSERT_EQ (NULL, edit.generate_diff (false));
1745 /* Verify limits of valid columns in replacement fixits. */
1747 /* Verify replacing the end of the line. */
1749 rich_location richloc (line_table, c11);
1750 source_range range = source_range::from_locations (c11, c14);
1751 richloc.add_fixit_replace (range, "change");
1753 /* Col 14 is at the end of the line, so the replacement
1754 should succeed. */
1755 edit_context edit;
1756 edit.add_fixits (&richloc);
1757 auto_free <char *> new_content = edit.get_content (tmp.get_filename ());
1758 if (c14 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1759 ASSERT_STREQ ("One-liner change\n", new_content);
1760 else
1761 ASSERT_EQ (NULL, new_content);
1764 /* Verify going beyond the end of the line. */
1766 rich_location richloc (line_table, c11);
1767 source_range range = source_range::from_locations (c11, c15);
1768 richloc.add_fixit_replace (range, "change");
1770 /* Col 15 is after the end of the line, so the replacement
1771 should fail; verify that the attempt fails gracefully. */
1772 edit_context edit;
1773 ASSERT_TRUE (edit.valid_p ());
1774 edit.add_fixits (&richloc);
1775 ASSERT_FALSE (edit.valid_p ());
1776 ASSERT_EQ (NULL, edit.get_content (filename));
1777 ASSERT_EQ (NULL, edit.generate_diff (false));
1781 /* Run all of the selftests within this file. */
1783 void
1784 edit_context_c_tests ()
1786 test_get_content ();
1787 for_each_line_table_case (test_applying_fixits_insert_before);
1788 for_each_line_table_case (test_applying_fixits_insert_after);
1789 for_each_line_table_case (test_applying_fixits_insert_after_at_line_end);
1790 for_each_line_table_case (test_applying_fixits_insert_after_failure);
1791 for_each_line_table_case (test_applying_fixits_insert_containing_newline);
1792 for_each_line_table_case (test_applying_fixits_growing_replace);
1793 for_each_line_table_case (test_applying_fixits_shrinking_replace);
1794 for_each_line_table_case (test_applying_fixits_replace_containing_newline);
1795 for_each_line_table_case (test_applying_fixits_remove);
1796 for_each_line_table_case (test_applying_fixits_multiple);
1797 for_each_line_table_case (test_applying_fixits_multiple_lines);
1798 for_each_line_table_case (test_applying_fixits_modernize_named_init);
1799 test_applying_fixits_unreadable_file ();
1800 test_applying_fixits_line_out_of_range ();
1801 for_each_line_table_case (test_applying_fixits_column_validation);
1804 } // namespace selftest
1806 #endif /* CHECKING_P */