use std::sync::Arc;
use gpui::{ElementId, EntityId, SharedString};
pub fn for_entity(name: impl Into<SharedString>, entity_id: EntityId) -> ElementId {
ElementId::NamedInteger(name.into(), entity_id.as_u64())
}
pub fn scoped(parent: &ElementId, part: impl Into<SharedString>) -> ElementId {
ElementId::NamedChild(Arc::new(parent.clone()), part.into())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::{Path, PathBuf};
#[test]
fn for_entity_separates_two_instances() {
let one = for_entity("textarea", EntityId::from(1u64));
let two = for_entity("textarea", EntityId::from(2u64));
assert_ne!(one, two);
assert_eq!(one, for_entity("textarea", EntityId::from(1u64)));
}
#[test]
fn scoped_parts_follow_their_parent() {
let left = ElementId::Name("left".into());
let right = ElementId::Name("right".into());
assert_ne!(scoped(&left, "dismiss"), scoped(&right, "dismiss"));
assert_ne!(scoped(&left, "dismiss"), scoped(&left, "close"));
assert_eq!(scoped(&left, "dismiss"), scoped(&left, "dismiss"));
assert_ne!(scoped(&left, "dismiss"), left);
}
#[test]
fn no_element_mints_a_constant_id() {
let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src");
let mut offenders = Vec::new();
let files = rust_files(&src);
assert!(
files.len() > 20,
"the scan found only {} source file(s) under {}, so it is not \
guarding anything — check how the source tree is being located \
before trusting a green result here",
files.len(),
src.display()
);
for file in files {
let source = fs::read_to_string(&file).expect("source file is readable");
let relative = file
.strip_prefix(&src)
.unwrap_or(&file)
.display()
.to_string();
for (line, text) in constant_ids(&source) {
offenders.push(format!(" src/{relative}:{line}: {text}"));
}
}
assert!(
offenders.is_empty(),
"these elements mint an id that is the same for every instance of them:\n{}\n\n\
Derive it instead — `element_id::for_entity(name, entity_id)` for an element \
backed by an entity, `element_id::scoped(&parent_id, part)` for a named part of \
one. See the `element_id` module docs for why.",
offenders.join("\n")
);
}
fn rust_files(dir: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();
let mut stack = vec![dir.to_path_buf()];
while let Some(dir) = stack.pop() {
for entry in fs::read_dir(&dir).expect("source directory is readable") {
let path = entry.expect("directory entry is readable").path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|ext| ext == "rs") {
files.push(path);
}
}
}
files.sort();
files
}
fn constant_ids(source: &str) -> Vec<(usize, String)> {
let mut hits = Vec::new();
let mut in_test_module = false;
for (index, line) in source.lines().enumerate() {
if in_test_module {
if line == "}" {
in_test_module = false;
}
continue;
}
if line.starts_with("#[cfg(test)]") {
in_test_module = true;
continue;
}
let trimmed = line.trim();
if trimmed.starts_with("//") {
continue;
}
if trimmed.contains(".id(\"") || trimmed.contains(".id(ElementId::Name(\"") {
hits.push((index + 1, trimmed.to_string()));
}
}
hits
}
#[test]
fn the_scan_reads_constants_and_not_derived_ids() {
let source = r#"
fn render(&self) -> impl IntoElement {
div().id("textarea").child(
div().id(ElementId::Name("nested".into())),
)
}
fn fine(&self) -> impl IntoElement {
// .id("in-a-comment")
div()
.id(self.id.clone())
.id(element_id::scoped(&self.id, "track"))
.id(ElementId::NamedInteger("tab".into(), index))
}
#[cfg(test)]
mod tests {
#[test]
fn a_caller_may_name_its_own_element() {
div().id("left");
}
}
"#;
assert_eq!(
constant_ids(source)
.into_iter()
.map(|(line, _)| line)
.collect::<Vec<_>>(),
vec![3, 4]
);
}
}