use php_ast::{
ClassConstDecl, ClassDecl, ClassMemberKind, EnumCase, EnumDecl, EnumMemberKind, FunctionDecl,
Ident, InterfaceDecl, MethodDecl, NamespaceBody, Param, PropertyDecl, Span, Stmt, StmtKind,
TraitDecl,
};
use crate::text::strip_variable_sigil;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Container {
Class,
Interface,
Trait,
Enum,
}
pub enum Declaration<'a> {
Function {
decl: &'a FunctionDecl<'a, 'a>,
stmt_span: Span,
},
Class {
decl: &'a ClassDecl<'a, 'a>,
name: Ident<'a>,
stmt_span: Span,
},
Interface {
decl: &'a InterfaceDecl<'a, 'a>,
stmt_span: Span,
},
Trait {
decl: &'a TraitDecl<'a, 'a>,
stmt_span: Span,
},
Enum {
decl: &'a EnumDecl<'a, 'a>,
stmt_span: Span,
},
Method {
method: &'a MethodDecl<'a, 'a>,
container: Container,
member_span: Span,
},
ClassConst {
konst: &'a ClassConstDecl<'a, 'a>,
container: Container,
member_span: Span,
},
Property {
property: &'a PropertyDecl<'a, 'a>,
container: Container,
member_span: Span,
},
PromotedParam { param: &'a Param<'a, 'a> },
EnumCase {
case: &'a EnumCase<'a, 'a>,
enum_name: Ident<'a>,
member_span: Span,
},
}
impl<'a> Declaration<'a> {
pub fn name(&self) -> &'a str {
match self {
Declaration::Function { decl, .. } => decl.name.or_error(),
Declaration::Class { name, .. } => name.or_error(),
Declaration::Interface { decl, .. } => decl.name.or_error(),
Declaration::Trait { decl, .. } => decl.name.or_error(),
Declaration::Enum { decl, .. } => decl.name.or_error(),
Declaration::Method { method, .. } => method.name.or_error(),
Declaration::ClassConst { konst, .. } => konst.name.or_error(),
Declaration::Property { property, .. } => property.name.or_error(),
Declaration::PromotedParam { param } => param.name.or_error(),
Declaration::EnumCase { case, .. } => case.name.or_error(),
}
}
pub fn span(&self) -> Span {
match self {
Declaration::Function { stmt_span, .. } => *stmt_span,
Declaration::Class { stmt_span, .. } => *stmt_span,
Declaration::Interface { stmt_span, .. } => *stmt_span,
Declaration::Trait { stmt_span, .. } => *stmt_span,
Declaration::Enum { stmt_span, .. } => *stmt_span,
Declaration::Method { member_span, .. } => *member_span,
Declaration::ClassConst { member_span, .. } => *member_span,
Declaration::Property { member_span, .. } => *member_span,
Declaration::PromotedParam { param } => param.span,
Declaration::EnumCase { member_span, .. } => *member_span,
}
}
}
pub fn resolve_declaration<'a>(
stmts: &'a [Stmt<'a, 'a>],
word: &str,
accept: &dyn Fn(&Declaration<'a>) -> bool,
) -> Option<Declaration<'a>> {
let bare = strip_variable_sigil(word);
for stmt in stmts {
match &stmt.kind {
StmtKind::Function(f) if f.name == word => {
let d = Declaration::Function {
decl: f,
stmt_span: stmt.span,
};
if accept(&d) {
return Some(d);
}
}
StmtKind::Class(c) => {
if let Some(name) = c.name
&& name.or_error() == word
{
let d = Declaration::Class {
decl: c,
name,
stmt_span: stmt.span,
};
if accept(&d) {
return Some(d);
}
}
if let Some(d) =
resolve_member(c.body.members.iter(), word, bare, Container::Class, accept)
{
return Some(d);
}
}
StmtKind::Interface(i) => {
if i.name == word {
let d = Declaration::Interface {
decl: i,
stmt_span: stmt.span,
};
if accept(&d) {
return Some(d);
}
}
if let Some(d) = resolve_member(
i.body.members.iter(),
word,
bare,
Container::Interface,
accept,
) {
return Some(d);
}
}
StmtKind::Trait(t) => {
if t.name == word {
let d = Declaration::Trait {
decl: t,
stmt_span: stmt.span,
};
if accept(&d) {
return Some(d);
}
}
if let Some(d) =
resolve_member(t.body.members.iter(), word, bare, Container::Trait, accept)
{
return Some(d);
}
}
StmtKind::Enum(e) => {
if e.name == word {
let d = Declaration::Enum {
decl: e,
stmt_span: stmt.span,
};
if accept(&d) {
return Some(d);
}
}
for member in e.body.members.iter() {
match &member.kind {
EnumMemberKind::Case(c) if c.name == word => {
let d = Declaration::EnumCase {
case: c,
enum_name: e.name,
member_span: member.span,
};
if accept(&d) {
return Some(d);
}
}
EnumMemberKind::Method(m) if m.name == word => {
let d = Declaration::Method {
method: m,
container: Container::Enum,
member_span: member.span,
};
if accept(&d) {
return Some(d);
}
}
EnumMemberKind::ClassConst(cc) if cc.name == word => {
let d = Declaration::ClassConst {
konst: cc,
container: Container::Enum,
member_span: member.span,
};
if accept(&d) {
return Some(d);
}
}
_ => {}
}
}
}
StmtKind::Namespace(ns) => {
if let NamespaceBody::Braced(inner) = &ns.body
&& let Some(d) = resolve_declaration(&inner.stmts, word, accept)
{
return Some(d);
}
}
_ => {}
}
}
None
}
fn resolve_member<'a>(
members: impl Iterator<Item = &'a php_ast::ClassMember<'a, 'a>>,
word: &str,
bare: &str,
container: Container,
accept: &dyn Fn(&Declaration<'a>) -> bool,
) -> Option<Declaration<'a>> {
for member in members {
match &member.kind {
ClassMemberKind::Method(m) => {
if m.name == word {
let d = Declaration::Method {
method: m,
container,
member_span: member.span,
};
if accept(&d) {
return Some(d);
}
}
if container == Container::Class && m.name == "__construct" {
for p in m.params.iter() {
if p.visibility.is_some() && p.name == bare {
let d = Declaration::PromotedParam { param: p };
if accept(&d) {
return Some(d);
}
}
}
}
}
ClassMemberKind::ClassConst(cc) if cc.name == word => {
let d = Declaration::ClassConst {
konst: cc,
container,
member_span: member.span,
};
if accept(&d) {
return Some(d);
}
}
ClassMemberKind::Property(p) if p.name == bare => {
let d = Declaration::Property {
property: p,
container,
member_span: member.span,
};
if accept(&d) {
return Some(d);
}
}
_ => {}
}
}
None
}