1use std::collections::{BTreeMap, BTreeSet};
9
10use presolve_parser::{ParsedFile, SourceSpan};
11
12use crate::{
13 normalize_authored_semantics_v1, AuthoredSemanticCandidateKindV1,
14 AuthoredSemanticNormalizationErrorV1, AuthoredSourceRangeV1,
15 CanonicalAuthoredDeclarationKindV1, CanonicalAuthoredSemanticModelV1, CanonicalIntrinsicKindV1,
16 ResolvedAuthoredSemanticCandidateV1, ResolvedIntrinsicIdentityV1,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct FormDefinitionSiteV1 {
22 pub subject: String,
23 pub declaration_source: AuthoredSourceRangeV1,
24 pub callee_source: AuthoredSourceRangeV1,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ResolvedFormDefinitionV1 {
30 pub callee_source: AuthoredSourceRangeV1,
31 pub form_identity: ResolvedIntrinsicIdentityV1,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FormDefinitionLoweringV1 {
37 pub sites: Vec<FormDefinitionSiteV1>,
38 pub model: CanonicalAuthoredSemanticModelV1,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum FormDefinitionLoweringErrorV1 {
43 ComponentSourcePathMismatch,
44 UnknownComponentDeclaration { start: usize, end: usize },
45 DuplicateResolution { start: usize, end: usize },
46 UnknownFormResolution { start: usize, end: usize },
47 InvalidAuthoredSemantics(AuthoredSemanticNormalizationErrorV1),
48}
49
50impl std::fmt::Display for FormDefinitionLoweringErrorV1 {
51 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Self::ComponentSourcePathMismatch => write!(
54 formatter,
55 "component and Form authored-semantic products must describe the same source file"
56 ),
57 Self::UnknownComponentDeclaration { start, end } => write!(
58 formatter,
59 "canonical component declaration has no source class at {start}..{end}"
60 ),
61 Self::DuplicateResolution { start, end } => {
62 write!(
63 formatter,
64 "duplicate Form definition resolution at {start}..{end}"
65 )
66 }
67 Self::UnknownFormResolution { start, end } => write!(
68 formatter,
69 "Form definition resolution has no source field callee at {start}..{end}"
70 ),
71 Self::InvalidAuthoredSemantics(error) => error.fmt(formatter),
72 }
73 }
74}
75
76impl std::error::Error for FormDefinitionLoweringErrorV1 {}
77
78pub fn form_definition_sites_v1(
80 parsed: &ParsedFile,
81 component_model: &CanonicalAuthoredSemanticModelV1,
82) -> Result<Vec<FormDefinitionSiteV1>, FormDefinitionLoweringErrorV1> {
83 if component_model.source_path != parsed.path {
84 return Err(FormDefinitionLoweringErrorV1::ComponentSourcePathMismatch);
85 }
86 let components = component_model
87 .declarations
88 .iter()
89 .filter(|declaration| declaration.kind == CanonicalAuthoredDeclarationKindV1::Component)
90 .map(|declaration| (declaration.subject.clone(), range_key(declaration.source)))
91 .collect::<BTreeSet<_>>();
92 for (subject, declaration_key) in &components {
93 if !parsed
94 .classes
95 .iter()
96 .any(|class| class.name == *subject && range_key(range(class.span)) == *declaration_key)
97 {
98 return Err(FormDefinitionLoweringErrorV1::UnknownComponentDeclaration {
99 start: declaration_key.0,
100 end: declaration_key.1,
101 });
102 }
103 }
104
105 let mut sites = parsed
106 .classes
107 .iter()
108 .filter(|class| components.contains(&(class.name.clone(), range_key(range(class.span)))))
109 .flat_map(|class| {
110 class.properties.iter().filter_map(move |property| {
111 let call = property.initializer_call.as_ref()?;
112 (!property.is_static).then_some(FormDefinitionSiteV1 {
113 subject: format!("{}.{}", class.name, property.name),
114 declaration_source: range(property.span),
115 callee_source: range(call.callee_span),
116 })
117 })
118 })
119 .collect::<Vec<_>>();
120 sites.sort_by_key(|site| {
121 (
122 site.callee_source.start,
123 site.callee_source.end,
124 site.subject.clone(),
125 )
126 });
127 Ok(sites)
128}
129
130pub fn lower_form_definitions_v1(
132 parsed: &ParsedFile,
133 component_model: &CanonicalAuthoredSemanticModelV1,
134 resolutions: impl IntoIterator<Item = ResolvedFormDefinitionV1>,
135) -> Result<FormDefinitionLoweringV1, FormDefinitionLoweringErrorV1> {
136 let sites = form_definition_sites_v1(parsed, component_model)?;
137 let known_sites = sites
138 .iter()
139 .map(|site| range_key(site.callee_source))
140 .collect::<BTreeSet<_>>();
141 let mut resolution_by_site = BTreeMap::new();
142 for resolution in resolutions {
143 let key = range_key(resolution.callee_source);
144 if !known_sites.contains(&key) {
145 return Err(FormDefinitionLoweringErrorV1::UnknownFormResolution {
146 start: key.0,
147 end: key.1,
148 });
149 }
150 if resolution_by_site.insert(key, resolution).is_some() {
151 return Err(FormDefinitionLoweringErrorV1::DuplicateResolution {
152 start: key.0,
153 end: key.1,
154 });
155 }
156 }
157 let candidates = sites.iter().filter_map(|site| {
158 let resolution = resolution_by_site.get(&range_key(site.callee_source))?;
159 Some(ResolvedAuthoredSemanticCandidateV1 {
160 subject: site.subject.clone(),
161 source: site.declaration_source,
162 kind: AuthoredSemanticCandidateKindV1::ResolvedIntrinsic {
163 intrinsic_kind: CanonicalIntrinsicKindV1::Form,
164 intrinsic_identity: resolution.form_identity.clone(),
165 },
166 })
167 });
168 let model = normalize_authored_semantics_v1(parsed, candidates)
169 .map_err(FormDefinitionLoweringErrorV1::InvalidAuthoredSemantics)?;
170 Ok(FormDefinitionLoweringV1 { sites, model })
171}
172
173fn range(span: SourceSpan) -> AuthoredSourceRangeV1 {
174 AuthoredSourceRangeV1 {
175 start: span.start,
176 end: span.end,
177 line: span.line,
178 column: span.column,
179 }
180}
181
182fn range_key(range: AuthoredSourceRangeV1) -> (usize, usize) {
183 (range.start, range.end)
184}
185
186#[cfg(test)]
187mod tests {
188 use presolve_parser::parse_file;
189
190 use crate::{
191 lower_component_inheritance_v1, CanonicalAuthoredDeclarationKindV1,
192 ResolvedComponentInheritanceV1, ResolvedIntrinsicIdentityV1,
193 };
194
195 use super::{form_definition_sites_v1, lower_form_definitions_v1, ResolvedFormDefinitionV1};
196
197 fn identity(name: &str) -> ResolvedIntrinsicIdentityV1 {
198 ResolvedIntrinsicIdentityV1 {
199 name: name.to_owned(),
200 flags: 32,
201 declaration_modules: vec!["node_modules/presolve/src/index.d.ts".to_owned()],
202 }
203 }
204
205 #[test]
206 fn lowers_only_authority_proven_form_definitions() {
207 let source = r#"
208class Profile extends Base {
209 profile = createForm({ fields: {} });
210 lookalike = helper({ fields: {} });
211}
212"#;
213 let parsed = parse_file("src/Profile.tsx", source);
214 let component_site = crate::component_inheritance_sites_v1(&parsed)
215 .into_iter()
216 .next()
217 .unwrap();
218 let components = lower_component_inheritance_v1(
219 &parsed,
220 [ResolvedComponentInheritanceV1 {
221 heritage_source: component_site.heritage_source,
222 component_identity: identity("Component"),
223 }],
224 )
225 .unwrap()
226 .model;
227 let sites = form_definition_sites_v1(&parsed, &components).unwrap();
228 let lowering = lower_form_definitions_v1(
229 &parsed,
230 &components,
231 [ResolvedFormDefinitionV1 {
232 callee_source: sites[0].callee_source,
233 form_identity: identity("defineForm"),
234 }],
235 )
236 .unwrap();
237 assert_eq!(lowering.model.declarations.len(), 1);
238 assert_eq!(
239 lowering.model.declarations[0].kind,
240 CanonicalAuthoredDeclarationKindV1::Form
241 );
242 assert_eq!(lowering.model.declarations[0].subject, "Profile.profile");
243 }
244}