nu-lint 1.3.0

Linter for Nu shell scripts that helpfully suggests improvements
Documentation
use nu_protocol::ast::ExternalArgument;

use super::has_external_recursive_flag;
use crate::{
    context::LintContext,
    effect::{is_dangerous_path, matches_long_flag, matches_short_flag},
};

pub type Predicate = fn(&LintContext<'_>, &[ExternalArgument]) -> bool;

pub const fn always(_context: &LintContext, _args: &[ExternalArgument]) -> bool {
    true
}

pub fn extract_external_arg_text<'a>(arg: &ExternalArgument, context: &'a LintContext) -> &'a str {
    match arg {
        ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) => context.expr_text(expr),
    }
}

pub fn has_flag(args: &[ExternalArgument], context: &LintContext, patterns: &[&str]) -> bool {
    let matches_pattern = |arg_text: &str, pattern: &str| match pattern.strip_prefix("--") {
        Some(_) => matches_long_flag(arg_text, pattern),
        None => pattern
            .strip_prefix('-')
            .filter(|rest| rest.len() == 1)
            .and_then(|rest| rest.chars().next())
            .is_some_and(|flag_char| {
                matches_long_flag(arg_text, pattern) || matches_short_flag(arg_text, flag_char)
            }),
    };

    args.iter()
        .map(|arg| extract_external_arg_text(arg, context))
        .any(|arg_text| {
            patterns
                .iter()
                .any(|pattern| matches_pattern(arg_text, pattern))
        })
}

pub fn get_subcommand<'a>(args: &[ExternalArgument], context: &'a LintContext) -> &'a str {
    args.first()
        .map_or("", |arg| extract_external_arg_text(arg, context))
}

pub fn has_dangerous_path_arg(context: &LintContext, args: &[ExternalArgument]) -> bool {
    args.iter()
        .map(|arg| extract_external_arg_text(arg, context))
        .any(is_dangerous_path)
}

pub fn rm_is_dangerous(context: &LintContext, args: &[ExternalArgument]) -> bool {
    has_dangerous_path_arg(context, args) || has_external_recursive_flag(args, context)
}