use std::collections::BTreeMap;
use crate::{ComponentNode, ExecutionBoundary, SemanticId, SemanticOwner, SourceProvenance};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComputedCachePolicy {
Memoized,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComputedPurity {
Unclassified,
Pure,
Impure,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComputedDiagnosticCode {
PurityViolation,
DependencyCycle,
InvalidDeclaration,
UnsupportedBody,
UnresolvedRead,
TypeMismatch,
SerializationViolation,
}
impl ComputedDiagnosticCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::PurityViolation => "PSC1034",
Self::DependencyCycle => "PSC1035",
Self::InvalidDeclaration => "PSC1036",
Self::UnsupportedBody => "PSC1037",
Self::UnresolvedRead => "PSC1038",
Self::TypeMismatch => "PSC1039",
Self::SerializationViolation => "PSC1040",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComputedPurityViolationKind {
StateMutation,
Action,
Effect,
Async,
Resource,
ArbitraryMethodCall,
NondeterministicOperation,
}
impl ComputedPurityViolationKind {
#[must_use]
pub const fn description(self) -> &'static str {
match self {
Self::StateMutation => "state mutation",
Self::Action => "action invocation",
Self::Effect => "effectful operation",
Self::Async => "async execution",
Self::Resource => "resource access",
Self::ArbitraryMethodCall => "arbitrary method call",
Self::NondeterministicOperation => "nondeterministic operation",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComputedPurityViolation {
pub kind: ComputedPurityViolationKind,
pub provenance: SourceProvenance,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComputedValue {
pub id: SemanticId,
pub owner: SemanticOwner,
pub method: SemanticId,
pub name: String,
pub cache_policy: ComputedCachePolicy,
pub purity: ComputedPurity,
pub purity_violations: Vec<ComputedPurityViolation>,
pub execution_boundary: ExecutionBoundary,
pub provenance: SourceProvenance,
}
#[must_use]
pub fn collect_computed_values(
components: &[ComponentNode],
provenance: &BTreeMap<SemanticId, SourceProvenance>,
) -> BTreeMap<SemanticId, ComputedValue> {
components
.iter()
.flat_map(|component| {
component
.methods
.iter()
.filter(|method| method.is_computed())
.map(move |method| {
let id = component.id.computed(&method.name);
let provenance = provenance
.get(&method.id)
.expect("computed methods should have canonical provenance")
.clone();
(
id.clone(),
ComputedValue {
id,
owner: SemanticOwner::entity(component.id.clone()),
method: method.id.clone(),
name: method.name.clone(),
cache_policy: ComputedCachePolicy::Memoized,
purity: ComputedPurity::Unclassified,
purity_violations: Vec::new(),
execution_boundary: ExecutionBoundary::Client,
provenance,
},
)
})
})
.collect()
}
#[cfg(test)]
mod tests {
use crate::{
build_component_graph, collect_computed_values, ComputedCachePolicy, ComputedPurity,
ExecutionBoundary,
};
#[test]
fn collects_stable_computed_entities_from_decorated_getters() {
let parsed = presolve_parser::parse_file(
"src/Computed.tsx",
r#"
@component("x-computed")
class Computed extends Component {
@computed()
get remainingCount(): number { return 1; }
}
"#,
);
let graph = build_component_graph(&parsed);
let component = &graph.components[0];
let computed = collect_computed_values(&graph.components, &graph.provenance)
.into_values()
.next()
.expect("computed entity");
assert_eq!(
computed.id.as_str(),
"component:x-computed/computed:remainingCount"
);
assert_eq!(computed.method, component.methods[0].id);
assert_eq!(computed.owner, component.methods[0].owner);
assert_eq!(computed.cache_policy, ComputedCachePolicy::Memoized);
assert_eq!(computed.purity, ComputedPurity::Unclassified);
assert!(computed.purity_violations.is_empty());
assert_eq!(computed.execution_boundary, ExecutionBoundary::Client);
}
}