mago-semantics 1.17.0

PHP Semantics Checker.
Documentation
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::HasSpan;
use mago_span::Span;
use mago_syntax::ast::Extends;
use mago_syntax::ast::Implements;

use crate::internal::consts::RESERVED_KEYWORDS;
use crate::internal::consts::SOFT_RESERVED_KEYWORDS_MINUS_SYMBOL_ALLOWED;
use crate::internal::context::Context;

#[inline]
pub fn check_extends(
    extends: &Extends,
    class_like_span: Span,
    class_like_kind: &str,
    class_like_name: &str,
    class_like_fqcn: &str,
    extension_limit: bool,
    context: &mut Context<'_, '_, '_>,
) {
    if extension_limit && extends.types.len() > 1 {
        context.report(
            Issue::error(format!(
                "{} `{}` can only extend one other type, found {}.",
                class_like_kind,
                class_like_name,
                extends.types.len()
            ))
            .with_annotation(Annotation::primary(extends.span()).with_message("Multiple extensions found here."))
            .with_annotation(
                Annotation::secondary(class_like_span)
                    .with_message(format!("{class_like_kind} `{class_like_fqcn}` declared here.")),
            )
            .with_help("Remove the extra extensions to ensure only one type is extended."),
        );
    }

    for extended_type in &extends.types {
        let extended_fqcn = context.get_name(extended_type.span().start);

        if extended_fqcn.eq_ignore_ascii_case(class_like_fqcn) {
            context.report(
                Issue::error(format!("{class_like_kind} `{class_like_name}` cannot extend itself."))
                    .with_annotation(
                        Annotation::primary(extended_type.span())
                            .with_message(format!("{class_like_kind} `{class_like_name}` extends itself here.")),
                    )
                    .with_annotation(
                        Annotation::secondary(class_like_span)
                            .with_message(format!("{class_like_kind} `{class_like_fqcn}` declared here.")),
                    )
                    .with_help("Remove the self-referencing extension."),
            );
        }
    }

    for extended_type in &extends.types {
        let extended_name = extended_type.value();

        if RESERVED_KEYWORDS.iter().any(|keyword| keyword.eq_ignore_ascii_case(extended_name))
            || SOFT_RESERVED_KEYWORDS_MINUS_SYMBOL_ALLOWED
                .iter()
                .any(|keyword| keyword.eq_ignore_ascii_case(extended_name))
        {
            context.report(
                Issue::error(format!(
                    "{class_like_kind} `{class_like_name}` cannot extend reserved keyword `{extended_name}`."
                ))
                .with_annotation(
                    Annotation::primary(extended_type.span()).with_message("Extension uses a reserved keyword."),
                )
                .with_annotation(
                    Annotation::secondary(class_like_span)
                        .with_message(format!("{class_like_kind} `{class_like_name}` declared here.")),
                )
                .with_help(format!(
                    "Change the extended type to a valid identifier. `{extended_name}` is a reserved keyword."
                )),
            );
        }
    }
}

#[inline]
pub fn check_implements(
    implements: &Implements,
    class_like_span: Span,
    class_like_kind: &str,
    class_like_name: &str,
    class_like_fqcn: &str,
    check_for_self_implement: bool,
    context: &mut Context<'_, '_, '_>,
) {
    if check_for_self_implement {
        for implemented_type in &implements.types {
            let implemented_fqcn = context.get_name(implemented_type.span().start);

            if implemented_fqcn.eq_ignore_ascii_case(class_like_fqcn) {
                context.report(
                    Issue::error(format!("{class_like_kind} `{class_like_name}` cannot implement itself."))
                        .with_annotation(
                            Annotation::primary(implemented_type.span())
                                .with_message(format!("{class_like_kind} `{class_like_name}` implements itself here.")),
                        )
                        .with_annotation(
                            Annotation::secondary(class_like_span)
                                .with_message(format!("{class_like_kind} `{class_like_fqcn}` declared here.")),
                        )
                        .with_help("Remove the self-referencing implementation."),
                );
            }
        }
    }

    for implemented_type in &implements.types {
        let implemented_name = implemented_type.value();

        if RESERVED_KEYWORDS.iter().any(|keyword| keyword.eq_ignore_ascii_case(implemented_name))
            || SOFT_RESERVED_KEYWORDS_MINUS_SYMBOL_ALLOWED
                .iter()
                .any(|keyword| keyword.eq_ignore_ascii_case(implemented_name))
        {
            context.report(
                Issue::error(format!(
                    "{class_like_kind} `{class_like_name}` cannot implement reserved keyword `{implemented_name}`."
                ))
                .with_annotation(
                    Annotation::primary(implemented_type.span()).with_message("This is a reserved keyword."),
                )
                .with_annotation(
                    Annotation::secondary(class_like_span)
                        .with_message(format!("{class_like_kind} `{class_like_name}` declared here.")),
                )
                .with_help(format!(
                    "Replace `{implemented_name}` with a valid identifier. Reserved keywords cannot be used as implemented types."
                )),
            );
        }
    }
}