diff --git a/cli/src/query_testing.rs b/cli/src/query_testing.rs index 9950f12f..1f88d619 100644 --- a/cli/src/query_testing.rs +++ b/cli/src/query_testing.rs @@ -18,9 +18,20 @@ pub struct CaptureInfo { #[derive(Debug, PartialEq, Eq)] pub struct Assertion { pub position: Point, + pub negative: bool, pub expected_capture_name: String, } +impl Assertion { + pub fn new(row: usize, col: usize, negative: bool, expected_capture_name: String) -> Self { + Self { + position: Point::new(row, col), + negative, + expected_capture_name, + } + } +} + /// Parse the given source code, finding all of the comments that contain /// highlighting assertions. Return a vector of (position, expected highlight name) /// pairs. @@ -54,6 +65,7 @@ pub fn parse_position_comments( // to its own column. let mut has_left_caret = false; let mut has_arrow = false; + let mut negative = false; let mut arrow_end = 0; for (i, c) in text.char_indices() { arrow_end = i + 1; @@ -69,6 +81,19 @@ pub fn parse_position_comments( has_left_caret = c == '<'; } + // find any ! after arrows but before capture name + if has_arrow { + for (i, c) in text[arrow_end..].char_indices() { + if c == '!' { + negative = true; + arrow_end += i + 1; + break; + } else if !c.is_whitespace() { + break; + } + } + } + // If the comment node contains an arrow and a highlight name, record the // highlight name and the position. if let (true, Some(mat)) = @@ -76,7 +101,8 @@ pub fn parse_position_comments( { assertion_ranges.push((node.start_position(), node.end_position())); result.push(Assertion { - position: position, + position, + negative, expected_capture_name: mat.as_str().to_string(), }); } diff --git a/cli/src/test_highlight.rs b/cli/src/test_highlight.rs index 2d9d536a..44a24244 100644 --- a/cli/src/test_highlight.rs +++ b/cli/src/test_highlight.rs @@ -94,6 +94,7 @@ pub fn iterate_assertions( let mut actual_highlights = Vec::<&String>::new(); for Assertion { position, + negative, expected_capture_name: expected_highlight, } in assertions { @@ -117,12 +118,13 @@ pub fn iterate_assertions( break 'highlight_loop; } - // If the highlight matches the assertion, this test passes. Otherwise, + // If the highlight matches the assertion, or if the highlight doesn't + // match the assertion but it's negative, this test passes. Otherwise, // add this highlight to the list of actual highlights that span the // assertion's position, in order to generate an error message in the event // of a failure. let highlight_name = &highlight_names[(highlight.2).0]; - if *highlight_name == *expected_highlight { + if (*highlight_name == *expected_highlight) == !negative { passed = true; break 'highlight_loop; } else { @@ -162,68 +164,7 @@ pub fn test_highlight( let assertions = parse_position_comments(highlighter.parser(), highlight_config.language, source)?; - iterate_assertions(&assertions, &highlights, &highlight_names)?; - - // Iterate through all of the highlighting assertions, checking each one against the - // actual highlights. - let mut i = 0; - let mut actual_highlights = Vec::<&String>::new(); - for Assertion { - position, - expected_capture_name: expected_highlight, - } in &assertions - { - let mut passed = false; - actual_highlights.clear(); - - 'highlight_loop: loop { - // The assertions are ordered by position, so skip past all of the highlights that - // end at or before this assertion's position. - if let Some(highlight) = highlights.get(i) { - if highlight.1 <= *position { - i += 1; - continue; - } - - // Iterate through all of the highlights that start at or before this assertion's, - // position, looking for one that matches the assertion. - let mut j = i; - while let (false, Some(highlight)) = (passed, highlights.get(j)) { - if highlight.0 > *position { - break 'highlight_loop; - } - - // If the highlight matches the assertion, this test passes. Otherwise, - // add this highlight to the list of actual highlights that span the - // assertion's position, in order to generate an error message in the event - // of a failure. - let highlight_name = &highlight_names[(highlight.2).0]; - if *highlight_name == *expected_highlight { - passed = true; - break 'highlight_loop; - } else { - actual_highlights.push(highlight_name); - } - - j += 1; - } - } else { - break; - } - } - - if !passed { - return Err(Failure { - row: position.row, - column: position.column, - expected_highlight: expected_highlight.clone(), - actual_highlights: actual_highlights.into_iter().cloned().collect(), - } - .into()); - } - } - - Ok(assertions.len()) + iterate_assertions(&assertions, &highlights, &highlight_names) } pub fn get_highlight_positions( diff --git a/cli/src/test_tags.rs b/cli/src/test_tags.rs index 8c1fb34a..d27e7244 100644 --- a/cli/src/test_tags.rs +++ b/cli/src/test_tags.rs @@ -96,6 +96,7 @@ pub fn test_tag( let mut actual_tags = Vec::<&String>::new(); for Assertion { position, + negative, expected_capture_name: expected_tag, } in &assertions { @@ -117,7 +118,7 @@ pub fn test_tag( } let tag_name = &tag.2; - if *tag_name == *expected_tag { + if (*tag_name == *expected_tag) == !negative { passed = true; break 'tag_loop; } else { diff --git a/cli/src/tests/test_highlight_test.rs b/cli/src/tests/test_highlight_test.rs index af2c15c5..d9b2c43a 100644 --- a/cli/src/tests/test_highlight_test.rs +++ b/cli/src/tests/test_highlight_test.rs @@ -23,6 +23,7 @@ fn test_highlight_test_with_basic_test() { " // ^ keyword", " return d + e;", " // ^ variable.parameter", + " // ^ !variable", "};", ] .join("\n"); @@ -32,18 +33,10 @@ fn test_highlight_test_with_basic_test() { assert_eq!( assertions, &[ - Assertion { - position: Point::new(1, 5), - expected_capture_name: "function".to_string() - }, - Assertion { - position: Point::new(1, 11), - expected_capture_name: "keyword".to_string() - }, - Assertion { - position: Point::new(4, 9), - expected_capture_name: "variable.parameter".to_string() - }, + Assertion::new(1, 5, false, String::from("function")), + Assertion::new(1, 11, false, String::from("keyword")), + Assertion::new(4, 9, false, String::from("variable.parameter")), + Assertion::new(4, 11, true, String::from("variable")), ] ); diff --git a/cli/src/tests/test_tags_test.rs b/cli/src/tests/test_tags_test.rs index 61f98abd..e682434e 100644 --- a/cli/src/tests/test_tags_test.rs +++ b/cli/src/tests/test_tags_test.rs @@ -16,6 +16,7 @@ fn test_tags_test_with_basic_test() { " # ^ reference.call", " return d(e)", " # ^ reference.call", + " # ^ !variable.parameter", "", ] .join("\n"); @@ -26,18 +27,10 @@ fn test_tags_test_with_basic_test() { assert_eq!( assertions, &[ - Assertion { - position: Point::new(1, 4), - expected_capture_name: "definition.function".to_string(), - }, - Assertion { - position: Point::new(3, 9), - expected_capture_name: "reference.call".to_string(), - }, - Assertion { - position: Point::new(5, 11), - expected_capture_name: "reference.call".to_string(), - }, + Assertion::new(1, 4, false, String::from("definition.function")), + Assertion::new(3, 9, false, String::from("reference.call")), + Assertion::new(5, 11, false, String::from("reference.call")), + Assertion::new(5, 13, true, String::from("variable.parameter")), ] );