1#![doc = include_str!("../README.md")]
7#![deny(unsafe_code)]
8#![deny(missing_docs)]
9
10use proc_macro2::Span;
11use quote::{quote_spanned, ToTokens};
12use syn::Ident;
13
14mod test;
15
16#[derive(Clone)]
18#[cfg_attr(feature = "derive_debug", derive(Debug))]
19pub enum Warning {
20 Deprecated {
22 name: String,
24 index: Option<usize>,
26 message: String,
28 links: Vec<String>,
30 span: Span,
32 },
33}
34
35#[derive(Clone)]
39#[cfg_attr(feature = "derive_debug", derive(Debug))]
40pub enum FormattedWarning {
41 Deprecated {
43 name: Ident,
48 note: String,
50 span: Option<Span>,
54 },
55}
56
57impl FormattedWarning {
58 #[must_use]
60 pub fn new_deprecated<S, T>(name: S, note: T, span: Span) -> Self
61 where
62 S: AsRef<str>,
63 T: Into<String>,
64 {
65 Self::Deprecated {
66 name: Ident::new(name.as_ref(), span),
67 note: note.into(),
68 span: Some(span),
69 }
70 }
71}
72
73#[derive(Default, Clone)]
101#[cfg_attr(feature = "derive_debug", derive(Debug))]
102pub struct DeprecatedWarningBuilder {
103 title: String,
104 index: Option<usize>,
105 old: Option<String>,
106 new: Option<String>,
107 links: Vec<String>,
108 span: Option<Span>,
109}
110
111impl DeprecatedWarningBuilder {
112 #[must_use]
116 pub fn from_title<S: Into<String>>(title: S) -> Self {
117 Self { title: title.into(), ..Default::default() }
118 }
119
120 #[must_use]
124 pub fn index<S: Into<usize>>(self, index: S) -> Self {
125 Self { index: Some(index.into()), ..self }
126 }
127
128 #[must_use]
132 pub fn old<S: Into<String>>(self, old: S) -> Self {
133 Self { old: Some(old.into()), ..self }
134 }
135
136 #[must_use]
140 pub fn new<S: Into<String>>(self, new: S) -> Self {
141 Self { new: Some(new.into()), ..self }
142 }
143
144 #[must_use]
146 pub fn help_link<S: Into<String>>(self, link: S) -> Self {
147 Self { links: vec![link.into()], ..self }
148 }
149
150 #[must_use]
152 pub fn help_links(self, links: &[&str]) -> Self {
153 Self { links: links.iter().map(|s| (*s).into()).collect(), ..self }
154 }
155
156 #[must_use]
158 pub fn span(self, span: Span) -> Self {
159 Self { span: Some(span), ..self }
160 }
161
162 #[deprecated(note = "Use `try_build` instead; Will be removed after Q1 2024")]
164 pub fn maybe_build(self) -> Result<Warning, String> {
165 self.try_build()
166 }
167
168 pub fn try_build(self) -> Result<Warning, String> {
170 let span = self.span.unwrap_or_else(Span::call_site);
171 let title = self.title;
172 let old = self.old.ok_or("Missing old")?;
173 let new = self.new.ok_or("Missing new")?;
174 let message = format!("It is deprecated to {}.\nPlease instead {}.", old, new);
175
176 Ok(Warning::Deprecated { name: title, index: self.index, message, links: self.links, span })
177 }
178
179 #[must_use]
181 #[deprecated(note = "Use `build_or_panic` instead; Will be removed after Q1 2024")]
182 pub fn build(self) -> Warning {
183 self.build_or_panic()
184 }
185
186 #[must_use]
188 pub fn build_or_panic(self) -> Warning {
189 self.try_build().expect("maybe_build failed")
190 }
191}
192
193impl Warning {
194 #[must_use]
196 pub fn new_deprecated<S: Into<String>>(title: S) -> DeprecatedWarningBuilder {
197 DeprecatedWarningBuilder::from_title(title)
198 }
199
200 fn final_deprecated_message(&self) -> String {
202 let (message, links) = match self {
203 Self::Deprecated { message, links, .. } => (message, links),
204 };
205
206 let lines = message.trim().lines().map(|line| line.trim_start());
207 let message = lines.map(|line| format!("\t\t{}", line)).collect::<Vec<_>>().join("\n");
209
210 if !links.is_empty() {
211 let link =
212 links.iter().map(|l| format!("<{}>", l)).collect::<Vec<_>>().join("\n\t\t\t");
213 format!("\n{}\n\n\t\tFor more info see:\n\t\t\t{}", message, link)
214 } else {
215 format!("\n{}", message)
216 }
217 }
218
219 fn final_deprecated_name(&self) -> Ident {
221 let (index, name, span) = match self {
222 Self::Deprecated { index, name, span, .. } => (*index, name, *span),
223 };
224
225 let name = match index {
226 Some(i) => format!("{}_{}", name, i),
227 None => name.clone(),
228 };
229
230 Ident::new(&name, span)
231 }
232}
233
234impl From<Warning> for FormattedWarning {
235 fn from(val: Warning) -> Self {
236 match val {
237 Warning::Deprecated { span, .. } => Self::Deprecated {
238 name: val.final_deprecated_name(),
239 note: val.final_deprecated_message(),
240 span: Some(span),
241 },
242 }
243 }
244}
245
246impl ToTokens for Warning {
247 fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
248 let formatted: FormattedWarning = self.clone().into();
249 formatted.to_tokens(stream);
250 }
251}
252
253impl ToTokens for FormattedWarning {
254 fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
255 let (name, note, span) = match self {
256 Self::Deprecated { name, note, span } => (name, note, span),
257 };
258 let span = span.unwrap_or_else(Span::call_site);
259
260 let q = quote_spanned!(span =>
261 #[allow(dead_code)]
265 #[allow(non_camel_case_types)]
266 #[allow(non_snake_case)]
267 fn #name() {
268 #[deprecated(note = #note)]
269 #[allow(non_upper_case_globals)]
270 const _w: () = ();
271 let _ = _w;
272 }
273 );
274 q.to_tokens(stream);
275 }
276}