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