emmylua_code_analysis 0.22.0

A library for analyzing lua code.
Documentation
use emmylua_parser::{
    LuaAssignStat, LuaAstNode, LuaAstToken, LuaExpr, LuaForRangeStat, LuaForStat, LuaFuncStat,
    LuaIndexExpr, LuaIndexKey, LuaLocalFuncStat, LuaLocalStat, LuaSyntaxId, LuaSyntaxKind,
    LuaVarExpr, NumberResult,
};

use crate::{
    LuaDeclExtra, LuaMemberFeature, LuaMemberId, LuaSemanticDeclId, LuaSignatureId, LuaType,
    LuaTypeCache,
    compilation::analyzer::common::bind_type,
    db_index::{LocalAttribute, LuaDecl, LuaMember, LuaMemberKey},
};

use super::{DeclAnalyzer, members::find_index_owner};

pub fn analyze_local_stat(analyzer: &mut DeclAnalyzer, stat: LuaLocalStat) -> Option<()> {
    let local_name_list = stat.get_local_name_list().collect::<Vec<_>>();
    let value_expr_list = stat.get_value_exprs().collect::<Vec<_>>();

    for (index, local_name) in local_name_list.iter().enumerate() {
        let name = if let Some(name_token) = local_name.get_name_token() {
            name_token.get_name_text().to_string()
        } else {
            continue;
        };
        let attrib = if let Some(attrib) = local_name.get_attrib() {
            if attrib.is_const() {
                Some(LocalAttribute::Const)
            } else if attrib.is_close() {
                Some(LocalAttribute::Close)
            } else {
                None
            }
        } else {
            None
        };

        let file_id = analyzer.get_file_id();
        let range = local_name.get_range();
        let expr_id = value_expr_list.get(index).map(|expr| expr.get_syntax_id());

        let decl = LuaDecl::new(
            &name,
            file_id,
            range,
            LuaDeclExtra::Local {
                kind: local_name.syntax().kind(),
                attrib,
            },
            expr_id,
        );
        analyzer.add_decl(decl);
    }

    Some(())
}

pub fn analyze_assign_stat(analyzer: &mut DeclAnalyzer, stat: LuaAssignStat) -> Option<()> {
    let (vars, value_exprs) = stat.get_var_and_expr_list();
    for (idx, var) in vars.iter().enumerate() {
        let value_expr_id = value_exprs.get(idx).map(|expr| expr.get_syntax_id());

        match &var {
            LuaVarExpr::NameExpr(name_expr) => {
                let name_token = name_expr.get_name_token()?;
                let position = name_token.get_position();
                let name = name_token.get_name_text();
                let file_id = analyzer.get_file_id();
                let range = name_token.get_range();
                if name == "_" {
                    let decl = LuaDecl::new(
                        name,
                        file_id,
                        range,
                        LuaDeclExtra::Local {
                            kind: name_expr.syntax().kind(),
                            attrib: Some(LocalAttribute::Const),
                        },
                        value_expr_id,
                    );

                    analyzer.add_decl(decl);
                    continue;
                }

                if let Some(decl) = analyzer.find_decl(name, position) {
                    let decl_id = decl.get_id();
                    analyzer
                        .db
                        .get_reference_index_mut()
                        .add_decl_reference(decl_id, file_id, range, true);
                } else {
                    let decl = LuaDecl::new(
                        name,
                        file_id,
                        range,
                        LuaDeclExtra::Global {
                            kind: LuaSyntaxKind::NameExpr.into(),
                        },
                        value_expr_id,
                    );

                    analyzer.add_decl(decl);
                }
            }
            LuaVarExpr::IndexExpr(index_expr) => {
                let index_key = index_expr.get_index_key()?;
                let key: LuaMemberKey = match index_key {
                    LuaIndexKey::Name(name) => LuaMemberKey::Name(name.get_name_text().into()),
                    LuaIndexKey::String(str) => LuaMemberKey::Name(str.get_value().into()),
                    LuaIndexKey::Integer(i) => {
                        if let NumberResult::Int(idx) = i.get_number_value() {
                            LuaMemberKey::Integer(idx)
                        } else {
                            continue;
                        }
                    }
                    LuaIndexKey::Idx(idx) => LuaMemberKey::Integer(idx as i64),
                    LuaIndexKey::Expr(_) => {
                        continue;
                    }
                };

                let file_id = analyzer.get_file_id();
                let member_id = LuaMemberId::new(index_expr.get_syntax_id(), file_id);
                let decl_feature = if analyzer.is_meta {
                    LuaMemberFeature::MetaDefine
                } else {
                    LuaMemberFeature::FileDefine
                };

                let (owner, global_id) = find_index_owner(analyzer, index_expr.clone());
                let member = LuaMember::new(member_id, key.clone(), decl_feature, global_id);

                analyzer.db.get_member_index_mut().add_member(owner, member);
                if let LuaMemberKey::Name(name) = &key {
                    analyze_maybe_global_index_expr(analyzer, index_expr, name, value_expr_id);
                }
            }
        }
    }

    Some(())
}

fn analyze_maybe_global_index_expr(
    analyzer: &mut DeclAnalyzer,
    index_expr: &LuaIndexExpr,
    index_name: &str,
    value_expr_id: Option<LuaSyntaxId>,
) -> Option<()> {
    let file_id = analyzer.get_file_id();
    let prefix = index_expr.get_prefix_expr()?;
    if let LuaExpr::NameExpr(name_expr) = prefix {
        let name_token = name_expr.get_name_token()?;
        let name_token_text = name_token.get_name_text();
        if name_token_text == "_G" || name_token_text == "_ENV" {
            let position = index_expr.get_position();
            let range = index_expr.get_range();
            if let Some(decl) = analyzer.find_decl(&index_name, position) {
                let decl_id = decl.get_id();
                analyzer
                    .db
                    .get_reference_index_mut()
                    .add_decl_reference(decl_id, file_id, range, true);
            } else {
                let decl = LuaDecl::new(
                    index_name,
                    file_id,
                    range,
                    LuaDeclExtra::Global {
                        kind: LuaSyntaxKind::IndexExpr.into(),
                    },
                    value_expr_id,
                );

                analyzer.add_decl(decl);
            }
        }
    }

    Some(())
}

