use std::cmp::Ordering;
use nu_protocol::{
BlockId, Span,
ast::{Call, Expr},
};
use crate::{
ast::{call::CallExt, expression::ExpressionExt},
context::LintContext,
span::FileSpan,
};
#[derive(Debug, Clone)]
pub struct CustomCommandDef {
pub body: BlockId,
pub name: String,
pub name_span: Span,
pub signature_span: Span,
pub export_span: Option<Span>,
pub signature: nu_protocol::Signature,
pub definition_span: Span,
pub body_expr_span: Span,
}
impl PartialEq for CustomCommandDef {
fn eq(&self, other: &Self) -> bool {
self.body == other.body
}
}
impl Eq for CustomCommandDef {}
impl PartialOrd for CustomCommandDef {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CustomCommandDef {
fn cmp(&self, other: &Self) -> Ordering {
self.body.cmp(&other.body)
}
}
impl CustomCommandDef {
pub fn try_from_call(call: &Call, context: &LintContext) -> Option<Self> {
let decl_name = call.get_call_name(context);
let is_exported = match decl_name.as_str() {
"export def" => true,
"def" => false,
_ => return None,
};
let name_arg = call.get_first_positional_arg()?;
let name = match &name_arg.expr {
Expr::String(s) | Expr::RawString(s) => s.clone(),
_ => context.expr_text(name_arg).to_string(),
};
let signature_expr = call.get_positional_arg(1)?;
let signature_span = signature_expr.span;
let body_expr = call.get_positional_arg(2)?;
let block_id = body_expr.extract_block_id()?;
let block = context.working_set.get_block(block_id);
let signature = (*block.signature).clone();
let export_span = is_exported.then(|| Span::new(call.head.start, call.head.start + 7));
let definition_span = call.span();
Some(Self {
body: block_id,
name,
name_span: name_arg.span,
signature_span,
export_span,
signature,
definition_span,
body_expr_span: body_expr.span,
})
}
pub fn is_main(&self) -> bool {
self.name == "main" || self.name.starts_with("main ")
}
pub const fn is_exported(&self) -> bool {
self.export_span.is_some()
}
pub const fn declaration_span(&self, context: &LintContext) -> FileSpan {
context.normalize_span(self.name_span)
}
}
#[cfg(test)]
mod tests {
use crate::context::LintContext;
#[test]
fn test_custom_command_spans() {
let code = r#"
def get_first [] {
$in | first
}
def main [] {
[1 2 3] | get_first
}
"#;
LintContext::test_with_parsed_source(code, |context| {
let commands = context.custom_commands();
assert_eq!(commands.len(), 2);
let get_first = commands.iter().find(|c| c.name == "get_first").unwrap();
assert!(
context
.span_text(get_first.definition_span)
.starts_with("def get_first")
);
assert!(
context
.span_text(get_first.body_expr_span)
.contains("$in | first")
);
});
}
}