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