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