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