1use crate::editor::FenceExpectedSyntax;
2use crate::{
3 AnalysisDiagnostic, AnalysisPayload, DocumentDiagram, DocumentDiagramKind, FenceDelimiter,
4 FenceLineItem, FenceMarker, FenceReferenceGroup, FenceSemanticItem, FenceTextIndex,
5 FenceTextIndexSource, SharedTextSlice, SourceDescriptor, SourceMap, Summary,
6};
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::BTreeMap;
10use std::fmt;
11
12#[derive(Debug, Clone)]
13pub struct AnalysisResult {
14 payload: AnalysisPayload,
15 source_map: SourceMap,
16 diagrams: Vec<AnalyzedDiagram>,
17}
18
19impl AnalysisResult {
20 pub fn new(
21 source: SourceDescriptor,
22 source_map: SourceMap,
23 diagnostics: Vec<AnalysisDiagnostic>,
24 diagrams: Vec<AnalyzedDiagram>,
25 ) -> Self {
26 Self {
27 payload: AnalysisPayload::new(source, diagnostics),
28 source_map,
29 diagrams,
30 }
31 }
32
33 pub fn payload(&self) -> &AnalysisPayload {
34 &self.payload
35 }
36
37 pub fn into_payload(self) -> AnalysisPayload {
38 self.payload
39 }
40
41 pub fn source_map(&self) -> &SourceMap {
42 &self.source_map
43 }
44
45 pub fn diagrams(&self) -> &[AnalyzedDiagram] {
46 &self.diagrams
47 }
48
49 pub fn diagnostics(&self) -> &[AnalysisDiagnostic] {
50 &self.payload.diagnostics
51 }
52
53 pub fn to_facts_payload(&self) -> AnalysisFactsPayload {
54 AnalysisFactsPayload::from_result(self)
55 }
56}
57
58#[derive(Debug, Clone)]
59pub struct AnalyzedDiagram {
60 pub source_id: String,
61 pub index: usize,
62 pub kind: DocumentDiagramKind,
63 pub source: SourceDescriptor,
64 pub start: usize,
65 pub body_start: usize,
66 pub body_end: usize,
67 pub end: usize,
68 pub text: SharedTextSlice,
69 pub fence_delimiter: Option<FenceDelimiter>,
70 pub diagnostics: Vec<AnalysisDiagnostic>,
71 pub syntax: AnalysisSyntaxFacts,
72}
73
74impl AnalyzedDiagram {
75 pub fn from_document_diagram(
76 diagram: &DocumentDiagram,
77 diagnostics: Vec<AnalysisDiagnostic>,
78 syntax: AnalysisSyntaxFacts,
79 ) -> Self {
80 Self {
81 source_id: diagram.id.clone(),
82 index: diagram.index,
83 kind: diagram.kind,
84 source: diagram.source.clone(),
85 start: diagram.start,
86 body_start: diagram.body_start,
87 body_end: diagram.body_end,
88 end: diagram.end,
89 text: diagram.text.clone(),
90 fence_delimiter: diagram.fence_delimiter,
91 diagnostics,
92 syntax,
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
98pub struct AnalysisSyntaxFacts {
99 pub diagram_type: Option<String>,
100 pub text_index: FenceTextIndex,
101 pub flowchart: Option<AnalysisFlowchartFacts>,
102}
103
104impl AnalysisSyntaxFacts {
105 pub fn new(diagram_type: Option<String>, text_index: FenceTextIndex) -> Self {
106 Self {
107 diagram_type,
108 text_index,
109 flowchart: None,
110 }
111 }
112
113 pub fn text_scan(text: &str, diagram_type: Option<String>) -> Self {
114 Self {
115 text_index: FenceTextIndex::from_text(text, diagram_type.as_deref()),
116 diagram_type,
117 flowchart: None,
118 }
119 }
120
121 pub fn source(&self) -> FenceTextIndexSource {
122 self.text_index.source()
123 }
124
125 pub fn with_flowchart(mut self, flowchart: Option<AnalysisFlowchartFacts>) -> Self {
126 self.flowchart = flowchart;
127 self
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct AnalysisFactsPayload {
133 pub version: u32,
134 pub valid: bool,
135 pub summary: Summary,
136 pub source: SourceDescriptor,
137 pub diagnostics: Vec<AnalysisDiagnostic>,
138 pub diagrams: Vec<AnalysisDiagramFacts>,
139}
140
141impl AnalysisFactsPayload {
142 pub fn from_result(result: &AnalysisResult) -> Self {
143 Self {
144 version: result.payload.version,
145 valid: result.payload.valid,
146 summary: result.payload.summary,
147 source: result.payload.source.clone(),
148 diagnostics: result.payload.diagnostics.clone(),
149 diagrams: result
150 .diagrams
151 .iter()
152 .map(|diagram| AnalysisDiagramFacts::from_diagram(diagram, &result.source_map))
153 .collect(),
154 }
155 }
156
157 pub fn to_json_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
158 serde_json::to_vec(self)
159 }
160
161 pub fn to_pretty_json_string(&self) -> Result<String, serde_json::Error> {
162 serde_json::to_string_pretty(self)
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct AnalysisDiagramFacts {
168 pub source_id: String,
169 pub index: usize,
170 pub kind: String,
171 pub source: SourceDescriptor,
172 pub span: Option<crate::DiagnosticSpan>,
173 pub body_span: Option<crate::DiagnosticSpan>,
174 pub text_len: usize,
175 pub fence_delimiter: Option<AnalysisFenceDelimiterFacts>,
176 pub syntax: AnalysisDiagramSyntaxFacts,
177}
178
179impl AnalysisDiagramFacts {
180 fn from_diagram(diagram: &AnalyzedDiagram, source_map: &SourceMap) -> Self {
181 Self {
182 source_id: diagram.source_id.clone(),
183 index: diagram.index,
184 kind: diagram_kind_name(diagram.kind).to_string(),
185 source: diagram.source.clone(),
186 span: source_map.span(diagram.start, diagram.end).ok(),
187 body_span: source_map.span(diagram.body_start, diagram.body_end).ok(),
188 text_len: diagram.text.len(),
189 fence_delimiter: diagram
190 .fence_delimiter
191 .map(AnalysisFenceDelimiterFacts::from),
192 syntax: AnalysisDiagramSyntaxFacts::from_syntax(
193 &diagram.syntax,
194 source_map,
195 diagram.body_start,
196 ),
197 }
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct AnalysisFenceDelimiterFacts {
203 pub marker: String,
204 pub len: usize,
205}
206
207impl From<FenceDelimiter> for AnalysisFenceDelimiterFacts {
208 fn from(value: FenceDelimiter) -> Self {
209 Self {
210 marker: fence_marker_name(value.marker()).to_string(),
211 len: value.len(),
212 }
213 }
214}
215
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub struct AnalysisDiagramSyntaxFacts {
218 pub diagram_type: Option<String>,
219 pub fact_source: FenceTextIndexSource,
220 pub parser_backed: bool,
221 pub recovered: bool,
222 pub source_mapped_spans: bool,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub flowchart: Option<AnalysisFlowchartFacts>,
225 pub node_ids: Vec<String>,
226 pub class_names: Vec<String>,
227 pub directive_prefixes: Vec<String>,
228 pub references: Vec<AnalysisReferenceFacts>,
229 pub outline_items: Vec<AnalysisLineItemFacts>,
230 pub semantic_items: Vec<AnalysisSemanticItemFacts>,
231 pub expected_syntax: Vec<AnalysisExpectedSyntaxFacts>,
232}
233
234impl AnalysisDiagramSyntaxFacts {
235 fn from_syntax(
236 syntax: &AnalysisSyntaxFacts,
237 source_map: &SourceMap,
238 body_start: usize,
239 ) -> Self {
240 let text_index = &syntax.text_index;
241 let fact_source = text_index.source();
242
243 Self {
244 diagram_type: syntax.diagram_type.clone(),
245 fact_source,
246 parser_backed: fact_source.is_parser_backed(),
247 recovered: fact_source.is_recovered(),
248 source_mapped_spans: fact_source.has_source_mapped_spans(),
249 flowchart: syntax.flowchart.clone(),
250 node_ids: text_index.node_ids().cloned().collect(),
251 class_names: text_index.class_names().cloned().collect(),
252 directive_prefixes: text_index.directive_prefixes().cloned().collect(),
253 references: text_index
254 .references()
255 .map(|(group, spans)| {
256 AnalysisReferenceFacts::from_reference(group, spans, source_map, body_start)
257 })
258 .collect(),
259 outline_items: text_index
260 .outline_items()
261 .iter()
262 .map(|item| AnalysisLineItemFacts::from_item(item, source_map, body_start))
263 .collect(),
264 semantic_items: text_index
265 .semantic_items()
266 .iter()
267 .map(|item| AnalysisSemanticItemFacts::from_item(item, source_map, body_start))
268 .collect(),
269 expected_syntax: text_index
270 .expected_syntax()
271 .iter()
272 .map(|expected| {
273 AnalysisExpectedSyntaxFacts::from_expected(expected, source_map, body_start)
274 })
275 .collect(),
276 }
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
281pub struct AnalysisFlowchartFacts {
282 #[serde(default)]
283 pub direction: Option<String>,
284 #[serde(default, rename = "classDefs")]
285 pub class_defs: BTreeMap<String, Vec<String>>,
286 #[serde(default, rename = "edgeDefaults")]
287 pub edge_defaults: Option<AnalysisFlowchartEdgeDefaults>,
288 #[serde(default, rename = "vertexCalls")]
289 pub vertex_calls: Vec<String>,
290 #[serde(default)]
291 pub nodes: Vec<AnalysisFlowchartNodeFacts>,
292 #[serde(default)]
293 pub edges: Vec<AnalysisFlowchartEdgeFacts>,
294 #[serde(default)]
295 pub subgraphs: Vec<AnalysisFlowchartSubgraphFacts>,
296 #[serde(default)]
297 pub tooltips: BTreeMap<String, String>,
298}
299
300impl AnalysisFlowchartFacts {
301 pub(crate) fn try_from_model(
302 model: &Value,
303 ) -> Result<Option<Self>, AnalysisFlowchartFactsProjectionError> {
304 let diagram_type = model.get("type").and_then(Value::as_str);
305 if !matches!(
306 diagram_type,
307 Some("flowchart" | "flowchart-v2" | "flowchart-elk")
308 ) {
309 return Ok(None);
310 }
311
312 serde_json::from_value(model.clone())
313 .map(Some)
314 .map_err(AnalysisFlowchartFactsProjectionError::from)
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319pub(crate) struct AnalysisFlowchartFactsProjectionError {
320 message: String,
321}
322
323impl fmt::Display for AnalysisFlowchartFactsProjectionError {
324 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
325 formatter.write_str(&self.message)
326 }
327}
328
329impl std::error::Error for AnalysisFlowchartFactsProjectionError {}
330
331impl From<serde_json::Error> for AnalysisFlowchartFactsProjectionError {
332 fn from(error: serde_json::Error) -> Self {
333 Self {
334 message: error.to_string(),
335 }
336 }
337}
338
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
340pub struct AnalysisFlowchartEdgeDefaults {
341 #[serde(default)]
342 pub interpolate: Option<String>,
343 #[serde(default)]
344 pub style: Vec<String>,
345}
346
347#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
348pub struct AnalysisFlowchartNodeFacts {
349 pub id: String,
350 #[serde(default)]
351 pub label: Option<String>,
352 #[serde(default, rename = "labelType")]
353 pub label_type: Option<String>,
354 #[serde(default, rename = "layoutShape")]
355 pub layout_shape: Option<String>,
356 #[serde(default)]
357 pub icon: Option<String>,
358 #[serde(default)]
359 pub form: Option<String>,
360 #[serde(default)]
361 pub pos: Option<String>,
362 #[serde(default)]
363 pub img: Option<String>,
364 #[serde(default)]
365 pub constraint: Option<String>,
366 #[serde(default, rename = "assetWidth")]
367 pub asset_width: Option<f64>,
368 #[serde(default, rename = "assetHeight")]
369 pub asset_height: Option<f64>,
370 #[serde(default)]
371 pub classes: Vec<String>,
372 #[serde(default)]
373 pub styles: Vec<String>,
374 #[serde(default)]
375 pub link: Option<String>,
376 #[serde(default, rename = "linkTarget")]
377 pub link_target: Option<String>,
378 #[serde(default, rename = "haveCallback")]
379 pub have_callback: bool,
380}
381
382#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
383pub struct AnalysisFlowchartEdgeFacts {
384 pub id: String,
385 pub from: String,
386 pub to: String,
387 #[serde(default)]
388 pub label: Option<String>,
389 #[serde(default, rename = "labelType")]
390 pub label_type: Option<String>,
391 #[serde(default, rename = "type")]
392 pub edge_type: Option<String>,
393 #[serde(default)]
394 pub stroke: Option<String>,
395 #[serde(default)]
396 pub interpolate: Option<String>,
397 #[serde(default)]
398 pub classes: Vec<String>,
399 #[serde(default)]
400 pub style: Vec<String>,
401 #[serde(default)]
402 pub animate: Option<bool>,
403 #[serde(default)]
404 pub animation: Option<String>,
405 #[serde(default)]
406 pub length: usize,
407}
408
409#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
410pub struct AnalysisFlowchartSubgraphFacts {
411 pub id: String,
412 pub title: String,
413 #[serde(default)]
414 pub dir: Option<String>,
415 #[serde(default, rename = "labelType")]
416 pub label_type: Option<String>,
417 #[serde(default)]
418 pub classes: Vec<String>,
419 #[serde(default)]
420 pub styles: Vec<String>,
421 #[serde(default)]
422 pub nodes: Vec<String>,
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
426pub struct AnalysisReferenceFacts {
427 pub name: String,
428 pub kind: crate::EditorSymbolKind,
429 pub spans: Vec<AnalysisFactSpan>,
430}
431
432impl AnalysisReferenceFacts {
433 fn from_reference(
434 group: &FenceReferenceGroup,
435 spans: &[crate::ByteSpan],
436 source_map: &SourceMap,
437 body_start: usize,
438 ) -> Self {
439 Self {
440 name: group.name.clone(),
441 kind: group.kind,
442 spans: spans
443 .iter()
444 .copied()
445 .map(|span| AnalysisFactSpan::from_local(span, source_map, body_start))
446 .collect(),
447 }
448 }
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
452pub struct AnalysisLineItemFacts {
453 pub name: String,
454 pub detail: Option<String>,
455 pub kind: crate::EditorSymbolKind,
456 pub span: AnalysisFactSpan,
457 pub selection: AnalysisFactSpan,
458}
459
460impl AnalysisLineItemFacts {
461 fn from_item(item: &FenceLineItem, source_map: &SourceMap, body_start: usize) -> Self {
462 Self {
463 name: item.name.clone(),
464 detail: item.detail.clone(),
465 kind: item.kind,
466 span: AnalysisFactSpan::from_local(item.span, source_map, body_start),
467 selection: AnalysisFactSpan::from_local(item.selection, source_map, body_start),
468 }
469 }
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
473pub struct AnalysisSemanticItemFacts {
474 pub name: String,
475 pub detail: Option<String>,
476 pub kind: crate::EditorSymbolKind,
477 pub role: crate::FenceSemanticRole,
478 pub span: AnalysisFactSpan,
479 pub selection: AnalysisFactSpan,
480}
481
482impl AnalysisSemanticItemFacts {
483 fn from_item(item: &FenceSemanticItem, source_map: &SourceMap, body_start: usize) -> Self {
484 Self {
485 name: item.name.clone(),
486 detail: item.detail.clone(),
487 kind: item.kind,
488 role: item.role,
489 span: AnalysisFactSpan::from_local(item.span, source_map, body_start),
490 selection: AnalysisFactSpan::from_local(item.selection, source_map, body_start),
491 }
492 }
493}
494
495#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
496pub struct AnalysisExpectedSyntaxFacts {
497 pub kind: crate::FenceExpectedSyntaxKind,
498 pub span: AnalysisFactSpan,
499}
500
501impl AnalysisExpectedSyntaxFacts {
502 fn from_expected(
503 expected: &FenceExpectedSyntax,
504 source_map: &SourceMap,
505 body_start: usize,
506 ) -> Self {
507 Self {
508 kind: expected.kind,
509 span: AnalysisFactSpan::from_local(expected.span, source_map, body_start),
510 }
511 }
512}
513
514#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
515pub struct AnalysisFactSpan {
516 pub local: crate::ByteSpan,
517 pub document: Option<crate::DiagnosticSpan>,
518}
519
520impl AnalysisFactSpan {
521 fn from_local(local: crate::ByteSpan, source_map: &SourceMap, body_start: usize) -> Self {
522 let document_start = body_start.saturating_add(local.start);
523 let document_end = body_start.saturating_add(local.end);
524 Self {
525 local,
526 document: source_map.span(document_start, document_end).ok(),
527 }
528 }
529}
530
531fn diagram_kind_name(kind: DocumentDiagramKind) -> &'static str {
532 match kind {
533 DocumentDiagramKind::WholeDocument => "whole_document",
534 DocumentDiagramKind::MermaidFence => "mermaid_fence",
535 }
536}
537
538fn fence_marker_name(marker: FenceMarker) -> &'static str {
539 match marker {
540 FenceMarker::Backtick => "backtick",
541 FenceMarker::Tilde => "tilde",
542 FenceMarker::Colon => "colon",
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549 use serde_json::json;
550
551 #[test]
552 fn flowchart_facts_accept_legacy_flowchart_models() {
553 let model = json!({
554 "type": "flowchart",
555 "direction": "LR",
556 "nodes": [
557 {
558 "id": "A",
559 "label": "Alpha"
560 }
561 ],
562 "edges": [
563 {
564 "id": "L_A_B_0",
565 "from": "A",
566 "to": "B",
567 "length": 1
568 }
569 ]
570 });
571
572 let facts = AnalysisFlowchartFacts::try_from_model(&model)
573 .expect("legacy flowchart model should deserialize")
574 .expect("legacy flowchart model should produce facts");
575
576 assert_eq!(facts.direction.as_deref(), Some("LR"));
577 assert!(
578 facts
579 .nodes
580 .iter()
581 .any(|node| node.id == "A" && node.label.as_deref() == Some("Alpha"))
582 );
583 assert!(
584 facts
585 .edges
586 .iter()
587 .any(|edge| edge.from == "A" && edge.to == "B")
588 );
589 }
590}