2 use rustc_ast::tokenstream::TokenStream;
3 use rustc_parse::{new_parser_from_source_str, parser::Parser, source_file_to_stream};
4 use rustc_session::parse::ParseSess;
5 use rustc_span::create_default_session_if_not_set_then;
6 use rustc_span::source_map::{FilePathMapping, SourceMap};
7 use rustc_span::{BytePos, Span};
9 use rustc_data_structures::sync::Lrc;
10 use rustc_errors::emitter::EmitterWriter;
11 use rustc_errors::{Handler, MultiSpan, PResult};
14 use std::io::prelude::*;
15 use std::iter::Peekable;
16 use std::path::{Path, PathBuf};
18 use std::sync::{Arc, Mutex};
20 /// Map string to parser (via tts).
21 fn string_to_parser(ps: &ParseSess, source_str: String) -> Parser<'_> {
22 new_parser_from_source_str(ps, PathBuf::from("bogofile").into(), source_str)
25 pub(crate) fn with_error_checking_parse<'a, T, F>(s: String, ps: &'a ParseSess, f: F) -> T
27 F: FnOnce(&mut Parser<'a>) -> PResult<'a, T>,
29 let mut p = string_to_parser(&ps, s);
30 let x = f(&mut p).unwrap();
31 p.sess.span_diagnostic.abort_if_errors();
35 /// Maps a string to tts, using a made-up filename.
36 pub(crate) fn string_to_stream(source_str: String) -> TokenStream {
37 let ps = ParseSess::new(FilePathMapping::empty());
38 source_file_to_stream(
40 ps.source_map().new_source_file(PathBuf::from("bogofile").into(), source_str),
46 /// Parses a string, returns a crate.
47 pub(crate) fn string_to_crate(source_str: String) -> ast::Crate {
48 let ps = ParseSess::new(FilePathMapping::empty());
49 with_error_checking_parse(source_str, &ps, |p| p.parse_crate_mod())
52 /// Does the given string match the pattern? whitespace in the first string
53 /// may be deleted or replaced with other whitespace to match the pattern.
54 /// This function is relatively Unicode-ignorant; fortunately, the careful design
55 /// of UTF-8 mitigates this ignorance. It doesn't do NKF-normalization(?).
56 pub(crate) fn matches_codepattern(a: &str, b: &str) -> bool {
57 let mut a_iter = a.chars().peekable();
58 let mut b_iter = b.chars().peekable();
61 let (a, b) = match (a_iter.peek(), b_iter.peek()) {
62 (None, None) => return true,
63 (None, _) => return false,
65 if rustc_lexer::is_whitespace(a) {
66 break; // Trailing whitespace check is out of loop for borrowck.
71 (Some(&a), Some(&b)) => (a, b),
74 if rustc_lexer::is_whitespace(a) && rustc_lexer::is_whitespace(b) {
75 // Skip whitespace for `a` and `b`.
76 scan_for_non_ws_or_end(&mut a_iter);
77 scan_for_non_ws_or_end(&mut b_iter);
78 } else if rustc_lexer::is_whitespace(a) {
79 // Skip whitespace for `a`.
80 scan_for_non_ws_or_end(&mut a_iter);
89 // Check if a has *only* trailing whitespace.
90 a_iter.all(rustc_lexer::is_whitespace)
93 /// Advances the given peekable `Iterator` until it reaches a non-whitespace character.
94 fn scan_for_non_ws_or_end<I: Iterator<Item = char>>(iter: &mut Peekable<I>) {
95 while iter.peek().copied().map(rustc_lexer::is_whitespace) == Some(true) {
100 /// Identifies a position in the text by the n'th occurrence of a string.
102 string: &'static str,
112 pub(crate) struct Shared<T: Write> {
113 pub data: Arc<Mutex<T>>,
116 impl<T: Write> Write for Shared<T> {
117 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
118 self.data.lock().unwrap().write(buf)
121 fn flush(&mut self) -> io::Result<()> {
122 self.data.lock().unwrap().flush()
126 fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) {
127 create_default_session_if_not_set_then(|_| {
128 let output = Arc::new(Mutex::new(Vec::new()));
130 let fallback_bundle =
131 rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
132 let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
133 source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned());
135 let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end);
136 let mut msp = MultiSpan::from_span(primary_span);
137 for span_label in span_labels {
138 let span = make_span(&file_text, &span_label.start, &span_label.end);
139 msp.push_span_label(span, span_label.label);
140 println!("span: {:?} label: {:?}", span, span_label.label);
141 println!("text: {:?}", source_map.span_to_snippet(span));
144 let emitter = EmitterWriter::new(
145 Box::new(Shared { data: output.clone() }),
146 Some(source_map.clone()),
156 let handler = Handler::with_emitter(true, None, Box::new(emitter));
157 #[allow(rustc::untranslatable_diagnostic)]
158 handler.span_err(msp, "foo");
161 expected_output.chars().next() == Some('\n'),
162 "expected output should begin with newline"
164 let expected_output = &expected_output[1..];
166 let bytes = output.lock().unwrap();
167 let actual_output = str::from_utf8(&bytes).unwrap();
168 println!("expected output:\n------\n{}------", expected_output);
169 println!("actual output:\n------\n{}------", actual_output);
171 assert!(expected_output == actual_output)
175 fn make_span(file_text: &str, start: &Position, end: &Position) -> Span {
176 let start = make_pos(file_text, start);
177 let end = make_pos(file_text, end) + end.string.len(); // just after matching thing ends
178 assert!(start <= end);
179 Span::with_root_ctxt(BytePos(start as u32), BytePos(end as u32))
182 fn make_pos(file_text: &str, pos: &Position) -> usize {
183 let mut remainder = file_text;
185 for _ in 0..pos.count {
186 if let Some(n) = remainder.find(&pos.string) {
188 remainder = &remainder[n + 1..];
190 panic!("failed to find {} instances of {:?} in {:?}", pos.count, pos.string, file_text);
204 start: Position { string: "{", count: 1 },
205 end: Position { string: "}", count: 1 },
231 start: Position { string: "{", count: 1 },
232 end: Position { string: "}", count: 1 },
261 start: Position { string: "X0", count: 1 },
262 end: Position { string: "X2", count: 1 },
263 label: "`X` is a good letter",
266 start: Position { string: "Y0", count: 1 },
267 end: Position { string: "Y2", count: 1 },
268 label: "`Y` is a good letter too",
281 | ||____^__- `Y` is a good letter too
283 | `X` is a good letter
300 start: Position { string: "X0", count: 1 },
301 end: Position { string: "X1", count: 1 },
302 label: "`X` is a good letter",
305 start: Position { string: "Y0", count: 1 },
306 end: Position { string: "Y1", count: 1 },
307 label: "`Y` is a good letter too",
319 | ||____-__^ `X` is a good letter
321 | `Y` is a good letter too
328 fn different_overlap() {
340 start: Position { string: "Y0", count: 1 },
341 end: Position { string: "X2", count: 1 },
342 label: "`X` is a good letter",
345 start: Position { string: "Z1", count: 1 },
346 end: Position { string: "X3", count: 1 },
347 label: "`Y` is a good letter too",
359 | ||____^ `X` is a good letter
361 | |____- `Y` is a good letter too
368 fn triple_overlap() {
379 start: Position { string: "X0", count: 1 },
380 end: Position { string: "X2", count: 1 },
381 label: "`X` is a good letter",
384 start: Position { string: "Y0", count: 1 },
385 end: Position { string: "Y2", count: 1 },
386 label: "`Y` is a good letter too",
389 start: Position { string: "Z0", count: 1 },
390 end: Position { string: "Z2", count: 1 },
405 | |||____^__-__- `Z` label
407 | |______| `Y` is a good letter too
408 | `X` is a good letter
415 fn triple_exact_overlap() {
426 start: Position { string: "X0", count: 1 },
427 end: Position { string: "X2", count: 1 },
428 label: "`X` is a good letter",
431 start: Position { string: "X0", count: 1 },
432 end: Position { string: "X2", count: 1 },
433 label: "`Y` is a good letter too",
436 start: Position { string: "X0", count: 1 },
437 end: Position { string: "X2", count: 1 },
450 | | `X` is a good letter
451 | |____`Y` is a good letter too
471 start: Position { string: "Y0", count: 1 },
472 end: Position { string: "X1", count: 1 },
473 label: "`X` is a good letter",
476 start: Position { string: "Y1", count: 1 },
477 end: Position { string: "Z2", count: 1 },
478 label: "`Y` is a good letter too",
481 start: Position { string: "X2", count: 1 },
482 end: Position { string: "Y3", count: 1 },
495 | | `X` is a good letter
497 | |___-______- `Y` is a good letter too
508 fn non_overlaping() {
520 start: Position { string: "X0", count: 1 },
521 end: Position { string: "X1", count: 1 },
522 label: "`X` is a good letter",
525 start: Position { string: "Y2", count: 1 },
526 end: Position { string: "Z3", count: 1 },
527 label: "`Y` is a good letter too",
536 | |____^ `X` is a good letter
540 | |__________- `Y` is a good letter too
547 fn overlaping_start_and_end() {
559 start: Position { string: "Y0", count: 1 },
560 end: Position { string: "X1", count: 1 },
561 label: "`X` is a good letter",
564 start: Position { string: "Z1", count: 1 },
565 end: Position { string: "Z3", count: 1 },
566 label: "`Y` is a good letter too",
578 | | `X` is a good letter
581 | |__________- `Y` is a good letter too
588 fn multiple_labels_primary_without_message() {
597 start: Position { string: "b", count: 1 },
598 end: Position { string: "}", count: 1 },
602 start: Position { string: "a", count: 1 },
603 end: Position { string: "d", count: 1 },
604 label: "`a` is a good letter",
607 start: Position { string: "c", count: 1 },
608 end: Position { string: "c", count: 1 },
617 | ----^^^^-^^-- `a` is a good letter
624 fn multiple_labels_secondary_without_message() {
633 start: Position { string: "a", count: 1 },
634 end: Position { string: "d", count: 1 },
635 label: "`a` is a good letter",
638 start: Position { string: "b", count: 1 },
639 end: Position { string: "}", count: 1 },
648 | ^^^^-------^^ `a` is a good letter
655 fn multiple_labels_primary_without_message_2() {
664 start: Position { string: "b", count: 1 },
665 end: Position { string: "}", count: 1 },
666 label: "`b` is a good letter",
669 start: Position { string: "a", count: 1 },
670 end: Position { string: "d", count: 1 },
674 start: Position { string: "c", count: 1 },
675 end: Position { string: "c", count: 1 },
686 | `b` is a good letter
693 fn multiple_labels_secondary_without_message_2() {
702 start: Position { string: "a", count: 1 },
703 end: Position { string: "d", count: 1 },
707 start: Position { string: "b", count: 1 },
708 end: Position { string: "}", count: 1 },
709 label: "`b` is a good letter",
719 | `b` is a good letter
726 fn multiple_labels_secondary_without_message_3() {
735 start: Position { string: "a", count: 1 },
736 end: Position { string: "b", count: 1 },
737 label: "`a` is a good letter",
740 start: Position { string: "c", count: 1 },
741 end: Position { string: "d", count: 1 },
752 | `a` is a good letter
759 fn multiple_labels_without_message() {
768 start: Position { string: "a", count: 1 },
769 end: Position { string: "d", count: 1 },
773 start: Position { string: "b", count: 1 },
774 end: Position { string: "}", count: 1 },
790 fn multiple_labels_without_message_2() {
799 start: Position { string: "b", count: 1 },
800 end: Position { string: "}", count: 1 },
804 start: Position { string: "a", count: 1 },
805 end: Position { string: "d", count: 1 },
809 start: Position { string: "c", count: 1 },
810 end: Position { string: "c", count: 1 },
826 fn multiple_labels_with_message() {
835 start: Position { string: "a", count: 1 },
836 end: Position { string: "d", count: 1 },
837 label: "`a` is a good letter",
840 start: Position { string: "b", count: 1 },
841 end: Position { string: "}", count: 1 },
842 label: "`b` is a good letter",
852 | | `b` is a good letter
853 | `a` is a good letter
860 fn single_label_with_message() {
868 start: Position { string: "a", count: 1 },
869 end: Position { string: "d", count: 1 },
870 label: "`a` is a good letter",
877 | ^^^^^^^^^^^^^ `a` is a good letter
884 fn single_label_without_message() {
892 start: Position { string: "a", count: 1 },
893 end: Position { string: "d", count: 1 },
930 start: Position { string: "Y0", count: 1 },
931 end: Position { string: "X1", count: 1 },
932 label: "`X` is a good letter",
935 start: Position { string: "Z1", count: 1 },
936 end: Position { string: "Z3", count: 1 },
937 label: "`Y` is a good letter too",
949 | | `X` is a good letter
956 | |__________- `Y` is a good letter too
963 fn long_snippet_multiple_spans() {
985 start: Position { string: "Y0", count: 1 },
986 end: Position { string: "Y3", count: 1 },
987 label: "`Y` is a good letter",
990 start: Position { string: "Z1", count: 1 },
991 end: Position { string: "Z2", count: 1 },
992 label: "`Z` is a good letter too",
1010 | ||__________- `Z` is a good letter too
1014 | |________^ `Y` is a good letter