use std::fs;
use std::path::{Path, PathBuf};
use pedant_core::resolution::rust::{
CargoTargetKind, ResolutionLimits, RustProject, RustResolutionSnapshot, RustResolver,
RustTargetResolution, TargetId,
};
use pedant_graph::{CodeGraph, build_rust_graph};
use super::corpus::{BUILD_SCRIPT_CORPUS, FixtureFile, GRAPH_CORPUS};
pub type DeclaredTarget = (&'static str, CargoTargetKind, &'static str);
const CORPUS_LIBRARY: DeclaredTarget = ("app", CargoTargetKind::Library, "app");
const BUILD_SCRIPT_ROOT: DeclaredTarget =
("app", CargoTargetKind::BuildScript, "build-script-build");
pub struct Fixture {
directory: tempfile::TempDir,
project: RustProject,
}
pub struct Resolved {
pub snapshot: RustResolutionSnapshot,
pub resolution: RustTargetResolution,
}
impl Fixture {
pub fn build(files: &[FixtureFile]) -> Self {
let directory = tempfile::tempdir().expect("a temporary directory should be available");
for (relative, contents) in files {
write_file(directory.path(), relative, contents.as_bytes());
}
let root = directory.path().join("repo");
let project = RustProject::load(&root, ResolutionLimits::default())
.unwrap_or_else(|error| panic!("the fixture project should load: {error}"));
Self { directory, project }
}
pub fn root(&self) -> PathBuf {
self.directory.path().join("repo")
}
pub fn target(&self, package: &str, kind: CargoTargetKind, name: &str) -> TargetId {
self.project
.targets()
.iter()
.find(|target| {
target.kind() == kind
&& target.name() == name
&& self
.project
.package(target.package())
.is_some_and(|owner| owner.name() == package)
})
.map(|target| target.id())
.unwrap_or_else(|| panic!("{package} declares no {kind:?} target named {name}"))
}
pub fn sole_library(&self) -> TargetId {
let libraries: Vec<TargetId> = self
.project
.targets()
.iter()
.filter(|target| target.kind() == CargoTargetKind::Library)
.map(|target| target.id())
.collect();
match libraries.as_slice() {
[only] => *only,
found => panic!("the fixture declares {} library targets", found.len()),
}
}
pub fn resolve(&self, target: TargetId) -> Resolved {
let snapshot = self
.project
.snapshot_resolution(target)
.unwrap_or_else(|error| panic!("the fixture target should snapshot: {error}"));
let resolution = RustResolver::resolve_syntactic(&snapshot)
.unwrap_or_else(|error| panic!("the fixture snapshot should resolve: {error}"));
Resolved {
snapshot,
resolution,
}
}
pub fn resolve_library(&self) -> Resolved {
self.resolve(self.sole_library())
}
pub fn rewrite(&mut self, relative: &str, contents: &str) {
write_file(self.directory.path(), relative, contents.as_bytes());
self.project = RustProject::load(&self.root(), ResolutionLimits::default())
.unwrap_or_else(|error| panic!("the edited fixture project should load: {error}"));
}
pub fn close(self) -> PathBuf {
let root = self.root();
drop(self.project);
self.directory
.close()
.expect("the fixture directory should close");
root
}
}
pub fn resolve_library(files: &[FixtureFile]) -> (Fixture, Resolved) {
let fixture = Fixture::build(files);
let resolved = fixture.resolve_library();
(fixture, resolved)
}
fn resolve_target(files: &[FixtureFile], declared: DeclaredTarget) -> (Fixture, Resolved) {
let (package, kind, name) = declared;
let fixture = Fixture::build(files);
let resolved = fixture.resolve(fixture.target(package, kind, name));
(fixture, resolved)
}
pub fn project_target(
files: &[FixtureFile],
declared: DeclaredTarget,
) -> (Fixture, Resolved, CodeGraph) {
let (fixture, resolved) = resolve_target(files, declared);
let graph = build_rust_graph(&resolved.snapshot, &resolved.resolution)
.unwrap_or_else(|error| panic!("{declared:?} should project: {error}"));
(fixture, resolved, graph)
}
pub fn resolve_corpus_library() -> (Fixture, Resolved) {
resolve_target(GRAPH_CORPUS, CORPUS_LIBRARY)
}
pub fn project_corpus_library() -> (Fixture, Resolved, CodeGraph) {
project_target(GRAPH_CORPUS, CORPUS_LIBRARY)
}
pub fn project_build_script() -> (Fixture, Resolved, CodeGraph) {
project_target(BUILD_SCRIPT_CORPUS, BUILD_SCRIPT_ROOT)
}
fn write_file(base: &Path, relative: &str, contents: &[u8]) {
let path = base.join(relative);
let parent = path.parent().expect("a fixture path has a parent");
fs::create_dir_all(parent).unwrap_or_else(|error| panic!("{}: {error}", parent.display()));
fs::write(&path, contents).unwrap_or_else(|error| panic!("{}: {error}", path.display()));
}