1use std::collections::BTreeMap;
2
3use crate::{ComponentNode, ExecutionBoundary, SemanticId, SemanticOwner, SourceProvenance};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ComputedCachePolicy {
8 Memoized,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ComputedPurity {
14 Unclassified,
15 Pure,
16 Impure,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
21pub enum ComputedDiagnosticCode {
22 PurityViolation,
23 DependencyCycle,
24 InvalidDeclaration,
25 UnsupportedBody,
26 UnresolvedRead,
27 TypeMismatch,
28 SerializationViolation,
29}
30
31impl ComputedDiagnosticCode {
32 #[must_use]
33 pub const fn as_str(self) -> &'static str {
34 match self {
35 Self::PurityViolation => "PSC1034",
36 Self::DependencyCycle => "PSC1035",
37 Self::InvalidDeclaration => "PSC1036",
38 Self::UnsupportedBody => "PSC1037",
39 Self::UnresolvedRead => "PSC1038",
40 Self::TypeMismatch => "PSC1039",
41 Self::SerializationViolation => "PSC1040",
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
48pub enum ComputedPurityViolationKind {
49 StateMutation,
50 Action,
51 Effect,
52 Async,
53 Resource,
54 ArbitraryMethodCall,
55 NondeterministicOperation,
56}
57
58impl ComputedPurityViolationKind {
59 #[must_use]
60 pub const fn description(self) -> &'static str {
61 match self {
62 Self::StateMutation => "state mutation",
63 Self::Action => "action invocation",
64 Self::Effect => "effectful operation",
65 Self::Async => "async execution",
66 Self::Resource => "resource access",
67 Self::ArbitraryMethodCall => "arbitrary method call",
68 Self::NondeterministicOperation => "nondeterministic operation",
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct ComputedPurityViolation {
76 pub kind: ComputedPurityViolationKind,
77 pub provenance: SourceProvenance,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct ComputedValue {
83 pub id: SemanticId,
84 pub owner: SemanticOwner,
85 pub method: SemanticId,
86 pub name: String,
87 pub cache_policy: ComputedCachePolicy,
88 pub purity: ComputedPurity,
89 pub purity_violations: Vec<ComputedPurityViolation>,
90 pub execution_boundary: ExecutionBoundary,
91 pub provenance: SourceProvenance,
92}
93
94#[must_use]
100pub fn collect_computed_values(
101 components: &[ComponentNode],
102 provenance: &BTreeMap<SemanticId, SourceProvenance>,
103) -> BTreeMap<SemanticId, ComputedValue> {
104 components
105 .iter()
106 .flat_map(|component| {
107 component
108 .methods
109 .iter()
110 .filter(|method| method.is_computed())
111 .map(move |method| {
112 let id = component.id.computed(&method.name);
113 let provenance = provenance
114 .get(&method.id)
115 .expect("computed methods should have canonical provenance")
116 .clone();
117 (
118 id.clone(),
119 ComputedValue {
120 id,
121 owner: SemanticOwner::entity(component.id.clone()),
122 method: method.id.clone(),
123 name: method.name.clone(),
124 cache_policy: ComputedCachePolicy::Memoized,
125 purity: ComputedPurity::Unclassified,
126 purity_violations: Vec::new(),
127 execution_boundary: ExecutionBoundary::Client,
128 provenance,
129 },
130 )
131 })
132 })
133 .collect()
134}
135
136#[cfg(test)]
137mod tests {
138 use crate::{
139 build_component_graph, collect_computed_values, ComputedCachePolicy, ComputedPurity,
140 ExecutionBoundary,
141 };
142
143 #[test]
144 fn collects_stable_computed_entities_from_decorated_getters() {
145 let parsed = presolve_parser::parse_file(
146 "src/Computed.tsx",
147 r#"
148@component("x-computed")
149class Computed extends Component {
150 @computed()
151 get remainingCount(): number { return 1; }
152}
153"#,
154 );
155 let graph = build_component_graph(&parsed);
156 let component = &graph.components[0];
157 let computed = collect_computed_values(&graph.components, &graph.provenance)
158 .into_values()
159 .next()
160 .expect("computed entity");
161
162 assert_eq!(
163 computed.id.as_str(),
164 "component:x-computed/computed:remainingCount"
165 );
166 assert_eq!(computed.method, component.methods[0].id);
167 assert_eq!(computed.owner, component.methods[0].owner);
168 assert_eq!(computed.cache_policy, ComputedCachePolicy::Memoized);
169 assert_eq!(computed.purity, ComputedPurity::Unclassified);
170 assert!(computed.purity_violations.is_empty());
171 assert_eq!(computed.execution_boundary, ExecutionBoundary::Client);
172 }
173}