diff --git a/crates/typst-library/src/diag.rs b/crates/typst-library/src/diag.rs index 2b9ff6376..1b055a8ac 100644 --- a/crates/typst-library/src/diag.rs +++ b/crates/typst-library/src/diag.rs @@ -234,18 +234,23 @@ impl From 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)), + ), + ); } } diff --git a/crates/typst-library/src/foundations/scope.rs b/crates/typst-library/src/foundations/scope.rs index 551e3dd78..22691869d 100644 --- a/crates/typst-library/src/foundations/scope.rs +++ b/crates/typst-library/src/foundations/scope.rs @@ -253,8 +253,8 @@ pub struct Binding { span: Span, /// The category of the binding. category: Option, - /// A deprecation message for the definition. - deprecation: Option<&'static str>, + /// The deprecation information if this item is deprecated. + deprecation: Option>, } /// The different kinds of slots. @@ -285,7 +285,19 @@ 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 + .get_or_insert_with(|| Box::new(DeprecationInfo::new())) + .deprecated_message(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 + .get_or_insert_with(|| Box::new(DeprecationInfo::new())) + .deprecated_until(version); self } @@ -300,8 +312,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(info) = &self.deprecation { + sink.emit(info.message, info.until); } &self.value } @@ -337,8 +349,8 @@ impl Binding { } /// A deprecation message for the value, if any. - pub fn deprecation(&self) -> Option<&'static str> { - self.deprecation + pub fn deprecation(&self) -> Option<&DeprecationInfo> { + self.deprecation.as_deref() } /// The category of the value, if any. @@ -356,6 +368,51 @@ pub enum Capturer { Context, } +/// Information about a deprecated binding. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct DeprecationInfo { + /// A deprecation message for the definition. + message: &'static str, + /// A version in which the deprecated binding is planned to be removed. + until: Option<&'static str>, +} + +impl DeprecationInfo { + /// Creates new deprecation info with a default message to display when + /// emitting the deprecation warning. + pub fn new() -> Self { + Self { message: "item is deprecated", until: None } + } + + /// Set the message to display when emitting the deprecation warning. + pub fn deprecated_message(&mut self, message: &'static str) -> &mut Self { + self.message = message; + self + } + + /// Set the version in which the binding is planned to be removed. + pub fn deprecated_until(&mut self, version: &'static str) -> &mut Self { + self.until = Some(version); + self + } + + /// The message to display when emitting the deprecation warning. + pub fn message(&self) -> &'static str { + self.message + } + + /// The version in which the binding is planned to be removed. + pub fn until(&self) -> Option<&'static str> { + self.until + } +} + +impl Default for DeprecationInfo { + fn default() -> Self { + Self::new() + } +} + /// The error message when trying to mutate a variable from the standard /// library. #[cold] diff --git a/crates/typst-library/src/foundations/symbol.rs b/crates/typst-library/src/foundations/symbol.rs index 898068afb..c3a13d07c 100644 --- a/crates/typst-library/src/foundations/symbol.rs +++ b/crates/typst-library/src/foundations/symbol.rs @@ -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); } diff --git a/crates/typst-library/src/loading/cbor.rs b/crates/typst-library/src/loading/cbor.rs index f6661395c..268da2543 100644 --- a/crates/typst-library/src/loading/cbor.rs +++ b/crates/typst-library/src/loading/cbor.rs @@ -33,7 +33,10 @@ pub fn cbor( impl cbor { /// Reads structured data from CBOR bytes. #[func(title = "Decode CBOR")] - #[deprecated = "`cbor.decode` is deprecated, directly pass bytes to `cbor` instead"] + #[deprecated( + message = "`cbor.decode` is deprecated, directly pass bytes to `cbor` instead", + until = "0.15.0" + )] pub fn decode( engine: &mut Engine, /// CBOR data. diff --git a/crates/typst-library/src/loading/csv.rs b/crates/typst-library/src/loading/csv.rs index a12eb9766..9c184c3a4 100644 --- a/crates/typst-library/src/loading/csv.rs +++ b/crates/typst-library/src/loading/csv.rs @@ -95,7 +95,10 @@ pub fn csv( impl csv { /// Reads structured data from a CSV string/bytes. #[func(title = "Decode CSV")] - #[deprecated = "`csv.decode` is deprecated, directly pass bytes to `csv` instead"] + #[deprecated( + message = "`csv.decode` is deprecated, directly pass bytes to `csv` instead", + until = "0.15.0" + )] pub fn decode( engine: &mut Engine, /// CSV data. diff --git a/crates/typst-library/src/loading/json.rs b/crates/typst-library/src/loading/json.rs index a503825b4..156920ca6 100644 --- a/crates/typst-library/src/loading/json.rs +++ b/crates/typst-library/src/loading/json.rs @@ -67,7 +67,10 @@ pub fn json( impl json { /// Reads structured data from a JSON string/bytes. #[func(title = "Decode JSON")] - #[deprecated = "`json.decode` is deprecated, directly pass bytes to `json` instead"] + #[deprecated( + message = "`json.decode` is deprecated, directly pass bytes to `json` instead", + until = "0.15.0" + )] pub fn decode( engine: &mut Engine, /// JSON data. diff --git a/crates/typst-library/src/loading/toml.rs b/crates/typst-library/src/loading/toml.rs index a14162d6b..ae03e9fac 100644 --- a/crates/typst-library/src/loading/toml.rs +++ b/crates/typst-library/src/loading/toml.rs @@ -41,7 +41,10 @@ pub fn toml( impl toml { /// Reads structured data from a TOML string/bytes. #[func(title = "Decode TOML")] - #[deprecated = "`toml.decode` is deprecated, directly pass bytes to `toml` instead"] + #[deprecated( + message = "`toml.decode` is deprecated, directly pass bytes to `toml` instead", + until = "0.15.0" + )] pub fn decode( engine: &mut Engine, /// TOML data. diff --git a/crates/typst-library/src/loading/xml.rs b/crates/typst-library/src/loading/xml.rs index 9a6197df7..41a7d938e 100644 --- a/crates/typst-library/src/loading/xml.rs +++ b/crates/typst-library/src/loading/xml.rs @@ -75,7 +75,10 @@ pub fn xml( impl xml { /// Reads structured data from an XML string/bytes. #[func(title = "Decode XML")] - #[deprecated = "`xml.decode` is deprecated, directly pass bytes to `xml` instead"] + #[deprecated( + message = "`xml.decode` is deprecated, directly pass bytes to `xml` instead", + until = "0.15.0" + )] pub fn decode( engine: &mut Engine, /// XML data. diff --git a/crates/typst-library/src/loading/yaml.rs b/crates/typst-library/src/loading/yaml.rs index e26438cf5..1be6b5979 100644 --- a/crates/typst-library/src/loading/yaml.rs +++ b/crates/typst-library/src/loading/yaml.rs @@ -54,7 +54,10 @@ pub fn yaml( impl yaml { /// Reads structured data from a YAML string/bytes. #[func(title = "Decode YAML")] - #[deprecated = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead"] + #[deprecated( + message = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead", + until = "0.15.0" + )] pub fn decode( engine: &mut Engine, /// YAML data. diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 1a0e1e97a..ceaca1bbe 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -178,7 +178,10 @@ pub struct ImageElem { impl ImageElem { /// Decode a raster or vector graphic from bytes or a string. #[func(title = "Decode Image")] - #[deprecated = "`image.decode` is deprecated, directly pass bytes to `image` instead"] + #[deprecated( + message = "`image.decode` is deprecated, directly pass bytes to `image` instead", + until = "0.15.0" + )] pub fn decode( span: Span, /// The data to decode as an image. Can be a string for SVGs. diff --git a/crates/typst-library/src/visualize/mod.rs b/crates/typst-library/src/visualize/mod.rs index 72a420657..d116db73b 100644 --- a/crates/typst-library/src/visualize/mod.rs +++ b/crates/typst-library/src/visualize/mod.rs @@ -46,6 +46,7 @@ pub(super) fn define(global: &mut Scope) { .deprecated("the `path` function is deprecated, use `curve` instead"); global .define("pattern", Type::of::()) - .deprecated("the name `pattern` is deprecated, use `tiling` instead"); + .deprecated("the name `pattern` is deprecated, use `tiling` instead") + .deprecated_until("0.15.0"); global.reset_category(); } diff --git a/crates/typst-macros/src/scope.rs b/crates/typst-macros/src/scope.rs index 4df4f2487..ece5bbf3b 100644 --- a/crates/typst-macros/src/scope.rs +++ b/crates/typst-macros/src/scope.rs @@ -1,7 +1,8 @@ use heck::ToKebabCase; use proc_macro2::TokenStream; use quote::quote; -use syn::{Result, parse_quote}; +use syn::punctuated::Punctuated; +use syn::{MetaNameValue, Result, Token, parse_quote}; use crate::util::{BareType, foundations}; @@ -52,13 +53,31 @@ pub fn scope(_: TokenStream, item: syn::Item) -> Result { _ => bail!(child, "unexpected item in scope"), }; - if let Some(message) = attrs.iter().find_map(|attr| match &attr.meta { - syn::Meta::NameValue(pair) if pair.path.is_ident("deprecated") => { - Some(&pair.value) + if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("deprecated")) { + match &attr.meta { + syn::Meta::NameValue(pair) if pair.path.is_ident("deprecated") => { + let message = &pair.value; + def = quote! { #def.deprecated(#message) } + } + syn::Meta::List(list) if list.path.is_ident("deprecated") => { + let args = list.parse_args_with( + Punctuated::::parse_separated_nonempty, + )?; + + if let Some(message) = args.iter().find_map(|pair| { + pair.path.is_ident("message").then_some(&pair.value) + }) { + def = quote! { #def.deprecated(#message) } + } + + if let Some(version) = args.iter().find_map(|pair| { + pair.path.is_ident("until").then_some(&pair.value) + }) { + def = quote! { #def.deprecated_until(#version) } + } + } + _ => {} } - _ => None, - }) { - def = quote! { #def.deprecated(#message) } } definitions.push(def); diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 352b90dec..4844619af 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -17,6 +17,7 @@ use serde::Deserialize; use serde_yaml as yaml; use std::sync::LazyLock; use typst::diag::{StrResult, bail}; +use typst::foundations::DeprecationInfo; use typst::foundations::{ AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope, Smart, Type, Value, @@ -381,7 +382,7 @@ fn func_page( parent: &str, func: &Func, path: &[&str], - deprecation: Option<&'static str>, + deprecation: Option<&DeprecationInfo>, ) -> PageModel { let model = func_model(resolver, func, path, false, deprecation); let name = func.name().unwrap(); @@ -402,7 +403,7 @@ fn func_model( func: &Func, path: &[&str], nested: bool, - deprecation: Option<&'static str>, + deprecation: Option<&DeprecationInfo>, ) -> FuncModel { let name = func.name().unwrap(); let scope = func.scope().unwrap(); @@ -438,7 +439,8 @@ fn func_model( oneliner: oneliner(details), element: func.element().is_some(), contextual: func.contextual().unwrap_or(false), - deprecation, + deprecation_message: deprecation.map(DeprecationInfo::message), + deprecation_until: deprecation.and_then(DeprecationInfo::until), details: Html::markdown(resolver, details, nesting), example: example.map(|md| Html::markdown(resolver, md, None)), self_, @@ -718,7 +720,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 +739,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().map(DeprecationInfo::message)), + deprecation_until: binding.deprecation().and_then(DeprecationInfo::until), }); } } diff --git a/docs/src/model.rs b/docs/src/model.rs index e3b15e377..612592cdd 100644 --- a/docs/src/model.rs +++ b/docs/src/model.rs @@ -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. diff --git a/tests/suite/loading/cbor.typ b/tests/suite/loading/cbor.typ index 4b50bb9c3..526ef8c53 100644 --- a/tests/suite/loading/cbor.typ +++ b/tests/suite/loading/cbor.typ @@ -1,3 +1,4 @@ --- cbor-decode-deprecated --- // Warning: 15-21 `cbor.decode` is deprecated, directly pass bytes to `cbor` instead +// Hint: 15-21 this will be removed in 0.15.0 #let _ = cbor.decode diff --git a/tests/suite/loading/csv.typ b/tests/suite/loading/csv.typ index 046345bec..ffcaa8508 100644 --- a/tests/suite/loading/csv.typ +++ b/tests/suite/loading/csv.typ @@ -32,4 +32,5 @@ --- csv-decode-deprecated --- // Warning: 14-20 `csv.decode` is deprecated, directly pass bytes to `csv` instead +// Hint: 14-20 this will be removed in 0.15.0 #let _ = csv.decode diff --git a/tests/suite/loading/json.typ b/tests/suite/loading/json.typ index 9e433992d..5675033af 100644 --- a/tests/suite/loading/json.typ +++ b/tests/suite/loading/json.typ @@ -11,6 +11,7 @@ --- json-decode-deprecated --- // Warning: 15-21 `json.decode` is deprecated, directly pass bytes to `json` instead +// Hint: 15-21 this will be removed in 0.15.0 #let _ = json.decode --- issue-3363-json-large-number --- diff --git a/tests/suite/loading/toml.typ b/tests/suite/loading/toml.typ index 9d65da452..e5df31118 100644 --- a/tests/suite/loading/toml.typ +++ b/tests/suite/loading/toml.typ @@ -42,4 +42,5 @@ --- toml-decode-deprecated --- // Warning: 15-21 `toml.decode` is deprecated, directly pass bytes to `toml` instead +// Hint: 15-21 this will be removed in 0.15.0 #let _ = toml.decode diff --git a/tests/suite/loading/xml.typ b/tests/suite/loading/xml.typ index eed7db0ae..b29388410 100644 --- a/tests/suite/loading/xml.typ +++ b/tests/suite/loading/xml.typ @@ -29,4 +29,5 @@ --- xml-decode-deprecated --- // Warning: 14-20 `xml.decode` is deprecated, directly pass bytes to `xml` instead +// Hint: 14-20 this will be removed in 0.15.0 #let _ = xml.decode diff --git a/tests/suite/loading/yaml.typ b/tests/suite/loading/yaml.typ index ad171c6ef..ac47f182e 100644 --- a/tests/suite/loading/yaml.typ +++ b/tests/suite/loading/yaml.typ @@ -18,4 +18,5 @@ --- yaml-decode-deprecated --- // Warning: 15-21 `yaml.decode` is deprecated, directly pass bytes to `yaml` instead +// Hint: 15-21 this will be removed in 0.15.0 #let _ = yaml.decode diff --git a/tests/suite/visualize/image.typ b/tests/suite/visualize/image.typ index 7c5fb2916..1d9696582 100644 --- a/tests/suite/visualize/image.typ +++ b/tests/suite/visualize/image.typ @@ -188,26 +188,31 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B --- image-decode-svg --- // Test parsing from svg data // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead +// Hint: 8-14 this will be removed in 0.15.0 #image.decode(``.text, format: "svg") --- image-decode-bad-svg --- // Error: 15-152 failed to parse SVG (missing root node at 1:1) // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead +// Hint: 8-14 this will be removed in 0.15.0 #image.decode(``.text, format: "svg") --- image-decode-detect-format --- // Test format auto detect // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead +// Hint: 8-14 this will be removed in 0.15.0 #image.decode(read("/assets/images/tiger.jpg", encoding: none), width: 80%) --- image-decode-specify-format --- // Test format manual // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead +// Hint: 8-14 this will be removed in 0.15.0 #image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "jpg", width: 80%) --- image-decode-specify-wrong-format --- // Error: 2-91 failed to decode image (Format error decoding Png: Invalid PNG signature.) // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead +// Hint: 8-14 this will be removed in 0.15.0 #image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "png", width: 80%) --- image-pixmap-empty --- diff --git a/tests/suite/visualize/tiling.typ b/tests/suite/visualize/tiling.typ index 904133411..a16f4fe92 100644 --- a/tests/suite/visualize/tiling.typ +++ b/tests/suite/visualize/tiling.typ @@ -161,5 +161,6 @@ #set page(width: auto, height: auto, margin: 0pt) // Warning: 10-17 the name `pattern` is deprecated, use `tiling` instead +// Hint: 10-17 this will be removed in 0.15.0 #let t = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%))) #rect(width: 50pt, height: 50pt, fill: t)