1/* Determining the results of applying fix-it hints.
2 Copyright (C) 2016-2023 Free Software Foundation, Inc.
3
4This file is part of GCC.
5
6GCC is free software; you can redistribute it and/or modify it under
7the terms of the GNU General Public License as published by the Free
8Software Foundation; either version 3, or (at your option) any later
9version.
10
11GCC is distributed in the hope that it will be useful, but WITHOUT ANY
12WARRANTY; without even the implied warranty of MERCHANTABILITY or
13FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14for more details.
15
16You should have received a copy of the GNU General Public License
17along with GCC; see the file COPYING3. If not see
18<http://www.gnu.org/licenses/>. */
19
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"
28
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.
32
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.
38
39 The "filename" params are required to outlive the edit_context (no
40 copy of the underlying str is taken, just the ptr). */
41
42/* Forward decls. class edit_context is declared within edit-context.h.
43 The other types are declared here. */
44class edit_context;
45class edited_file;
46class edited_line;
47class line_event;
48
49/* A struct to hold the params of a print_diff call. */
50
51class diff
52{
53public:
54 diff (pretty_printer *pp, bool show_filenames)
55 : m_pp (pp), m_show_filenames (show_filenames) {}
56
57 pretty_printer *m_pp;
58 bool m_show_filenames;
59};
60
61/* The state of one named file within an edit_context: the filename,
62 and the lines that have been edited so far. */
63
64class edited_file
65{
66 public:
67 edited_file (const char *filename);
68 static void delete_cb (edited_file *file);
69
70 const char *get_filename () const { return m_filename; }
71 char *get_content ();
72
73 bool apply_fixit (int line, int start_column,
74 int next_column,
75 const char *replacement_str,
76 int replacement_len);
77 int get_effective_column (int line, int column);
78
79 static int call_print_diff (const char *, edited_file *file,
80 void *user_data)
81 {
82 diff *d = (diff *)user_data;
83 file->print_diff (pp: d->m_pp, show_filenames: d->m_show_filenames);
84 return 0;
85 }
86
87 private:
88 bool print_content (pretty_printer *pp);
89 void print_diff (pretty_printer *pp, bool show_filenames);
90 int print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
91 int old_end_of_hunk, int new_start_of_hunk);
92 edited_line *get_line (int line);
93 edited_line *get_or_insert_line (int line);
94 int get_num_lines (bool *missing_trailing_newline);
95
96 int get_effective_line_count (int old_start_of_hunk,
97 int old_end_of_hunk);
98
99 void print_run_of_changed_lines (pretty_printer *pp,
100 int start_of_run,
101 int end_of_run);
102
103 const char *m_filename;
104 typed_splay_tree<int, edited_line *> m_edited_lines;
105 int m_num_lines;
106};
107
108/* A line added before an edited_line. */
109
110class added_line
111{
112 public:
113 added_line (const char *content, int len)
114 : m_content (xstrndup (content, len)), m_len (len) {}
115 ~added_line () { free (ptr: m_content); }
116
117 const char *get_content () const { return m_content; }
118 int get_len () const { return m_len; }
119
120 private:
121 char *m_content;
122 int m_len;
123};
124
125/* Class for representing edit events that have occurred on one line of
126 one file: the replacement of some text betweeen some columns
127 on the line.
128
129 Subsequent events will need their columns adjusting if they're
130 are on this line and their column is >= the start point. */
131
132class line_event
133{
134 public:
135 line_event (int start, int next, int len) : m_start (start),
136 m_delta (len - (next - start)) {}
137
138 int get_effective_column (int orig_column) const
139 {
140 if (orig_column >= m_start)
141 return orig_column += m_delta;
142 else
143 return orig_column;
144 }
145
146 private:
147 int m_start;
148 int m_delta;
149};
150
151/* The state of one edited line within an edited_file.
152 As well as the current content of the line, it contains a record of
153 the changes, so that further changes can be applied in the correct
154 place.
155
156 When handling fix-it hints containing newlines, new lines are added
157 as added_line predecessors to an edited_line. Hence it's possible
158 for an "edited_line" to not actually have been changed, but to merely
159 be a placeholder for the lines added before it. This can be tested
160 for with actuall_edited_p, and has a slight effect on how diff hunks
161 are generated. */
162
163class edited_line
164{
165 public:
166 edited_line (const char *filename, int line_num);
167 ~edited_line ();
168 static void delete_cb (edited_line *el);
169
170 int get_line_num () const { return m_line_num; }
171 const char *get_content () const { return m_content; }
172 int get_len () const { return m_len; }
173
174 int get_effective_column (int orig_column) const;
175 bool apply_fixit (int start_column,
176 int next_column,
177 const char *replacement_str,
178 int replacement_len);
179
180 int get_effective_line_count () const;
181
182 /* Has the content of this line actually changed, or are we merely
183 recording predecessor added_lines? */
184 bool actually_edited_p () const { return m_line_events.length () > 0; }
185
186 void print_content (pretty_printer *pp) const;
187 void print_diff_lines (pretty_printer *pp) const;
188
189 private:
190 void ensure_capacity (int len);
191 void ensure_terminated ();
192
193 int m_line_num;
194 char *m_content;
195 int m_len;
196 int m_alloc_sz;
197 auto_vec <line_event> m_line_events;
198 auto_vec <added_line *> m_predecessors;
199};
200
201/* Forward decls. */
202
203static void
204print_diff_line (pretty_printer *pp, char prefix_char,
205 const char *line, int line_size);
206
207/* Implementation of class edit_context. */
208
209/* edit_context's ctor. */
210
211edit_context::edit_context ()
212: m_valid (true),
213 m_files (strcmp, NULL, edited_file::delete_cb)
214{}
215
216/* Add any fixits within RICHLOC to this context, recording the
217 changes that they make. */
218
219void
220edit_context::add_fixits (rich_location *richloc)
221{
222 if (!m_valid)
223 return;
224 if (richloc->seen_impossible_fixit_p ())
225 {
226 m_valid = false;
227 return;
228 }
229 for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
230 {
231 const fixit_hint *hint = richloc->get_fixit_hint (idx: i);
232 if (!apply_fixit (hint))
233 m_valid = false;
234 }
235}
236
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. */
240
241char *
242edit_context::get_content (const char *filename)
243{
244 if (!m_valid)
245 return NULL;
246 edited_file &file = get_or_insert_file (filename);
247 return file.get_content ();
248}
249
250/* Map a location before the edits to a column number after the edits.
251 This method is for the selftests. */
252
253int
254edit_context::get_effective_column (const char *filename, int line,
255 int column)
256{
257 edited_file *file = get_file (filename);
258 if (!file)
259 return column;
260 return file->get_effective_column (line, column);
261}
262
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. */
266
267char *
268edit_context::generate_diff (bool show_filenames)
269{
270 if (!m_valid)
271 return NULL;
272
273 pretty_printer pp;
274 print_diff (pp: &pp, show_filenames);
275 return xstrdup (pp_formatted_text (&pp));
276}
277
278/* Print a unified diff to PP, showing the changes made within the
279 context. */
280
281void
282edit_context::print_diff (pretty_printer *pp, bool show_filenames)
283{
284 if (!m_valid)
285 return;
286 diff d (pp, show_filenames);
287 m_files.foreach (foreach_fn: edited_file::call_print_diff, user_data: &d);
288}
289
290/* Attempt to apply the given fixit. Return true if it can be
291 applied, or false otherwise. */
292
293bool
294edit_context::apply_fixit (const fixit_hint *hint)
295{
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;
306
307 edited_file &file = get_or_insert_file (filename: start.file);
308 if (!m_valid)
309 return false;
310 return file.apply_fixit (line: start.line, start_column: start.column, next_column: next_loc.column,
311 replacement_str: hint->get_string (),
312 replacement_len: hint->get_length ());
313}
314
315/* Locate the edited_file * for FILENAME, if any
316 Return NULL if there isn't one. */
317
318edited_file *
319edit_context::get_file (const char *filename)
320{
321 gcc_assert (filename);
322 return m_files.lookup (key: filename);
323}
324
325/* Locate the edited_file for FILENAME, adding one if there isn't one. */
326
327edited_file &
328edit_context::get_or_insert_file (const char *filename)
329{
330 gcc_assert (filename);
331
332 edited_file *file = get_file (filename);
333 if (file)
334 return *file;
335
336 /* Not found. */
337 file = new edited_file (filename);
338 m_files.insert (key: filename, value: file);
339 return *file;
340}
341
342/* Implementation of class edited_file. */
343
344/* Callback for m_edited_lines, for comparing line numbers. */
345
346static int line_comparator (int a, int b)
347{
348 return a - b;
349}
350
351/* edited_file's constructor. */
352
353edited_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)
357{
358}
359
360/* A callback for deleting edited_file *, for use as a
361 delete_value_fn for edit_context::m_files. */
362
363void
364edited_file::delete_cb (edited_file *file)
365{
366 delete file;
367}
368
369/* Get the content of the file, with fix-its applied.
370 The ptr should be freed by the caller. */
371
372char *
373edited_file::get_content ()
374{
375 pretty_printer pp;
376 if (!print_content (pp: &pp))
377 return NULL;
378 return xstrdup (pp_formatted_text (&pp));
379}
380
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. */
385
386bool
387edited_file::apply_fixit (int line, int start_column, int next_column,
388 const char *replacement_str,
389 int replacement_len)
390{
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);
396}
397
398/* Given line LINE, map from COLUMN in the input file to its current
399 column after edits have been applied. */
400
401int
402edited_file::get_effective_column (int line, int column)
403{
404 const edited_line *el = get_line (line);
405 if (!el)
406 return column;
407 return el->get_effective_column (orig_column: column);
408}
409
410/* Attempt to print the content of the file to PP, with edits applied.
411 Return true if successful, false otherwise. */
412
413bool
414edited_file::print_content (pretty_printer *pp)
415{
416 bool missing_trailing_newline;
417 int line_count = get_num_lines (missing_trailing_newline: &missing_trailing_newline);
418 for (int line_num = 1; line_num <= line_count; line_num++)
419 {
420 edited_line *el = get_line (line: line_num);
421 if (el)
422 el->print_content (pp);
423 else
424 {
425 char_span line = location_get_source_line (file_path: m_filename, line: line_num);
426 if (!line)
427 return false;
428 for (size_t i = 0; i < line.length (); i++)
429 pp_character (pp, line[i]);
430 }
431 if (line_num < line_count)
432 pp_character (pp, '\n');
433 }
434
435 if (!missing_trailing_newline)
436 pp_character (pp, '\n');
437
438 return true;
439}
440
441/* Print a unified diff to PP, showing any changes that have occurred
442 to this file. */
443
444void
445edited_file::print_diff (pretty_printer *pp, bool show_filenames)
446{
447 if (show_filenames)
448 {
449 pp_string (pp, colorize_start (pp_show_color (pp), name: "diff-filename"));
450 /* Avoid -Wformat-diag in non-diagnostic output. */
451 pp_string (pp, "--- ");
452 pp_string (pp, m_filename);
453 pp_newline (pp);
454 pp_string (pp, "+++ ");
455 pp_string (pp, m_filename);
456 pp_newline (pp);
457 pp_string (pp, colorize_stop (pp_show_color (pp)));
458 }
459
460 edited_line *el = m_edited_lines.min ();
461
462 bool missing_trailing_newline;
463 int line_count = get_num_lines (missing_trailing_newline: &missing_trailing_newline);
464
465 const int context_lines = 3;
466
467 /* Track new line numbers minus old line numbers. */
468
469 int line_delta = 0;
470
471 while (el)
472 {
473 int start_of_hunk = el->get_line_num ();
474 start_of_hunk -= context_lines;
475 if (start_of_hunk < 1)
476 start_of_hunk = 1;
477
478 /* Locate end of hunk, merging in changed lines
479 that are sufficiently close. */
480 while (true)
481 {
482 edited_line *next_el
483 = m_edited_lines.successor (key: el->get_line_num ());
484 if (!next_el)
485 break;
486
487 int end_of_printed_hunk = el->get_line_num () + context_lines;
488 if (!el->actually_edited_p ())
489 end_of_printed_hunk--;
490
491 if (end_of_printed_hunk
492 >= next_el->get_line_num () - context_lines)
493 el = next_el;
494 else
495 break;
496 }
497
498 int end_of_hunk = el->get_line_num ();
499 end_of_hunk += context_lines;
500 if (!el->actually_edited_p ())
501 end_of_hunk--;
502 if (end_of_hunk > line_count)
503 end_of_hunk = line_count;
504
505 int new_start_of_hunk = start_of_hunk + line_delta;
506 line_delta += print_diff_hunk (pp, old_start_of_hunk: start_of_hunk, old_end_of_hunk: end_of_hunk,
507 new_start_of_hunk);
508 el = m_edited_lines.successor (key: el->get_line_num ());
509 }
510}
511
512/* Print one hunk within a unified diff to PP, covering the
513 given range of lines. OLD_START_OF_HUNK and OLD_END_OF_HUNK are
514 line numbers in the unedited version of the file.
515 NEW_START_OF_HUNK is a line number in the edited version of the file.
516 Return the change in the line count within the hunk. */
517
518int
519edited_file::print_diff_hunk (pretty_printer *pp, int old_start_of_hunk,
520 int old_end_of_hunk, int new_start_of_hunk)
521{
522 int old_num_lines = old_end_of_hunk - old_start_of_hunk + 1;
523 int new_num_lines
524 = get_effective_line_count (old_start_of_hunk, old_end_of_hunk);
525
526 pp_string (pp, colorize_start (pp_show_color (pp), name: "diff-hunk"));
527 pp_printf (pp, "%s -%i,%i +%i,%i %s",
528 "@@", old_start_of_hunk, old_num_lines,
529 new_start_of_hunk, new_num_lines, "@@\n");
530 pp_string (pp, colorize_stop (pp_show_color (pp)));
531
532 int line_num = old_start_of_hunk;
533 while (line_num <= old_end_of_hunk)
534 {
535 edited_line *el = get_line (line: line_num);
536 if (el)
537 {
538 /* We have an edited line.
539 Consolidate into runs of changed lines. */
540 const int first_changed_line_in_run = line_num;
541 while (get_line (line: line_num))
542 line_num++;
543 const int last_changed_line_in_run = line_num - 1;
544 print_run_of_changed_lines (pp, start_of_run: first_changed_line_in_run,
545 end_of_run: last_changed_line_in_run);
546 }
547 else
548 {
549 /* Unchanged line. */
550 char_span old_line = location_get_source_line (file_path: m_filename, line: line_num);
551 print_diff_line (pp, prefix_char: ' ', line: old_line.get_buffer (), line_size: old_line.length ());
552 line_num++;
553 }
554 }
555
556 return new_num_lines - old_num_lines;
557}
558
559/* Subroutine of edited_file::print_diff_hunk: given a run of lines
560 from START_OF_RUN to END_OF_RUN that all have edited_line instances,
561 print the diff to PP. */
562
563void
564edited_file::print_run_of_changed_lines (pretty_printer *pp,
565 int start_of_run,
566 int end_of_run)
567{
568 /* Show old version of lines. */
569 pp_string (pp, colorize_start (pp_show_color (pp),
570 name: "diff-delete"));
571 for (int line_num = start_of_run;
572 line_num <= end_of_run;
573 line_num++)
574 {
575 edited_line *el_in_run = get_line (line: line_num);
576 gcc_assert (el_in_run);
577 if (el_in_run->actually_edited_p ())
578 {
579 char_span old_line = location_get_source_line (file_path: m_filename, line: line_num);
580 print_diff_line (pp, prefix_char: '-', line: old_line.get_buffer (),
581 line_size: old_line.length ());
582 }
583 }
584 pp_string (pp, colorize_stop (pp_show_color (pp)));
585
586 /* Show new version of lines. */
587 pp_string (pp, colorize_start (pp_show_color (pp),
588 name: "diff-insert"));
589 for (int line_num = start_of_run;
590 line_num <= end_of_run;
591 line_num++)
592 {
593 edited_line *el_in_run = get_line (line: line_num);
594 gcc_assert (el_in_run);
595 el_in_run->print_diff_lines (pp);
596 }
597 pp_string (pp, colorize_stop (pp_show_color (pp)));
598}
599
600/* Print one line within a diff, starting with PREFIX_CHAR,
601 followed by the LINE of content, of length LEN. LINE is
602 not necessarily 0-terminated. Print a trailing newline. */
603
604static void
605print_diff_line (pretty_printer *pp, char prefix_char,
606 const char *line, int len)
607{
608 pp_character (pp, prefix_char);
609 for (int i = 0; i < len; i++)
610 pp_character (pp, line[i]);
611 pp_character (pp, '\n');
612}
613
614/* Determine the number of lines that will be present after
615 editing for the range of lines from OLD_START_OF_HUNK to
616 OLD_END_OF_HUNK inclusive. */
617
618int
619edited_file::get_effective_line_count (int old_start_of_hunk,
620 int old_end_of_hunk)
621{
622 int line_count = 0;
623 for (int old_line_num = old_start_of_hunk; old_line_num <= old_end_of_hunk;
624 old_line_num++)
625 {
626 edited_line *el = get_line (line: old_line_num);
627 if (el)
628 line_count += el->get_effective_line_count ();
629 else
630 line_count++;
631 }
632 return line_count;
633}
634
635/* Get the state of LINE within the file, or NULL if it is untouched. */
636
637edited_line *
638edited_file::get_line (int line)
639{
640 return m_edited_lines.lookup (key: line);
641}
642
643/* Get the state of LINE within the file, creating a state for it
644 if necessary. Return NULL if an error occurs. */
645
646edited_line *
647edited_file::get_or_insert_line (int line)
648{
649 edited_line *el = get_line (line);
650 if (el)
651 return el;
652 el = new edited_line (m_filename, line);
653 if (el->get_content () == NULL)
654 {
655 delete el;
656 return NULL;
657 }
658 m_edited_lines.insert (key: line, value: el);
659 return el;
660}
661
662/* Get the total number of lines in m_content, writing
663 true to *MISSING_TRAILING_NEWLINE if the final line
664 if missing a newline, false otherwise. */
665
666int
667edited_file::get_num_lines (bool *missing_trailing_newline)
668{
669 gcc_assert (missing_trailing_newline);
670 if (m_num_lines == -1)
671 {
672 m_num_lines = 0;
673 while (true)
674 {
675 char_span line
676 = location_get_source_line (file_path: m_filename, line: m_num_lines + 1);
677 if (line)
678 m_num_lines++;
679 else
680 break;
681 }
682 }
683 *missing_trailing_newline = location_missing_trailing_newline (file_path: m_filename);
684 return m_num_lines;
685}
686
687/* Implementation of class edited_line. */
688
689/* edited_line's ctor. */
690
691edited_line::edited_line (const char *filename, int line_num)
692: m_line_num (line_num),
693 m_content (NULL), m_len (0), m_alloc_sz (0),
694 m_line_events (),
695 m_predecessors ()
696{
697 char_span line = location_get_source_line (file_path: filename, line: line_num);
698 if (!line)
699 return;
700 m_len = line.length ();
701 ensure_capacity (len: m_len);
702 memcpy (dest: m_content, src: line.get_buffer (), n: m_len);
703 ensure_terminated ();
704}
705
706/* edited_line's dtor. */
707
708edited_line::~edited_line ()
709{
710 unsigned i;
711 added_line *pred;
712
713 free (ptr: m_content);
714 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
715 delete pred;
716}
717
718/* A callback for deleting edited_line *, for use as a
719 delete_value_fn for edited_file::m_edited_lines. */
720
721void
722edited_line::delete_cb (edited_line *el)
723{
724 delete el;
725}
726
727/* Map a location before the edits to a column number after the edits,
728 within a specific line. */
729
730int
731edited_line::get_effective_column (int orig_column) const
732{
733 int i;
734 line_event *event;
735 FOR_EACH_VEC_ELT (m_line_events, i, event)
736 orig_column = event->get_effective_column (orig_column);
737 return orig_column;
738}
739
740/* Attempt to replace columns START_COLUMN up to but not including
741 NEXT_COLUMN of the line with the string REPLACEMENT_STR of
742 length REPLACEMENT_LEN, updating the in-memory copy of the line,
743 and the record of edits to the line.
744 Return true if successful; false if an error occurred. */
745
746bool
747edited_line::apply_fixit (int start_column,
748 int next_column,
749 const char *replacement_str,
750 int replacement_len)
751{
752 /* Handle newlines. They will only ever be at the end of the
753 replacement text, thanks to the filtering in rich_location. */
754 if (replacement_len > 1)
755 if (replacement_str[replacement_len - 1] == '\n')
756 {
757 /* Stash in m_predecessors, stripping off newline. */
758 m_predecessors.safe_push (obj: new added_line (replacement_str,
759 replacement_len - 1));
760 return true;
761 }
762
763 start_column = get_effective_column (orig_column: start_column);
764 next_column = get_effective_column (orig_column: next_column);
765
766 int start_offset = start_column - 1;
767 int next_offset = next_column - 1;
768
769 gcc_assert (start_offset >= 0);
770 gcc_assert (next_offset >= 0);
771
772 if (start_column > next_column)
773 return false;
774 if (start_offset >= (m_len + 1))
775 return false;
776 if (next_offset >= (m_len + 1))
777 return false;
778
779 size_t victim_len = next_offset - start_offset;
780
781 /* Ensure buffer is big enough. */
782 size_t new_len = m_len + replacement_len - victim_len;
783 ensure_capacity (len: new_len);
784
785 char *suffix = m_content + next_offset;
786 gcc_assert (suffix <= m_content + m_len);
787 size_t len_suffix = (m_content + m_len) - suffix;
788
789 /* Move successor content into position. They overlap, so use memmove. */
790 memmove (dest: m_content + start_offset + replacement_len,
791 src: suffix, n: len_suffix);
792
793 /* Replace target content. They don't overlap, so use memcpy. */
794 memcpy (dest: m_content + start_offset,
795 src: replacement_str,
796 n: replacement_len);
797
798 m_len = new_len;
799
800 ensure_terminated ();
801
802 /* Record the replacement, so that future changes to the line can have
803 their column information adjusted accordingly. */
804 m_line_events.safe_push (obj: line_event (start_column, next_column,
805 replacement_len));
806 return true;
807}
808
809/* Determine the number of lines that will be present after
810 editing for this line. Typically this is just 1, but
811 if newlines have been added before this line, they will
812 also be counted. */
813
814int
815edited_line::get_effective_line_count () const
816{
817 return m_predecessors.length () + 1;
818}
819
820/* Subroutine of edited_file::print_content.
821 Print this line and any new lines added before it, to PP. */
822
823void
824edited_line::print_content (pretty_printer *pp) const
825{
826 unsigned i;
827 added_line *pred;
828 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
829 {
830 pp_string (pp, pred->get_content ());
831 pp_newline (pp);
832 }
833 pp_string (pp, m_content);
834}
835
836/* Subroutine of edited_file::print_run_of_changed_lines for
837 printing diff hunks to PP.
838 Print the '+' line for this line, and any newlines added
839 before it.
840 Note that if this edited_line was actually edited, the '-'
841 line has already been printed. If it wasn't, then we merely
842 have a placeholder edited_line for adding newlines to, and
843 we need to print a ' ' line for the edited_line as we haven't
844 printed it yet. */
845
846void
847edited_line::print_diff_lines (pretty_printer *pp) const
848{
849 unsigned i;
850 added_line *pred;
851 FOR_EACH_VEC_ELT (m_predecessors, i, pred)
852 print_diff_line (pp, prefix_char: '+', line: pred->get_content (),
853 len: pred->get_len ());
854 if (actually_edited_p ())
855 print_diff_line (pp, prefix_char: '+', line: m_content, len: m_len);
856 else
857 print_diff_line (pp, prefix_char: ' ', line: m_content, len: m_len);
858}
859
860/* Ensure that the buffer for m_content is at least large enough to hold
861 a string of length LEN and its 0-terminator, doubling on repeated
862 allocations. */
863
864void
865edited_line::ensure_capacity (int len)
866{
867 /* Allow 1 extra byte for 0-termination. */
868 if (m_alloc_sz < (len + 1))
869 {
870 size_t new_alloc_sz = (len + 1) * 2;
871 m_content = (char *)xrealloc (m_content, new_alloc_sz);
872 m_alloc_sz = new_alloc_sz;
873 }
874}
875
876/* Ensure that m_content is 0-terminated. */
877
878void
879edited_line::ensure_terminated ()
880{
881 /* 0-terminate the buffer. */
882 gcc_assert (m_len < m_alloc_sz);
883 m_content[m_len] = '\0';
884}
885
886#if CHECKING_P
887
888/* Selftests of code-editing. */
889
890namespace selftest {
891
892/* A wrapper class for ensuring that the underlying pointer is freed. */
893
894template <typename POINTER_T>
895class auto_free
896{
897 public:
898 auto_free (POINTER_T p) : m_ptr (p) {}
899 ~auto_free () { free (m_ptr); }
900
901 operator POINTER_T () { return m_ptr; }
902
903 private:
904 POINTER_T m_ptr;
905};
906
907/* Verify that edit_context::get_content works for unedited files. */
908
909static void
910test_get_content ()
911{
912 /* Test of empty file. */
913 {
914 const char *content = ("");
915 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
916 edit_context edit;
917 auto_free <char *> result = edit.get_content (filename: tmp.get_filename ());
918 ASSERT_STREQ ("", result);
919 }
920
921 /* Test of simple content. */
922 {
923 const char *content = ("/* before */\n"
924 "foo = bar.field;\n"
925 "/* after */\n");
926 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
927 edit_context edit;
928 auto_free <char *> result = edit.get_content (filename: tmp.get_filename ());
929 ASSERT_STREQ ("/* before */\n"
930 "foo = bar.field;\n"
931 "/* after */\n", result);
932 }
933
934 /* Test of omitting the trailing newline on the final line. */
935 {
936 const char *content = ("/* before */\n"
937 "foo = bar.field;\n"
938 "/* after */");
939 temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
940 edit_context edit;
941 auto_free <char *> result = edit.get_content (filename: tmp.get_filename ());
942 /* We should respect the omitted trailing newline. */
943 ASSERT_STREQ ("/* before */\n"
944 "foo = bar.field;\n"
945 "/* after */", result);
946 }
947}
948
949/* Test applying an "insert" fixit, using insert_before. */
950
951static void
952test_applying_fixits_insert_before (const line_table_case &case_)
953{
954 /* Create a tempfile and write some text to it.
955 .........................0000000001111111.
956 .........................1234567890123456. */
957 const char *old_content = ("/* before */\n"
958 "foo = bar.field;\n"
959 "/* after */\n");
960 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
961 const char *filename = tmp.get_filename ();
962 line_table_test ltt (case_);
963 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 2);
964
965 /* Add a comment in front of "bar.field". */
966 location_t start = linemap_position_for_column (line_table, 7);
967 rich_location richloc (line_table, start);
968 richloc.add_fixit_insert_before (new_content: "/* inserted */");
969
970 if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
971 return;
972
973 edit_context edit;
974 edit.add_fixits (richloc: &richloc);
975 auto_free <char *> new_content = edit.get_content (filename);
976 if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
977 ASSERT_STREQ ("/* before */\n"
978 "foo = /* inserted */bar.field;\n"
979 "/* after */\n", new_content);
980
981 /* Verify that locations on other lines aren't affected by the change. */
982 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
983 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
984
985 /* Verify locations on the line before the change. */
986 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
987 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
988
989 /* Verify locations on the line at and after the change. */
990 ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
991 ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));
992
993 /* Verify diff. */
994 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
995 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
996 " /* before */\n"
997 "-foo = bar.field;\n"
998 "+foo = /* inserted */bar.field;\n"
999 " /* after */\n", diff);
1000}
1001
1002/* Test applying an "insert" fixit, using insert_after, with
1003 a range of length > 1 (to ensure that the end-point of
1004 the input range is used). */
1005
1006static void
1007test_applying_fixits_insert_after (const line_table_case &case_)
1008{
1009 /* Create a tempfile and write some text to it.
1010 .........................0000000001111111.
1011 .........................1234567890123456. */
1012 const char *old_content = ("/* before */\n"
1013 "foo = bar.field;\n"
1014 "/* after */\n");
1015 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1016 const char *filename = tmp.get_filename ();
1017 line_table_test ltt (case_);
1018 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 2);
1019
1020 /* Add a comment after "field". */
1021 location_t start = linemap_position_for_column (line_table, 11);
1022 location_t finish = linemap_position_for_column (line_table, 15);
1023 location_t field = make_location (caret: start, start, finish);
1024 rich_location richloc (line_table, field);
1025 richloc.add_fixit_insert_after (new_content: "/* inserted */");
1026
1027 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1028 return;
1029
1030 /* Verify that the text was inserted after the end of "field". */
1031 edit_context edit;
1032 edit.add_fixits (richloc: &richloc);
1033 auto_free <char *> new_content = edit.get_content (filename);
1034 ASSERT_STREQ ("/* before */\n"
1035 "foo = bar.field/* inserted */;\n"
1036 "/* after */\n", new_content);
1037
1038 /* Verify diff. */
1039 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1040 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1041 " /* before */\n"
1042 "-foo = bar.field;\n"
1043 "+foo = bar.field/* inserted */;\n"
1044 " /* after */\n", diff);
1045}
1046
1047/* Test applying an "insert" fixit, using insert_after at the end of
1048 a line (contrast with test_applying_fixits_insert_after_failure
1049 below). */
1050
1051static void
1052test_applying_fixits_insert_after_at_line_end (const line_table_case &case_)
1053{
1054 /* Create a tempfile and write some text to it.
1055 .........................0000000001111111.
1056 .........................1234567890123456. */
1057 const char *old_content = ("/* before */\n"
1058 "foo = bar.field;\n"
1059 "/* after */\n");
1060 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1061 const char *filename = tmp.get_filename ();
1062 line_table_test ltt (case_);
1063 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 2);
1064
1065 /* Add a comment after the semicolon. */
1066 location_t loc = linemap_position_for_column (line_table, 16);
1067 rich_location richloc (line_table, loc);
1068 richloc.add_fixit_insert_after (new_content: "/* inserted */");
1069
1070 if (loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1071 return;
1072
1073 edit_context edit;
1074 edit.add_fixits (richloc: &richloc);
1075 auto_free <char *> new_content = edit.get_content (filename);
1076 ASSERT_STREQ ("/* before */\n"
1077 "foo = bar.field;/* inserted */\n"
1078 "/* after */\n", new_content);
1079
1080 /* Verify diff. */
1081 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1082 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1083 " /* before */\n"
1084 "-foo = bar.field;\n"
1085 "+foo = bar.field;/* inserted */\n"
1086 " /* after */\n", diff);
1087}
1088
1089/* Test of a failed attempt to apply an "insert" fixit, using insert_after,
1090 due to the relevant linemap ending. Contrast with
1091 test_applying_fixits_insert_after_at_line_end above. */
1092
1093static void
1094test_applying_fixits_insert_after_failure (const line_table_case &case_)
1095{
1096 /* Create a tempfile and write some text to it.
1097 .........................0000000001111111.
1098 .........................1234567890123456. */
1099 const char *old_content = ("/* before */\n"
1100 "foo = bar.field;\n"
1101 "/* after */\n");
1102 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1103 const char *filename = tmp.get_filename ();
1104 line_table_test ltt (case_);
1105 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 2);
1106
1107 /* Add a comment after the semicolon. */
1108 location_t loc = linemap_position_for_column (line_table, 16);
1109 rich_location richloc (line_table, loc);
1110
1111 /* We want a failure of linemap_position_for_loc_and_offset.
1112 We can do this by starting a new linemap at line 3, so that
1113 there is no appropriate location value for the insertion point
1114 within the linemap for line 2. */
1115 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 3);
1116
1117 /* The failure fails to happen at the transition point from
1118 packed ranges to unpacked ranges (where there are some "spare"
1119 location_t values). Skip the test there. */
1120 if (loc >= LINE_MAP_MAX_LOCATION_WITH_PACKED_RANGES)
1121 return;
1122
1123 /* Offsetting "loc" should now fail (by returning the input loc. */
1124 ASSERT_EQ (loc, linemap_position_for_loc_and_offset (line_table, loc, 1));
1125
1126 /* Hence attempting to use add_fixit_insert_after at the end of the line
1127 should now fail. */
1128 richloc.add_fixit_insert_after (new_content: "/* inserted */");
1129 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1130
1131 edit_context edit;
1132 edit.add_fixits (richloc: &richloc);
1133 ASSERT_FALSE (edit.valid_p ());
1134 ASSERT_EQ (NULL, edit.get_content (filename));
1135 ASSERT_EQ (NULL, edit.generate_diff (false));
1136}
1137
1138/* Test applying an "insert" fixit that adds a newline. */
1139
1140static void
1141test_applying_fixits_insert_containing_newline (const line_table_case &case_)
1142{
1143 /* Create a tempfile and write some text to it.
1144 .........................0000000001111111.
1145 .........................1234567890123456. */
1146 const char *old_content = (" case 'a':\n" /* line 1. */
1147 " x = a;\n" /* line 2. */
1148 " case 'b':\n" /* line 3. */
1149 " x = b;\n");/* line 4. */
1150
1151 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1152 const char *filename = tmp.get_filename ();
1153 line_table_test ltt (case_);
1154 linemap_add (line_table, LC_ENTER, sysp: false, to_file: tmp.get_filename (), to_line: 3);
1155
1156 /* Add a "break;" on a line by itself before line 3 i.e. before
1157 column 1 of line 3. */
1158 location_t case_start = linemap_position_for_column (line_table, 5);
1159 location_t case_finish = linemap_position_for_column (line_table, 13);
1160 location_t case_loc = make_location (caret: case_start, start: case_start, finish: case_finish);
1161 rich_location richloc (line_table, case_loc);
1162 location_t line_start = linemap_position_for_column (line_table, 1);
1163 richloc.add_fixit_insert_before (where: line_start, new_content: " break;\n");
1164
1165 if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1166 return;
1167
1168 edit_context edit;
1169 edit.add_fixits (richloc: &richloc);
1170 auto_free <char *> new_content = edit.get_content (filename);
1171 ASSERT_STREQ ((" case 'a':\n"
1172 " x = a;\n"
1173 " break;\n"
1174 " case 'b':\n"
1175 " x = b;\n"),
1176 new_content);
1177
1178 /* Verify diff. */
1179 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1180 ASSERT_STREQ (("@@ -1,4 +1,5 @@\n"
1181 " case 'a':\n"
1182 " x = a;\n"
1183 "+ break;\n"
1184 " case 'b':\n"
1185 " x = b;\n"),
1186 diff);
1187}
1188
1189/* Test applying a "replace" fixit that grows the affected line. */
1190
1191static void
1192test_applying_fixits_growing_replace (const line_table_case &case_)
1193{
1194 /* Create a tempfile and write some text to it.
1195 .........................0000000001111111.
1196 .........................1234567890123456. */
1197 const char *old_content = ("/* before */\n"
1198 "foo = bar.field;\n"
1199 "/* after */\n");
1200 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1201 const char *filename = tmp.get_filename ();
1202 line_table_test ltt (case_);
1203 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 2);
1204
1205 /* Replace "field" with "m_field". */
1206 location_t start = linemap_position_for_column (line_table, 11);
1207 location_t finish = linemap_position_for_column (line_table, 15);
1208 location_t field = make_location (caret: start, start, finish);
1209 rich_location richloc (line_table, field);
1210 richloc.add_fixit_replace (new_content: "m_field");
1211
1212 edit_context edit;
1213 edit.add_fixits (richloc: &richloc);
1214 auto_free <char *> new_content = edit.get_content (filename);
1215 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1216 {
1217 ASSERT_STREQ ("/* before */\n"
1218 "foo = bar.m_field;\n"
1219 "/* after */\n", new_content);
1220
1221 /* Verify location of ";" after the change. */
1222 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 16));
1223
1224 /* Verify diff. */
1225 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1226 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1227 " /* before */\n"
1228 "-foo = bar.field;\n"
1229 "+foo = bar.m_field;\n"
1230 " /* after */\n", diff);
1231 }
1232}
1233
1234/* Test applying a "replace" fixit that shrinks the affected line. */
1235
1236static void
1237test_applying_fixits_shrinking_replace (const line_table_case &case_)
1238{
1239 /* Create a tempfile and write some text to it.
1240 .........................000000000111111111.
1241 .........................123456789012345678. */
1242 const char *old_content = ("/* before */\n"
1243 "foo = bar.m_field;\n"
1244 "/* after */\n");
1245 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1246 const char *filename = tmp.get_filename ();
1247 line_table_test ltt (case_);
1248 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 2);
1249
1250 /* Replace "field" with "m_field". */
1251 location_t start = linemap_position_for_column (line_table, 11);
1252 location_t finish = linemap_position_for_column (line_table, 17);
1253 location_t m_field = make_location (caret: start, start, finish);
1254 rich_location richloc (line_table, m_field);
1255 richloc.add_fixit_replace (new_content: "field");
1256
1257 edit_context edit;
1258 edit.add_fixits (richloc: &richloc);
1259 auto_free <char *> new_content = edit.get_content (filename);
1260 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1261 {
1262 ASSERT_STREQ ("/* before */\n"
1263 "foo = bar.field;\n"
1264 "/* after */\n", new_content);
1265
1266 /* Verify location of ";" after the change. */
1267 ASSERT_EQ (16, edit.get_effective_column (filename, 2, 18));
1268
1269 /* Verify diff. */
1270 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1271 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1272 " /* before */\n"
1273 "-foo = bar.m_field;\n"
1274 "+foo = bar.field;\n"
1275 " /* after */\n", diff);
1276 }
1277}
1278
1279/* Replacement fix-it hint containing a newline. */
1280
1281static void
1282test_applying_fixits_replace_containing_newline (const line_table_case &case_)
1283{
1284 /* Create a tempfile and write some text to it.
1285 .........................0000000001111.
1286 .........................1234567890123. */
1287 const char *old_content = "foo = bar ();\n";
1288
1289 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1290 const char *filename = tmp.get_filename ();
1291 line_table_test ltt (case_);
1292 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 1);
1293
1294 /* Replace the " = " with "\n = ", as if we were reformatting an
1295 overly long line. */
1296 location_t start = linemap_position_for_column (line_table, 4);
1297 location_t finish = linemap_position_for_column (line_table, 6);
1298 location_t loc = linemap_position_for_column (line_table, 13);
1299 rich_location richloc (line_table, loc);
1300 source_range range = source_range::from_locations (start, finish);
1301 richloc.add_fixit_replace (src_range: range, new_content: "\n = ");
1302
1303 /* Newlines are only supported within fix-it hints that
1304 are at the start of lines (for entirely new lines), hence
1305 this fix-it should not be displayed. */
1306 ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
1307
1308 if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
1309 return;
1310
1311 edit_context edit;
1312 edit.add_fixits (richloc: &richloc);
1313 auto_free <char *> new_content = edit.get_content (filename);
1314 //ASSERT_STREQ ("foo\n = bar ();\n", new_content);
1315}
1316
1317/* Test applying a "remove" fixit. */
1318
1319static void
1320test_applying_fixits_remove (const line_table_case &case_)
1321{
1322 /* Create a tempfile and write some text to it.
1323 .........................000000000111111111.
1324 .........................123456789012345678. */
1325 const char *old_content = ("/* before */\n"
1326 "foo = bar.m_field;\n"
1327 "/* after */\n");
1328 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1329 const char *filename = tmp.get_filename ();
1330 line_table_test ltt (case_);
1331 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 2);
1332
1333 /* Remove ".m_field". */
1334 location_t start = linemap_position_for_column (line_table, 10);
1335 location_t finish = linemap_position_for_column (line_table, 17);
1336 rich_location richloc (line_table, start);
1337 source_range range;
1338 range.m_start = start;
1339 range.m_finish = finish;
1340 richloc.add_fixit_remove (src_range: range);
1341
1342 edit_context edit;
1343 edit.add_fixits (richloc: &richloc);
1344 auto_free <char *> new_content = edit.get_content (filename);
1345 if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1346 {
1347 ASSERT_STREQ ("/* before */\n"
1348 "foo = bar;\n"
1349 "/* after */\n", new_content);
1350
1351 /* Verify location of ";" after the change. */
1352 ASSERT_EQ (10, edit.get_effective_column (filename, 2, 18));
1353
1354 /* Verify diff. */
1355 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1356 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1357 " /* before */\n"
1358 "-foo = bar.m_field;\n"
1359 "+foo = bar;\n"
1360 " /* after */\n", diff);
1361 }
1362}
1363
1364/* Test applying multiple fixits to one line. */
1365
1366static void
1367test_applying_fixits_multiple (const line_table_case &case_)
1368{
1369 /* Create a tempfile and write some text to it.
1370 .........................00000000011111111.
1371 .........................12345678901234567. */
1372 const char *old_content = ("/* before */\n"
1373 "foo = bar.field;\n"
1374 "/* after */\n");
1375 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1376 const char *filename = tmp.get_filename ();
1377 line_table_test ltt (case_);
1378 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 2);
1379
1380 location_t c7 = linemap_position_for_column (line_table, 7);
1381 location_t c9 = linemap_position_for_column (line_table, 9);
1382 location_t c11 = linemap_position_for_column (line_table, 11);
1383 location_t c15 = linemap_position_for_column (line_table, 15);
1384 location_t c17 = linemap_position_for_column (line_table, 17);
1385
1386 if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1387 return;
1388
1389 /* Add a comment in front of "bar.field". */
1390 rich_location insert_a (line_table, c7);
1391 insert_a.add_fixit_insert_before (where: c7, new_content: "/* alpha */");
1392
1393 /* Add a comment after "bar.field;". */
1394 rich_location insert_b (line_table, c17);
1395 insert_b.add_fixit_insert_before (where: c17, new_content: "/* beta */");
1396
1397 /* Replace "bar" with "pub". */
1398 rich_location replace_a (line_table, c7);
1399 replace_a.add_fixit_replace (src_range: source_range::from_locations (start: c7, finish: c9),
1400 new_content: "pub");
1401
1402 /* Replace "field" with "meadow". */
1403 rich_location replace_b (line_table, c7);
1404 replace_b.add_fixit_replace (src_range: source_range::from_locations (start: c11, finish: c15),
1405 new_content: "meadow");
1406
1407 edit_context edit;
1408 edit.add_fixits (richloc: &insert_a);
1409 ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
1410 ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
1411 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
1412 ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
1413 ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
1414 ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
1415
1416 edit.add_fixits (richloc: &insert_b);
1417 edit.add_fixits (richloc: &replace_a);
1418 edit.add_fixits (richloc: &replace_b);
1419
1420 if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1421 {
1422 auto_free <char *> new_content = edit.get_content (filename: tmp.get_filename ());
1423 ASSERT_STREQ ("/* before */\n"
1424 "foo = /* alpha */pub.meadow;/* beta */\n"
1425 "/* after */\n",
1426 new_content);
1427
1428 /* Verify diff. */
1429 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1430 ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
1431 " /* before */\n"
1432 "-foo = bar.field;\n"
1433 "+foo = /* alpha */pub.meadow;/* beta */\n"
1434 " /* after */\n", diff);
1435 }
1436}
1437
1438/* Subroutine of test_applying_fixits_multiple_lines.
1439 Add the text "CHANGED: " to the front of the given line. */
1440
1441static location_t
1442change_line (edit_context &edit, int line_num)
1443{
1444 const line_map_ordinary *ord_map
1445 = LINEMAPS_LAST_ORDINARY_MAP (set: line_table);
1446 const int column = 1;
1447 location_t loc =
1448 linemap_position_for_line_and_column (set: line_table, ord_map,
1449 line_num, column);
1450
1451 expanded_location exploc = expand_location (loc);
1452 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1453 {
1454 ASSERT_EQ (line_num, exploc.line);
1455 ASSERT_EQ (column, exploc.column);
1456 }
1457
1458 rich_location insert (line_table, loc);
1459 insert.add_fixit_insert_before (new_content: "CHANGED: ");
1460 edit.add_fixits (richloc: &insert);
1461 return loc;
1462}
1463
1464/* Subroutine of test_applying_fixits_multiple_lines.
1465 Add the text "INSERTED\n" in front of the given line. */
1466
1467static location_t
1468insert_line (edit_context &edit, int line_num)
1469{
1470 const line_map_ordinary *ord_map
1471 = LINEMAPS_LAST_ORDINARY_MAP (set: line_table);
1472 const int column = 1;
1473 location_t loc =
1474 linemap_position_for_line_and_column (set: line_table, ord_map,
1475 line_num, column);
1476
1477 expanded_location exploc = expand_location (loc);
1478 if (loc <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1479 {
1480 ASSERT_EQ (line_num, exploc.line);
1481 ASSERT_EQ (column, exploc.column);
1482 }
1483
1484 rich_location insert (line_table, loc);
1485 insert.add_fixit_insert_before (new_content: "INSERTED\n");
1486 edit.add_fixits (richloc: &insert);
1487 return loc;
1488}
1489
1490/* Test of editing multiple lines within a long file,
1491 to ensure that diffs are generated as expected. */
1492
1493static void
1494test_applying_fixits_multiple_lines (const line_table_case &case_)
1495{
1496 /* Create a tempfile and write many lines of text to it. */
1497 named_temp_file tmp (".txt");
1498 const char *filename = tmp.get_filename ();
1499 FILE *f = fopen (filename: filename, modes: "w");
1500 ASSERT_NE (f, NULL);
1501 for (int i = 1; i <= 1000; i++)
1502 fprintf (stream: f, format: "line %i\n", i);
1503 fclose (stream: f);
1504
1505 line_table_test ltt (case_);
1506 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 1);
1507 linemap_position_for_column (line_table, 127);
1508
1509 edit_context edit;
1510
1511 /* A run of consecutive lines. */
1512 change_line (edit, line_num: 2);
1513 change_line (edit, line_num: 3);
1514 change_line (edit, line_num: 4);
1515 insert_line (edit, line_num: 5);
1516
1517 /* A run of nearby lines, within the contextual limit. */
1518 change_line (edit, line_num: 150);
1519 change_line (edit, line_num: 151);
1520 location_t last_loc = change_line (edit, line_num: 153);
1521
1522 if (last_loc > LINE_MAP_MAX_LOCATION_WITH_COLS)
1523 return;
1524
1525 /* Verify diff. */
1526 auto_free <char *> diff = edit.generate_diff (show_filenames: false);
1527 ASSERT_STREQ ("@@ -1,7 +1,8 @@\n"
1528 " line 1\n"
1529 "-line 2\n"
1530 "-line 3\n"
1531 "-line 4\n"
1532 "+CHANGED: line 2\n"
1533 "+CHANGED: line 3\n"
1534 "+CHANGED: line 4\n"
1535 "+INSERTED\n"
1536 " line 5\n"
1537 " line 6\n"
1538 " line 7\n"
1539 "@@ -147,10 +148,10 @@\n"
1540 " line 147\n"
1541 " line 148\n"
1542 " line 149\n"
1543 "-line 150\n"
1544 "-line 151\n"
1545 "+CHANGED: line 150\n"
1546 "+CHANGED: line 151\n"
1547 " line 152\n"
1548 "-line 153\n"
1549 "+CHANGED: line 153\n"
1550 " line 154\n"
1551 " line 155\n"
1552 " line 156\n", diff);
1553
1554 /* Ensure tmp stays alive until this point, so that the tempfile
1555 persists until after the generate_diff call. */
1556 tmp.get_filename ();
1557}
1558
1559/* Test of converting an initializer for a named field from
1560 the old GCC extension to C99 syntax.
1561 Exercises a shrinking replacement followed by a growing
1562 replacement on the same line. */
1563
1564static void
1565test_applying_fixits_modernize_named_init (const line_table_case &case_)
1566{
1567 /* Create a tempfile and write some text to it.
1568 .........................00000000011111111.
1569 .........................12345678901234567. */
1570 const char *old_content = ("/* before */\n"
1571 "bar : 1,\n"
1572 "/* after */\n");
1573 temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
1574 const char *filename = tmp.get_filename ();
1575 line_table_test ltt (case_);
1576 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 2);
1577
1578 location_t c1 = linemap_position_for_column (line_table, 1);
1579 location_t c3 = linemap_position_for_column (line_table, 3);
1580 location_t c8 = linemap_position_for_column (line_table, 8);
1581
1582 if (c8 > LINE_MAP_MAX_LOCATION_WITH_COLS)
1583 return;
1584
1585 /* Replace "bar" with ".". */
1586 rich_location r1 (line_table, c8);
1587 r1.add_fixit_replace (src_range: source_range::from_locations (start: c1, finish: c3),
1588 new_content: ".");
1589
1590 /* Replace ":" with "bar =". */
1591 rich_location r2 (line_table, c8);
1592 r2.add_fixit_replace (src_range: source_range::from_locations (start: c8, finish: c8),
1593 new_content: "bar =");
1594
1595 /* The order should not matter. Do r1 then r2. */
1596 {
1597 edit_context edit;
1598 edit.add_fixits (richloc: &r1);
1599
1600 /* Verify state after first replacement. */
1601 {
1602 auto_free <char *> new_content = edit.get_content (filename: tmp.get_filename ());
1603 /* We should now have:
1604 ............00000000011.
1605 ............12345678901. */
1606 ASSERT_STREQ ("/* before */\n"
1607 ". : 1,\n"
1608 "/* after */\n",
1609 new_content);
1610 /* Location of the "1". */
1611 ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
1612 /* Location of the ",". */
1613 ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
1614 }
1615
1616 edit.add_fixits (richloc: &r2);
1617
1618 auto_free <char *> new_content = edit.get_content (filename: tmp.get_filename ());
1619 /* Verify state after second replacement.
1620 ............00000000011111111.
1621 ............12345678901234567. */
1622 ASSERT_STREQ ("/* before */\n"
1623 ". bar = 1,\n"
1624 "/* after */\n",
1625 new_content);
1626 }
1627
1628 /* Try again, doing r2 then r1; the new_content should be the same. */
1629 {
1630 edit_context edit;
1631 edit.add_fixits (richloc: &r2);
1632 edit.add_fixits (richloc: &r1);
1633 auto_free <char *> new_content = edit.get_content (filename: tmp.get_filename ());
1634 /*.............00000000011111111.
1635 .............12345678901234567. */
1636 ASSERT_STREQ ("/* before */\n"
1637 ". bar = 1,\n"
1638 "/* after */\n",
1639 new_content);
1640 }
1641}
1642
1643/* Test of a fixit affecting a file that can't be read. */
1644
1645static void
1646test_applying_fixits_unreadable_file ()
1647{
1648 const char *filename = "this-does-not-exist.txt";
1649 line_table_test ltt;
1650 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 1);
1651
1652 location_t loc = linemap_position_for_column (line_table, 1);
1653
1654 rich_location insert (line_table, loc);
1655 insert.add_fixit_insert_before (new_content: "change 1");
1656 insert.add_fixit_insert_before (new_content: "change 2");
1657
1658 edit_context edit;
1659 /* Attempting to add the fixits affecting the unreadable file
1660 should transition the edit from valid to invalid. */
1661 ASSERT_TRUE (edit.valid_p ());
1662 edit.add_fixits (richloc: &insert);
1663 ASSERT_FALSE (edit.valid_p ());
1664 ASSERT_EQ (NULL, edit.get_content (filename));
1665 ASSERT_EQ (NULL, edit.generate_diff (false));
1666}
1667
1668/* Verify that we gracefully handle an attempt to edit a line
1669 that's beyond the end of the file. */
1670
1671static void
1672test_applying_fixits_line_out_of_range ()
1673{
1674 /* Create a tempfile and write some text to it.
1675 ........................00000000011111111.
1676 ........................12345678901234567. */
1677 const char *old_content = "One-liner file\n";
1678 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1679 const char *filename = tmp.get_filename ();
1680 line_table_test ltt;
1681 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 2);
1682
1683 /* Try to insert a string in line 2. */
1684 location_t loc = linemap_position_for_column (line_table, 1);
1685
1686 rich_location insert (line_table, loc);
1687 insert.add_fixit_insert_before (new_content: "change");
1688
1689 /* Verify that attempting the insertion puts an edit_context
1690 into an invalid state. */
1691 edit_context edit;
1692 ASSERT_TRUE (edit.valid_p ());
1693 edit.add_fixits (richloc: &insert);
1694 ASSERT_FALSE (edit.valid_p ());
1695 ASSERT_EQ (NULL, edit.get_content (filename));
1696 ASSERT_EQ (NULL, edit.generate_diff (false));
1697}
1698
1699/* Verify the boundary conditions of column values in fix-it
1700 hints applied to edit_context instances. */
1701
1702static void
1703test_applying_fixits_column_validation (const line_table_case &case_)
1704{
1705 /* Create a tempfile and write some text to it.
1706 ........................00000000011111111.
1707 ........................12345678901234567. */
1708 const char *old_content = "One-liner file\n";
1709 temp_source_file tmp (SELFTEST_LOCATION, ".txt", old_content);
1710 const char *filename = tmp.get_filename ();
1711 line_table_test ltt (case_);
1712 linemap_add (line_table, LC_ENTER, sysp: false, to_file: filename, to_line: 1);
1713
1714 location_t c11 = linemap_position_for_column (line_table, 11);
1715 location_t c14 = linemap_position_for_column (line_table, 14);
1716 location_t c15 = linemap_position_for_column (line_table, 15);
1717 location_t c16 = linemap_position_for_column (line_table, 16);
1718
1719 /* Verify limits of valid columns in insertion fixits. */
1720
1721 /* Verify inserting at the end of the line. */
1722 {
1723 rich_location richloc (line_table, c11);
1724 richloc.add_fixit_insert_before (where: c15, new_content: " change");
1725
1726 /* Col 15 is at the end of the line, so the insertion
1727 should succeed. */
1728 edit_context edit;
1729 edit.add_fixits (richloc: &richloc);
1730 auto_free <char *> new_content = edit.get_content (filename: tmp.get_filename ());
1731 if (c15 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1732 ASSERT_STREQ ("One-liner file change\n", new_content);
1733 else
1734 ASSERT_EQ (NULL, new_content);
1735 }
1736
1737 /* Verify inserting beyond the end of the line. */
1738 {
1739 rich_location richloc (line_table, c11);
1740 richloc.add_fixit_insert_before (where: c16, new_content: " change");
1741
1742 /* Col 16 is beyond the end of the line, so the insertion
1743 should fail gracefully. */
1744 edit_context edit;
1745 ASSERT_TRUE (edit.valid_p ());
1746 edit.add_fixits (richloc: &richloc);
1747 ASSERT_FALSE (edit.valid_p ());
1748 ASSERT_EQ (NULL, edit.get_content (filename));
1749 ASSERT_EQ (NULL, edit.generate_diff (false));
1750 }
1751
1752 /* Verify limits of valid columns in replacement fixits. */
1753
1754 /* Verify replacing the end of the line. */
1755 {
1756 rich_location richloc (line_table, c11);
1757 source_range range = source_range::from_locations (start: c11, finish: c14);
1758 richloc.add_fixit_replace (src_range: range, new_content: "change");
1759
1760 /* Col 14 is at the end of the line, so the replacement
1761 should succeed. */
1762 edit_context edit;
1763 edit.add_fixits (richloc: &richloc);
1764 auto_free <char *> new_content = edit.get_content (filename: tmp.get_filename ());
1765 if (c14 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
1766 ASSERT_STREQ ("One-liner change\n", new_content);
1767 else
1768 ASSERT_EQ (NULL, new_content);
1769 }
1770
1771 /* Verify going beyond the end of the line. */
1772 {
1773 rich_location richloc (line_table, c11);
1774 source_range range = source_range::from_locations (start: c11, finish: c15);
1775 richloc.add_fixit_replace (src_range: range, new_content: "change");
1776
1777 /* Col 15 is after the end of the line, so the replacement
1778 should fail; verify that the attempt fails gracefully. */
1779 edit_context edit;
1780 ASSERT_TRUE (edit.valid_p ());
1781 edit.add_fixits (richloc: &richloc);
1782 ASSERT_FALSE (edit.valid_p ());
1783 ASSERT_EQ (NULL, edit.get_content (filename));
1784 ASSERT_EQ (NULL, edit.generate_diff (false));
1785 }
1786}
1787
1788/* Run all of the selftests within this file. */
1789
1790void
1791edit_context_cc_tests ()
1792{
1793 test_get_content ();
1794 for_each_line_table_case (testcase: test_applying_fixits_insert_before);
1795 for_each_line_table_case (testcase: test_applying_fixits_insert_after);
1796 for_each_line_table_case (testcase: test_applying_fixits_insert_after_at_line_end);
1797 for_each_line_table_case (testcase: test_applying_fixits_insert_after_failure);
1798 for_each_line_table_case (testcase: test_applying_fixits_insert_containing_newline);
1799 for_each_line_table_case (testcase: test_applying_fixits_growing_replace);
1800 for_each_line_table_case (testcase: test_applying_fixits_shrinking_replace);
1801 for_each_line_table_case (testcase: test_applying_fixits_replace_containing_newline);
1802 for_each_line_table_case (testcase: test_applying_fixits_remove);
1803 for_each_line_table_case (testcase: test_applying_fixits_multiple);
1804 for_each_line_table_case (testcase: test_applying_fixits_multiple_lines);
1805 for_each_line_table_case (testcase: test_applying_fixits_modernize_named_init);
1806 test_applying_fixits_unreadable_file ();
1807 test_applying_fixits_line_out_of_range ();
1808 for_each_line_table_case (testcase: test_applying_fixits_column_validation);
1809}
1810
1811} // namespace selftest
1812
1813#endif /* CHECKING_P */
1814

source code of gcc/edit-context.cc