diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2022-03-30 09:59:27 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-30 09:59:27 +1100 |
commit | 061090de7e95e8e7a97f3277bd1a72899ebd1570 (patch) | |
tree | 85fbf3ed3dc4cf51a15c2baaf8257a47149c43ef /cli/lsp/testing/definitions.rs | |
parent | 4a0b2c28a15d76c0c40bf07c3753dfbcce4dace1 (diff) |
feat(lsp): add experimental testing API (#13798)
Ref: denoland/vscode_deno#629
Diffstat (limited to 'cli/lsp/testing/definitions.rs')
-rw-r--r-- | cli/lsp/testing/definitions.rs | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/cli/lsp/testing/definitions.rs b/cli/lsp/testing/definitions.rs new file mode 100644 index 000000000..0fa6a8fd5 --- /dev/null +++ b/cli/lsp/testing/definitions.rs @@ -0,0 +1,180 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use super::lsp_custom; + +use crate::checksum; +use crate::lsp::client::TestingNotification; + +use deno_ast::swc::common::Span; +use deno_ast::SourceTextInfo; +use deno_core::ModuleSpecifier; +use lspower::lsp; +use std::collections::HashMap; + +fn span_to_range( + span: &Span, + source_text_info: &SourceTextInfo, +) -> Option<lsp::Range> { + let start = source_text_info.line_and_column_index(span.lo); + let end = source_text_info.line_and_column_index(span.hi); + Some(lsp::Range { + start: lsp::Position { + line: start.line_index as u32, + character: start.column_index as u32, + }, + end: lsp::Position { + line: end.line_index as u32, + character: end.column_index as u32, + }, + }) +} + +#[derive(Debug, Clone, PartialEq)] +pub struct TestDefinition { + pub id: String, + pub level: usize, + pub name: String, + pub span: Span, + pub steps: Option<Vec<TestDefinition>>, +} + +impl TestDefinition { + pub fn new( + specifier: &ModuleSpecifier, + name: String, + span: Span, + steps: Option<Vec<TestDefinition>>, + ) -> Self { + let id = checksum::gen(&[specifier.as_str().as_bytes(), name.as_bytes()]); + Self { + id, + level: 0, + name, + span, + steps, + } + } + + pub fn new_step( + name: String, + span: Span, + parent: String, + level: usize, + steps: Option<Vec<TestDefinition>>, + ) -> Self { + let id = checksum::gen(&[ + parent.as_bytes(), + &level.to_be_bytes(), + name.as_bytes(), + ]); + Self { + id, + level, + name, + span, + steps, + } + } + + fn as_test_data( + &self, + source_text_info: &SourceTextInfo, + ) -> lsp_custom::TestData { + lsp_custom::TestData { + id: self.id.clone(), + label: self.name.clone(), + steps: self.steps.as_ref().map(|steps| { + steps + .iter() + .map(|step| step.as_test_data(source_text_info)) + .collect() + }), + range: span_to_range(&self.span, source_text_info), + } + } + + fn find_step(&self, name: &str, level: usize) -> Option<&TestDefinition> { + if let Some(steps) = &self.steps { + for step in steps { + if step.name == name && step.level == level { + return Some(step); + } else if let Some(step) = step.find_step(name, level) { + return Some(step); + } + } + } + None + } +} + +#[derive(Debug, Clone)] +pub struct TestDefinitions { + /// definitions of tests and their steps which were statically discovered from + /// the source document. + pub discovered: Vec<TestDefinition>, + /// Tests and steps which the test runner notified us of, which were + /// dynamically added + pub injected: Vec<lsp_custom::TestData>, + /// The version of the document that the discovered tests relate to. + pub script_version: String, +} + +impl TestDefinitions { + /// Return the test definitions as a testing module notification. + pub fn as_notification( + &self, + specifier: &ModuleSpecifier, + maybe_root: Option<&ModuleSpecifier>, + source_text_info: &SourceTextInfo, + ) -> TestingNotification { + let label = if let Some(root) = maybe_root { + specifier.as_str().replace(root.as_str(), "") + } else { + specifier + .path_segments() + .and_then(|s| s.last().map(|s| s.to_string())) + .unwrap_or_else(|| "<unknown>".to_string()) + }; + let mut tests_map: HashMap<String, lsp_custom::TestData> = self + .injected + .iter() + .map(|td| (td.id.clone(), td.clone())) + .collect(); + tests_map.extend(self.discovered.iter().map(|td| { + let test_data = td.as_test_data(source_text_info); + (test_data.id.clone(), test_data) + })); + TestingNotification::Module(lsp_custom::TestModuleNotificationParams { + text_document: lsp::TextDocumentIdentifier { + uri: specifier.clone(), + }, + kind: lsp_custom::TestModuleNotificationKind::Replace, + label, + tests: tests_map.into_values().collect(), + }) + } + + /// Return a test definition identified by the test ID. + pub fn get_by_id<S: AsRef<str>>(&self, id: S) -> Option<&TestDefinition> { + self + .discovered + .iter() + .find(|td| td.id.as_str() == id.as_ref()) + } + + /// Return a test definition by the test name. + pub fn get_by_name(&self, name: &str) -> Option<&TestDefinition> { + self.discovered.iter().find(|td| td.name.as_str() == name) + } + + pub fn get_step_by_name( + &self, + test_name: &str, + level: usize, + name: &str, + ) -> Option<&TestDefinition> { + self + .get_by_name(test_name) + .and_then(|td| td.find_step(name, level)) + } +} |