emmylua_code_analysis 0.22.0

A library for analyzing lua code.
Documentation
use emmylua_parser::{LuaAssignStat, LuaAst, LuaAstNode, LuaBlock, LuaVarExpr};

use crate::{DiagnosticCode, LuaDeclId, SemanticModel, resolve_global_decl_id};

use super::{Checker, DiagnosticContext};

pub struct GlobalInNonModuleChecker;

impl Checker for GlobalInNonModuleChecker {
    const CODES: &[DiagnosticCode] = &[DiagnosticCode::GlobalInNonModule];

    fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) {
        let root = semantic_model.get_root().clone();
        for assign_stat in root.descendants::<LuaAssignStat>() {
            check_assign_stat(context, semantic_model, assign_stat);
        }
    }
}

fn check_assign_stat(
    context: &mut DiagnosticContext,
    semantic_model: &SemanticModel,
    assign_stat: LuaAssignStat,
) -> Option<()> {
    let file_id = semantic_model.get_file_id();

    let (vars, _) = assign_stat.get_var_and_expr_list();
    for var in vars {
        let decl_id = LuaDeclId::new(file_id, var.get_position());
        if let Some(decl) = semantic_model.get_db().get_decl_index().get_decl(&decl_id)
            && decl.is_global()
            && is_global_define_in_non_module_scope(semantic_model, var.clone(), decl_id)
        {
            context.add_diagnostic(
                DiagnosticCode::GlobalInNonModule,
                var.get_range(),
                t!("Global variable should only be defined in module scope").to_string(),
                None,
            );
        }
    }

    Some(())
}

fn is_global_define_in_non_module_scope(
    semantic_model: &SemanticModel,
    var: LuaVarExpr,
    decl_id: LuaDeclId,
) -> bool {
    for block in var.ancestors::<LuaBlock>() {
        let parent = block.get_parent::<LuaAst>();
        match parent {
            Some(LuaAst::LuaChunk(_)) => {
                return false;
            }
            Some(LuaAst::LuaClosureExpr(_)) => {
                break;
            }
            _ => {}
        }
    }

    let name = var.get_text();
    let Some(global_id) = resolve_global_decl_id(
        semantic_model.get_db(),
        &mut semantic_model.get_cache().borrow_mut(),
        &name,
        None,
    ) else {
        return true;
    };

    global_id == decl_id
}