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 { 53 diff (pretty_printer *pp, bool show_filenames) 54 : m_pp (pp), m_show_filenames (show_filenames) {} 55 56 pretty_printer *m_pp; 57 bool m_show_filenames; 58 }; 59 60 /* The state of one named file within an edit_context: the filename, 61 and the lines that have been edited so far. */ 62 63 class edited_file 64 { 65 public: 66 edited_file (const char *filename); 67 static void delete_cb (edited_file *file); 68 69 const char *get_filename () const { return m_filename; } 70 char *get_content (); 71 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 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: 112 added_line (const char *content, int len) 113 : m_content (xstrndup (content, len)), m_len (len) {} 114 ~added_line () { free (m_content); } 115 116 const char *get_content () const { return m_content; } 117 int get_len () const { return m_len; } 118 119 private: 120 char *m_content; 121 int m_len; 122 }; 123 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 143 int get_line_num () const { return m_line_num; } 144 const char *get_content () const { return m_content; } 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? */ 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: 184 line_event (int start, int next, int len) : m_start (start), 185 m_next (next), m_delta (len - (next - start)) {} 186 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 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 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 * 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 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 * 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 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 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 * 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 & 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 346 static int line_comparator (int a, int b) 347 { 348 return a - b; 349 } 350 351 /* edited_file's constructor. */ 352 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 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 * 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 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 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 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 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 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 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 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 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 * 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 * 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 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 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 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 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 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 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 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 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 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 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 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: 899 auto_free (POINTER_T p) : m_ptr (p) {} 900 ~auto_free () { free (m_ptr); } 901 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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