minify_html_onepass/
err.rs

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
use std::fmt::Display;

/// Represents the type of minification error.
#[derive(Debug, Eq, PartialEq)]
pub enum ErrorType {
  ClosingTagMismatch { expected: String, got: String },
  NotFound(&'static str),
  UnexpectedEnd,
  UnexpectedClosingTag,
}

impl ErrorType {
  /// Generates an English message describing the error with any additional context.
  pub fn message(&self) -> String {
    match self {
      ErrorType::ClosingTagMismatch { expected, got } => {
        format!(
          "Closing tag name does not match opening tag (expected \"{}\", got \"{}\").",
          expected, got
        )
      }
      ErrorType::NotFound(exp) => {
        format!("Expected {}.", exp)
      }
      ErrorType::UnexpectedEnd => "Unexpected end of source code.".to_string(),
      ErrorType::UnexpectedClosingTag => "Unexpected closing tag.".to_string(),
    }
  }
}

/// Details about a minification failure, including where it occurred and why.
#[derive(Debug)]
pub struct Error {
  pub error_type: ErrorType,
  pub position: usize,
}

impl Display for Error {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(
      f,
      "{} [position {}]",
      self.error_type.message(),
      self.position
    )
  }
}

impl std::error::Error for Error {}

/// User-friendly details about a minification failure, including an English message description of
/// the reason, and generated printable contextual representation of the code where the error
/// occurred.
#[derive(Debug)]
pub struct FriendlyError {
  pub position: usize,
  pub message: String,
  pub code_context: String,
}

pub type ProcessingResult<T> = Result<T, ErrorType>;

#[inline(always)]
fn maybe_mark_indicator(
  line: &mut Vec<u8>,
  marker: u8,
  maybe_pos: isize,
  lower: usize,
  upper: usize,
) -> bool {
  let pos = maybe_pos as usize;
  if maybe_pos > -1 && pos >= lower && pos < upper {
    let pos_in_line = pos - lower;
    while line.len() <= pos_in_line {
      line.push(b' ');
    }
    line.insert(
      pos_in_line,
      if line[pos_in_line] != b' ' {
        b'B'
      } else {
        marker
      },
    );
    true
  } else {
    false
  }
}

// Pass -1 for read_pos or write_pos to prevent them from being represented.
pub fn debug_repr(code: &[u8], read_pos: isize, write_pos: isize) -> String {
  let only_one_pos = read_pos == -1 || write_pos == -1;
  let read_marker = if only_one_pos { b'^' } else { b'R' };
  let write_marker = if only_one_pos { b'^' } else { b'W' };
  let mut lines = Vec::<(isize, String)>::new();
  let mut cur_pos = 0;
  for (line_no, line) in code.split(|c| *c == b'\n').enumerate() {
    // Include '\n'. Note that the last line might not have '\n' but that's OK for these calculations.
    let len = line.len() + 1;
    let line_as_string = unsafe { String::from_utf8_unchecked(line.to_vec()) };
    lines.push(((line_no + 1) as isize, line_as_string));
    let new_pos = cur_pos + len;

    // Rust does lazy allocation by default, so this is not wasteful.
    let mut indicator_line = Vec::new();
    maybe_mark_indicator(
      &mut indicator_line,
      write_marker,
      write_pos,
      cur_pos,
      new_pos,
    );
    let marked_read =
      maybe_mark_indicator(&mut indicator_line, read_marker, read_pos, cur_pos, new_pos);
    if !indicator_line.is_empty() {
      lines.push((-1, unsafe { String::from_utf8_unchecked(indicator_line) }));
    };
    cur_pos = new_pos;
    if marked_read {
      break;
    };
  }

  let line_no_col_width = lines.len().to_string().len();
  let mut res = String::new();
  for (line_no, line) in lines {
    res.push_str(&format!(
      "{:>indent$}|{}\n",
      if line_no == -1 {
        ">".repeat(line_no_col_width)
      } else {
        line_no.to_string()
      },
      line,
      indent = line_no_col_width,
    ));
  }
  res
}

impl Display for FriendlyError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{} [position {}]", self.message, self.position)
  }
}

impl std::error::Error for FriendlyError {}