mago-analyzer 1.47.2

A PHP static analyzer that can detect type errors in PHP code, and provide suggestions for fixing them.
Documentation
use mago_allocator::Arena;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::HasSpan;
use mago_span::Span;
use mago_syntax::cst::Identifier;
use mago_syntax::cst::Use;
use mago_syntax::cst::UseItems;
use mago_syntax::cst::UseType;
use mago_word::concat_word;

use crate::analyzable::Analyzable;
use crate::artifacts::AnalysisArtifacts;
use crate::code::IssueCode;
use crate::context::Context;
use crate::context::block::BlockContext;
use crate::error::AnalysisError;
use mago_bytes::BytesDisplay;
use mago_bytes::trim_start_byte;

impl<'ast, 'arena> Analyzable<'ast, 'arena> for Use<'arena> {
    fn analyze<'ctx, A>(
        &'ast self,
        context: &mut Context<'ctx, 'arena, A>,
        _block_context: &mut BlockContext<'ctx>,
        _artifacts: &mut AnalysisArtifacts,
    ) -> Result<(), AnalysisError>
    where
        A: Arena,
    {
        match &self.items {
            UseItems::Sequence(sequence) => {
                for item in sequence.items.iter() {
                    let fqn = trim_start_byte(item.name.value(), b'\\');
                    check_class_like_import(context, &item.name, fqn);
                }
            }
            UseItems::TypedSequence(typed_sequence) => {
                for item in typed_sequence.items.iter() {
                    let fqn = trim_start_byte(item.name.value(), b'\\');
                    check_typed_import(context, &item.name, fqn, &typed_sequence.r#type);
                }
            }
            UseItems::TypedList(typed_list) => {
                let prefix = trim_start_byte(typed_list.namespace.value(), b'\\');
                for item in typed_list.items.iter() {
                    let fqn = concat_word!(prefix, b"\\", item.name.value());
                    check_typed_import(context, &item.name, fqn.as_bytes(), &typed_list.r#type);
                }
            }
            UseItems::MixedList(mixed_list) => {
                let prefix = trim_start_byte(mixed_list.namespace.value(), b'\\');
                for maybe_typed_item in mixed_list.items.iter() {
                    let fqn = concat_word!(prefix, b"\\", maybe_typed_item.item.name.value());
                    check_maybe_typed_import(
                        context,
                        &maybe_typed_item.item.name,
                        fqn.as_bytes(),
                        maybe_typed_item.r#type.as_ref(),
                    );
                }
            }
        }

        Ok(())
    }
}

fn check_class_like_import<A>(context: &mut Context<'_, '_, A>, name: &Identifier<'_>, fqn: &[u8])
where
    A: Arena,
{
    if !context.codebase.class_like_exists(fqn) && !context.codebase.namespace_exists(fqn) {
        report_non_existent_import(context, name.span(), fqn, "class, interface, trait, or enum");
    }
}

fn check_typed_import<A>(context: &mut Context<'_, '_, A>, name: &Identifier<'_>, fqn: &[u8], use_type: &UseType<'_>)
where
    A: Arena,
{
    match use_type {
        UseType::Function(_) => {
            if !context.codebase.function_exists(fqn) {
                report_non_existent_import(context, name.span(), fqn, "function");
            }
        }
        UseType::Const(_) => {
            if !context.codebase.constant_exists(fqn) {
                report_non_existent_import(context, name.span(), fqn, "constant");
            }
        }
    }
}

fn check_maybe_typed_import<A>(
    context: &mut Context<'_, '_, A>,
    name: &Identifier<'_>,
    fqn: &[u8],
    use_type: Option<&UseType<'_>>,
) where
    A: Arena,
{
    match use_type {
        Some(UseType::Function(_)) => {
            if !context.codebase.function_exists(fqn) {
                report_non_existent_import(context, name.span(), fqn, "function");
            }
        }
        Some(UseType::Const(_)) => {
            if !context.codebase.constant_exists(fqn) {
                report_non_existent_import(context, name.span(), fqn, "constant");
            }
        }
        None => {
            if !context.codebase.class_like_exists(fqn) && !context.codebase.namespace_exists(fqn) {
                report_non_existent_import(context, name.span(), fqn, "class, interface, trait, or enum");
            }
        }
    }
}

fn report_non_existent_import<A>(context: &mut Context<'_, '_, A>, span: Span, fqn: &[u8], symbol_type: &str)
where
    A: Arena,
{
    let fqn = BytesDisplay(fqn);
    context.collector.report_with_code(
        IssueCode::NonExistentUseImport,
        Issue::error(format!("Imported {symbol_type} `{fqn}` does not exist."))
            .with_annotation(Annotation::primary(span).with_message(format!("`{fqn}` not found")))
            .with_help(format!("Ensure that the {symbol_type} `{fqn}` is defined in the codebase.")),
    );
}