Skip to main content

rolldown_ariadne/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3
4mod display;
5mod draw;
6mod source;
7mod write;
8
9pub use crate::{
10    draw::{ColorGenerator, Fmt},
11    source::{sources, Cache, FileCache, FnCache, Line, Source},
12};
13pub use yansi::Color;
14
15#[cfg(any(feature = "concolor", doc))]
16pub use crate::draw::StdoutFmt;
17
18use crate::display::*;
19use std::{
20    cmp::{Eq, PartialEq},
21    fmt,
22    hash::Hash,
23    io::{self, Write},
24    ops::Range,
25    ops::RangeInclusive,
26};
27use unicode_width::UnicodeWidthChar;
28
29/// A trait implemented by spans within a character-based source.
30pub trait Span {
31    /// The identifier used to uniquely refer to a source. In most cases, this is the fully-qualified path of the file.
32    type SourceId: PartialEq + ToOwned + ?Sized;
33
34    /// Get the identifier of the source that this span refers to.
35    fn source(&self) -> &Self::SourceId;
36
37    /// Get the start offset of this span.
38    ///
39    /// Offsets are zero-indexed character offsets from the beginning of the source.
40    fn start(&self) -> usize;
41
42    /// Get the (exclusive) end offset of this span.
43    ///
44    /// The end offset should *always* be greater than or equal to the start offset as given by [`Span::start`].
45    ///
46    /// Offsets are zero-indexed character offsets from the beginning of the source.
47    fn end(&self) -> usize;
48
49    /// Get the length of this span (difference between the start of the span and the end of the span).
50    fn len(&self) -> usize {
51        self.end().saturating_sub(self.start())
52    }
53
54    /// Returns `true` if this span has length zero.
55    fn is_empty(&self) -> bool {
56        self.len() == 0
57    }
58
59    /// Determine whether the span contains the given offset.
60    fn contains(&self, offset: usize) -> bool {
61        (self.start()..self.end()).contains(&offset)
62    }
63}
64
65impl Span for Range<usize> {
66    type SourceId = ();
67
68    fn source(&self) -> &Self::SourceId {
69        &()
70    }
71    fn start(&self) -> usize {
72        self.start
73    }
74    fn end(&self) -> usize {
75        self.end
76    }
77}
78
79impl<Id: fmt::Debug + Hash + PartialEq + Eq + ToOwned> Span for (Id, Range<usize>) {
80    type SourceId = Id;
81
82    fn source(&self) -> &Self::SourceId {
83        &self.0
84    }
85    fn start(&self) -> usize {
86        self.1.start
87    }
88    fn end(&self) -> usize {
89        self.1.end
90    }
91}
92
93impl Span for RangeInclusive<usize> {
94    type SourceId = ();
95
96    fn source(&self) -> &Self::SourceId {
97        &()
98    }
99    fn start(&self) -> usize {
100        *self.start()
101    }
102    fn end(&self) -> usize {
103        *self.end() + 1
104    }
105}
106
107impl<Id: fmt::Debug + Hash + PartialEq + Eq + ToOwned> Span for (Id, RangeInclusive<usize>) {
108    type SourceId = Id;
109
110    fn source(&self) -> &Self::SourceId {
111        &self.0
112    }
113    fn start(&self) -> usize {
114        *self.1.start()
115    }
116    fn end(&self) -> usize {
117        *self.1.end() + 1
118    }
119}
120
121/// A type that represents the way a label should be displayed.
122#[derive(Clone, Debug, Hash, PartialEq, Eq)]
123pub struct LabelDisplay {
124    msg: Option<String>,
125    color: Option<Color>,
126    order: i32,
127    priority: i32,
128}
129
130impl LabelDisplay {
131    /// getter for msg
132    pub fn msg(&self) -> Option<&str> {
133        self.msg.as_deref()
134    }
135}
136
137/// A type that represents a labelled section of source code.
138#[derive(Clone, Debug, Hash, PartialEq, Eq)]
139pub struct Label<S = Range<usize>> {
140    span: S,
141    display_info: LabelDisplay,
142}
143
144impl<S: Span> Label<S> {
145    /// Create a new [`Label`].
146    /// If the span is specified as a `Range<usize>` the numbers have to be zero-indexed character offsets.
147    ///
148    /// # Panics
149    ///
150    /// Panics if the given span is backwards.
151    pub fn new(span: S) -> Self {
152        assert!(span.start() <= span.end(), "Label start is after its end");
153
154        Self {
155            span,
156            display_info: LabelDisplay {
157                msg: None,
158                color: None,
159                order: 0,
160                priority: 0,
161            },
162        }
163    }
164
165    /// Give this label a message.
166    pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
167        self.display_info.msg = Some(msg.to_string());
168        self
169    }
170
171    /// Give this label a highlight colour.
172    pub fn with_color(mut self, color: Color) -> Self {
173        self.display_info.color = Some(color);
174        self
175    }
176
177    /// Specify the order of this label relative to other labels.
178    ///
179    /// Lower values correspond to this label having an earlier order.
180    ///
181    /// If unspecified, labels default to an order of `0`.
182    ///
183    /// When labels are displayed after a line the crate needs to decide which labels should be displayed first. By
184    /// Default, the orders labels based on where their associated line meets the text (see [`LabelAttach`]).
185    /// Additionally, multi-line labels are ordered before inline labels. You can use this function to override this
186    /// behaviour.
187    pub fn with_order(mut self, order: i32) -> Self {
188        self.display_info.order = order;
189        self
190    }
191
192    /// Specify the priority of this label relative to other labels.
193    ///
194    /// Higher values correspond to this label having a higher priority.
195    ///
196    /// If unspecified, labels default to a priority of `0`.
197    ///
198    /// Label spans can overlap. When this happens, the crate needs to decide which labels to prioritise for various
199    /// purposes such as highlighting. By default, spans with a smaller length get a higher priority. You can use this
200    /// function to override this behaviour.
201    pub fn with_priority(mut self, priority: i32) -> Self {
202        self.display_info.priority = priority;
203        self
204    }
205
206    /// getter for span
207    pub fn span(&self) -> &S {
208        &self.span
209    }
210
211    /// getter for display_info
212    pub fn display_info(&self) -> &LabelDisplay {
213        &self.display_info
214    }
215}
216
217/// A type representing a diagnostic that is ready to be written to output.
218pub struct Report<'a, S: Span = Range<usize>> {
219    kind: ReportKind<'a>,
220    code: Option<String>,
221    msg: Option<String>,
222    notes: Vec<String>,
223    help: Vec<String>,
224    span: S,
225    labels: Vec<Label<S>>,
226    config: Config,
227}
228
229impl<S: Span> Report<'_, S> {
230    /// Begin building a new [`Report`].
231    ///
232    /// The span is the primary location at which the error should be reported.
233    pub fn build(kind: ReportKind, span: S) -> ReportBuilder<S> {
234        ReportBuilder {
235            kind,
236            code: None,
237            msg: None,
238            notes: vec![],
239            help: vec![],
240            span,
241            labels: Vec::new(),
242            config: Config::default(),
243        }
244    }
245
246    /// Write this diagnostic out to `stderr`.
247    pub fn eprint<C: Cache<S::SourceId>>(&self, cache: C) -> io::Result<()> {
248        self.write(cache, io::stderr())
249    }
250
251    /// Write this diagnostic out to `stdout`.
252    ///
253    /// In most cases, [`Report::eprint`] is the
254    /// ['more correct'](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)) function to use.
255    pub fn print<C: Cache<S::SourceId>>(&self, cache: C) -> io::Result<()> {
256        self.write_for_stdout(cache, io::stdout())
257    }
258}
259
260impl<S: Span> fmt::Debug for Report<'_, S> {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        f.debug_struct("Report")
263            .field("kind", &self.kind)
264            .field("code", &self.code)
265            .field("msg", &self.msg)
266            .field("notes", &self.notes)
267            .field("help", &self.help)
268            .field("config", &self.config)
269            .finish()
270    }
271}
272/// A type that defines the kind of report being produced.
273#[derive(Copy, Clone, Debug, PartialEq, Eq)]
274pub enum ReportKind<'a> {
275    /// The report is an error and indicates a critical problem that prevents the program performing the requested
276    /// action.
277    Error,
278    /// The report is a warning and indicates a likely problem, but not to the extent that the requested action cannot
279    /// be performed.
280    Warning,
281    /// The report is advice to the user about a potential anti-pattern of other benign issues.
282    Advice,
283    /// The report is of a kind not built into Ariadne.
284    Custom(&'a str, Color),
285}
286
287impl fmt::Display for ReportKind<'_> {
288    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
289        match self {
290            ReportKind::Error => write!(f, "Error"),
291            ReportKind::Warning => write!(f, "Warning"),
292            ReportKind::Advice => write!(f, "Advice"),
293            ReportKind::Custom(s, _) => write!(f, "{}", s),
294        }
295    }
296}
297
298/// A type used to build a [`Report`].
299pub struct ReportBuilder<'a, S: Span> {
300    kind: ReportKind<'a>,
301    code: Option<String>,
302    msg: Option<String>,
303    notes: Vec<String>,
304    help: Vec<String>,
305    span: S,
306    labels: Vec<Label<S>>,
307    config: Config,
308}
309
310impl<'a, S: Span> ReportBuilder<'a, S> {
311    /// Give this report a numerical code that may be used to more precisely look up the error in documentation.
312    pub fn with_code<C: fmt::Display>(mut self, code: C) -> Self {
313        self.code = Some(format!("{:02}", code));
314        self
315    }
316
317    /// Set the message of this report.
318    pub fn set_message<M: ToString>(&mut self, msg: M) {
319        self.msg = Some(msg.to_string());
320    }
321
322    /// Add a message to this report.
323    pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
324        self.msg = Some(msg.to_string());
325        self
326    }
327
328    /// Set the note of this report.
329    pub fn set_note<N: ToString>(&mut self, note: N) {
330        self.notes = vec![note.to_string()];
331    }
332
333    /// Adds a note to this report.
334    pub fn add_note<N: ToString>(&mut self, note: N) {
335        self.notes.push(note.to_string());
336    }
337
338    /// Removes all notes in this report.
339    pub fn with_notes<N: IntoIterator<Item = impl ToString>>(&mut self, notes: N) {
340        for note in notes {
341            self.add_note(note)
342        }
343    }
344
345    /// Set the note of this report.
346    pub fn with_note<N: ToString>(mut self, note: N) -> Self {
347        self.add_note(note);
348        self
349    }
350
351    /// Set the help message of this report.
352    pub fn set_help<N: ToString>(&mut self, note: N) {
353        self.help = vec![note.to_string()];
354    }
355
356    /// Add a help message to this report.
357    pub fn add_help<N: ToString>(&mut self, note: N) {
358        self.help.push(note.to_string());
359    }
360
361    /// Set the help messages of this report.
362    pub fn with_helps<N: IntoIterator<Item = impl ToString>>(&mut self, helps: N) {
363        for help in helps {
364            self.add_help(help)
365        }
366    }
367
368    /// Set the help message of this report.
369    pub fn with_help<N: ToString>(mut self, note: N) -> Self {
370        self.add_help(note);
371        self
372    }
373
374    /// Add a label to the report.
375    pub fn add_label(&mut self, label: Label<S>) {
376        self.add_labels(std::iter::once(label));
377    }
378
379    /// Add multiple labels to the report.
380    pub fn add_labels<L: IntoIterator<Item = Label<S>>>(&mut self, labels: L) {
381        let config = &self.config; // This would not be necessary in Rust 2021 edition
382        self.labels.extend(labels.into_iter().map(|mut label| {
383            label.display_info.color = config.filter_color(label.display_info.color);
384            label
385        }));
386    }
387
388    /// Add a label to the report.
389    pub fn with_label(mut self, label: Label<S>) -> Self {
390        self.add_label(label);
391        self
392    }
393
394    /// Add multiple labels to the report.
395    pub fn with_labels<L: IntoIterator<Item = Label<S>>>(mut self, labels: L) -> Self {
396        self.add_labels(labels);
397        self
398    }
399
400    /// Use the given [`Config`] to determine diagnostic attributes.
401    pub fn with_config(mut self, config: Config) -> Self {
402        self.config = config;
403        self
404    }
405
406    /// Finish building the [`Report`].
407    pub fn finish(self) -> Report<'a, S> {
408        Report {
409            kind: self.kind,
410            code: self.code,
411            msg: self.msg,
412            notes: self.notes,
413            help: self.help,
414            span: self.span,
415            labels: self.labels,
416            config: self.config,
417        }
418    }
419}
420
421impl<S: Span> fmt::Debug for ReportBuilder<'_, S> {
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        f.debug_struct("ReportBuilder")
424            .field("kind", &self.kind)
425            .field("code", &self.code)
426            .field("msg", &self.msg)
427            .field("notes", &self.notes)
428            .field("help", &self.help)
429            .field("config", &self.config)
430            .finish()
431    }
432}
433
434/// The attachment point of inline label arrows
435#[derive(Copy, Clone, Debug, PartialEq, Eq)]
436pub enum LabelAttach {
437    /// Arrows should attach to the start of the label span.
438    Start,
439    /// Arrows should attach to the middle of the label span (or as close to the middle as we can get).
440    Middle,
441    /// Arrows should attach to the end of the label span.
442    End,
443}
444
445/// Possible character sets to use when rendering diagnostics.
446#[derive(Copy, Clone, Debug, PartialEq, Eq)]
447pub enum CharSet {
448    /// Unicode characters (an attempt is made to use only commonly-supported characters).
449    Unicode,
450    /// ASCII-only characters.
451    Ascii,
452}
453
454/// Possible character sets to use when rendering diagnostics.
455#[derive(Copy, Clone, Debug, PartialEq, Eq)]
456pub enum IndexType {
457    /// Byte spans. Always results in O(1) lookups
458    Byte,
459    /// Char based spans. May incur O(n) lookups
460    Char,
461}
462
463/// A type used to configure a report
464#[derive(Copy, Clone, Debug, PartialEq, Eq)]
465pub struct Config {
466    cross_gap: bool,
467    label_attach: LabelAttach,
468    compact: bool,
469    underlines: bool,
470    multiline_arrows: bool,
471    color: bool,
472    tab_width: usize,
473    char_set: CharSet,
474    index_type: IndexType,
475    severity_prefix: bool,
476}
477
478impl Config {
479    /// When label lines cross one-another, should there be a gap?
480    ///
481    /// The alternative to this is to insert crossing characters. However, these interact poorly with label colours.
482    ///
483    /// If unspecified, this defaults to [`false`].
484    pub const fn with_cross_gap(mut self, cross_gap: bool) -> Self {
485        self.cross_gap = cross_gap;
486        self
487    }
488    /// Where should inline labels attach to their spans?
489    ///
490    /// If unspecified, this defaults to [`LabelAttach::Middle`].
491    pub const fn with_label_attach(mut self, label_attach: LabelAttach) -> Self {
492        self.label_attach = label_attach;
493        self
494    }
495    /// Should the report remove gaps to minimise used space?
496    ///
497    /// If unspecified, this defaults to [`false`].
498    pub const fn with_compact(mut self, compact: bool) -> Self {
499        self.compact = compact;
500        self
501    }
502    /// Should underlines be used for label span where possible?
503    ///
504    /// If unspecified, this defaults to [`true`].
505    pub const fn with_underlines(mut self, underlines: bool) -> Self {
506        self.underlines = underlines;
507        self
508    }
509    /// Should arrows be used to point to the bounds of multi-line spans?
510    ///
511    /// If unspecified, this defaults to [`true`].
512    pub const fn with_multiline_arrows(mut self, multiline_arrows: bool) -> Self {
513        self.multiline_arrows = multiline_arrows;
514        self
515    }
516    /// Should colored output should be enabled?
517    ///
518    /// If unspecified, this defaults to [`true`].
519    pub const fn with_color(mut self, color: bool) -> Self {
520        self.color = color;
521        self
522    }
523    /// How many characters width should tab characters be?
524    ///
525    /// If unspecified, this defaults to `4`.
526    pub const fn with_tab_width(mut self, tab_width: usize) -> Self {
527        self.tab_width = tab_width;
528        self
529    }
530    /// What character set should be used to display dynamic elements such as boxes and arrows?
531    ///
532    /// If unspecified, this defaults to [`CharSet::Unicode`].
533    pub const fn with_char_set(mut self, char_set: CharSet) -> Self {
534        self.char_set = char_set;
535        self
536    }
537    /// Should this report use byte spans instead of char spans?
538    ///
539    /// If unspecified, this defaults to 'false'
540    pub const fn with_index_type(mut self, index_type: IndexType) -> Self {
541        self.index_type = index_type;
542        self
543    }
544    /// Should the severity prefix (e.g. `Error:`, `Warning:`) be shown in the header?
545    ///
546    /// If unspecified, this defaults to [`true`].
547    pub const fn with_severity_prefix(mut self, severity_prefix: bool) -> Self {
548        self.severity_prefix = severity_prefix;
549        self
550    }
551
552    fn error_color(&self) -> Option<Color> {
553        Some(Color::Red).filter(|_| self.color)
554    }
555    fn warning_color(&self) -> Option<Color> {
556        Some(Color::Yellow).filter(|_| self.color)
557    }
558    fn advice_color(&self) -> Option<Color> {
559        Some(Color::Fixed(147)).filter(|_| self.color)
560    }
561    fn margin_color(&self) -> Option<Color> {
562        Some(Color::Fixed(246)).filter(|_| self.color)
563    }
564    fn skipped_margin_color(&self) -> Option<Color> {
565        Some(Color::Fixed(240)).filter(|_| self.color)
566    }
567    fn unimportant_color(&self) -> Option<Color> {
568        Some(Color::Fixed(249)).filter(|_| self.color)
569    }
570    fn note_color(&self) -> Option<Color> {
571        Some(Color::Fixed(115)).filter(|_| self.color)
572    }
573    fn filter_color(&self, color: Option<Color>) -> Option<Color> {
574        color.filter(|_| self.color)
575    }
576
577    // Find the character that should be drawn and the number of times it should be drawn for each char
578    fn char_width(&self, c: char, col: usize) -> (char, usize) {
579        match c {
580            '\t' => {
581                // Find the column that the tab should end at
582                let tab_end = (col / self.tab_width + 1) * self.tab_width;
583                (' ', tab_end - col)
584            }
585            c if c.is_whitespace() => (' ', 1),
586            _ => (c, c.width().unwrap_or(1)),
587        }
588    }
589
590    /// Create a new, default config.
591    pub const fn new() -> Self {
592        Self {
593            cross_gap: true,
594            label_attach: LabelAttach::Middle,
595            compact: false,
596            underlines: true,
597            multiline_arrows: true,
598            color: true,
599            tab_width: 4,
600            char_set: CharSet::Unicode,
601            index_type: IndexType::Char,
602            severity_prefix: true,
603        }
604    }
605}
606
607impl Default for Config {
608    fn default() -> Self {
609        Self::new()
610    }
611}
612
613#[test]
614#[should_panic]
615#[allow(clippy::reversed_empty_ranges)]
616fn backwards_label_should_panic() {
617    Label::new(1..0);
618}