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