archunit/common/extraction/
dependency.rsuse std::slice;
use super::ImportKind;
use super::ignore_directive::DeclarationSpan;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ExtractionDiagnosticKind {
ReadFile,
ParseFile,
MissingModule,
AmbiguousModule,
ModuleCycle,
InvalidPathAttribute,
AmbiguousReference,
UnknownReference,
}
impl ExtractionDiagnosticKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ReadFile => "read-file",
Self::ParseFile => "parse-file",
Self::MissingModule => "missing-module",
Self::AmbiguousModule => "ambiguous-module",
Self::ModuleCycle => "module-cycle",
Self::InvalidPathAttribute => "invalid-path-attribute",
Self::AmbiguousReference => "ambiguous-reference",
Self::UnknownReference => "unknown-reference",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum DependencyTarget {
Internal(String),
External(String),
}
impl DependencyTarget {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::Internal(target) | Self::External(target) => target,
}
}
#[must_use]
pub const fn is_external(&self) -> bool {
matches!(self, Self::External(_))
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub struct ExtractionDiagnostic {
source: String,
line: Option<usize>,
kind: ExtractionDiagnosticKind,
subject: Option<String>,
candidates: Vec<String>,
detail: Option<String>,
}
impl ExtractionDiagnostic {
pub(crate) fn new(
source: impl Into<String>,
line: Option<usize>,
kind: ExtractionDiagnosticKind,
subject: Option<String>,
mut candidates: Vec<String>,
detail: Option<String>,
) -> Self {
candidates.sort();
candidates.dedup();
Self {
source: source.into(),
line,
kind,
subject,
candidates,
detail,
}
}
#[must_use]
pub fn source(&self) -> &str {
&self.source
}
#[must_use]
pub const fn line(&self) -> Option<usize> {
self.line
}
#[must_use]
pub const fn kind(&self) -> ExtractionDiagnosticKind {
self.kind
}
#[must_use]
pub fn subject(&self) -> Option<&str> {
self.subject.as_deref()
}
#[must_use]
pub fn candidates(&self) -> &[String] {
&self.candidates
}
#[must_use]
pub fn detail(&self) -> Option<&str> {
self.detail.as_deref()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub struct DependencyReference {
source: String,
referenced_path: String,
target: Option<DependencyTarget>,
kind: ImportKind,
line: usize,
}
impl DependencyReference {
pub(crate) fn new(
source: impl Into<String>,
referenced_path: impl Into<String>,
target: Option<DependencyTarget>,
kind: ImportKind,
line: usize,
) -> Self {
Self {
source: source.into(),
referenced_path: referenced_path.into(),
target,
kind,
line,
}
}
#[must_use]
pub fn source(&self) -> &str {
&self.source
}
#[must_use]
pub fn referenced_path(&self) -> &str {
&self.referenced_path
}
#[must_use]
pub const fn target(&self) -> Option<&DependencyTarget> {
self.target.as_ref()
}
#[must_use]
pub fn internal_target(&self) -> Option<&str> {
match &self.target {
Some(DependencyTarget::Internal(target)) => Some(target),
Some(DependencyTarget::External(_)) | None => None,
}
}
#[must_use]
pub fn external_target(&self) -> Option<&str> {
match &self.target {
Some(DependencyTarget::External(target)) => Some(target),
Some(DependencyTarget::Internal(_)) | None => None,
}
}
#[must_use]
pub const fn kind(&self) -> ImportKind {
self.kind
}
#[must_use]
pub const fn line(&self) -> usize {
self.line
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct DependencyExtraction {
references: Vec<DependencyReference>,
diagnostics: Vec<ExtractionDiagnostic>,
}
impl DependencyExtraction {
pub(crate) fn new(
mut references: Vec<DependencyReference>,
mut diagnostics: Vec<ExtractionDiagnostic>,
) -> Self {
references.sort();
references.dedup();
diagnostics.sort();
diagnostics.dedup();
Self {
references,
diagnostics,
}
}
#[must_use]
pub fn references(&self) -> &[DependencyReference] {
&self.references
}
#[must_use]
pub fn diagnostics(&self) -> &[ExtractionDiagnostic] {
&self.diagnostics
}
pub fn iter(&self) -> slice::Iter<'_, DependencyReference> {
self.references.iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct LogicalModule {
pub package: String,
pub dependency_scope: super::cargo_project::CargoDependencyScope,
pub target: String,
pub segments: Vec<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct RawReference {
pub source: String,
pub module: LogicalModule,
pub segments: Vec<String>,
pub leading_colon: bool,
pub kind: ImportKind,
pub line: usize,
pub binding: Option<String>,
pub declaration: Option<DeclarationSpan>,
pub ignored: bool,
}
impl RawReference {
pub fn rendered_path(&self) -> String {
let path = self.segments.join("::");
if self.leading_colon {
format!("::{path}")
} else {
path
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct InternalResolution {
pub source: String,
pub module_segments: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::{
DependencyExtraction, DependencyReference, DependencyTarget, ExtractionDiagnostic,
ExtractionDiagnosticKind,
};
use crate::common::ImportKind;
#[test]
fn extraction_results_sort_and_deduplicate_data() {
let reference = DependencyReference::new(
"src/lib.rs",
"crate::api::Handler",
Some(DependencyTarget::Internal("src/api.rs".to_owned())),
ImportKind::PathReference,
4,
);
let diagnostic = ExtractionDiagnostic::new(
"src/lib.rs",
Some(3),
ExtractionDiagnosticKind::MissingModule,
Some("missing".to_owned()),
Vec::new(),
None,
);
let result = DependencyExtraction::new(
vec![reference.clone(), reference],
vec![diagnostic.clone(), diagnostic],
);
assert_eq!(result.references().len(), 1);
assert_eq!(result.diagnostics().len(), 1);
assert_eq!(result.diagnostics()[0].kind().as_str(), "missing-module");
}
}