mago-semantics 1.20.1

PHP Semantics Checker.
Documentation
use mago_php_version::feature::Feature;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::HasSpan;
use mago_span::Span;
use mago_syntax::ast::ClassLikeConstant;
use mago_syntax::ast::Modifier;

use crate::internal::context::Context;

#[inline]
pub fn check_class_like_constant<'ast, 'arena>(
    class_like_constant: &'ast ClassLikeConstant<'arena>,
    class_like_span: Span,
    class_like_kind: &str,
    class_like_name: &str,
    class_like_fqcn: &str,
    context: &mut Context<'_, 'ast, 'arena>,
) {
    let first_item = class_like_constant.first_item();
    let first_item_name = first_item.name.value;

    let mut last_final: Option<Span> = None;
    let mut last_visibility: Option<Span> = None;
    for modifier in &class_like_constant.modifiers {
        match modifier {
            Modifier::Readonly(k)
            | Modifier::Static(k)
            | Modifier::Abstract(k)
            | Modifier::PrivateSet(k)
            | Modifier::ProtectedSet(k)
            | Modifier::PublicSet(k) => {
                context.report(
                    Issue::error(format!("`{}` modifier is not allowed on constants", k.value))
                        .with_annotation(Annotation::primary(modifier.span()))
                        .with_annotations([
                            Annotation::secondary(first_item.span()).with_message(format!(
                                "{class_like_kind} constant `{class_like_name}::{first_item_name}` is declared here."
                            )),
                            Annotation::secondary(class_like_span)
                                .with_message(format!("{class_like_kind} `{class_like_fqcn}` is declared here.")),
                        ]),
                );
            }
            Modifier::Final(_) => {
                if !context.version.is_supported(Feature::FinalConstants) {
                    context.report(
                        Issue::error("Final class constants are only available in PHP 8.1 and above.").with_annotation(
                            Annotation::primary(modifier.span()).with_message("Final modifier used here."),
                        ),
                    );
                }

                if let Some(last_final) = last_final {
                    context.report(
                        Issue::error("duplicate `final` modifier on constant")
                            .with_annotation(Annotation::primary(modifier.span()))
                            .with_annotations([
                                Annotation::secondary(last_final).with_message("previous `final` modifier"),
                                Annotation::secondary(first_item.span()).with_message(format!(
                                    "{class_like_kind} constant `{class_like_name}::{first_item_name}` is declared here."
                                )),
                                Annotation::secondary(class_like_span).with_message(format!(
                                    "{class_like_kind} `{class_like_fqcn}` is declared here."
                                )),
                            ]),
                    );
                }

                last_final = Some(modifier.span());
            }
            Modifier::Private(_) | Modifier::Protected(_) | Modifier::Public(_) => {
                if !context.version.is_supported(Feature::ClassLikeConstantVisibilityModifiers) {
                    context.report(
                        Issue::error(
                            "Visibility modifiers for class constants are only available in PHP 7.1 and above.",
                        )
                        .with_annotation(
                            Annotation::primary(modifier.span()).with_message("Visibility modifier used here."),
                        ),
                    );
                }

                if let Some(last_visibility) = last_visibility {
                    context.report(
                        Issue::error("duplicate visibility modifier on constant")
                            .with_annotation(Annotation::primary(modifier.span()))
                            .with_annotations([
                                Annotation::secondary(last_visibility).with_message("previous visibility modifier"),
                                Annotation::secondary(first_item.span()).with_message(format!(
                                    "{class_like_kind} constant `{class_like_name}::{first_item_name}` is declared here."
                                )),
                                Annotation::secondary(class_like_span).with_message(format!(
                                    "{class_like_kind} `{class_like_fqcn}` is declared here."
                                )),
                            ]),
                    );
                }

                last_visibility = Some(modifier.span());
            }
        }
    }

    if let Some(type_hint) = &class_like_constant.hint
        && !context.version.is_supported(Feature::TypedClassLikeConstants)
    {
        context.report(
            Issue::error("Typed class constants are only available in PHP 8.3 and above.")
                .with_annotation(Annotation::primary(type_hint.span()).with_message("Type hint used here.")),
        );
    }

    for item in &class_like_constant.items {
        let item_name = item.name.value;

        if !item.value.is_constant(&context.version, false) {
            context.report(
                Issue::error(format!(
                    "Constant `{class_like_name}::{item_name}` value contains a non-constant expression."
                ))
                .with_annotation(Annotation::primary(item.value.span()))
                .with_annotations([
                    Annotation::secondary(item.name.span()).with_message(format!(
                        "{class_like_kind} constant `{class_like_name}::{item_name}` is declared here."
                    )),
                    Annotation::secondary(class_like_span)
                        .with_message(format!("{class_like_kind} `{class_like_fqcn}` is declared here.")),
                ]),
            );
        }
    }
}