pub fn analyze_for_stat(analyzer: &mut DeclAnalyzer, stat: LuaForStat) -> Option<()> {
    let it_var = stat.get_var_name()?;
    let name = it_var.get_name_text();
    let file_id = analyzer.get_file_id();
    let range = it_var.get_range();
    let decl = LuaDecl::new(
        name,
        file_id,
        range,
        LuaDeclExtra::Local {
            kind: it_var.syntax().kind(),
            attrib: Some(LocalAttribute::IterConst),
        },
        None,
    );
    let decl_id = decl.get_id();
    analyzer.add_decl(decl);
    bind_type(
        analyzer.db,
        decl_id.into(),
        LuaTypeCache::DocType(LuaType::Integer),
    );

    Some(())
}

pub fn analyze_for_range_stat(analyzer: &mut DeclAnalyzer, stat: LuaForRangeStat) {
    let var_list = stat.get_var_name_list();
    let file_id = analyzer.get_file_id();
    for var in var_list {
        let name = var.get_name_text();
        let range = var.get_range();

        let decl = LuaDecl::new(
            name,
            file_id,
            range,
            LuaDeclExtra::Local {
                kind: var.syntax().kind(),
                attrib: Some(LocalAttribute::IterConst),
            },
            None,
        );

        analyzer.add_decl(decl);
    }
}

pub fn analyze_func_stat(analyzer: &mut DeclAnalyzer, stat: LuaFuncStat) -> Option<()> {
    let func_name = stat.get_func_name()?;
    let file_id = analyzer.get_file_id();
    let property_owner_id = match func_name {
        LuaVarExpr::NameExpr(name_expr) => {
            let name_token = name_expr.get_name_token()?;
            let position = name_token.get_position();
            let name = name_token.get_name_text();
            let range = name_token.get_range();
            if analyzer.find_decl(name, position).is_none() || analyzer.is_meta {
                let decl = LuaDecl::new(
                    name,
                    file_id,
                    range,
                    LuaDeclExtra::Global {
                        kind: LuaSyntaxKind::NameExpr.into(),
                    },
                    None,
                );

                let decl_id = analyzer.add_decl(decl);
                LuaSemanticDeclId::LuaDecl(decl_id)
            } else {
                return Some(());
            }
        }
        LuaVarExpr::IndexExpr(index_expr) => {
            let index_key = index_expr.get_index_key()?;
            let key: LuaMemberKey = match index_key {
                LuaIndexKey::Name(name) => LuaMemberKey::Name(name.get_name_text().into()),
                LuaIndexKey::String(str) => LuaMemberKey::Name(str.get_value().into()),
                LuaIndexKey::Integer(i) => {
                    if let NumberResult::Int(idx) = i.get_number_value() {
                        LuaMemberKey::Integer(idx)
                    } else {
                        return None;
                    }
                }
                LuaIndexKey::Idx(idx) => LuaMemberKey::Integer(idx as i64),
                LuaIndexKey::Expr(_) => {
                    return None;
                }
            };

            let file_id = analyzer.get_file_id();
            let member_id = LuaMemberId::new(index_expr.get_syntax_id(), file_id);
            let decl_feature = if analyzer.is_meta {
                LuaMemberFeature::MetaMethodDecl
            } else {
                LuaMemberFeature::FileMethodDecl
            };

            let (owner_id, global_id) = find_index_owner(analyzer, index_expr.clone());
            let member = LuaMember::new(member_id, key.clone(), decl_feature, global_id);
            let member_id = analyzer
                .db
                .get_member_index_mut()
                .add_member(owner_id, member);

            if let LuaMemberKey::Name(name) = &key {
                analyze_maybe_global_index_expr(analyzer, &index_expr, name, None);
            }
            LuaSemanticDeclId::Member(member_id)
        }
    };

    let closure = stat.get_closure()?;
    let file_id = analyzer.get_file_id();
    let closure_owner_id =
        LuaSemanticDeclId::Signature(LuaSignatureId::from_closure(file_id, &closure));
    analyzer.db.get_property_index_mut().add_owner_map(
        property_owner_id,
        closure_owner_id,
        file_id,
    );

    Some(())
}

pub fn analyze_local_func_stat(analyzer: &mut DeclAnalyzer, stat: LuaLocalFuncStat) -> Option<()> {
    let local_name = stat.get_local_name()?;
    let name_token = local_name.get_name_token()?;
    let name = name_token.get_name_text();
    let range = local_name.get_range();
    let file_id = analyzer.get_file_id();
    let decl = LuaDecl::new(
        name,
        file_id,
        range,
        LuaDeclExtra::Local {
            kind: local_name.syntax().kind(),
            attrib: None,
        },
        None,
    );

    let decl_id = analyzer.add_decl(decl);
    let closure = stat.get_closure()?;
    let closure_owner_id =
        LuaSemanticDeclId::Signature(LuaSignatureId::from_closure(file_id, &closure));
    let property_decl_id = LuaSemanticDeclId::LuaDecl(decl_id);
    analyzer
        .db
        .get_property_index_mut()
        .add_owner_map(property_decl_id, closure_owner_id, file_id);

    Some(())
}