From d21c84abd40a04eb740f51c47c1a2d62384b3e36 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 30 Jun 2020 12:13:08 +0200 Subject: Generalize annotations --- crates/test_utils/src/lib.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index caf847273..f9d6c6c96 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -11,6 +11,7 @@ pub mod mark; mod fixture; use std::{ + convert::TryInto, env, fs, path::{Path, PathBuf}, }; @@ -168,8 +169,10 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> { for line in lines_with_ends(text) { if let Some(idx) = line.find("//^") { let offset = prev_line_start.unwrap() + TextSize::of(&line[..idx + "//".len()]); - let data = line[idx + "//^".len()..].trim().to_string(); - res.push((TextRange::at(offset, 1.into()), data)) + let marker_and_data = &line[idx + "//".len()..]; + let len = marker_and_data.chars().take_while(|&it| it == '^').count(); + let data = marker_and_data[len..].trim().to_string(); + res.push((TextRange::at(offset, len.try_into().unwrap()), data)) } prev_line_start = Some(line_start); line_start += TextSize::of(line); @@ -184,15 +187,15 @@ fn test_extract_annotations() { fn main() { let x = 92; //^ def - z + 1 -} //^ i32 + zoo + 1 +} //^^^ i32 "#, ); let res = extract_annotations(&text) .into_iter() .map(|(range, ann)| (&text[range], ann)) .collect::>(); - assert_eq!(res, vec![("x", "def".into()), ("z", "i32".into()),]); + assert_eq!(res, vec![("x", "def".into()), ("zoo", "i32".into()),]); } // Comparison functionality borrowed from cargo: -- cgit v1.2.3 From 442c13ba176a40491deb7f9d2a2e1e24eca29f63 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 30 Jun 2020 18:04:25 +0200 Subject: Simplify most of the inlay hints tests --- crates/test_utils/src/lib.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index f9d6c6c96..e32a0a0c3 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -11,7 +11,7 @@ pub mod mark; mod fixture; use std::{ - convert::TryInto, + convert::{TryFrom, TryInto}, env, fs, path::{Path, PathBuf}, }; @@ -169,10 +169,9 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> { for line in lines_with_ends(text) { if let Some(idx) = line.find("//^") { let offset = prev_line_start.unwrap() + TextSize::of(&line[..idx + "//".len()]); - let marker_and_data = &line[idx + "//".len()..]; - let len = marker_and_data.chars().take_while(|&it| it == '^').count(); - let data = marker_and_data[len..].trim().to_string(); - res.push((TextRange::at(offset, len.try_into().unwrap()), data)) + for (line_range, text) in extract_line_annotations(&line[idx + "//".len()..]) { + res.push((line_range + offset, text)) + } } prev_line_start = Some(line_start); line_start += TextSize::of(line); @@ -180,13 +179,28 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> { res } +fn extract_line_annotations(mut line: &str) -> Vec<(TextRange, String)> { + let mut res = Vec::new(); + let mut offset: TextSize = 0.into(); + while !line.is_empty() { + let len = line.chars().take_while(|&it| it == '^').count(); + assert!(len > 0); + let range = TextRange::at(offset, len.try_into().unwrap()); + let next = line[len..].find('^').map_or(line.len(), |it| it + len); + res.push((range, line[len..][..next - len].trim().to_string())); + line = &line[next..]; + offset += TextSize::try_from(next).unwrap(); + } + res +} + #[test] fn test_extract_annotations() { let text = stdx::trim_indent( r#" fn main() { - let x = 92; - //^ def + let (x, y) = (9, 2); + //^ def ^ def zoo + 1 } //^^^ i32 "#, @@ -195,7 +209,7 @@ fn main() { .into_iter() .map(|(range, ann)| (&text[range], ann)) .collect::>(); - assert_eq!(res, vec![("x", "def".into()), ("zoo", "i32".into()),]); + assert_eq!(res, vec![("x", "def".into()), ("y", "def".into()), ("zoo", "i32".into()),]); } // Comparison functionality borrowed from cargo: -- cgit v1.2.3 From 7b0113b3d588fdc1f95eca1286fb2f6881abe65a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 1 Jul 2020 12:30:17 +0200 Subject: Move parser specific tests utils to parser tests --- crates/test_utils/src/lib.rs | 106 ++----------------------------------------- 1 file changed, 5 insertions(+), 101 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index e32a0a0c3..fba5f4281 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -13,7 +13,7 @@ mod fixture; use std::{ convert::{TryFrom, TryInto}, env, fs, - path::{Path, PathBuf}, + path::PathBuf, }; use serde_json::Value; @@ -299,85 +299,6 @@ pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a } } -/// Calls callback `f` with input code and file paths for each `.rs` file in `test_data_dir` -/// subdirectories defined by `paths`. -/// -/// If the content of the matching output file differs from the output of `f()` -/// the test will fail. -/// -/// If there is no matching output file it will be created and filled with the -/// output of `f()`, but the test will fail. -pub fn dir_tests(test_data_dir: &Path, paths: &[&str], outfile_extension: &str, f: F) -where - F: Fn(&str, &Path) -> String, -{ - for (path, input_code) in collect_rust_files(test_data_dir, paths) { - let actual = f(&input_code, &path); - let path = path.with_extension(outfile_extension); - if !path.exists() { - println!("\nfile: {}", path.display()); - println!("No .txt file with expected result, creating...\n"); - println!("{}\n{}", input_code, actual); - fs::write(&path, &actual).unwrap(); - panic!("No expected result"); - } - let expected = read_text(&path); - assert_equal_text(&expected, &actual, &path); - } -} - -/// Collects all `.rs` files from `dir` subdirectories defined by `paths`. -pub fn collect_rust_files(root_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> { - paths - .iter() - .flat_map(|path| { - let path = root_dir.to_owned().join(path); - rust_files_in_dir(&path).into_iter() - }) - .map(|path| { - let text = read_text(&path); - (path, text) - }) - .collect() -} - -/// Collects paths to all `.rs` files from `dir` in a sorted `Vec`. -fn rust_files_in_dir(dir: &Path) -> Vec { - let mut acc = Vec::new(); - for file in fs::read_dir(&dir).unwrap() { - let file = file.unwrap(); - let path = file.path(); - if path.extension().unwrap_or_default() == "rs" { - acc.push(path); - } - } - acc.sort(); - acc -} - -/// Returns the path to the root directory of `rust-analyzer` project. -pub fn project_dir() -> PathBuf { - let dir = env!("CARGO_MANIFEST_DIR"); - PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned() -} - -/// Read file and normalize newlines. -/// -/// `rustc` seems to always normalize `\r\n` newlines to `\n`: -/// -/// ``` -/// let s = " -/// "; -/// assert_eq!(s.as_bytes(), &[10]); -/// ``` -/// -/// so this should always be correct. -pub fn read_text(path: &Path) -> String { - fs::read_to_string(path) - .unwrap_or_else(|_| panic!("File at {:?} should be valid", path)) - .replace("\r\n", "\n") -} - /// Returns `false` if slow tests should not run, otherwise returns `true` and /// also creates a file at `./target/.slow_tests_cookie` which serves as a flag /// that slow tests did run. @@ -392,25 +313,8 @@ pub fn skip_slow_tests() -> bool { should_skip } -/// Asserts that `expected` and `actual` strings are equal. If they differ only -/// in trailing or leading whitespace the test won't fail and -/// the contents of `actual` will be written to the file located at `path`. -fn assert_equal_text(expected: &str, actual: &str, path: &Path) { - if expected == actual { - return; - } - let dir = project_dir(); - let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path); - if expected.trim() == actual.trim() { - println!("whitespace difference, rewriting"); - println!("file: {}\n", pretty_path.display()); - fs::write(path, actual).unwrap(); - return; - } - if env::var("UPDATE_EXPECTATIONS").is_ok() { - println!("rewriting {}", pretty_path.display()); - fs::write(path, actual).unwrap(); - return; - } - assert_eq_text!(expected, actual, "file: {}", pretty_path.display()); +/// Returns the path to the root directory of `rust-analyzer` project. +pub fn project_dir() -> PathBuf { + let dir = env!("CARGO_MANIFEST_DIR"); + PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned() } -- cgit v1.2.3 From 8295dc42a0fc9e8641606f75a5ba2a46fe48379c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 1 Jul 2020 18:17:08 +0200 Subject: Fold multiline calls --- crates/test_utils/src/lib.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index fba5f4281..e4aa894ac 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -118,8 +118,8 @@ pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) { } /// Extracts ranges, marked with ` ` pairs from the `text` -pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec, String) { - let open = format!("<{}>", tag); +pub fn extract_tags(mut text: &str, tag: &str) -> (Vec<(TextRange, Option)>, String) { + let open = format!("<{}", tag); let close = format!("", tag); let mut ranges = Vec::new(); let mut res = String::new(); @@ -134,22 +134,35 @@ pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec, String) { res.push_str(&text[..i]); text = &text[i..]; if text.starts_with(&open) { - text = &text[open.len()..]; + let close_open = text.find('>').unwrap(); + let attr = text[open.len()..close_open].trim(); + let attr = if attr.is_empty() { None } else { Some(attr.to_string()) }; + text = &text[close_open + '>'.len_utf8()..]; let from = TextSize::of(&res); - stack.push(from); + stack.push((from, attr)); } else if text.starts_with(&close) { text = &text[close.len()..]; - let from = stack.pop().unwrap_or_else(|| panic!("unmatched ", tag)); + let (from, attr) = + stack.pop().unwrap_or_else(|| panic!("unmatched ", tag)); let to = TextSize::of(&res); - ranges.push(TextRange::new(from, to)); + ranges.push((TextRange::new(from, to), attr)); + } else { + res.push('<'); + text = &text['<'.len_utf8()..]; } } } } assert!(stack.is_empty(), "unmatched <{}>", tag); - ranges.sort_by_key(|r| (r.start(), r.end())); + ranges.sort_by_key(|r| (r.0.start(), r.0.end())); (ranges, res) } +#[test] +fn test_extract_tags() { + let (tags, text) = extract_tags(r#"fn main() {}"#, "tag"); + let actual = tags.into_iter().map(|(range, attr)| (&text[range], attr)).collect::>(); + assert_eq!(actual, vec![("fn main() {}", Some("fn".into())), ("main", None),]); +} /// Inserts `<|>` marker into the `text` at `offset`. pub fn add_cursor(text: &str, offset: TextSize) -> String { -- cgit v1.2.3 From ebdee366b0e16d1d019db2f5c22a730b4451194f Mon Sep 17 00:00:00 2001 From: kjeremy Date: Mon, 6 Jul 2020 17:13:55 -0400 Subject: Clippy perf warnings Removes redundant clones --- crates/test_utils/src/fixture.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs index fad8f7e2c..ed764046b 100644 --- a/crates/test_utils/src/fixture.rs +++ b/crates/test_utils/src/fixture.rs @@ -62,7 +62,7 @@ impl Fixture { let components = meta.split_ascii_whitespace().collect::>(); let path = components[0].to_string(); - assert!(path.starts_with("/")); + assert!(path.starts_with('/')); let mut krate = None; let mut deps = Vec::new(); -- cgit v1.2.3 From c6f35401219e32c7d62e106a45637e7f5304723c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 14 Jul 2020 09:04:11 +0200 Subject: Use relaxed ordering for marks We dont' need this for perf. `Relaxed` ordering is enough here, as we only have one location. I prefer to use minimal ordering, because that makes it easier to reason about the code. --- crates/test_utils/src/mark.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/mark.rs b/crates/test_utils/src/mark.rs index 7c309a894..97f5a93ad 100644 --- a/crates/test_utils/src/mark.rs +++ b/crates/test_utils/src/mark.rs @@ -62,7 +62,7 @@ pub struct MarkChecker { impl MarkChecker { pub fn new(mark: &'static AtomicUsize) -> MarkChecker { - let value_on_entry = mark.load(Ordering::SeqCst); + let value_on_entry = mark.load(Ordering::Relaxed); MarkChecker { mark, value_on_entry } } } @@ -72,7 +72,7 @@ impl Drop for MarkChecker { if std::thread::panicking() { return; } - let value_on_exit = self.mark.load(Ordering::SeqCst); + let value_on_exit = self.mark.load(Ordering::Relaxed); assert!(value_on_exit > self.value_on_entry, "mark was not hit") } } -- cgit v1.2.3 From 0b0865ab226d57c88e22b6b395d033f68f2c11af Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 14 Jul 2020 14:01:54 +0200 Subject: Generaize annotation extraction --- crates/test_utils/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index e4aa894ac..4c89ed87b 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -180,7 +180,7 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> { let mut prev_line_start: Option = None; let mut line_start: TextSize = 0.into(); for line in lines_with_ends(text) { - if let Some(idx) = line.find("//^") { + if let Some(idx) = line.find("//") { let offset = prev_line_start.unwrap() + TextSize::of(&line[..idx + "//".len()]); for (line_range, text) in extract_line_annotations(&line[idx + "//".len()..]) { res.push((line_range + offset, text)) @@ -195,7 +195,15 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> { fn extract_line_annotations(mut line: &str) -> Vec<(TextRange, String)> { let mut res = Vec::new(); let mut offset: TextSize = 0.into(); - while !line.is_empty() { + loop { + match line.find('^') { + Some(idx) => { + offset += TextSize::try_from(idx).unwrap(); + line = &line[idx..]; + } + None => break, + }; + let len = line.chars().take_while(|&it| it == '^').count(); assert!(len > 0); let range = TextRange::at(offset, len.try_into().unwrap()); -- cgit v1.2.3 From abeb003df47de4a1b7aa36a7c4d7987d8cf40ace Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 14 Jul 2020 14:57:33 +0200 Subject: Allow multiline annotations --- crates/test_utils/src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 11 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 4c89ed87b..ad586c882 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -179,24 +179,51 @@ pub fn extract_annotations(text: &str) -> Vec<(TextRange, String)> { let mut res = Vec::new(); let mut prev_line_start: Option = None; let mut line_start: TextSize = 0.into(); + let mut prev_line_annotations: Vec<(TextSize, usize)> = Vec::new(); for line in lines_with_ends(text) { + let mut this_line_annotations = Vec::new(); if let Some(idx) = line.find("//") { - let offset = prev_line_start.unwrap() + TextSize::of(&line[..idx + "//".len()]); - for (line_range, text) in extract_line_annotations(&line[idx + "//".len()..]) { - res.push((line_range + offset, text)) + let annotation_offset = TextSize::of(&line[..idx + "//".len()]); + for annotation in extract_line_annotations(&line[idx + "//".len()..]) { + match annotation { + LineAnnotation::Annotation { mut range, content } => { + range += annotation_offset; + this_line_annotations.push((range.end(), res.len())); + res.push((range + prev_line_start.unwrap(), content)) + } + LineAnnotation::Continuation { mut offset, content } => { + offset += annotation_offset; + let &(_, idx) = prev_line_annotations + .iter() + .find(|&&(off, _idx)| off == offset) + .unwrap(); + res[idx].1.push('\n'); + res[idx].1.push_str(&content); + res[idx].1.push('\n'); + } + } } } + prev_line_start = Some(line_start); line_start += TextSize::of(line); + + prev_line_annotations = this_line_annotations; } res } -fn extract_line_annotations(mut line: &str) -> Vec<(TextRange, String)> { +enum LineAnnotation { + Annotation { range: TextRange, content: String }, + Continuation { offset: TextSize, content: String }, +} + +fn extract_line_annotations(mut line: &str) -> Vec { let mut res = Vec::new(); let mut offset: TextSize = 0.into(); + let marker: fn(char) -> bool = if line.contains('^') { |c| c == '^' } else { |c| c == '|' }; loop { - match line.find('^') { + match line.find(marker) { Some(idx) => { offset += TextSize::try_from(idx).unwrap(); line = &line[idx..]; @@ -204,14 +231,28 @@ fn extract_line_annotations(mut line: &str) -> Vec<(TextRange, String)> { None => break, }; - let len = line.chars().take_while(|&it| it == '^').count(); - assert!(len > 0); + let mut len = line.chars().take_while(|&it| it == '^').count(); + let mut continuation = false; + if len == 0 { + assert!(line.starts_with('|')); + continuation = true; + len = 1; + } let range = TextRange::at(offset, len.try_into().unwrap()); - let next = line[len..].find('^').map_or(line.len(), |it| it + len); - res.push((range, line[len..][..next - len].trim().to_string())); + let next = line[len..].find(marker).map_or(line.len(), |it| it + len); + let content = line[len..][..next - len].trim().to_string(); + + let annotation = if continuation { + LineAnnotation::Continuation { offset: range.end(), content } + } else { + LineAnnotation::Annotation { range, content } + }; + res.push(annotation); + line = &line[next..]; offset += TextSize::try_from(next).unwrap(); } + res } @@ -223,14 +264,18 @@ fn main() { let (x, y) = (9, 2); //^ def ^ def zoo + 1 -} //^^^ i32 +} //^^^ type: + // | i32 "#, ); let res = extract_annotations(&text) .into_iter() .map(|(range, ann)| (&text[range], ann)) .collect::>(); - assert_eq!(res, vec![("x", "def".into()), ("y", "def".into()), ("zoo", "i32".into()),]); + assert_eq!( + res, + vec![("x", "def".into()), ("y", "def".into()), ("zoo", "type:\ni32\n".into()),] + ); } // Comparison functionality borrowed from cargo: -- cgit v1.2.3 From be49547b446cba240f8f2a9592284e77d4a6896f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 30 Jul 2020 22:19:58 +0200 Subject: Use split_once polyfill --- crates/test_utils/src/fixture.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'crates/test_utils/src') diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs index ed764046b..e40b61a94 100644 --- a/crates/test_utils/src/fixture.rs +++ b/crates/test_utils/src/fixture.rs @@ -2,7 +2,7 @@ //! rust-analyzer database from a single string. use rustc_hash::FxHashMap; -use stdx::{lines_with_ends, split_delim, trim_indent}; +use stdx::{lines_with_ends, split_once, trim_indent}; #[derive(Debug, Eq, PartialEq)] pub struct Fixture { @@ -71,14 +71,14 @@ impl Fixture { let mut cfg_key_values = Vec::new(); let mut env = FxHashMap::default(); for component in components[1..].iter() { - let (key, value) = split_delim(component, ':').unwrap(); + let (key, value) = split_once(component, ':').unwrap(); match key { "crate" => krate = Some(value.to_string()), "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), "edition" => edition = Some(value.to_string()), "cfg" => { for entry in value.split(',') { - match split_delim(entry, '=') { + match split_once(entry, '=') { Some((k, v)) => cfg_key_values.push((k.to_string(), v.to_string())), None => cfg_atoms.push(entry.to_string()), } @@ -86,7 +86,7 @@ impl Fixture { } "env" => { for key in value.split(',') { - if let Some((k, v)) = split_delim(key, '=') { + if let Some((k, v)) = split_once(key, '=') { env.insert(k.into(), v.into()); } } -- cgit v1.2.3