use mago_allocator::Arena;
use mago_word::Word;
use mago_codex::metadata::CodebaseMetadata;
use mago_codex::ttype::resolution::TypeResolutionContext;
use mago_collector::Collector;
use mago_database::file::File;
use mago_names::ResolvedNames;
use mago_names::scope::NamespaceScope;
use mago_phpdoc_syntax::PHPDocParser;
use mago_phpdoc_syntax::cst::Element;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_reporting::IssueCollection;
use mago_span::HasSpan;
use mago_span::Span;
use mago_syntax::comments::docblock::PrecedingDocblocks;
use mago_syntax::cst::Identifier;
use mago_syntax::cst::Trivia;
use crate::analysis_result::AnalysisResult;
use crate::artifacts::AnalysisArtifacts;
use crate::code::IssueCode;
use crate::context::assertion::AssertionContext;
use crate::context::block::BlockContext;
use crate::plugin::PluginRegistry;
use crate::settings::Settings;
pub mod assertion;
pub mod block;
pub mod block_flags;
pub mod scope;
pub mod utils;
#[derive(Debug)]
#[allow(clippy::field_scoped_visibility_modifiers)]
#[allow(clippy::struct_field_names)]
pub struct Context<'ctx, 'arena, A>
where
A: Arena,
{
pub(super) arena: &'arena A,
pub(super) codebase: &'ctx CodebaseMetadata,
pub(super) source_file: &'ctx File,
pub(super) resolved_names: &'ctx ResolvedNames<'arena>,
pub(super) type_resolution_context: TypeResolutionContext,
pub(super) comments: &'arena [Trivia<'arena>],
pub(super) settings: &'ctx Settings,
pub(super) scope: NamespaceScope,
pub(super) collector: Collector<'ctx, 'arena, A>,
pub(super) statement_span: Span,
pub(super) plugin_registry: &'ctx PluginRegistry,
}
impl<'ctx, 'arena, A> Context<'ctx, 'arena, A>
where
A: Arena,
{
pub fn new(
arena: &'arena A,
codebase: &'ctx CodebaseMetadata,
source: &'ctx File,
resolved_names: &'ctx ResolvedNames<'arena>,
settings: &'ctx Settings,
statement_span: Span,
comments: &'arena [Trivia<'arena>],
collector: Collector<'ctx, 'arena, A>,
plugin_registry: &'ctx PluginRegistry,
) -> Self {
Self {
arena,
codebase,
source_file: source,
resolved_names,
type_resolution_context: TypeResolutionContext::new(),
comments,
settings,
scope: NamespaceScope::default(),
statement_span,
collector,
plugin_registry,
}
}
pub fn resolve_function_name<'ast>(&self, identifier: &'ast Identifier<'arena>) -> &'arena [u8] {
if self.resolved_names.is_imported(identifier) {
return self.resolved_names.get(identifier);
}
let name = identifier.value();
if let Some(stripped) = name.strip_prefix(b"\\") {
return stripped;
}
let fqfn = self.resolved_names.get(&identifier);
if self.codebase.function_exists(fqfn) {
return fqfn;
}
if !name.contains(&b'\\') && self.codebase.function_exists(name) {
return name;
}
fqfn
}
pub fn get_assertion_context_from_block(
&self,
block_context: &BlockContext<'ctx>,
) -> AssertionContext<'ctx, 'arena, A> {
self.get_assertion_context(block_context.scope.get_class_like_name())
}
#[inline]
pub fn get_assertion_context(&self, this_class_name: Option<Word>) -> AssertionContext<'ctx, 'arena, A> {
AssertionContext {
arena: self.arena,
resolved_names: self.resolved_names,
codebase: self.codebase,
this_class_name,
trust_existence_checks: self.settings.trust_existence_checks,
}
}
pub fn get_parsed_docblocks(&mut self) -> Vec<Element<'arena>> {
let mut elements = vec![];
for trivia in PrecedingDocblocks::new(self.comments, self.statement_span.start.offset) {
let document = PHPDocParser::parse_with_span(self.arena, trivia.value, trivia.span);
for error in document.errors {
let error_span = error.span();
let mut issue = Issue::error(error.to_string())
.with_annotation(
Annotation::primary(error_span).with_message("This part of the docblock has a syntax error"),
)
.with_note(error.note());
if trivia.span != error_span {
issue = issue.with_annotation(
Annotation::secondary(trivia.span).with_message("The error is within this docblock"),
);
}
issue = issue.with_annotation(
Annotation::secondary(self.statement_span)
.with_message("This docblock is associated with the following statement"),
);
issue = issue.with_help(error.help());
self.collector.report_with_code(IssueCode::InvalidDocblock, issue);
}
elements.extend(document.elements.iter().copied());
}
elements
}
pub fn record<T>(&mut self, callback: impl FnOnce(&mut Context<'ctx, 'arena, A>) -> T) -> (T, IssueCollection) {
self.collector.start_recording();
let result = callback(self);
let issues = self.collector.finish_recording().unwrap_or_default();
(result, issues)
}
pub fn finish(self, artifacts: AnalysisArtifacts, analysis_result: &mut AnalysisResult) {
analysis_result.issues.extend(self.collector.finish());
analysis_result.symbol_references.extend(artifacts.symbol_references);
}
pub fn finish_collector(self, analysis_result: &mut AnalysisResult) {
analysis_result.issues.extend(self.collector.finish());
}
}