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