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
168
169
170
171
172
173
174
175
176
177
//! The deprecation-warning registry.
//!
//! Each deprecation dart-sass 1.100 fires has a stable `[id]` tag, an optional
//! `More info` URL line, and a (possibly multi-line, possibly dynamic) message
//! body. This module models the ids the evaluator emits and renders the header
//! block (everything above the snippet), byte-for-byte from the captured
//! fixtures in `tests/fixtures/diagnostics/deprecation-*`.
//!
//! The snippet + 4-space-indented stack trace are appended by the evaluator
//! (it owns the source/url/glyph context); this module only produces the
//! `DEPRECATION WARNING [id]: …` header and any `More info` lines.
/// A single deprecation occurrence: its id tag, the message body (which may be
/// several lines and carry dynamic content), and the optional `More info` line.
pub(crate) struct Deprecation {
/// The `[id]` tag printed in the header.
pub id: &'static str,
/// The message body, printed right after `DEPRECATION WARNING [id]: `. May
/// contain embedded newlines for multi-line messages.
pub message: String,
/// The text of the trailing line, e.g.
/// `More info and automated migrator: https://sass-lang.com/d/import` or
/// `call-string`'s `Recommendation: …`, or `None` for the ids dart-sass
/// prints without one.
pub more_info: Option<String>,
}
impl Deprecation {
/// The `@import` deprecation — a fully static message.
pub(crate) fn import() -> Self {
Deprecation {
id: "import",
message: "Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.".to_string(),
more_info: Some("More info and automated migrator: https://sass-lang.com/d/import".to_string()),
}
}
/// The `global-builtin` deprecation: a global function that has a
/// `sass:*` module equivalent. `replacement` is the member dart names
/// (`map.get`, `color.adjust`, `math.is-unitless`), and the info line is
/// the migrator's — dart prints the `@import` URL here, not one of its own.
pub(crate) fn global_builtin(replacement: &str) -> Self {
Deprecation {
id: "global-builtin",
message: format!(
"Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0.\nUse {replacement} instead."
),
more_info: Some("More info and automated migrator: https://sass-lang.com/d/import".to_string()),
}
}
/// The `feature-exists` deprecation: the function itself is going away, in
/// its global spelling and as `meta.feature-exists` alike.
pub(crate) fn feature_exists() -> Self {
Deprecation {
id: "feature-exists",
message: "The feature-exists() function is deprecated.".to_string(),
more_info: Some("More info: https://sass-lang.com/d/feature-exists".to_string()),
}
}
/// The `call-string` deprecation: `call("name")` looks a function up by
/// name instead of taking a reference. The trailing line is a
/// `Recommendation:` rather than a `More info:`, in the same slot.
pub(crate) fn call_string(name: &str) -> Self {
Deprecation {
id: "call-string",
message: "Passing a string to call() is deprecated and will be illegal in Dart Sass 2.0.0."
.to_string(),
// The name is a STRING in the suggested code, so it is serialized
// as one — dart writes `call(get-function('a\\"b'))` for a name
// that holds a quote.
more_info: Some(format!(
"Recommendation: call(get-function({}))",
crate::value::serialize_quoted(name)
)),
}
}
/// The `color-functions` deprecation: a legacy `sass:color` member that
/// Color 4 replaced. `qualified` is the name dart prints — `red()` for the
/// global spelling, `color.red()` for one reached through the module — and
/// `suggestions` is the replacement code, one line each, computed from the
/// call's own arguments. dart labels one "Suggestion" and several
/// "Suggestions".
pub(crate) fn color_functions(qualified: &str, suggestions: &[String]) -> Self {
let label = if suggestions.len() == 1 {
"Suggestion"
} else {
"Suggestions"
};
Deprecation {
id: "color-functions",
message: format!(
"{qualified}() is deprecated. {label}:\n\n{}",
suggestions.join("\n")
),
more_info: Some("More info: https://sass-lang.com/d/color-functions".to_string()),
}
}
/// The `bogus-combinators` deprecation for a selector dart-sass drops:
/// a repeated combinator run (`a > + b`) or a leading one outside a
/// relative context. The rule is omitted from the CSS, and this warning is
/// the only sign that it was — sasso dropped these silently until now
/// (#119), which on Lichess's tree was 45 rules vanishing with no notice.
///
/// `selector` is the RESOLVED selector, parents included, because that is
/// what dart names: `.tview2 .inaccuracy > + lines`, not the `.#{$name} >
/// + lines` that produced it.
///
/// dart's other `bogus-combinators` message — a TRAILING combinator on a
/// rule that has declarations of its own ("is only valid for nesting and
/// shouldn't have children other than style rules") — carries a second
/// span labelling the offending child, which this renderer has no way to
/// produce yet. Trailing combinators stay silent for now.
pub(crate) fn bogus_combinators(selector: &str) -> Self {
Deprecation {
id: "bogus-combinators",
message: format!(
"The selector \"{selector}\" is invalid CSS. It will be omitted from the generated CSS.\nThis will be an error in Dart Sass 2.0.0."
),
more_info: Some("More info: https://sass-lang.com/d/bogus-combinators".to_string()),
}
}
/// The `if-function` deprecation: the legacy `if($c, $t, $f)` in favour of
/// the modern CSS `if()`. `suggestion` is the rewritten call, present only
/// when the arguments are the three positional ones the rewrite needs —
/// dart omits the line entirely for a named, splatted or wrong-arity call
/// and still deprecates it.
pub(crate) fn if_function(suggestion: Option<&str>) -> Self {
let mut message = "The Sass if() syntax is deprecated in favor of the modern CSS syntax.".to_string();
if let Some(s) = suggestion {
message.push_str("\n\nSuggestion: ");
message.push_str(s);
}
Deprecation {
id: "if-function",
message,
more_info: Some("More info: https://sass-lang.com/d/if-function".to_string()),
}
}
/// Render the header block: `DEPRECATION WARNING [id]: <message>` followed
/// by a blank line and the `More info` line (when present), then a blank
/// line and the top snippet-gutter is left to the caller. Returns the lines
/// from the header down to (and including) the blank line that precedes the
/// snippet — i.e. everything before the ` ,` gutter row.
pub(crate) fn render_header(&self) -> String {
let mut out = format!("DEPRECATION WARNING [{}]: {}\n", self.id, self.message);
if let Some(info) = &self.more_info {
out.push('\n');
out.push_str(info);
out.push('\n');
}
out.push('\n');
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn import_header_matches_fixture_prefix() {
let d = Deprecation::import();
let expected = "\
DEPRECATION WARNING [import]: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
More info and automated migrator: https://sass-lang.com/d/import
";
assert_eq!(d.render_header(), expected);
}
}