ariadne

repository·main·Indexed 24 days ago

https://github.com/zesterer/ariadne

A Rust crate for generating high-quality, visually rich compiler diagnostics. Ariadne supports multi-line spans, multi-file errors, and sophisticated layout heuristics to prevent overlapping labels. It provides tools for colored highlighting (8-bit and 24-bit), customizable report configurations via the `Config` struct, and flexible source management through `Source` and `Cache` modules.

Tokens
4.6K
Snippets
3
Records
26
Agent score
79%

What's inside ariadne

  1. Ariadne Overview and Features

    main

    Ariadne is a crate for generating high-quality, fancy compiler diagnostics. It is a sister project to chumsky.

    Key Features

    • Flexible Spans: Supports inline and multi-line labels with arbitrary span configurations.
    • Multi-file Support: Capable of reporting errors across multiple files.
    • Visuals: Coloured labels and highlighting with 8-bit and 24-bit color support via yansi.
    • Smart Layout: Built-in heuristics to handle label priority, ordering, and avoid overlapping or crossover.
    • Robustness: Correct handling of variable-width characters like tabs.
    • Customization: Includes a ColorGenerator for distinct visual elements and various options for tab width, underlines, and label attach points.

    Technical Details

    • MSRV: 1.85.0
    • Stability: Follows semver for the API, but note that the visual layout of error messages may change due to internal layout heuristic tweaks.
  2. Create and print compiler diagnostics with Ariadne

    main

    Ariadne is used to generate fancy, multi-line compiler diagnostics. To report an error, follow these steps:

    1. Build the report: Use Report::build(ReportKind, (file_name, span)) to initialize a ReportBuilder.
    2. Add details: Use methods like .with_message(), .with_label(), and .with_note() to attach information. Labels can be created with Label::new((file_name, span)) and customized with .with_color() or .with_message().
    3. Finalize: Call .finish() to obtain a Report.
    4. Output: Use .eprint() to print to stderr (requiring a tuple of (file_name, Source)) or .print() for stdout. You can also use .write() to send the report to any Write destination.

    Labels support arbitrary multi-line spans, color highlighting (8-bit and 24-bit), and automatic overlap/crossover heuristics.

    use ariadne::{Color, ColorGenerator, Fmt, Label, Report, ReportKind, Source};
    
    // Generate & choose some colours for each of our elements
    let mut colors = ColorGenerator::new();
    let a = colors.next();
    let b = colors.next();
    let out = Color::Blue;
    
    Report::build(ReportKind::Error, ("sample.tao", 12..12))
        .with_message(format!("Incompatible types"))
        .with_label(Label::new(("sample.tao", 32..33))
            .with_message(format!("This is of type {}", "Nat".fg(a)))
            .with_color(a))
        .with_label(Label::new(("sample.tao", 52..55))
            .with_message(format!("This is of type {}", "Str".fg(b)))
            .with_color(b))
        .with_label(Label::new(("sample.tao", 11..58))
            .with_message(format!("The values are outputs of this {} expression", "match".fg(out)))
            .with_color(out))
        .with_note(format!("Outputs of {} expressions must coerce to the same type", "match".fg(out)))
        .finish()
        .eprint(("sample.tao", Source::from(include_str!("sample.tao"))))
        .unwrap();
  3. Core types and modules in Ariadne

    main

    Ariadne provides tools for generating diagnostic reports with visual spans and source code context. The public API is organized into several key modules:

    • Report and ReportBuilder: Used to construct diagnostic messages and define their severity (ReportKind).
    • Span: Represents a specific range within a source.
    • Label: Provides descriptive text associated with a Span to highlight errors or warnings.
    • Source: Manages the source code being reported on, including support for different backends like files or functions via sources.
    • Cache: Handles source code caching (e.g., FileCache, FnCache) to optimize performance.
    • draw: Utilities for visual formatting, including ColorGenerator and Fmt.
    • config: Configuration settings for the reporter.
  4. Configure Ariadne Cargo features for color control

    main

    Ariadne provides integration with the concolor crate to manage global color output (e.g., disabling color when outputting to a non-TTY).

    • Use the concolor feature to enable integration. The top-level binary crate should define the concolor features.
    • Use the auto-color feature as a convenience if Ariadne is your only dependency using concolor. This automatically enables concolor's auto feature for automatic color detection.

    Example Cargo.toml configuration for automatic color support:

    [dependencies]
    ariadne = { version = "...", features = ["auto-color"] }
    [dependencies]
    ariadne = { version = "...", features = ["auto-color"] }
  5. Use the `Cache` trait to manage multiple sources

    main

    The Cache<Id> trait defines a mechanism for fetching and displaying Source objects identified by a unique Id. This is useful for managing multiple files or code snippets in a diagnostic context without loading everything into memory upfront.

    Implementations of Cache allow you to:

    • Fetch: Retrieve a &Source<Self::Storage> using .fetch(&id).
    • Display: Get a displayable representation of an ID via .display(id).

    Common cache types provided by Ariadne:

    • FileCache: Fetches Source objects directly from the filesystem using Path as the ID.
    • FnCache: A flexible cache that uses a provided closure/function to fetch data when an ID is not already present in its internal map.
    • sources helper: A convenience function to create a cache from an existing iterator of (Id, AsRef<str>) pairs.
  6. Customize `Config` using builder methods

    main

    The Config struct provides several with_* methods to modify its properties. Most methods follow the pattern pub const fn with_property(mut self, value: Type) -> Self.

    MethodDescriptionDefault
    with_cross_gap(bool)Whether to insert a gap when label lines cross.true
    with_label_attach(LabelAttach)Where inline labels attach to their spans.LabelAttach::Middle
    with_compact(bool)Whether to remove gaps to minimize used space.false
    with_underlines(bool)Whether to use underlines for label spans.true
    with_multiline_arrows(bool)Whether to use arrows for multi-line spans.true
    with_color(bool)Whether to enable colored output.true
    with_tab_width(usize)The character width of tab characters.4
    with_char_set(CharSet)The character set for dynamic elements (boxes/arrows).CharSet::Unicode
    with_index_type(IndexType)Whether to use byte spans or char spans.IndexType::Char
    with_minimise_crossings(bool)Whether to prioritize minimizing label crossings over ordering.false
    with_context_lines(usize)Number of extra context lines around labels.0
    with_ansi_mode(AnsiMode)Whether to include ANSI escape codes in the output.AnsiMode::On
    with_enumerated_notes(bool)Whether to number separate notes.true
    with_enumerated_helps(bool)Whether to number separate helps.true
  7. Configure report rendering with `Config`

    main

    The Config struct allows you to customize how diagnostic reports are rendered. You can create a new configuration using Config::new() or Config::default() and then use a builder-style pattern with with_* methods to override specific settings.

    Common configuration tasks include:

    • Adjusting visual elements like colors, underlines, and arrows.
    • Setting character sets (Unicode vs ASCII).
    • Configuring layout details like tab width, context lines, and label attachment.
    • Controlling how spans are indexed (Byte vs Char).
  8. Implement the ReportStyle trait for custom message coloring

    main

    The ReportStyle trait allows you to define how different types of messages (errors, warnings, notes, etc.) are visually styled in reports. To implement it, your type must also implement Display and Debug.

    By default, get_color returns None. When implementing it, you can return an Option<Color> (using the yansi::Color enum) which will be respected if the global Config.color setting is enabled.

  9. Implement the Span trait for source ranges

    main

    The Span trait defines a standard interface for representing a range of characters within a source file. If you are building tools that need to reference specific locations in text (like error reporting or syntax highlighting), you can implement Span for your own types or use the provided implementations for standard Rust ranges.

    Key characteristics of a Span:

    • Source Identification: Uses an associated type SourceId (typically a file path) to identify which source the span belongs to.
    • Zero-indexed Offsets: start() and end() return zero-indexed character offsets from the beginning of the source.
    • Exclusive End: The end() offset is exclusive. For example, a span covering characters at index 0 and 1 will have a start() of 0 and an end() of 2.

    Provided implementations include:

    • std::ops::Range<usize>: Represents a span in a single, implicit source (where SourceId is ()).
    • (Id, std::ops::Range<usize>): Represents a span associated with a specific Id.
    • std::ops::RangeInclusive<usize>: Converts an inclusive range into an exclusive-end Span by adding 1 to the end offset.
  10. Create and configure a Label

    main

    A Label represents a labelled section of source code, typically used to attach messages and colors to specific code spans (like a Range<usize>). You can use a builder-like pattern to customize the message, color, order, and priority of the label.

    If using Range<usize> for the span, the offsets must be zero-indexed character offsets. Note that Label::new will panic if the provided span is backwards (e.g., 1..0).

  11. Represent source code with the `Source` struct

    main

    The Source<I> struct represents a single input source (like a file) and provides utilities for navigating its text, lines, and offsets. It is generic over I, which must implement AsRef<str> (e.g., String or &str).

    Key capabilities:

    • Text Access: Retrieve the full text via .text().
    • Line Navigation: Access specific lines via .line(idx) or iterate over all lines with .lines().
    • Offset Mapping: Convert character or byte offsets into line/column locations using .get_offset_line(offset) or .get_byte_line(byte_offset). This returns a Location containing the Line and zero-indexed column indices.
    • Span Mapping: Get the range of line indices covered by a span using .get_line_range(span).
    • Line Text: Retrieve the raw string slice for a specific line using .get_line_text(line).

    Note: Creating a Source from a long string via Source::from(input) can be expensive as it pre-calculates line offsets and lengths.