]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/errors/snippet/test.rs
Better handling of tab in error
[rust.git] / src / libsyntax / errors / snippet / test.rs
1 // Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // Code for testing annotated snippets.
12
13 #![cfg(test)]
14
15 use codemap::{BytePos, CodeMap, FileMap, NO_EXPANSION, Span};
16 use std::rc::Rc;
17 use super::{RenderedLine, SnippetData};
18
19 /// Returns the span corresponding to the `n`th occurrence of
20 /// `substring` in `source_text`.
21 trait CodeMapExtension {
22     fn span_substr(&self,
23                    file: &Rc<FileMap>,
24                    source_text: &str,
25                    substring: &str,
26                    n: usize)
27                    -> Span;
28 }
29
30 impl CodeMapExtension for CodeMap {
31     fn span_substr(&self,
32                    file: &Rc<FileMap>,
33                    source_text: &str,
34                    substring: &str,
35                    n: usize)
36                    -> Span
37     {
38         println!("span_substr(file={:?}/{:?}, substring={:?}, n={})",
39                  file.name, file.start_pos, substring, n);
40         let mut i = 0;
41         let mut hi = 0;
42         loop {
43             let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
44                 panic!("source_text `{}` does not have {} occurrences of `{}`, only {}",
45                        source_text, n, substring, i);
46             });
47             let lo = hi + offset;
48             hi = lo + substring.len();
49             if i == n {
50                 let span = Span {
51                     lo: BytePos(lo as u32 + file.start_pos.0),
52                     hi: BytePos(hi as u32 + file.start_pos.0),
53                     expn_id: NO_EXPANSION,
54                 };
55                 assert_eq!(&self.span_to_snippet(span).unwrap()[..],
56                            substring);
57                 return span;
58             }
59             i += 1;
60         }
61     }
62 }
63
64 fn splice(start: Span, end: Span) -> Span {
65     Span {
66         lo: start.lo,
67         hi: end.hi,
68         expn_id: NO_EXPANSION,
69     }
70 }
71
72 fn make_string(lines: &[RenderedLine]) -> String {
73     lines.iter()
74          .flat_map(|rl| {
75              rl.text.iter()
76                     .map(|s| &s.text[..])
77                     .chain(Some("\n"))
78          })
79          .collect()
80 }
81
82 #[test]
83 fn tab() {
84     let file_text = "
85 fn foo() {
86 \tbar;
87 }
88 ";
89
90     let cm = Rc::new(CodeMap::new());
91     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
92     let span_bar = cm.span_substr(&foo, file_text, "bar", 0);
93
94     let mut snippet = SnippetData::new(cm, Some(span_bar));
95     snippet.push(span_bar, true, None);
96
97     let lines = snippet.render_lines();
98     let text = make_string(&lines);
99     assert_eq!(&text[..], &"
100  --> foo.rs:3:2
101 3 |> \tbar;
102   |> \t^^^
103 "[1..]);
104 }
105
106 #[test]
107 fn one_line() {
108     let file_text = r#"
109 fn foo() {
110     vec.push(vec.pop().unwrap());
111 }
112 "#;
113
114     let cm = Rc::new(CodeMap::new());
115     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
116     let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0);
117     let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1);
118     let span_semi = cm.span_substr(&foo, file_text, ";", 0);
119
120     let mut snippet = SnippetData::new(cm, None);
121     snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here")));
122     snippet.push(span_vec1, false, Some(format!("error occurs here")));
123     snippet.push(span_semi, false, Some(format!("previous borrow ends here")));
124
125     let lines = snippet.render_lines();
126     println!("{:#?}", lines);
127
128     let text: String = make_string(&lines);
129
130     println!("text=\n{}", text);
131     assert_eq!(&text[..], &r#"
132  ::: foo.rs
133 3 |>     vec.push(vec.pop().unwrap());
134   |>     ---      ---                - previous borrow ends here
135   |>     |        |
136   |>     |        error occurs here
137   |>     previous borrow of `vec` occurs here
138 "#[1..]);
139 }
140
141 #[test]
142 fn two_files() {
143     let file_text_foo = r#"
144 fn foo() {
145     vec.push(vec.pop().unwrap());
146 }
147 "#;
148
149     let file_text_bar = r#"
150 fn bar() {
151     // these blank links here
152     // serve to ensure that the line numbers
153     // from bar.rs
154     // require more digits
155
156
157
158
159
160
161
162
163
164
165     vec.push();
166
167     // this line will get elided
168
169     vec.pop().unwrap());
170 }
171 "#;
172
173     let cm = Rc::new(CodeMap::new());
174     let foo_map = cm.new_filemap_and_lines("foo.rs", file_text_foo);
175     let span_foo_vec0 = cm.span_substr(&foo_map, file_text_foo, "vec", 0);
176     let span_foo_vec1 = cm.span_substr(&foo_map, file_text_foo, "vec", 1);
177     let span_foo_semi = cm.span_substr(&foo_map, file_text_foo, ";", 0);
178
179     let bar_map = cm.new_filemap_and_lines("bar.rs", file_text_bar);
180     let span_bar_vec0 = cm.span_substr(&bar_map, file_text_bar, "vec", 0);
181     let span_bar_vec1 = cm.span_substr(&bar_map, file_text_bar, "vec", 1);
182     let span_bar_semi = cm.span_substr(&bar_map, file_text_bar, ";", 0);
183
184     let mut snippet = SnippetData::new(cm, Some(span_foo_vec1));
185     snippet.push(span_foo_vec0, false, Some(format!("a")));
186     snippet.push(span_foo_vec1, true, Some(format!("b")));
187     snippet.push(span_foo_semi, false, Some(format!("c")));
188     snippet.push(span_bar_vec0, false, Some(format!("d")));
189     snippet.push(span_bar_vec1, false, Some(format!("e")));
190     snippet.push(span_bar_semi, false, Some(format!("f")));
191
192     let lines = snippet.render_lines();
193     println!("{:#?}", lines);
194
195     let text: String = make_string(&lines);
196
197     println!("text=\n{}", text);
198
199     // Note that the `|>` remain aligned across both files:
200     assert_eq!(&text[..], &r#"
201    --> foo.rs:3:14
202 3   |>     vec.push(vec.pop().unwrap());
203     |>     ---      ^^^                - c
204     |>     |        |
205     |>     |        b
206     |>     a
207    ::: bar.rs
208 17  |>     vec.push();
209     |>     ---       - f
210     |>     |
211     |>     d
212 ...
213 21  |>     vec.pop().unwrap());
214     |>     --- e
215 "#[1..]);
216 }
217
218 #[test]
219 fn multi_line() {
220     let file_text = r#"
221 fn foo() {
222     let name = find_id(&data, 22).unwrap();
223
224     // Add one more item we forgot to the vector. Silly us.
225     data.push(Data { name: format!("Hera"), id: 66 });
226
227     // Print everything out.
228     println!("Name: {:?}", name);
229     println!("Data: {:?}", data);
230 }
231 "#;
232
233     let cm = Rc::new(CodeMap::new());
234     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
235     let span_data0 = cm.span_substr(&foo, file_text, "data", 0);
236     let span_data1 = cm.span_substr(&foo, file_text, "data", 1);
237     let span_rbrace = cm.span_substr(&foo, file_text, "}", 3);
238
239     let mut snippet = SnippetData::new(cm, None);
240     snippet.push(span_data0, false, Some(format!("immutable borrow begins here")));
241     snippet.push(span_data1, false, Some(format!("mutable borrow occurs here")));
242     snippet.push(span_rbrace, false, Some(format!("immutable borrow ends here")));
243
244     let lines = snippet.render_lines();
245     println!("{:#?}", lines);
246
247     let text: String = make_string(&lines);
248
249     println!("text=\n{}", text);
250     assert_eq!(&text[..], &r#"
251    ::: foo.rs
252 3   |>     let name = find_id(&data, 22).unwrap();
253     |>                         ---- immutable borrow begins here
254 ...
255 6   |>     data.push(Data { name: format!("Hera"), id: 66 });
256     |>     ---- mutable borrow occurs here
257 ...
258 11  |> }
259     |> - immutable borrow ends here
260 "#[1..]);
261 }
262
263 #[test]
264 fn overlapping() {
265     let file_text = r#"
266 fn foo() {
267     vec.push(vec.pop().unwrap());
268 }
269 "#;
270
271     let cm = Rc::new(CodeMap::new());
272     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
273     let span0 = cm.span_substr(&foo, file_text, "vec.push", 0);
274     let span1 = cm.span_substr(&foo, file_text, "vec", 0);
275     let span2 = cm.span_substr(&foo, file_text, "ec.push", 0);
276     let span3 = cm.span_substr(&foo, file_text, "unwrap", 0);
277
278     let mut snippet = SnippetData::new(cm, None);
279     snippet.push(span0, false, Some(format!("A")));
280     snippet.push(span1, false, Some(format!("B")));
281     snippet.push(span2, false, Some(format!("C")));
282     snippet.push(span3, false, Some(format!("D")));
283
284     let lines = snippet.render_lines();
285     println!("{:#?}", lines);
286     let text: String = make_string(&lines);
287
288     println!("text=r#\"\n{}\".trim_left()", text);
289     assert_eq!(&text[..], &r#"
290  ::: foo.rs
291 3 |>     vec.push(vec.pop().unwrap());
292   |>     --------           ------ D
293   |>     ||
294   |>     |C
295   |>     A
296   |>     B
297 "#[1..]);
298 }
299
300 #[test]
301 fn one_line_out_of_order() {
302     let file_text = r#"
303 fn foo() {
304     vec.push(vec.pop().unwrap());
305 }
306 "#;
307
308     let cm = Rc::new(CodeMap::new());
309     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
310     let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0);
311     let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1);
312     let span_semi = cm.span_substr(&foo, file_text, ";", 0);
313
314     // intentionally don't push the snippets left to right
315     let mut snippet = SnippetData::new(cm, None);
316     snippet.push(span_vec1, false, Some(format!("error occurs here")));
317     snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here")));
318     snippet.push(span_semi, false, Some(format!("previous borrow ends here")));
319
320     let lines = snippet.render_lines();
321     println!("{:#?}", lines);
322     let text: String = make_string(&lines);
323
324     println!("text=r#\"\n{}\".trim_left()", text);
325     assert_eq!(&text[..], &r#"
326  ::: foo.rs
327 3 |>     vec.push(vec.pop().unwrap());
328   |>     ---      ---                - previous borrow ends here
329   |>     |        |
330   |>     |        error occurs here
331   |>     previous borrow of `vec` occurs here
332 "#[1..]);
333 }
334
335 #[test]
336 fn elide_unnecessary_lines() {
337     let file_text = r#"
338 fn foo() {
339     let mut vec = vec![0, 1, 2];
340     let mut vec2 = vec;
341     vec2.push(3);
342     vec2.push(4);
343     vec2.push(5);
344     vec2.push(6);
345     vec.push(7);
346 }
347 "#;
348
349     let cm = Rc::new(CodeMap::new());
350     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
351     let span_vec0 = cm.span_substr(&foo, file_text, "vec", 3);
352     let span_vec1 = cm.span_substr(&foo, file_text, "vec", 8);
353
354     let mut snippet = SnippetData::new(cm, None);
355     snippet.push(span_vec0, false, Some(format!("`vec` moved here because it \
356         has type `collections::vec::Vec<i32>`")));
357     snippet.push(span_vec1, false, Some(format!("use of moved value: `vec`")));
358
359     let lines = snippet.render_lines();
360     println!("{:#?}", lines);
361     let text: String = make_string(&lines);
362     println!("text=r#\"\n{}\".trim_left()", text);
363     assert_eq!(&text[..], &r#"
364    ::: foo.rs
365 4   |>     let mut vec2 = vec;
366     |>                    --- `vec` moved here because it has type `collections::vec::Vec<i32>`
367 ...
368 9   |>     vec.push(7);
369     |>     --- use of moved value: `vec`
370 "#[1..]);
371 }
372
373 #[test]
374 fn spans_without_labels() {
375     let file_text = r#"
376 fn foo() {
377     let mut vec = vec![0, 1, 2];
378     let mut vec2 = vec;
379     vec2.push(3);
380     vec2.push(4);
381     vec2.push(5);
382     vec2.push(6);
383     vec.push(7);
384 }
385 "#;
386
387     let cm = Rc::new(CodeMap::new());
388     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
389
390     let mut snippet = SnippetData::new(cm.clone(), None);
391     for i in 0..4 {
392         let span_veci = cm.span_substr(&foo, file_text, "vec", i);
393         snippet.push(span_veci, false, None);
394     }
395
396     let lines = snippet.render_lines();
397     let text: String = make_string(&lines);
398     println!("text=&r#\"\n{}\n\"#[1..]", text);
399     assert_eq!(text, &r#"
400  ::: foo.rs
401 3 |>     let mut vec = vec![0, 1, 2];
402   |>             ---   ---
403 4 |>     let mut vec2 = vec;
404   |>             ---    ---
405 "#[1..]);
406 }
407
408 #[test]
409 fn span_long_selection() {
410     let file_text = r#"
411 impl SomeTrait for () {
412     fn foo(x: u32) {
413         // impl 1
414         // impl 2
415         // impl 3
416     }
417 }
418 "#;
419
420     let cm = Rc::new(CodeMap::new());
421     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
422
423     let mut snippet = SnippetData::new(cm.clone(), None);
424     let fn_span = cm.span_substr(&foo, file_text, "fn", 0);
425     let rbrace_span = cm.span_substr(&foo, file_text, "}", 0);
426     snippet.push(splice(fn_span, rbrace_span), false, None);
427     let lines = snippet.render_lines();
428     let text: String = make_string(&lines);
429     println!("r#\"\n{}\"", text);
430     assert_eq!(text, &r#"
431  ::: foo.rs
432 3 |>     fn foo(x: u32) {
433   |>     -
434 "#[1..]);
435 }
436
437 #[test]
438 fn span_overlap_label() {
439     // Test that we don't put `x_span` to the right of its highlight,
440     // since there is another highlight that overlaps it.
441
442     let file_text = r#"
443     fn foo(x: u32) {
444     }
445 }
446 "#;
447
448     let cm = Rc::new(CodeMap::new());
449     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
450
451     let mut snippet = SnippetData::new(cm.clone(), None);
452     let fn_span = cm.span_substr(&foo, file_text, "fn foo(x: u32)", 0);
453     let x_span = cm.span_substr(&foo, file_text, "x", 0);
454     snippet.push(fn_span, false, Some(format!("fn_span")));
455     snippet.push(x_span, false, Some(format!("x_span")));
456     let lines = snippet.render_lines();
457     let text: String = make_string(&lines);
458     println!("r#\"\n{}\"", text);
459     assert_eq!(text, &r#"
460  ::: foo.rs
461 2 |>     fn foo(x: u32) {
462   |>     --------------
463   |>     |      |
464   |>     |      x_span
465   |>     fn_span
466 "#[1..]);
467 }
468
469 #[test]
470 fn span_overlap_label2() {
471     // Test that we don't put `x_span` to the right of its highlight,
472     // since there is another highlight that overlaps it. In this
473     // case, the overlap is only at the beginning, but it's still
474     // better to show the beginning more clearly.
475
476     let file_text = r#"
477     fn foo(x: u32) {
478     }
479 }
480 "#;
481
482     let cm = Rc::new(CodeMap::new());
483     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
484
485     let mut snippet = SnippetData::new(cm.clone(), None);
486     let fn_span = cm.span_substr(&foo, file_text, "fn foo(x", 0);
487     let x_span = cm.span_substr(&foo, file_text, "x: u32)", 0);
488     snippet.push(fn_span, false, Some(format!("fn_span")));
489     snippet.push(x_span, false, Some(format!("x_span")));
490     let lines = snippet.render_lines();
491     let text: String = make_string(&lines);
492     println!("r#\"\n{}\"", text);
493     assert_eq!(text, &r#"
494  ::: foo.rs
495 2 |>     fn foo(x: u32) {
496   |>     --------------
497   |>     |      |
498   |>     |      x_span
499   |>     fn_span
500 "#[1..]);
501 }
502
503 #[test]
504 fn span_overlap_label3() {
505     // Test that we don't put `x_span` to the right of its highlight,
506     // since there is another highlight that overlaps it. In this
507     // case, the overlap is only at the beginning, but it's still
508     // better to show the beginning more clearly.
509
510     let file_text = r#"
511     fn foo() {
512        let closure = || {
513            inner
514        };
515     }
516 }
517 "#;
518
519     let cm = Rc::new(CodeMap::new());
520     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
521
522     let mut snippet = SnippetData::new(cm.clone(), None);
523
524     let closure_span = {
525         let closure_start_span = cm.span_substr(&foo, file_text, "||", 0);
526         let closure_end_span = cm.span_substr(&foo, file_text, "}", 0);
527         splice(closure_start_span, closure_end_span)
528     };
529
530     let inner_span = cm.span_substr(&foo, file_text, "inner", 0);
531
532     snippet.push(closure_span, false, Some(format!("foo")));
533     snippet.push(inner_span, false, Some(format!("bar")));
534
535     let lines = snippet.render_lines();
536     let text: String = make_string(&lines);
537     println!("r#\"\n{}\"", text);
538     assert_eq!(text, &r#"
539  ::: foo.rs
540 3 |>        let closure = || {
541   |>                      - foo
542 4 |>            inner
543   |>            ----- bar
544 "#[1..]);
545 }
546
547 #[test]
548 fn span_empty() {
549     // In one of the unit tests, we found that the parser sometimes
550     // gives empty spans, and in particular it supplied an EOF span
551     // like this one, which points at the very end. We want to
552     // fallback gracefully in this case.
553
554     let file_text = r#"
555 fn main() {
556     struct Foo;
557
558     impl !Sync for Foo {}
559
560     unsafe impl Send for &'static Foo {
561     // error: cross-crate traits with a default impl, like `core::marker::Send`,
562     //        can only be implemented for a struct/enum type, not
563     //        `&'static Foo`
564 }"#;
565
566
567     let cm = Rc::new(CodeMap::new());
568     let foo = cm.new_filemap_and_lines("foo.rs", file_text);
569
570     let mut rbrace_span = cm.span_substr(&foo, file_text, "}", 1);
571     rbrace_span.lo = rbrace_span.hi;
572
573     let mut snippet = SnippetData::new(cm.clone(), Some(rbrace_span));
574     snippet.push(rbrace_span, false, None);
575     let lines = snippet.render_lines();
576     let text: String = make_string(&lines);
577     println!("r#\"\n{}\"", text);
578     assert_eq!(text, &r#"
579   --> foo.rs:11:2
580 11 |> }
581    |>  -
582 "#[1..]);
583 }