1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use super::Span;
use crate::sass::{FormalArgs, Name};
use std::fmt::{self, Write};
use std::str::from_utf8;

/// A position in sass input.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SourcePos {
    /// The text of the source line containing this pos.
    pub line: String,
    /// The line number of this pos.
    pub line_no: u32,
    /// The position on the line.
    pub line_pos: usize,
    /// The length of the interesting part.
    length: usize,
    /// The source file name and from where it was loaded.
    pub file: SourceName,
}

impl SourcePos {
    /// Create a new SourcePos from a start and an end Span.
    pub fn from_to(start: Span, end: Span) -> Self {
        let mut result = Self::from(start);
        result.length =
            std::cmp::max(1, end.location_offset() - start.location_offset());
        result
    }

    pub(crate) fn mock_function(
        name: &Name,
        args: &FormalArgs,
        module: &Name,
    ) -> Self {
        let args = args.to_string();
        SourcePos {
            line: format!("@function {}{} {{", name, args),
            line_no: 1,
            line_pos: 11,
            length: name.as_ref().chars().count() + args.chars().count(),
            file: SourceName::root(module.as_ref()),
        }
    }

    /// Show this source position.
    ///
    /// Dislays the line containg the position, highlighting
    /// the position.
    /// This is typically used when there is one source position
    /// relevant for an error.
    /// This includes [Self::show_files].
    pub fn show(&self, out: &mut impl Write) -> fmt::Result {
        self.show_impl(out, "", '^', "")?;
        self.show_files(out)
    }
    /// Show this source position.
    ///
    /// Dislays the line containg the position, highlighting
    /// the position with a specific identifier.
    /// This is typically used when there is more than one source
    /// position relevant for an error.
    /// This does not include [Self::show_files].
    pub fn show_detail(
        &self,
        out: &mut impl Write,
        marker: char,
        what: &str,
    ) -> fmt::Result {
        self.show_impl(out, &format!("--> {}", self.file.name), marker, what)
    }
    fn show_impl(
        &self,
        out: &mut impl Write,
        arrow: &str,
        marker: char,
        what: &str,
    ) -> fmt::Result {
        let line_no = self.line_no.to_string();
        write!(
            out,
            "{0:lnw$} ,{arrow}\
             \n{ln} | {line}\
             \n{0:lnw$} |{0:>lpos$}{mark}{what}\
             \n{0:lnw$} '",
            "",
            arrow = arrow,
            line = self.line,
            ln = line_no,
            lnw = line_no.len(),
            lpos = self.line_pos,
            mark = marker.to_string().repeat(self.length),
            what = what,
        )
    }
    /// Show the file name of this pos and where it was imported from.
    pub fn show_files(&self, out: &mut impl Write) -> fmt::Result {
        let mut nextpos = Some(self);
        while let Some(pos) = nextpos {
            write!(
                out,
                "\n{0:lnw$} {file} {row}:{col}  {cause}",
                "",
                lnw = self.line_no.to_string().len(),
                file = pos.file.name(),
                row = pos.line_no,
                col = pos.line_pos,
                cause = if pos.file.imported_from().is_some() {
                    "import"
                } else {
                    "root stylesheet"
                },
            )?;
            nextpos = pos.file.imported_from();
        }
        Ok(())
    }
}

impl From<Span<'_>> for SourcePos {
    fn from(span: Span) -> Self {
        SourcePos {
            line: from_utf8(span.get_line_beginning())
                .unwrap_or("<<failed to display line>>")
                .to_string(),
            line_no: span.location_line(),
            line_pos: span.get_utf8_column(),
            length: 1,
            file: span.extra.clone(),
        }
    }
}

/// The name of a scss source file.
///
/// This also contains the information if this was the root stylesheet
/// or where it was imported from.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SourceName {
    name: String,
    imported: Option<Box<SourcePos>>,
}

impl SourceName {
    /// Create a new top-level SourceName.
    pub fn root<T: ToString>(name: T) -> Self {
        SourceName {
            name: name.to_string(),
            imported: None,
        }
    }
    /// Create a name for a file imported from a specific pos.
    pub fn imported<T: ToString>(name: T, from: SourcePos) -> Self {
        SourceName {
            name: name.to_string(),
            imported: Some(Box::new(from)),
        }
    }

    /// Get the name of this source.
    pub fn name(&self) -> &str {
        &self.name
    }
    /// Get where this source is imported from, if not top-level.
    pub fn imported_from(&self) -> Option<&SourcePos> {
        self.imported.as_ref().map(|b| b.as_ref())
    }
}