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 7278d887cf
commit 00f979b351
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. /// Destination for a deprecation message when accessing a deprecated value.
pub trait DeprecationSink { pub trait DeprecationSink {
/// Emits the given deprecation message into this sink. /// Emits the given deprecation message into this sink alongside a version
fn emit(self, message: &str); /// in which the deprecated item is planned to be removed.
fn emit(self, message: &str, until: Option<&str>);
} }
impl DeprecationSink for () { impl DeprecationSink for () {
fn emit(self, _: &str) {} fn emit(self, _: &str, _: Option<&str>) {}
} }
impl DeprecationSink for (&mut Engine<'_>, Span) { impl DeprecationSink for (&mut Engine<'_>, Span) {
/// Emits the deprecation message as a warning. /// Emits the deprecation message as a warning.
fn emit(self, message: &str) { fn emit(self, message: &str, version: Option<&str>) {
self.0.sink.warn(SourceDiagnostic::warning(self.1, message)); 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. /// The category of the binding.
category: Option<Category>, category: Option<Category>,
/// A deprecation message for the definition. /// 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. /// The different kinds of slots.
@ -274,7 +278,8 @@ impl Binding {
span, span,
kind: BindingKind::Normal, kind: BindingKind::Normal,
category: None, 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`. /// Marks this binding as deprecated, with the given `message`.
pub fn deprecated(&mut self, message: &'static str) -> &mut Self { 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 self
} }
@ -300,8 +313,8 @@ impl Binding {
/// - pass `()` to ignore the message. /// - pass `()` to ignore the message.
/// - pass `(&mut engine, span)` to emit a warning into the engine. /// - pass `(&mut engine, span)` to emit a warning into the engine.
pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value { pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value {
if let Some(message) = self.deprecation { if let Some(message) = self.deprecation_message {
sink.emit(message); sink.emit(message, self.deprecation_until);
} }
&self.value &self.value
} }
@ -337,8 +350,13 @@ impl Binding {
} }
/// A deprecation message for the value, if any. /// A deprecation message for the value, if any.
pub fn deprecation(&self) -> Option<&'static str> { pub fn deprecation_message(&self) -> Option<&'static str> {
self.deprecation 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. /// 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))) modifiers.best_match_in(list.variants().map(|(m, _, d)| (m, d)))
{ {
if let Some(message) = deprecation { if let Some(message) = deprecation {
sink.emit(message) sink.emit(message, None)
} }
return Ok(self); return Ok(self);
} }

View File

@ -291,8 +291,14 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
match binding.read() { match binding.read() {
Value::Func(func) => { Value::Func(func) => {
let name = func.name().unwrap(); let name = func.name().unwrap();
let subpage = let subpage = func_page(
func_page(resolver, &route, func, path, binding.deprecation()); resolver,
&route,
func,
path,
binding.deprecation_message(),
binding.deprecation_until(),
);
items.push(CategoryItem { items.push(CategoryItem {
name: name.into(), name: name.into(),
route: subpage.route.clone(), route: subpage.route.clone(),
@ -381,9 +387,11 @@ fn func_page(
parent: &str, parent: &str,
func: &Func, func: &Func,
path: &[&str], path: &[&str],
deprecation: Option<&'static str>, deprecation_message: Option<&'static str>,
deprecation_until: Option<&'static str>,
) -> PageModel { ) -> 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(); let name = func.name().unwrap();
PageModel { PageModel {
route: eco_format!("{parent}{}/", urlify(name)), route: eco_format!("{parent}{}/", urlify(name)),
@ -402,7 +410,8 @@ fn func_model(
func: &Func, func: &Func,
path: &[&str], path: &[&str],
nested: bool, nested: bool,
deprecation: Option<&'static str>, deprecation_message: Option<&'static str>,
deprecation_until: Option<&'static str>,
) -> FuncModel { ) -> FuncModel {
let name = func.name().unwrap(); let name = func.name().unwrap();
let scope = func.scope().unwrap(); let scope = func.scope().unwrap();
@ -438,7 +447,8 @@ fn func_model(
oneliner: oneliner(details), oneliner: oneliner(details),
element: func.element().is_some(), element: func.element().is_some(),
contextual: func.contextual().unwrap_or(false), contextual: func.contextual().unwrap_or(false),
deprecation, deprecation_message,
deprecation_until,
details: Html::markdown(resolver, details, nesting), details: Html::markdown(resolver, details, nesting),
example: example.map(|md| Html::markdown(resolver, md, None)), example: example.map(|md| Html::markdown(resolver, md, None)),
self_, self_,
@ -521,7 +531,14 @@ fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec<FuncM
.iter() .iter()
.filter_map(|(_, binding)| { .filter_map(|(_, binding)| {
let Value::Func(func) = binding.read() else { return None }; 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() .collect()
} }
@ -602,7 +619,14 @@ fn group_page(
let Ok(ref func) = binding.read().clone().cast::<Func>() else { let Ok(ref func) = binding.read().clone().cast::<Func>() else {
panic!("not a function") 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 id_base = urlify(&eco_format!("functions-{}", func.name));
let children = func_outline(&func, &id_base); let children = func_outline(&func, &id_base);
outline_items.push(OutlineItem { outline_items.push(OutlineItem {
@ -669,7 +693,7 @@ fn type_model(resolver: &dyn Resolver, ty: &Type) -> TypeModel {
constructor: ty constructor: ty
.constructor() .constructor()
.ok() .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()), 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)]| { let shorthand = |list: &[(&'static str, char)]| {
list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) 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) .filter(|(other, _, _)| other != &variant)
.map(|(other, _, _)| complete(other)) .map(|(other, _, _)| complete(other))
.collect(), .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 oneliner: EcoString,
pub element: bool, pub element: bool,
pub contextual: bool, pub contextual: bool,
pub deprecation: Option<&'static str>, pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
pub details: Html, pub details: Html,
/// This example is only for nested function models. Others can have /// This example is only for nested function models. Others can have
/// their example directly in their details. /// their example directly in their details.
@ -165,7 +166,8 @@ pub struct SymbolModel {
pub markup_shorthand: Option<&'static str>, pub markup_shorthand: Option<&'static str>,
pub math_shorthand: Option<&'static str>, pub math_shorthand: Option<&'static str>,
pub math_class: 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. /// Shorthands listed on a category page.