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