Skip to main content

nu_lint/
span.rs

1use std::ops::Range;
2
3/// A span relative to the current file being linted (starts at 0)
4///
5/// Use for:
6/// - Creating `Replacement` spans
7/// - Regex match positions on `whole_source()`
8/// - Manual line/column calculations
9/// - Slicing source strings
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct FileSpan {
12    pub start: usize,
13    pub end: usize,
14}
15
16/// A span that can be either global (AST) or file-relative
17///
18/// Rules return this type, and the engine normalizes all to `FileSpan` before
19/// output.
20///
21/// Global spans are from AST nodes (`nu_protocol::Span`) and include stdlib
22/// offset. File spans are relative to the current file being linted (starts at
23/// 0).
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum LintSpan {
26    Global(nu_protocol::Span),
27    File(FileSpan),
28}
29
30impl FileSpan {
31    #[must_use]
32    pub const fn new(start: usize, end: usize) -> Self {
33        Self { start, end }
34    }
35
36    /// Convert to global span by adding the file offset
37    #[must_use]
38    pub fn to_global_span(self, file_offset: usize) -> nu_protocol::Span {
39        nu_protocol::Span::new(self.start + file_offset, self.end + file_offset)
40    }
41
42    /// Create a span that encompasses both self and other
43    #[must_use]
44    pub fn merge(self, other: Self) -> Self {
45        Self {
46            start: self.start.min(other.start),
47            end: self.end.max(other.end),
48        }
49    }
50
51    #[must_use]
52    pub const fn len(&self) -> usize {
53        self.end.saturating_sub(self.start)
54    }
55
56    #[must_use]
57    pub const fn is_empty(&self) -> bool {
58        self.start >= self.end
59    }
60
61    #[must_use]
62    pub const fn as_range(&self) -> Range<usize> {
63        self.start..self.end
64    }
65}
66
67impl LintSpan {
68    /// Convert to file-relative span, normalizing if needed
69    #[must_use]
70    pub const fn to_file_span(self, file_offset: usize) -> FileSpan {
71        match self {
72            Self::Global(g) => FileSpan {
73                start: g.start.saturating_sub(file_offset),
74                end: g.end.saturating_sub(file_offset),
75            },
76            Self::File(f) => f,
77        }
78    }
79
80    /// Get the file-relative span, panicking if not normalized.
81    ///
82    /// This should only be called after `normalize_spans()` has been invoked.
83    #[must_use]
84    pub fn file_span(&self) -> FileSpan {
85        match self {
86            Self::File(f) => *f,
87            Self::Global(_) => panic!("Span not normalized - call normalize_spans first"),
88        }
89    }
90}
91
92impl From<nu_protocol::Span> for LintSpan {
93    fn from(span: nu_protocol::Span) -> Self {
94        Self::Global(span)
95    }
96}
97
98impl From<FileSpan> for LintSpan {
99    fn from(span: FileSpan) -> Self {
100        Self::File(span)
101    }
102}
103
104impl From<FileSpan> for nu_protocol::Span {
105    fn from(span: FileSpan) -> Self {
106        Self::new(span.start, span.end)
107    }
108}