nu_protocol/
deprecation.rs

1use crate::{FromValue, ParseWarning, ShellError, Type, Value, ast::Call};
2
3// Make nu_protocol available in this namespace, consumers of this crate will
4// have this without such an export.
5// The `FromValue` derive macro fully qualifies paths to "nu_protocol".
6use crate::{self as nu_protocol, ReportMode, Span};
7
8/// A entry which indicates that some part of, or all of, a command is deprecated
9///
10/// Commands can implement [`Command::deprecation_info`] to return deprecation entries,
11/// which will cause a parse-time warning. Additionally, custom commands can use the
12/// @deprecated attribute to add a `DeprecationEntry`.
13#[derive(FromValue)]
14pub struct DeprecationEntry {
15    /// The type of deprecation
16    // might need to revisit this if we added additional DeprecationTypes
17    #[nu_value(rename = "flag", default)]
18    pub ty: DeprecationType,
19    /// How this deprecation should be reported
20    #[nu_value(rename = "report")]
21    pub report_mode: ReportMode,
22    /// When this deprecation started
23    pub since: Option<String>,
24    /// When this item is expected to be removed
25    pub expected_removal: Option<String>,
26    /// Help text, possibly including a suggestion for what to use instead
27    pub help: Option<String>,
28}
29
30/// What this deprecation affects
31#[derive(Default)]
32pub enum DeprecationType {
33    /// Deprecation of whole command
34    #[default]
35    Command,
36    /// Deprecation of a flag/switch
37    Flag(String),
38}
39
40impl FromValue for DeprecationType {
41    fn from_value(v: Value) -> Result<Self, ShellError> {
42        match v {
43            Value::String { val, .. } => Ok(DeprecationType::Flag(val)),
44            Value::Nothing { .. } => Ok(DeprecationType::Command),
45            v => Err(ShellError::CantConvert {
46                to_type: Self::expected_type().to_string(),
47                from_type: v.get_type().to_string(),
48                span: v.span(),
49                help: None,
50            }),
51        }
52    }
53
54    fn expected_type() -> Type {
55        Type::String
56    }
57}
58
59impl FromValue for ReportMode {
60    fn from_value(v: Value) -> Result<Self, ShellError> {
61        let span = v.span();
62        let Value::String { val, .. } = v else {
63            return Err(ShellError::CantConvert {
64                to_type: Self::expected_type().to_string(),
65                from_type: v.get_type().to_string(),
66                span: v.span(),
67                help: None,
68            });
69        };
70        match val.as_str() {
71            "first" => Ok(ReportMode::FirstUse),
72            "every" => Ok(ReportMode::EveryUse),
73            _ => Err(ShellError::InvalidValue {
74                valid: "first or every".into(),
75                actual: val,
76                span,
77            }),
78        }
79    }
80
81    fn expected_type() -> Type {
82        Type::String
83    }
84}
85
86impl DeprecationEntry {
87    fn check(&self, call: &Call) -> bool {
88        match &self.ty {
89            DeprecationType::Command => true,
90            DeprecationType::Flag(flag) => call.get_named_arg(flag).is_some(),
91        }
92    }
93
94    fn type_name(&self) -> String {
95        match &self.ty {
96            DeprecationType::Command => "Command".to_string(),
97            DeprecationType::Flag(_) => "Flag".to_string(),
98        }
99    }
100
101    fn label(&self, command_name: &str) -> String {
102        let name = match &self.ty {
103            DeprecationType::Command => command_name,
104            DeprecationType::Flag(flag) => &format!("{command_name} --{flag}"),
105        };
106        let since = match &self.since {
107            Some(since) => format!("was deprecated in {since}"),
108            None => "is deprecated".to_string(),
109        };
110        let removal = match &self.expected_removal {
111            Some(expected) => format!("and will be removed in {expected}"),
112            None => "and will be removed in a future release".to_string(),
113        };
114        format!("{name} {since} {removal}.")
115    }
116
117    fn span(&self, call: &Call) -> Span {
118        match &self.ty {
119            DeprecationType::Command => call.span(),
120            DeprecationType::Flag(flag) => call
121                .get_named_arg(flag)
122                .map(|arg| arg.span)
123                .unwrap_or(Span::unknown()),
124        }
125    }
126
127    pub fn parse_warning(self, command_name: &str, call: &Call) -> Option<ParseWarning> {
128        if !self.check(call) {
129            return None;
130        }
131
132        let dep_type = self.type_name();
133        let label = self.label(command_name);
134        let span = self.span(call);
135        let report_mode = self.report_mode;
136        Some(ParseWarning::Deprecated {
137            dep_type,
138            label,
139            span,
140            report_mode,
141            help: self.help,
142        })
143    }
144}