use nu_protocol::{
Span,
ast::{Expr, Pipeline},
};
use crate::{
Fix, LintLevel, Replacement,
context::LintContext,
rule::{DetectFix, Rule},
violation::Detection,
};
struct FixData {
redundant_in_spans: Vec<Span>,
}
fn find_redundant_in_span(pipeline: &Pipeline, context: &LintContext) -> Option<Span> {
let [element] = pipeline.elements.as_slice() else {
return None;
};
let Expr::Collect(_, inner_expr) = &element.expr.expr else {
return None;
};
let Expr::Subexpression(block_id) = &inner_expr.expr else {
return None;
};
let inner_block = context.working_set.get_block(*block_id);
let [inner_pipeline] = inner_block.pipelines.as_slice() else {
return None;
};
if inner_pipeline.elements.len() < 2 {
return None;
}
let first = inner_pipeline.elements.first()?;
if !matches!(&first.expr.expr, Expr::Var(_))
&& !matches!(&first.expr.expr, Expr::FullCellPath(f) if matches!(f.head.expr, Expr::Var(_)))
{
return None;
}
let second = inner_pipeline.elements.get(1)?;
let in_span_start = first.expr.span.start;
let second_start = second.expr.span.start;
Some(Span::new(in_span_start, second_start))
}
fn collect_redundant_in_spans(pipelines: &[Pipeline], context: &LintContext) -> Vec<Span> {
pipelines
.iter()
.filter_map(|p| find_redundant_in_span(p, context))
.collect()
}
struct RemoveRedundantIn;
impl DetectFix for RemoveRedundantIn {
type FixInput<'a> = FixData;
fn id(&self) -> &'static str {
"remove_redundant_in"
}
fn short_description(&self) -> &'static str {
"Redundant `$in` at pipeline start"
}
fn source_link(&self) -> Option<&'static str> {
Some("https://www.nushell.sh/book/special_variables.html")
}
fn level(&self) -> LintLevel {
LintLevel::Hint
}
fn detect<'a>(&self, context: &'a LintContext) -> Vec<(Detection, Self::FixInput<'a>)> {
context
.custom_commands()
.iter()
.filter_map(|def| {
let signature = &def.signature;
let block = context.working_set.get_block(def.body);
let redundant_in_spans = collect_redundant_in_spans(&block.pipelines, context);
if redundant_in_spans.is_empty() {
return None;
}
let mut violation = Detection::from_file_span(
format!("Redundant $in in function '{}'", signature.name),
def.declaration_span(context),
)
.with_primary_label("function with redundant $in");
if let Some(body_span) = block.span {
violation = violation.with_extra_label("$in used at pipeline start", body_span);
}
let fix_data = FixData { redundant_in_spans };
Some((violation, fix_data))
})
.collect()
}
fn fix(&self, _context: &LintContext, fix_data: &Self::FixInput<'_>) -> Option<Fix> {
if fix_data.redundant_in_spans.is_empty() {
return None;
}
let replacements: Vec<Replacement> = fix_data
.redundant_in_spans
.iter()
.map(|span| Replacement::new(*span, String::new()))
.collect();
Some(Fix {
explanation: "Remove redundant $in at pipeline start".into(),
replacements,
})
}
}
pub static RULE: &dyn Rule = &RemoveRedundantIn;
#[cfg(test)]
mod detect_bad;
#[cfg(test)]
mod generated_fix;
#[cfg(test)]
mod ignore_good;