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