Skip to main content

ruff_diagnostics/
source_map.rs

1use ruff_text_size::{Ranged, TextSize};
2
3use crate::Edit;
4
5/// Lightweight sourcemap marker representing the source and destination
6/// position for an [`Edit`].
7#[derive(Debug, PartialEq, Eq)]
8pub struct SourceMarker {
9    /// Position of the marker in the original source.
10    source: TextSize,
11    /// Position of the marker in the transformed code.
12    dest: TextSize,
13}
14
15impl SourceMarker {
16    pub fn new(source: TextSize, dest: TextSize) -> Self {
17        Self { source, dest }
18    }
19
20    pub const fn source(&self) -> TextSize {
21        self.source
22    }
23
24    pub const fn dest(&self) -> TextSize {
25        self.dest
26    }
27}
28
29/// A collection of [`SourceMarker`].
30///
31/// Sourcemaps are used to map positions in the original source to positions in
32/// the transformed code. Here, only the boundaries of edits are tracked instead
33/// of every single character.
34///
35/// This mapping maintains the invariant that markers are in source order.
36#[derive(Default, PartialEq, Eq)]
37pub struct SourceMap(Vec<SourceMarker>);
38
39impl SourceMap {
40    /// Returns a slice of all the markers in the sourcemap in source order.
41    pub fn markers(&self) -> &[SourceMarker] {
42        &self.0
43    }
44
45    /// Push the start marker for an [`Edit`].
46    ///
47    /// The `output_length` is the length of the transformed string before the
48    /// edit is applied.
49    ///
50    /// ## Panics
51    ///
52    /// If the start of `edit` is less than previous markers.
53    pub fn push_start_marker(&mut self, edit: &Edit, output_length: TextSize) {
54        self.push_marker(edit.start(), output_length);
55    }
56
57    /// Push the end marker for an [`Edit`].
58    ///
59    /// The `output_length` is the length of the transformed string after the
60    /// edit has been applied.
61    ///
62    /// ## Panics
63    ///
64    /// If `edit` falls before previous markers.
65    pub fn push_end_marker(&mut self, edit: &Edit, output_length: TextSize) {
66        if edit.is_insertion() {
67            self.push_marker(edit.start(), output_length);
68        } else {
69            // Deletion or replacement
70            self.push_marker(edit.end(), output_length);
71        }
72    }
73
74    /// Push a new marker to the sourcemap.
75    ///
76    /// ## Panics
77    ///
78    /// If `offset` is less than previous markers.
79    pub fn push_marker(&mut self, offset: TextSize, output_length: TextSize) {
80        assert!(
81            self.0.last().is_none_or(|last| offset >= last.source),
82            "Markers must be pushed in source order",
83        );
84
85        self.0.push(SourceMarker {
86            source: offset,
87            dest: output_length,
88        });
89    }
90}