/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
cli/lsp/testing/definitions.rs
254 строки
7 KB
em
fix(lsp): discover all tests when names are duplicated (#34624)
01 июн 2026, 04:28
Не верифицирован
01 июн 2026, 04:28
7b230ea
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use deno_core::ModuleSpecifier; use deno_core::error::AnyError; use deno_lib::util::checksum; use indexmap::IndexMap; use indexmap::IndexSet; use lsp::Range; use tower_lsp::lsp_types as lsp; use super::lsp_custom; use super::lsp_custom::TestData; use crate::lsp::client::TestingNotification; use crate::lsp::logging::lsp_warn; use crate::lsp::urls::url_to_uri; use crate::tools::test::TestDescription; use crate::tools::test::TestStepDescription; #[derive(Debug, Clone, PartialEq)] pub struct TestDefinition { pub id: String, pub name: String, /// 0-based occurrence index for tests that share the same `name` and /// `parent_id`. The first registration with a given (parent_id, name) pair /// gets `0`, the next `1`, etc. Used to disambiguate duplicate test names /// (see https://github.com/denoland/deno/issues/20371) without changing the /// displayed `name`. pub name_index: u32, pub range: Option<Range>, pub is_dynamic: bool, pub parent_id: Option<String>, pub step_ids: IndexSet<String>, } #[derive(Debug, Clone, PartialEq)] pub struct TestModule { pub specifier: ModuleSpecifier, pub defs: IndexMap<String, TestDefinition>, } impl TestModule { pub fn new(specifier: ModuleSpecifier) -> Self { Self { specifier, defs: Default::default(), } } /// Compute the SHA-256 id for a test with the given `name`, `name_index` /// (occurrence index for duplicate names), and `parent_id` chain. The id is /// derived from the specifier + each ancestor's `name` (and `name_index` if /// non-zero) + the test's own `name` (and `name_index` if non-zero). /// /// `name_index == 0` preserves the original id format, so the common case /// of a unique test name hashes to the same value as before this method /// existed. fn compute_id( &self, name: &str, name_index: u32, parent_id: Option<&str>, ) -> String { let mut id_components: Vec<Vec<u8>> = Vec::with_capacity(8); id_components.push(name.as_bytes().to_vec()); if name_index > 0 { id_components.push(format!("#{name_index}").into_bytes()); } let mut current_parent_id = parent_id; while let Some(pid) = current_parent_id { let parent = match self.defs.get(pid) { Some(d) => d, None => { lsp_warn!( "Internal Error: parent_id \"{}\" of test \"{}\" was not registered.", pid, name ); id_components.push(b"<unknown>".to_vec()); break; } }; id_components.push(parent.name.as_bytes().to_vec()); if parent.name_index > 0 { id_components.push(format!("#{}", parent.name_index).into_bytes()); } current_parent_id = parent.parent_id.as_deref(); } id_components.push(self.specifier.as_str().as_bytes().to_vec()); id_components.reverse(); checksum::r#gen(&id_components) } /// Determine the next `name_index` to assign for a test with this `name` /// and `parent_id` — i.e., how many existing definitions already share /// those keys. fn next_name_index(&self, name: &str, parent_id: Option<&str>) -> u32 { self .defs .values() .filter(|d| d.name == name && d.parent_id.as_deref() == parent_id) .count() as u32 } /// Returns `(id, is_newly_registered)`. /// /// Used by the static collector — each call inserts a fresh entry, /// assigning the next available `name_index` for the `(parent_id, name)` /// pair so duplicate-named tests in the source each get a unique id. pub fn register( &mut self, name: String, range: Option<Range>, is_dynamic: bool, parent_id: Option<String>, ) -> (String, bool) { let name_index = self.next_name_index(&name, parent_id.as_deref()); self.register_with_index(name, name_index, range, is_dynamic, parent_id) } /// Returns `(id, is_newly_registered)`. /// /// Used by dynamic (runtime-driven) registration. The caller is expected to /// pass the same `name_index` sequence (0, 1, 2, …) that the static /// collector assigned, so dynamic events resolve to the matching static /// definition. fn register_with_index( &mut self, name: String, name_index: u32, range: Option<Range>, is_dynamic: bool, parent_id: Option<String>, ) -> (String, bool) { let id = self.compute_id(&name, name_index, parent_id.as_deref()); if self.defs.contains_key(&id) { return (id, false); } if let Some(parent_id) = &parent_id { let parent = self.defs.get_mut(parent_id).unwrap(); parent.step_ids.insert(id.clone()); } self.defs.insert( id.clone(), TestDefinition { id: id.clone(), name, name_index, range, is_dynamic, parent_id, step_ids: Default::default(), }, ); (id, true) } /// Returns `(id, was_newly_registered)`. pub fn register_dynamic( &mut self, desc: &TestDescription, name_index: u32, ) -> (String, bool) { self.register_with_index(desc.name.clone(), name_index, None, true, None) } /// Returns `(id, was_newly_registered)`. pub fn register_step_dynamic( &mut self, desc: &TestStepDescription, parent_static_id: &str, name_index: u32, ) -> (String, bool) { self.register_with_index( desc.name.clone(), name_index, None, true, Some(parent_static_id.to_string()), ) } pub fn get(&self, id: &str) -> Option<&TestDefinition> { self.defs.get(id) } pub fn get_test_data(&self, id: &str) -> TestData { fn get_test_data_inner(tm: &TestModule, id: &str) -> TestData { let def = tm.defs.get(id).unwrap(); TestData { id: def.id.clone(), label: def.name.clone(), steps: def .step_ids .iter() .map(|id| get_test_data_inner(tm, id)) .collect(), range: def.range, } } let def = self.defs.get(id).unwrap(); let mut current_data = get_test_data_inner(self, &def.id); let mut current_parent_id = &def.parent_id; while let Some(parent_id) = current_parent_id { let parent = self.defs.get(parent_id).unwrap(); current_data = TestData { id: parent.id.clone(), label: parent.name.clone(), steps: vec![current_data], range: None, }; current_parent_id = &parent.parent_id; } current_data } /// Return the test definitions as a testing module notification. pub fn as_replace_notification( &self, maybe_root_uri: Option<&ModuleSpecifier>, ) -> Result<TestingNotification, AnyError> { let label = self.label(maybe_root_uri); Ok(TestingNotification::Module( lsp_custom::TestModuleNotificationParams { text_document: lsp::TextDocumentIdentifier { uri: url_to_uri(&self.specifier)?, }, kind: lsp_custom::TestModuleNotificationKind::Replace, label, tests: self .defs .iter() .filter(|(_, def)| def.parent_id.is_none()) .map(|(id, _)| self.get_test_data(id)) .collect(), }, )) } pub fn label(&self, maybe_root_uri: Option<&ModuleSpecifier>) -> String { if let Some(root) = maybe_root_uri { self.specifier.as_str().replace(root.as_str(), "") } else { self .specifier .path_segments() .and_then(|mut s| s.next_back().map(|s| s.to_string())) .unwrap_or_else(|| "<unknown>".to_string()) } } pub fn is_empty(&self) -> bool { self.defs.is_empty() } }