add module for testing tags

This commit is contained in:
Michael Davis 2021-12-18 09:46:40 -06:00
parent 26899f7246
commit 7df82c825f
No known key found for this signature in database
GPG key ID: 25D3AFE4BA2A0C49

153
cli/src/test_tags.rs Normal file
View file

@ -0,0 +1,153 @@
use crate::query_testing::{parse_position_comments, Assertion};
use ansi_term::Colour;
use anyhow::{anyhow, Result};
use std::fs;
use std::path::Path;
use tree_sitter::Point;
use tree_sitter_loader::Loader;
use tree_sitter_tags::{TagsConfiguration, TagsContext};
#[derive(Debug)]
pub struct Failure {
row: usize,
column: usize,
expected_tag: String,
actual_tags: Vec<String>,
}
impl std::error::Error for Failure {}
impl std::fmt::Display for Failure {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Failure - row: {}, column: {}, expected tag: '{}', actual tag: ",
self.row, self.column, self.expected_tag
)?;
if self.actual_tags.is_empty() {
write!(f, "none.")?;
} else {
for (i, actual_tag) in self.actual_tags.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "'{}'", actual_tag)?;
}
}
Ok(())
}
}
pub fn test_tags(loader: &Loader, directory: &Path) -> Result<()> {
let mut failed = false;
let mut tags_context = TagsContext::new();
println!("tags:");
for tag_test_file in fs::read_dir(directory)? {
let tag_test_file = tag_test_file?;
let test_file_path = tag_test_file.path();
let test_file_name = tag_test_file.file_name();
let (language, language_config) = loader
.language_configuration_for_file_name(&test_file_path)?
.ok_or_else(|| anyhow!("No language found for path {:?}", test_file_path))?;
let tags_config = language_config
.tags_config(language)?
.ok_or_else(|| anyhow!("No tags config found for {:?}", test_file_path))?;
match test_tag(
&mut tags_context,
tags_config,
fs::read(&test_file_path)?.as_slice(),
) {
Ok(assertion_count) => {
println!(
" ✓ {} ({} assertions)",
Colour::Green.paint(test_file_name.to_string_lossy().as_ref()),
assertion_count
);
}
Err(e) => {
println!(
" ✗ {}",
Colour::Red.paint(test_file_name.to_string_lossy().as_ref())
);
println!(" {}", e);
failed = true;
}
}
}
if failed {
Err(anyhow!(""))
} else {
Ok(())
}
}
pub fn test_tag(
tags_context: &mut TagsContext,
tags_config: &TagsConfiguration,
source: &[u8],
) -> Result<usize> {
let (tags_iter, _has_error) = tags_context.generate_tags(&tags_config, &source, None)?;
let tags: Vec<Tag> = tags_iter.filter_map(|t| t.ok()).collect();
let assertions = parse_position_comments(tags_context.parser(), tags_config.language, source)?;
// Iterate through all of the assertions, checking against the actual tags.
let mut i = 0;
let mut actual_tags = Vec::<String>::new();
for Assertion {
position,
expected_capture_name: expected_tag,
} in &assertions
{
let mut passed = false;
'tag_loop: loop {
if let Some(tag) = tags.get(i) {
if tag.span.end <= *position {
i += 1;
continue;
}
// Iterate through all of the tags that start at or before this assertion's
// position, looking for one that matches the assertion
let mut j = i;
while let (false, Some(tag)) = (passed, tags.get(j)) {
if tag.span.start > *position {
break 'tag_loop;
}
let tag_postfix = tags_config.syntax_type_name(tag.syntax_type_id).to_string();
let tag_name = if tag.is_definition {
format!("definition.{}", tag_postfix)
} else {
format!("reference.{}", tag_postfix)
};
if tag_name == *expected_tag {
passed = true;
break 'tag_loop;
} else {
actual_tags.push(tag_name);
}
j += 1;
}
} else {
break;
}
}
if !passed {
return Err(Failure {
row: position.row,
column: position.column,
expected_tag: expected_tag.clone(),
actual_tags: actual_tags.into_iter().collect(),
}
.into());
}
}
Ok(assertions.len())
}