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