use crate::{FromValue, ParseWarning, ShellError, Type, Value, ast::Call}; // Make nu_protocol available in this namespace, consumers of this crate will // have this without such an export. // The `FromValue` derive macro fully qualifies paths to "nu_protocol". use crate::{self as nu_protocol, ReportMode, Span}; /// A entry which indicates that some part of, or all of, a command is deprecated /// /// Commands can implement [`Command::deprecation_info`] to return deprecation entries, /// which will cause a parse-time warning. Additionally, custom commands can use the /// @deprecated attribute to add a `DeprecationEntry`. #[derive(FromValue)] pub struct DeprecationEntry { /// The type of deprecation // might need to revisit this if we added additional DeprecationTypes #[nu_value(rename = "flag", default)] pub ty: DeprecationType, /// How this deprecation should be reported #[nu_value(rename = "report")] pub report_mode: ReportMode, /// When this deprecation started pub since: Option, /// When this item is expected to be removed pub expected_removal: Option, /// Help text, possibly including a suggestion for what to use instead pub help: Option, } /// What this deprecation affects #[derive(Default)] pub enum DeprecationType { /// Deprecation of whole command #[default] Command, /// Deprecation of a flag/switch Flag(String), } impl FromValue for DeprecationType { fn from_value(v: Value) -> Result { match v { Value::String { val, .. } => Ok(DeprecationType::Flag(val)), Value::Nothing { .. } => Ok(DeprecationType::Command), v => Err(ShellError::CantConvert { to_type: Self::expected_type().to_string(), from_type: v.get_type().to_string(), span: v.span(), help: None, }), } } fn expected_type() -> Type { Type::String } } impl FromValue for ReportMode { fn from_value(v: Value) -> Result { let span = v.span(); let Value::String { val, .. } = v else { return Err(ShellError::CantConvert { to_type: Self::expected_type().to_string(), from_type: v.get_type().to_string(), span: v.span(), help: None, }); }; match val.as_str() { "first" => Ok(ReportMode::FirstUse), "every" => Ok(ReportMode::EveryUse), _ => Err(ShellError::InvalidValue { valid: "first or every".into(), actual: val, span, }), } } fn expected_type() -> Type { Type::String } } impl DeprecationEntry { fn check(&self, call: &Call) -> bool { match &self.ty { DeprecationType::Command => true, DeprecationType::Flag(flag) => call.get_named_arg(flag).is_some(), } } fn type_name(&self) -> String { match &self.ty { DeprecationType::Command => "Command".to_string(), DeprecationType::Flag(_) => "Flag".to_string(), } } fn label(&self, command_name: &str) -> String { let name = match &self.ty { DeprecationType::Command => command_name, DeprecationType::Flag(flag) => &format!("{command_name} --{flag}"), }; let since = match &self.since { Some(since) => format!("was deprecated in {since}"), None => "is deprecated".to_string(), }; let removal = match &self.expected_removal { Some(expected) => format!("and will be removed in {expected}"), None => "and will be removed in a future release".to_string(), }; format!("{name} {since} {removal}.") } fn span(&self, call: &Call) -> Span { match &self.ty { DeprecationType::Command => call.span(), DeprecationType::Flag(flag) => call .get_named_arg(flag) .map(|arg| arg.span) .unwrap_or(Span::unknown()), } } pub fn parse_warning(self, command_name: &str, call: &Call) -> Option { if !self.check(call) { return None; } let dep_type = self.type_name(); let label = self.label(command_name); let span = self.span(call); let report_mode = self.report_mode; Some(ParseWarning::DeprecationWarning { dep_type, label, span, report_mode, help: self.help, }) } }