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

source code of gcc/edit-context.cc