Add version to Binding deprecation warnings

This allows displaying a hint in which the version a binding may be
removed in. It helps to signal how urgent a package update may be to
package authors. The version is also sent to the proprietary doc
generator.
This commit is contained in:
tinger 2025-07-16 11:53:46 +02:00
parent b036fd97ab
commit e6ac31e92c
5 changed files with 77 additions and 26 deletions

View File

@ -234,18 +234,23 @@ impl From<SyntaxError> for SourceDiagnostic {
/// Destination for a deprecation message when accessing a deprecated value.
pub trait DeprecationSink {
/// Emits the given deprecation message into this sink.
fn emit(self, message: &str);
/// Emits the given deprecation message into this sink alongside a version
/// in which the deprecated item is planned to be removed.
fn emit(self, message: &str, until: Option<&str>);
}
impl DeprecationSink for () {
fn emit(self, _: &str) {}
fn emit(self, _: &str, _: Option<&str>) {}
}
impl DeprecationSink for (&mut Engine<'_>, Span) {
/// Emits the deprecation message as a warning.
fn emit(self, message: &str) {
self.0.sink.warn(SourceDiagnostic::warning(self.1, message));
fn emit(self, message: &str, version: Option<&str>) {
self.0.sink.warn(
SourceDiagnostic::warning(self.1, message).with_hints(
version.map(|v| eco_format!("this will be removed in {}", v)),
),
);
}
}

View File

@ -254,7 +254,11 @@ pub struct Binding {
/// The category of the binding.
category: Option<Category>,
/// A deprecation message for the definition.
deprecation: Option<&'static str>,
deprecation_message: Option<&'static str>,
/// A version in which the deprecated binding is planned to be removed.
///
/// This is ignored if `deprecation` is `None`.
deprecation_until: Option<&'static str>,
}
/// The different kinds of slots.
@ -274,7 +278,8 @@ impl Binding {
span,
kind: BindingKind::Normal,
category: None,
deprecation: None,
deprecation_message: None,
deprecation_until: None,
}
}
@ -285,7 +290,15 @@ impl Binding {
/// Marks this binding as deprecated, with the given `message`.
pub fn deprecated(&mut self, message: &'static str) -> &mut Self {
self.deprecation = Some(message);
self.deprecation_message = Some(message);
self
}
/// Set the version in which the binding is planned to be removed.
///
/// This is ignored if [`Binding::deprecated`] isn't also set.
pub fn deprecated_until(&mut self, version: &'static str) -> &mut Self {
self.deprecation_until = Some(version);
self
}
@ -300,8 +313,8 @@ impl Binding {
/// - pass `()` to ignore the message.
/// - pass `(&mut engine, span)` to emit a warning into the engine.
pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value {
if let Some(message) = self.deprecation {
sink.emit(message);
if let Some(message) = self.deprecation_message {
sink.emit(message, self.deprecation_until);
}
&self.value
}
@ -337,8 +350,13 @@ impl Binding {
}
/// A deprecation message for the value, if any.
pub fn deprecation(&self) -> Option<&'static str> {
self.deprecation
pub fn deprecation_message(&self) -> Option<&'static str> {
self.deprecation_message
}
/// The version in which a deprecated binding is planned to be removed.
pub fn deprecation_until(&self) -> Option<&'static str> {
self.deprecation_until
}
/// The category of the value, if any.

View File

@ -151,7 +151,7 @@ impl Symbol {
modifiers.best_match_in(list.variants().map(|(m, _, d)| (m, d)))
{
if let Some(message) = deprecation {
sink.emit(message)
sink.emit(message, None)
}
return Ok(self);
}

View File

@ -291,8 +291,14 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
match binding.read() {
Value::Func(func) => {
let name = func.name().unwrap();
let subpage =
func_page(resolver, &route, func, path, binding.deprecation());
let subpage = func_page(
resolver,
&route,
func,
path,
binding.deprecation_message(),
binding.deprecation_until(),
);
items.push(CategoryItem {
name: name.into(),
route: subpage.route.clone(),
@ -381,9 +387,11 @@ fn func_page(
parent: &str,
func: &Func,
path: &[&str],
deprecation: Option<&'static str>,
deprecation_message: Option<&'static str>,
deprecation_until: Option<&'static str>,
) -> PageModel {
let model = func_model(resolver, func, path, false, deprecation);
let model =
func_model(resolver, func, path, false, deprecation_message, deprecation_until);
let name = func.name().unwrap();
PageModel {
route: eco_format!("{parent}{}/", urlify(name)),
@ -402,7 +410,8 @@ fn func_model(
func: &Func,
path: &[&str],
nested: bool,
deprecation: Option<&'static str>,
deprecation_message: Option<&'static str>,
deprecation_until: Option<&'static str>,
) -> FuncModel {
let name = func.name().unwrap();
let scope = func.scope().unwrap();
@ -438,7 +447,8 @@ fn func_model(
oneliner: oneliner(details),
element: func.element().is_some(),
contextual: func.contextual().unwrap_or(false),
deprecation,
deprecation_message,
deprecation_until,
details: Html::markdown(resolver, details, nesting),
example: example.map(|md| Html::markdown(resolver, md, None)),
self_,
@ -521,7 +531,14 @@ fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec<FuncM
.iter()
.filter_map(|(_, binding)| {
let Value::Func(func) = binding.read() else { return None };
Some(func_model(resolver, func, &[name], true, binding.deprecation()))
Some(func_model(
resolver,
func,
&[name],
true,
binding.deprecation_message(),
binding.deprecation_until(),
))
})
.collect()
}
@ -602,7 +619,14 @@ fn group_page(
let Ok(ref func) = binding.read().clone().cast::<Func>() else {
panic!("not a function")
};
let func = func_model(resolver, func, &path, true, binding.deprecation());
let func = func_model(
resolver,
func,
&path,
true,
binding.deprecation_message(),
binding.deprecation_until(),
);
let id_base = urlify(&eco_format!("functions-{}", func.name));
let children = func_outline(&func, &id_base);
outline_items.push(OutlineItem {
@ -669,7 +693,7 @@ fn type_model(resolver: &dyn Resolver, ty: &Type) -> TypeModel {
constructor: ty
.constructor()
.ok()
.map(|func| func_model(resolver, &func, &[], true, None)),
.map(|func| func_model(resolver, &func, &[], true, None, None)),
scope: scope_models(resolver, ty.short_name(), ty.scope()),
}
}
@ -718,7 +742,7 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
}
};
for (variant, c, deprecation) in symbol.variants() {
for (variant, c, deprecation_message) in symbol.variants() {
let shorthand = |list: &[(&'static str, char)]| {
list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s)
};
@ -737,7 +761,9 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
.filter(|(other, _, _)| other != &variant)
.map(|(other, _, _)| complete(other))
.collect(),
deprecation: deprecation.or_else(|| binding.deprecation()),
deprecation_message: deprecation_message
.or_else(|| binding.deprecation_message()),
deprecation_until: binding.deprecation_until(),
});
}
}

View File

@ -89,7 +89,8 @@ pub struct FuncModel {
pub oneliner: EcoString,
pub element: bool,
pub contextual: bool,
pub deprecation: Option<&'static str>,
pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
pub details: Html,
/// This example is only for nested function models. Others can have
/// their example directly in their details.
@ -165,7 +166,8 @@ pub struct SymbolModel {
pub markup_shorthand: Option<&'static str>,
pub math_shorthand: Option<&'static str>,
pub math_class: Option<&'static str>,
pub deprecation: Option<&'static str>,
pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
}
/// Shorthands listed on a category page.