nu-lint 1.3.0

Linter for Nu shell scripts that helpfully suggests improvements
Documentation
use lsp_types::DiagnosticTag;
use nu_protocol::ast::{Expr, Expression, ExternalArgument};

use crate::{
    config::LintLevel,
    context::LintContext,
    rule::{DetectFix, Rule},
    violation::Detection,
};

struct AvoidNuSubprocess;

impl DetectFix for AvoidNuSubprocess {
    type FixInput<'a> = ();

    fn id(&self) -> &'static str {
        "redundant_nu_subprocess"
    }

    fn short_description(&self) -> &'static str {
        "Redundant `nu -c` subprocess call"
    }

    fn level(&self) -> LintLevel {
        LintLevel::Error
    }

    fn diagnostic_tags(&self) -> &'static [DiagnosticTag] {
        &[DiagnosticTag::UNNECESSARY]
    }

    fn detect<'a>(&self, context: &'a LintContext) -> Vec<(Detection, Self::FixInput<'a>)> {
        Self::no_fix(context.detect(|expr, ctx| Self::check_expression(expr, ctx)))
    }
}

impl AvoidNuSubprocess {
    fn check_expression(expr: &Expression, ctx: &LintContext) -> Vec<Detection> {
        let Expr::ExternalCall(head, args) = &expr.expr else {
            return vec![];
        };

        let cmd_name = ctx.expr_text(head);

        if !Self::is_nu_with_c_flag(cmd_name, args, ctx) {
            return vec![];
        }

        vec![
            Detection::from_global_span(
                "Avoid spawning `nu -c` subprocess from within a Nu script",
                expr.span,
            )
            .with_primary_label("subprocess spawned here"),
        ]
    }

    fn is_nu_with_c_flag(cmd_name: &str, args: &[ExternalArgument], ctx: &LintContext) -> bool {
        if cmd_name != "nu" {
            return false;
        }

        args.iter().any(|arg| {
            let ExternalArgument::Regular(expr) = arg else {
                return false;
            };
            let arg_text = ctx.expr_text(expr);
            arg_text == "-c" || arg_text == "--commands"
        })
    }
}

pub static RULE: &dyn Rule = &AvoidNuSubprocess;

#[cfg(test)]
mod detect_bad;
#[cfg(test)]
mod ignore_good;