use nu_protocol::ast::{Call, Expr};
use crate::{
Fix, LintLevel, Replacement,
ast::{call::CallExt, declaration::CustomCommandDef},
context::LintContext,
rule::{DetectFix, Rule},
violation::Detection,
};
fn has_shebang(ctx: &LintContext) -> bool {
let source = unsafe { ctx.source() };
source.starts_with("#!")
}
fn check_export_main_in_script(
call: &Call,
context: &LintContext,
) -> Option<(Detection, CustomCommandDef)> {
if !has_shebang(context) {
return None;
}
let func_def = call.custom_command_def(context)?;
if !func_def.is_exported() {
return None;
}
if !func_def.is_main() {
return None;
}
let export_span = func_def.export_span?;
let violation = Detection::from_global_span(
format!(
"Unnecessary 'export' in script entry point '{}'",
func_def.name
),
export_span,
)
.with_primary_label("remove 'export' keyword")
.with_extra_label("script entry point", func_def.name_span);
Some((violation, func_def))
}
struct ScriptExportMain;
impl DetectFix for ScriptExportMain {
type FixInput<'a> = CustomCommandDef;
fn id(&self) -> &'static str {
"script_export_main"
}
fn short_description(&self) -> &'static str {
"In scripts, 'def main' is the entry point and doesn't need 'export'"
}
fn source_link(&self) -> Option<&'static str> {
Some("https://www.nushell.sh/book/scripts.html#parameterizing-scripts")
}
fn level(&self) -> LintLevel {
LintLevel::Hint
}
fn detect<'a>(&self, context: &'a LintContext) -> Vec<(Detection, Self::FixInput<'a>)> {
context.detect_with_fix_data(|expr, ctx| {
if let Expr::Call(call) = &expr.expr {
check_export_main_in_script(call, ctx).into_iter().collect()
} else {
vec![]
}
})
}
fn fix(&self, _context: &LintContext, func_def: &Self::FixInput<'_>) -> Option<Fix> {
let export_span = func_def.export_span?;
Some(Fix {
explanation: format!("Remove 'export' keyword from '{}'", func_def.name).into(),
replacements: vec![Replacement::new(export_span, "")],
})
}
}
pub static RULE: &dyn Rule = &ScriptExportMain;
#[cfg(test)]
mod detect_bad;
#[cfg(test)]
mod generated_fix;
#[cfg(test)]
mod ignore_good;