mago-analyzer 1.21.0

A PHP static analyzer that can detect type errors in PHP code, and provide suggestions for fixing them.
Documentation
use mago_codex::ttype::TType;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::HasSpan;
use mago_syntax::ast::Constant;

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 crate::statement::attributes::AttributeTarget;
use crate::statement::attributes::analyze_attributes;

impl<'ast, 'arena> Analyzable<'ast, 'arena> for Constant<'arena> {
    fn analyze<'ctx>(
        &'ast self,
        context: &mut Context<'ctx, 'arena>,
        block_context: &mut BlockContext<'ctx>,
        artifacts: &mut AnalysisArtifacts,
    ) -> Result<(), AnalysisError> {
        analyze_attributes(
            context,
            block_context,
            artifacts,
            self.attribute_lists.as_slice(),
            AttributeTarget::Constant,
        );

        for item in &self.items {
            let name = context.resolved_names.get(&item.name);

            let Some(constant_metadata) = context.codebase.get_constant(name) else {
                return Err(AnalysisError::InternalError(
                    format!("Constant metadata for `{name}` not found during analysis."),
                    item.name.span(),
                ));
            };

            item.value.analyze(context, block_context, artifacts)?;

            if constant_metadata.inferred_type.is_none() && constant_metadata.type_metadata.is_none() {
                if let Some(analysis_type) = artifacts.get_expression_type(&item.value) {
                    let analysis_type_str = analysis_type.get_id();

                    context.collector.report_with_code(
                        IssueCode::NonDocumentedConstant,
                        Issue::warning(format!(
                            "Type of constant `{name}` is not explicitly defined.",
                        ))
                        .with_annotation(
                            Annotation::primary(item.value.span())
                                .with_message(format!("The type was inferred as `{analysis_type_str}`, but an explicit `@var` tag is needed for reliable analysis.")),
                        )
                        .with_note(
                            "The type of a constant must be known before its usage is analyzed."
                        )
                        .with_note(
                            "Without a `@var` tag for this complex value, the constant is assumed to be `mixed`, which can hide potential type errors."
                        )
                        .with_help(format!(
                            "Add a docblock to make the type explicit: `/** @var {analysis_type_str} */`",
                        )),
                    );
                } else {
                    context.collector.report_with_code(
                        IssueCode::NonDocumentedConstant,
                        Issue::warning(format!("Could not determine the type of constant `{name}`."))
                            .with_annotation(
                                Annotation::primary(item.value.span())
                                    .with_message("The type of this expression could not be determined"),
                            )
                            .with_note("Without a `@var` tag, this constant will be treated as `mixed`.")
                            .with_help(
                                "Add a docblock to specify the constant's type, for example: `/** @var string */`",
                            ),
                    );
                }
            }
        }

        Ok(())
    }
}