1use std::fmt;
30
31#[derive(#[automatically_derived]
impl ::core::fmt::Debug for LifecycleStage {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
LifecycleStage::Experimental => "Experimental",
LifecycleStage::Stable => "Stable",
LifecycleStage::Superseded => "Superseded",
LifecycleStage::SoftDeprecated => "SoftDeprecated",
LifecycleStage::Deprecated => "Deprecated",
LifecycleStage::Defunct => "Defunct",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for LifecycleStage { }
#[automatically_derived]
impl ::core::clone::Clone for LifecycleStage {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for LifecycleStage { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for LifecycleStage { }
#[automatically_derived]
impl ::core::cmp::PartialEq for LifecycleStage {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for LifecycleStage { }Eq, #[automatically_derived]
impl ::core::default::Default for LifecycleStage {
#[inline]
fn default() -> Self { Self::Stable }
}Default)]
33pub enum LifecycleStage {
34 Experimental,
36 #[default]
38 Stable,
39 Superseded,
41 SoftDeprecated,
43 Deprecated,
45 Defunct,
47}
48
49impl LifecycleStage {
50 pub fn from_str(s: &str) -> Option<Self> {
55 match s.to_lowercase().as_str() {
56 "experimental" => Some(Self::Experimental),
57 "stable" => Some(Self::Stable),
58 "superseded" => Some(Self::Superseded),
59 "soft-deprecated" | "soft_deprecated" => Some(Self::SoftDeprecated),
60 "deprecated" => Some(Self::Deprecated),
61 "defunct" => Some(Self::Defunct),
62 _ => None,
63 }
64 }
65
66 pub fn badge(&self) -> Option<&'static str> {
72 match self {
73 Self::Experimental => Some(r#"`r lifecycle::badge("experimental")`"#),
74 Self::Stable => None, Self::Superseded => Some(r#"`r lifecycle::badge("superseded")`"#),
76 Self::SoftDeprecated | Self::Deprecated => {
77 Some(r#"`r lifecycle::badge("deprecated")`"#)
78 }
79 Self::Defunct => Some(r#"`r lifecycle::badge("deprecated")`"#),
80 }
81 }
82
83 pub fn signal_fn(&self) -> Option<&'static str> {
89 match self {
90 Self::Experimental => Some("lifecycle::signal_stage"),
91 Self::Stable => None,
92 Self::Superseded => Some("lifecycle::signal_stage"),
93 Self::SoftDeprecated => Some("lifecycle::deprecate_soft"),
94 Self::Deprecated => Some("lifecycle::deprecate_warn"),
95 Self::Defunct => Some("lifecycle::deprecate_stop"),
96 }
97 }
98
99 pub fn import_from_fn(&self) -> Option<&'static str> {
103 match self {
104 Self::Experimental | Self::Superseded => Some("signal_stage"),
105 Self::Stable => None,
106 Self::SoftDeprecated => Some("deprecate_soft"),
107 Self::Deprecated => Some("deprecate_warn"),
108 Self::Defunct => Some("deprecate_stop"),
109 }
110 }
111
112 pub fn keywords(&self) -> Option<&'static str> {
117 match self {
118 Self::Experimental => Some("internal"),
119 Self::Stable => None,
120 Self::Superseded => None,
121 Self::SoftDeprecated | Self::Deprecated | Self::Defunct => None,
122 }
123 }
124}
125
126impl fmt::Display for LifecycleStage {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 Self::Experimental => f.write_fmt(format_args!("experimental"))write!(f, "experimental"),
130 Self::Stable => f.write_fmt(format_args!("stable"))write!(f, "stable"),
131 Self::Superseded => f.write_fmt(format_args!("superseded"))write!(f, "superseded"),
132 Self::SoftDeprecated => f.write_fmt(format_args!("soft-deprecated"))write!(f, "soft-deprecated"),
133 Self::Deprecated => f.write_fmt(format_args!("deprecated"))write!(f, "deprecated"),
134 Self::Defunct => f.write_fmt(format_args!("defunct"))write!(f, "defunct"),
135 }
136 }
137}
138
139#[derive(#[automatically_derived]
impl ::core::fmt::Debug for LifecycleSpec {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["stage", "when", "what", "with", "details", "id"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.stage, &self.when, &self.what, &self.with, &self.details,
&&self.id];
::core::fmt::Formatter::debug_struct_fields_finish(f, "LifecycleSpec",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for LifecycleSpec {
#[inline]
fn clone(&self) -> Self {
LifecycleSpec {
stage: ::core::clone::Clone::clone(&self.stage),
when: ::core::clone::Clone::clone(&self.when),
what: ::core::clone::Clone::clone(&self.what),
with: ::core::clone::Clone::clone(&self.with),
details: ::core::clone::Clone::clone(&self.details),
id: ::core::clone::Clone::clone(&self.id),
}
}
}Clone, #[automatically_derived]
impl ::core::default::Default for LifecycleSpec {
#[inline]
fn default() -> Self {
LifecycleSpec {
stage: ::core::default::Default::default(),
when: ::core::default::Default::default(),
what: ::core::default::Default::default(),
with: ::core::default::Default::default(),
details: ::core::default::Default::default(),
id: ::core::default::Default::default(),
}
}
}Default)]
141pub struct LifecycleSpec {
142 pub stage: LifecycleStage,
144 pub when: Option<String>,
146 pub what: Option<String>,
149 pub with: Option<String>,
151 pub details: Option<String>,
153 pub id: Option<String>,
155}
156
157impl LifecycleSpec {
158 pub fn new(stage: LifecycleStage) -> Self {
160 Self {
161 stage,
162 ..Default::default()
163 }
164 }
165
166 pub fn from_deprecated(since: Option<&str>, note: Option<&str>) -> Self {
172 let mut spec = Self::new(LifecycleStage::Deprecated);
173 spec.when = since.map(String::from);
174
175 if let Some(note) = note {
177 if let Some(rest) = note.to_lowercase().strip_prefix("use ") {
178 if let Some(end) = rest.find(" instead") {
179 spec.with = Some(rest[..end].to_string());
180 } else {
181 spec.with = Some(rest.to_string());
182 }
183 }
184 spec.details = Some(note.to_string());
185 }
186
187 spec
188 }
189
190 pub fn r_prelude(&self, fn_name: &str) -> Option<String> {
199 let signal_fn = self.stage.signal_fn()?;
200
201 let esc = crate::naming::r_string_escape;
203 let what = esc(self.what.as_deref().unwrap_or(fn_name));
204
205 match self.stage {
206 LifecycleStage::Experimental | LifecycleStage::Superseded => {
207 Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}(\"{1}\", \"{2}()\")",
signal_fn, self.stage, what))
})format!("{}(\"{}\", \"{}()\")", signal_fn, self.stage, what))
209 }
210 LifecycleStage::SoftDeprecated
211 | LifecycleStage::Deprecated
212 | LifecycleStage::Defunct => {
213 let when = esc(self.when.as_deref().unwrap_or("0.0.0"));
215 let what_arg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}()\"", what))
})format!("\"{}()\"", what);
216 let with_arg = self
217 .with
218 .as_ref()
219 .map(|w| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!(", \"{0}\"", esc(w)))
})format!(", \"{}\"", esc(w)))
220 .unwrap_or_default();
221 let details_arg = self
222 .details
223 .as_ref()
224 .map(|d| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!(", details = \"{0}\"", esc(d)))
})format!(", details = \"{}\"", esc(d)))
225 .unwrap_or_default();
226 let id_arg = self
227 .id
228 .as_ref()
229 .map(|id| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!(", id = \"{0}\"", esc(id)))
})format!(", id = \"{}\"", esc(id)))
230 .unwrap_or_default();
231
232 Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}(\"{1}\", {2}{3}{4}{5})",
signal_fn, when, what_arg, with_arg, details_arg, id_arg))
})format!(
233 "{}(\"{}\", {}{}{}{})",
234 signal_fn, when, what_arg, with_arg, details_arg, id_arg
235 ))
236 }
237 LifecycleStage::Stable => None,
238 }
239 }
240}
241
242pub fn collect_lifecycle_imports<'a>(
248 specs: impl Iterator<Item = &'a LifecycleSpec>,
249) -> Option<String> {
250 let mut fns = std::collections::BTreeSet::new();
251 for spec in specs {
252 if spec.stage.badge().is_some() {
253 fns.insert("badge");
254 }
255 if let Some(fn_name) = spec.stage.import_from_fn() {
256 fns.insert(fn_name);
257 }
258 }
259 if fns.is_empty() {
260 None
261 } else {
262 let mut import = String::from("@importFrom lifecycle");
263 for f in fns {
264 import.push(' ');
265 import.push_str(f);
266 }
267 Some(import)
268 }
269}
270
271pub fn parse_lifecycle_attr(meta: &syn::Meta) -> syn::Result<Option<LifecycleSpec>> {
277 use syn::spanned::Spanned;
278
279 match meta {
280 syn::Meta::NameValue(nv) if nv.path.is_ident("lifecycle") => {
281 if let syn::Expr::Lit(syn::ExprLit {
283 lit: syn::Lit::Str(lit),
284 ..
285 }) = &nv.value
286 {
287 let stage = LifecycleStage::from_str(&lit.value()).ok_or_else(|| {
288 syn::Error::new_spanned(
289 lit,
290 "invalid lifecycle stage; expected one of: experimental, stable, superseded, soft-deprecated, deprecated, defunct",
291 )
292 })?;
293 return Ok(Some(LifecycleSpec::new(stage)));
294 }
295 Err(syn::Error::new_spanned(
296 &nv.value,
297 "lifecycle expects a string literal",
298 ))
299 }
300 syn::Meta::List(list) if list.path.is_ident("lifecycle") => {
301 let mut spec = LifecycleSpec::default();
303
304 let nested: syn::punctuated::Punctuated<syn::Meta, ::syn::token::Commasyn::Token![,]> =
305 list.parse_args_with(syn::punctuated::Punctuated::parse_terminated)?;
306
307 for meta in nested {
308 if let syn::Meta::NameValue(nv) = meta {
309 let key = nv.path.get_ident().map(|i| i.to_string());
310 let value = match &nv.value {
311 syn::Expr::Lit(expr_lit) => {
312 if let syn::Lit::Str(lit) = &expr_lit.lit {
313 lit.value()
314 } else {
315 return Err(syn::Error::new_spanned(
316 &nv.value,
317 "expected string literal",
318 ));
319 }
320 }
321 _ => {
322 return Err(syn::Error::new_spanned(
323 &nv.value,
324 "expected string literal",
325 ));
326 }
327 };
328
329 match key.as_deref() {
330 Some("stage") => {
331 spec.stage = LifecycleStage::from_str(&value).ok_or_else(|| {
332 syn::Error::new(nv.value.span(), "invalid lifecycle stage")
333 })?;
334 }
335 Some("when") => spec.when = Some(value),
336 Some("what") => spec.what = Some(value),
337 Some("with") => spec.with = Some(value),
338 Some("details") => spec.details = Some(value),
339 Some("id") => spec.id = Some(value),
340 _ => {
341 return Err(syn::Error::new_spanned(
342 &nv.path,
343 "unknown lifecycle option; expected: stage, when, what, with, details, id",
344 ));
345 }
346 }
347 }
348 }
349
350 Ok(Some(spec))
351 }
352 _ => Ok(None),
353 }
354}
355
356pub fn parse_rust_deprecated(attr: &syn::Attribute) -> Option<LifecycleSpec> {
362 if !attr.path().is_ident("deprecated") {
363 return None;
364 }
365
366 let mut since = None;
367 let mut note = None;
368
369 match &attr.meta {
370 syn::Meta::Path(_) => {
371 }
373 syn::Meta::NameValue(nv) => {
374 if let syn::Expr::Lit(syn::ExprLit {
376 lit: syn::Lit::Str(lit),
377 ..
378 }) = &nv.value
379 {
380 note = Some(lit.value());
381 }
382 }
383 syn::Meta::List(list) => {
384 if let Ok(nested) = list.parse_args_with(
386 syn::punctuated::Punctuated::<syn::Meta, ::syn::token::Commasyn::Token![,]>::parse_terminated,
387 ) {
388 for meta in nested {
389 if let syn::Meta::NameValue(nv) = meta
390 && let syn::Expr::Lit(syn::ExprLit {
391 lit: syn::Lit::Str(lit),
392 ..
393 }) = &nv.value
394 {
395 if nv.path.is_ident("since") {
396 since = Some(lit.value());
397 } else if nv.path.is_ident("note") {
398 note = Some(lit.value());
399 }
400 }
401 }
402 }
403 }
404 }
405
406 Some(LifecycleSpec::from_deprecated(
407 since.as_deref(),
408 note.as_deref(),
409 ))
410}
411
412pub fn inject_lifecycle_badge(tags: &mut Vec<String>, spec: &LifecycleSpec) {
417 let Some(badge) = spec.stage.badge() else {
418 return;
419 };
420
421 let desc_idx = tags.iter().position(|t| t.starts_with("@description"));
423
424 if let Some(idx) = desc_idx {
425 let existing = &tags[idx];
427 let desc_content = existing
428 .strip_prefix("@description")
429 .unwrap_or("")
430 .trim_start();
431 tags[idx] = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("@description {0} {1}", badge,
desc_content))
})format!("@description {} {}", badge, desc_content);
432 } else {
433 tags.insert(0, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("@description {0}", badge))
})format!("@description {}", badge));
435 }
436
437 if let Some(keywords) = spec.stage.keywords()
439 && !tags.iter().any(|t| t.starts_with("@keywords"))
440 {
441 tags.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("@keywords {0}", keywords))
})format!("@keywords {}", keywords));
442 }
443
444 inject_lifecycle_imports(tags, spec);
446}
447
448pub fn inject_lifecycle_imports(tags: &mut Vec<String>, spec: &LifecycleSpec) {
453 let mut fns_to_import = Vec::new();
455
456 if spec.stage.badge().is_some() {
458 fns_to_import.push("badge");
459 }
460
461 if let Some(fn_name) = spec.stage.import_from_fn() {
463 fns_to_import.push(fn_name);
464 }
465
466 if fns_to_import.is_empty() {
467 return;
468 }
469
470 let already_imported: Vec<&str> = tags
472 .iter()
473 .filter_map(|t| t.strip_prefix("@importFrom lifecycle "))
474 .flat_map(|s| s.split_whitespace())
475 .collect();
476
477 fns_to_import.retain(|f| !already_imported.contains(f));
478
479 if !fns_to_import.is_empty() {
480 tags.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("@importFrom lifecycle {0}",
fns_to_import.join(" ")))
})format!("@importFrom lifecycle {}", fns_to_import.join(" ")));
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn test_stage_from_str() {
490 assert_eq!(
491 LifecycleStage::from_str("experimental"),
492 Some(LifecycleStage::Experimental)
493 );
494 assert_eq!(
495 LifecycleStage::from_str("deprecated"),
496 Some(LifecycleStage::Deprecated)
497 );
498 assert_eq!(
499 LifecycleStage::from_str("soft-deprecated"),
500 Some(LifecycleStage::SoftDeprecated)
501 );
502 assert_eq!(LifecycleStage::from_str("unknown"), None);
503 }
504
505 #[test]
506 fn test_r_prelude_deprecated() {
507 let spec = LifecycleSpec {
508 stage: LifecycleStage::Deprecated,
509 when: Some("0.4.0".into()),
510 what: None,
511 with: Some("new_fn()".into()),
512 details: None,
513 id: None,
514 };
515 let prelude = spec.r_prelude("old_fn").unwrap();
516 assert!(prelude.contains("lifecycle::deprecate_warn"));
517 assert!(prelude.contains("0.4.0"));
518 assert!(prelude.contains("old_fn()"));
519 assert!(prelude.contains("new_fn()"));
520 }
521
522 #[test]
524 fn test_r_prelude_escapes_string_arguments() {
525 let spec = LifecycleSpec {
526 stage: LifecycleStage::Deprecated,
527 when: Some("0.4.0".into()),
528 what: None,
529 with: Some("new_fn()".into()),
530 details: Some(r#"Use "C:\data" instead."#.into()),
531 id: None,
532 };
533 let prelude = spec.r_prelude("old_fn").unwrap();
534 assert!(
535 prelude.contains(r#"details = "Use \"C:\\data\" instead.""#),
536 "{prelude}"
537 );
538 }
539
540 #[test]
541 fn test_r_prelude_experimental() {
542 let spec = LifecycleSpec::new(LifecycleStage::Experimental);
543 let prelude = spec.r_prelude("my_fn").unwrap();
544 assert!(prelude.contains("lifecycle::signal_stage"));
545 assert!(prelude.contains("experimental"));
546 assert!(prelude.contains("my_fn()"));
547 }
548
549 #[test]
550 fn test_from_deprecated_note() {
551 let spec = LifecycleSpec::from_deprecated(Some("1.0.0"), Some("Use bar() instead"));
552 assert_eq!(spec.stage, LifecycleStage::Deprecated);
553 assert_eq!(spec.when, Some("1.0.0".into()));
554 assert_eq!(spec.with, Some("bar()".into()));
555 }
556
557 #[test]
558 fn test_import_from_fn() {
559 assert_eq!(
560 LifecycleStage::Experimental.import_from_fn(),
561 Some("signal_stage")
562 );
563 assert_eq!(LifecycleStage::Stable.import_from_fn(), None);
564 assert_eq!(
565 LifecycleStage::Superseded.import_from_fn(),
566 Some("signal_stage")
567 );
568 assert_eq!(
569 LifecycleStage::SoftDeprecated.import_from_fn(),
570 Some("deprecate_soft")
571 );
572 assert_eq!(
573 LifecycleStage::Deprecated.import_from_fn(),
574 Some("deprecate_warn")
575 );
576 assert_eq!(
577 LifecycleStage::Defunct.import_from_fn(),
578 Some("deprecate_stop")
579 );
580 }
581
582 #[test]
583 fn test_inject_lifecycle_imports_deprecated() {
584 let spec = LifecycleSpec::new(LifecycleStage::Deprecated);
585 let mut tags = vec!["@title My function".to_string()];
586 inject_lifecycle_badge(&mut tags, &spec);
587 let import_tag = tags
589 .iter()
590 .find(|t| t.starts_with("@importFrom lifecycle"))
591 .expect("should have @importFrom lifecycle tag");
592 assert!(import_tag.contains("badge"));
593 assert!(import_tag.contains("deprecate_warn"));
594 }
595
596 #[test]
597 fn test_inject_lifecycle_imports_experimental() {
598 let spec = LifecycleSpec::new(LifecycleStage::Experimental);
599 let mut tags = vec!["@title My function".to_string()];
600 inject_lifecycle_badge(&mut tags, &spec);
601 let import_tag = tags
602 .iter()
603 .find(|t| t.starts_with("@importFrom lifecycle"))
604 .expect("should have @importFrom lifecycle tag");
605 assert!(import_tag.contains("badge"));
606 assert!(import_tag.contains("signal_stage"));
607 }
608
609 #[test]
610 fn test_inject_lifecycle_imports_stable_no_import() {
611 let spec = LifecycleSpec::new(LifecycleStage::Stable);
612 let mut tags = vec!["@title My function".to_string()];
613 inject_lifecycle_badge(&mut tags, &spec);
614 assert!(!tags.iter().any(|t| t.starts_with("@importFrom")));
616 }
617
618 #[test]
619 fn test_inject_lifecycle_imports_no_duplicates() {
620 let spec = LifecycleSpec::new(LifecycleStage::Deprecated);
621 let mut tags = vec![
622 "@title My function".to_string(),
623 "@importFrom lifecycle deprecate_warn badge".to_string(),
624 ];
625 inject_lifecycle_badge(&mut tags, &spec);
626 let import_count = tags
628 .iter()
629 .filter(|t| t.starts_with("@importFrom lifecycle"))
630 .count();
631 assert_eq!(import_count, 1);
632 }
633
634 #[test]
635 fn test_collect_lifecycle_imports_mixed_methods() {
636 let specs = [
637 LifecycleSpec::new(LifecycleStage::Deprecated),
638 LifecycleSpec::new(LifecycleStage::Experimental),
639 LifecycleSpec::new(LifecycleStage::Stable),
640 ];
641 let result = collect_lifecycle_imports(specs.iter());
642 let import = result.expect("should produce import tag");
643 assert_eq!(
645 import,
646 "@importFrom lifecycle badge deprecate_warn signal_stage"
647 );
648 }
649
650 #[test]
651 fn test_collect_lifecycle_imports_no_lifecycle() {
652 let specs: Vec<LifecycleSpec> = vec![];
653 let result = collect_lifecycle_imports(specs.iter());
654 assert!(result.is_none());
655 }
656
657 #[test]
658 fn test_collect_lifecycle_imports_only_stable() {
659 let specs = [LifecycleSpec::new(LifecycleStage::Stable)];
660 let result = collect_lifecycle_imports(specs.iter());
661 assert!(result.is_none());
662 }
663}