use nu_protocol::ast::{Argument, Expr, Expression};
use crate::{
ast::call::CallExt,
config::LintLevel,
context::LintContext,
rule::{DetectFix, Rule},
violation::Detection,
};
struct AvoidSelfImport;
impl DetectFix for AvoidSelfImport {
type FixInput<'a> = ();
fn id(&self) -> &'static str {
"self_import"
}
fn short_description(&self) -> &'static str {
"Circular import: script imports itself"
}
fn level(&self) -> LintLevel {
LintLevel::Error
}
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 AvoidSelfImport {
fn check_expression(expr: &Expression, ctx: &LintContext) -> Vec<Detection> {
let Expr::Call(call) = &expr.expr else {
return vec![];
};
let is_use_or_source =
call.get_call_name(ctx) == "use" || call.get_call_name(ctx) == "source";
if !is_use_or_source {
return vec![];
}
let has_self_reference = call.arguments.iter().any(|arg| {
let arg_expr = match arg {
Argument::Positional(e)
| Argument::Unknown(e)
| Argument::Spread(e)
| Argument::Named((_, _, Some(e))) => e,
Argument::Named(_) => return false,
};
let arg_text = ctx.expr_text(arg_expr);
Self::contains_current_file_reference(arg_text)
});
if !has_self_reference {
return vec![];
}
let command_name = call.get_call_name(ctx);
vec![
Detection::from_global_span(
format!(
"Avoid `{command_name}` with reference to current file (self-import pattern)"
),
expr.span,
)
.with_primary_label("self-import here"),
]
}
fn contains_current_file_reference(text: &str) -> bool {
text.contains("$env.CURRENT_FILE")
|| text.contains("$nu.current-file")
|| text.contains("(path self)")
}
}
pub static RULE: &dyn Rule = &AvoidSelfImport;
#[cfg(test)]
mod detect_bad;
#[cfg(test)]
mod ignore_good;