bms_rs/diagnostics.rs
1//! Fancy diagnostics support using `ariadne`.
2//!
3//! This module provides convenient methods to convert errors carrying `SourcePosMixin`
4//! (such as `LexWarningWithRange`, `ParseWarningWithRange`, `AstBuildWarningWithRange`,
5//! `AstParseWarningWithRange`, and the aggregated `BmsWarning`) to `ariadne::Report`
6//! without modifying existing error type definitions.
7//!
8//! Since `SourcePosMixin` contains index span information (start/end byte offsets), this module
9//! lets ariadne automatically handle row/column calculations for display purposes.
10//!
11//! # Usage Example
12//!
13//! ```rust
14//! use bms_rs::{
15//! bms::{BmsWarning, default_config, command::channel::mapper::KeyLayoutBeat, parse_bms},
16//! diagnostics::emit_bms_warnings,
17//! };
18//!
19//! // Parse BMS file
20//! let bms_source = "#TITLE Test\n#ARTIST Composer\n#INVALID command\n";
21//! let output = parse_bms(bms_source, default_config()).expect("must be parsed");
22//!
23//! // Output all warnings
24//! emit_bms_warnings("test.bms", bms_source, &output.warnings);
25//! ```
26
27use ariadne::{Color, Label, Report, ReportKind, Source};
28
29/// Simple source container that holds the filename and source text.
30/// Ariadne will automatically handle row/column calculations from byte offsets.
31///
32/// # Usage Example
33///
34/// ```rust
35/// use bms_rs::diagnostics::SimpleSource;
36///
37/// // Create source container
38/// let source_text = "#TITLE test\n#ARTIST composer\n";
39/// let source = SimpleSource::new("test.bms", source_text);
40///
41/// // Get source text
42/// assert_eq!(source.text(), source_text);
43/// ```
44pub struct SimpleSource<'a> {
45 /// Name of the source file.
46 name: &'a str,
47 /// Source text content.
48 text: &'a str,
49}
50
51impl<'a> SimpleSource<'a> {
52 /// Create a new source container instance.
53 ///
54 /// # Parameters
55 /// * `name` - Name of the source file
56 /// * `text` - Complete text content of the source file
57 #[must_use]
58 pub const fn new(name: &'a str, text: &'a str) -> Self {
59 Self { name, text }
60 }
61
62 /// Get source text content.
63 ///
64 /// # Returns
65 /// Returns the complete text content of the source file
66 #[must_use]
67 pub const fn text(&self) -> &'a str {
68 self.text
69 }
70
71 /// Get source file name.
72 ///
73 /// # Returns
74 /// Returns the name of the source file
75 #[must_use]
76 pub const fn name(&self) -> &'a str {
77 self.name
78 }
79}
80
81/// Trait for converting positioned errors to `ariadne::Report`.
82///
83/// # Usage Example
84///
85/// ```rust
86/// use bms_rs::{diagnostics::{SimpleSource, ToAriadne, emit_bms_warnings}, bms::BmsWarning};
87/// use ariadne::Source;
88///
89/// // Assume there are warnings generated during BMS parsing
90/// let warnings: Vec<BmsWarning> = vec![/* warnings obtained from parsing */];
91/// let source_text = "#TITLE test\n#ARTIST composer\n";
92///
93/// // Simpler way: use convenience function
94/// emit_bms_warnings("test.bms", source_text, &warnings);
95///
96/// // Or handle each warning manually:
97/// let source = SimpleSource::new("test.bms", source_text);
98/// let ariadne_source = Source::from(source_text);
99///
100/// for warning in &warnings {
101/// let report = warning.to_report(&source);
102/// // Use ariadne to render the report - ariadne will automatically handle row/column calculation
103/// let _ = report.print(("test.bms".to_string(), ariadne_source.clone()));
104/// }
105/// ```
106pub trait ToAriadne {
107 /// Convert error to ariadne Report.
108 ///
109 /// # Parameters
110 /// * `src` - Source file container (used for filename, ariadne handles row/column calculation)
111 ///
112 /// # Returns
113 /// Returns the constructed ariadne Report
114 fn to_report<'a>(&self, src: &SimpleSource<'a>)
115 -> Report<'a, (String, std::ops::Range<usize>)>;
116}
117
118/// Helper to build a styled ariadne `Report` consistently.
119///
120/// This reduces duplication across multiple `ToAriadne` implementations.
121#[must_use]
122pub fn build_report<'a>(
123 src: &SimpleSource<'a>,
124 kind: ReportKind<'a>,
125 range: std::ops::Range<usize>,
126 title: &str,
127 label_message: impl ToString,
128 color: Color,
129) -> Report<'a, (String, std::ops::Range<usize>)> {
130 let filename = src.name().to_string();
131 Report::build(kind, (filename.clone(), range.clone()))
132 .with_message(title)
133 .with_label(
134 Label::new((filename, range))
135 .with_message(label_message.to_string())
136 .with_color(color),
137 )
138 .finish()
139}
140
141/// Convenience method: batch render `BmsWarning` list.
142///
143/// This function automatically creates `SimpleSource` and generates beautiful diagnostic output for each warning.
144/// Ariadne will automatically handle row/column calculations from the provided byte ranges.
145///
146/// # Usage Example
147///
148/// ```rust
149/// use bms_rs::{diagnostics::emit_bms_warnings, bms::BmsWarning};
150///
151/// // BMS source text
152/// let bms_source = "#TITLE My Song\n#ARTIST Composer\n#BPM 120\n";
153///
154/// // Assume warning list obtained from parsing
155/// let warnings: Vec<BmsWarning> = vec![/* parsing warnings */];
156///
157/// // Batch output all warnings - ariadne will automatically calculate row/column positions
158/// emit_bms_warnings("my_song.bms", bms_source, &warnings);
159/// ```
160///
161/// # Parameters
162/// * `name` - Name of the source file, used for display in diagnostic information
163/// * `source` - Complete BMS source text
164/// * `warnings` - List of warnings to display
165pub fn emit_bms_warnings<'a>(
166 name: &'a str,
167 source: &'a str,
168 warnings: impl IntoIterator<Item = &'a crate::bms::BmsWarning>,
169) {
170 let simple = SimpleSource::new(name, source);
171 let ariadne_source = Source::from(source);
172 for w in warnings {
173 let report = w.to_report(&simple);
174 let _ = report.print((name.to_string(), ariadne_source.clone()));
175 }
176}