1use php_ast::{
4 ClassMemberKind, EnumMemberKind, Expr, ExprKind, NamespaceBody, Span, Stmt, StmtKind, TypeHint,
5 TypeHintKind,
6};
7
8use crate::ast::str_offset;
9
10pub fn refs_in_stmts(source: &str, stmts: &[Stmt<'_, '_>], word: &str, out: &mut Vec<Span>) {
11 for stmt in stmts {
12 refs_in_stmt(source, stmt, word, out);
13 }
14}
15
16pub fn refs_in_stmts_with_use(
19 source: &str,
20 stmts: &[Stmt<'_, '_>],
21 word: &str,
22 out: &mut Vec<Span>,
23) {
24 refs_in_stmts(source, stmts, word, out);
25 use_refs(stmts, word, out);
26}
27
28fn use_refs(stmts: &[Stmt<'_, '_>], word: &str, out: &mut Vec<Span>) {
29 for stmt in stmts {
30 match &stmt.kind {
31 StmtKind::Use(u) => {
32 for use_item in u.uses.iter() {
33 let fqn = use_item.name.to_string_repr().into_owned();
34 let alias_match = use_item.alias.map(|a| a == word).unwrap_or(false);
35 let last_seg = fqn.rsplit('\\').next().unwrap_or(&fqn);
36 if alias_match || last_seg == word {
37 let name_span = use_item.name.span();
38 let offset = (fqn.len() - last_seg.len()) as u32;
39 let syn_span = Span {
40 start: name_span.start + offset,
41 end: name_span.start + fqn.len() as u32,
42 };
43 out.push(syn_span);
44 }
45 }
46 }
47 StmtKind::Namespace(ns) => {
48 if let NamespaceBody::Braced(inner) = &ns.body {
49 use_refs(inner, word, out);
50 }
51 }
52 _ => {}
53 }
54 }
55}
56
57trait RefVisitor {
60 fn visit_expr(&self, expr: &Expr<'_, '_>, out: &mut Vec<Span>);
61 fn visit_stmt(&self, stmt: &Stmt<'_, '_>, out: &mut Vec<Span>) -> bool;
63}
64
65fn walk_refs(v: &impl RefVisitor, stmts: &[Stmt<'_, '_>], out: &mut Vec<Span>) {
66 for stmt in stmts {
67 walk_ref_stmt(v, stmt, out);
68 }
69}
70
71fn walk_ref_stmt(v: &impl RefVisitor, stmt: &Stmt<'_, '_>, out: &mut Vec<Span>) {
72 if v.visit_stmt(stmt, out) {
73 return;
74 }
75 match &stmt.kind {
76 StmtKind::Expression(e) => v.visit_expr(e, out),
77 StmtKind::Return(Some(e)) => v.visit_expr(e, out),
78 StmtKind::Echo(exprs) => {
79 for expr in exprs.iter() {
80 v.visit_expr(expr, out);
81 }
82 }
83 StmtKind::If(i) => {
84 v.visit_expr(&i.condition, out);
85 walk_ref_stmt(v, i.then_branch, out);
86 for ei in i.elseif_branches.iter() {
87 v.visit_expr(&ei.condition, out);
88 walk_ref_stmt(v, &ei.body, out);
89 }
90 if let Some(e) = &i.else_branch {
91 walk_ref_stmt(v, e, out);
92 }
93 }
94 StmtKind::While(w) => {
95 v.visit_expr(&w.condition, out);
96 walk_ref_stmt(v, w.body, out);
97 }
98 StmtKind::DoWhile(d) => {
99 walk_ref_stmt(v, d.body, out);
100 v.visit_expr(&d.condition, out);
101 }
102 StmtKind::Foreach(f) => {
103 v.visit_expr(&f.expr, out);
104 walk_ref_stmt(v, f.body, out);
105 }
106 StmtKind::For(f) => {
107 for e in f.init.iter() {
108 v.visit_expr(e, out);
109 }
110 for cond in f.condition.iter() {
111 v.visit_expr(cond, out);
112 }
113 for e in f.update.iter() {
114 v.visit_expr(e, out);
115 }
116 walk_ref_stmt(v, f.body, out);
117 }
118 StmtKind::TryCatch(t) => {
119 walk_refs(v, &t.body, out);
120 for catch in t.catches.iter() {
121 walk_refs(v, &catch.body, out);
122 }
123 if let Some(finally) = &t.finally {
124 walk_refs(v, finally, out);
125 }
126 }
127 StmtKind::Block(stmts) => walk_refs(v, stmts, out),
128 _ => {}
129 }
130}
131
132struct AllRefsVisitor<'a> {
135 source: &'a str,
136 word: &'a str,
137}
138
139impl RefVisitor for AllRefsVisitor<'_> {
140 fn visit_expr(&self, expr: &Expr<'_, '_>, out: &mut Vec<Span>) {
141 refs_in_expr(self.source, expr, self.word, out);
142 }
143
144 fn visit_stmt(&self, stmt: &Stmt<'_, '_>, out: &mut Vec<Span>) -> bool {
145 match &stmt.kind {
146 StmtKind::Function(f) => {
147 if f.name == self.word {
148 let start = str_offset(self.source, f.name);
149 out.push(Span {
150 start,
151 end: start + f.name.len() as u32,
152 });
153 }
154 walk_refs(self, &f.body, out);
155 true
156 }
157 StmtKind::Class(c) => {
158 if let Some(name) = c.name
159 && name == self.word
160 {
161 let start = str_offset(self.source, name);
162 out.push(Span {
163 start,
164 end: start + name.len() as u32,
165 });
166 }
167 for member in c.members.iter() {
168 match &member.kind {
169 ClassMemberKind::Method(m) => {
170 if m.name == self.word {
171 let start = str_offset(self.source, m.name);
172 out.push(Span {
173 start,
174 end: start + m.name.len() as u32,
175 });
176 }
177 if let Some(body) = &m.body {
178 walk_refs(self, body, out);
179 }
180 }
181 ClassMemberKind::Property(p) => {
182 if let Some(default) = &p.default {
183 refs_in_expr(self.source, default, self.word, out);
184 }
185 }
186 _ => {}
187 }
188 }
189 true
190 }
191 StmtKind::Interface(i) => {
192 if i.name == self.word {
193 let start = str_offset(self.source, i.name);
194 out.push(Span {
195 start,
196 end: start + i.name.len() as u32,
197 });
198 }
199 for member in i.members.iter() {
200 if let ClassMemberKind::Method(m) = &member.kind
201 && m.name == self.word
202 {
203 let start = str_offset(self.source, m.name);
204 out.push(Span {
205 start,
206 end: start + m.name.len() as u32,
207 });
208 }
209 }
210 true
211 }
212 StmtKind::Trait(t) => {
213 if t.name == self.word {
214 let start = str_offset(self.source, t.name);
215 out.push(Span {
216 start,
217 end: start + t.name.len() as u32,
218 });
219 }
220 for member in t.members.iter() {
221 match &member.kind {
222 ClassMemberKind::Method(m) => {
223 if m.name == self.word {
224 let start = str_offset(self.source, m.name);
225 out.push(Span {
226 start,
227 end: start + m.name.len() as u32,
228 });
229 }
230 if let Some(body) = &m.body {
231 walk_refs(self, body, out);
232 }
233 }
234 ClassMemberKind::Property(p) => {
235 if let Some(default) = &p.default {
236 refs_in_expr(self.source, default, self.word, out);
237 }
238 }
239 _ => {}
240 }
241 }
242 true
243 }
244 StmtKind::Enum(e) => {
245 if e.name == self.word {
246 let start = str_offset(self.source, e.name);
247 out.push(Span {
248 start,
249 end: start + e.name.len() as u32,
250 });
251 }
252 for member in e.members.iter() {
253 match &member.kind {
254 EnumMemberKind::Method(m) => {
255 if m.name == self.word {
256 let start = str_offset(self.source, m.name);
257 out.push(Span {
258 start,
259 end: start + m.name.len() as u32,
260 });
261 }
262 if let Some(body) = &m.body {
263 walk_refs(self, body, out);
264 }
265 }
266 EnumMemberKind::Case(c) => {
267 if let Some(value) = &c.value {
268 refs_in_expr(self.source, value, self.word, out);
269 }
270 }
271 _ => {}
272 }
273 }
274 true
275 }
276 StmtKind::Namespace(ns) => {
277 if let NamespaceBody::Braced(inner) = &ns.body {
278 walk_refs(self, inner, out);
279 }
280 true
281 }
282 StmtKind::StaticVar(vars) => {
283 for var in vars.iter() {
284 if let Some(v) = &var.default {
285 refs_in_expr(self.source, v, self.word, out);
286 }
287 }
288 true
289 }
290 _ => false,
291 }
292 }
293}
294
295fn refs_in_stmt(source: &str, stmt: &Stmt<'_, '_>, word: &str, out: &mut Vec<Span>) {
296 let v = AllRefsVisitor { source, word };
297 walk_ref_stmt(&v, stmt, out);
298}
299
300pub fn var_refs_in_stmts(stmts: &[Stmt<'_, '_>], var_name: &str, out: &mut Vec<Span>) {
307 for stmt in stmts {
308 var_refs_in_stmt(stmt, var_name, out);
309 }
310}
311
312fn var_refs_in_stmt(stmt: &Stmt<'_, '_>, var_name: &str, out: &mut Vec<Span>) {
313 match &stmt.kind {
314 StmtKind::Function(_) | StmtKind::Class(_) | StmtKind::Trait(_) | StmtKind::Enum(_) => {}
316 StmtKind::Expression(e) => var_refs_in_expr(e, var_name, out),
317 StmtKind::Return(Some(e)) => var_refs_in_expr(e, var_name, out),
318 StmtKind::Return(None) | StmtKind::Break(_) | StmtKind::Continue(_) => {}
319 StmtKind::Echo(exprs) => {
320 for e in exprs.iter() {
321 var_refs_in_expr(e, var_name, out);
322 }
323 }
324 StmtKind::If(i) => {
325 var_refs_in_expr(&i.condition, var_name, out);
326 var_refs_in_stmt(i.then_branch, var_name, out);
327 for ei in i.elseif_branches.iter() {
328 var_refs_in_expr(&ei.condition, var_name, out);
329 var_refs_in_stmt(&ei.body, var_name, out);
330 }
331 if let Some(e) = &i.else_branch {
332 var_refs_in_stmt(e, var_name, out);
333 }
334 }
335 StmtKind::While(w) => {
336 var_refs_in_expr(&w.condition, var_name, out);
337 var_refs_in_stmt(w.body, var_name, out);
338 }
339 StmtKind::DoWhile(d) => {
340 var_refs_in_stmt(d.body, var_name, out);
341 var_refs_in_expr(&d.condition, var_name, out);
342 }
343 StmtKind::Foreach(f) => {
344 var_refs_in_expr(&f.expr, var_name, out);
345 if let Some(k) = &f.key {
346 var_refs_in_expr(k, var_name, out);
347 }
348 var_refs_in_expr(&f.value, var_name, out);
349 var_refs_in_stmt(f.body, var_name, out);
350 }
351 StmtKind::For(f) => {
352 for e in f.init.iter() {
353 var_refs_in_expr(e, var_name, out);
354 }
355 for cond in f.condition.iter() {
356 var_refs_in_expr(cond, var_name, out);
357 }
358 for e in f.update.iter() {
359 var_refs_in_expr(e, var_name, out);
360 }
361 var_refs_in_stmt(f.body, var_name, out);
362 }
363 StmtKind::TryCatch(t) => {
364 var_refs_in_stmts(&t.body, var_name, out);
365 for catch in t.catches.iter() {
366 var_refs_in_stmts(&catch.body, var_name, out);
367 }
368 if let Some(finally) = &t.finally {
369 var_refs_in_stmts(finally, var_name, out);
370 }
371 }
372 StmtKind::Block(inner) => var_refs_in_stmts(inner, var_name, out),
373 StmtKind::StaticVar(vars) => {
374 for v in vars.iter() {
375 if let Some(def) = &v.default {
376 var_refs_in_expr(def, var_name, out);
377 }
378 }
379 }
380 StmtKind::Namespace(ns) => {
381 if let NamespaceBody::Braced(inner) = &ns.body {
382 var_refs_in_stmts(inner, var_name, out);
383 }
384 }
385 _ => {}
386 }
387}
388
389fn var_refs_in_expr(expr: &Expr<'_, '_>, var_name: &str, out: &mut Vec<Span>) {
390 match &expr.kind {
391 ExprKind::Variable(name) => {
392 if name.as_str() == var_name {
393 out.push(expr.span);
394 }
395 }
396 ExprKind::Assign(a) => {
397 var_refs_in_expr(a.target, var_name, out);
398 var_refs_in_expr(a.value, var_name, out);
399 }
400 ExprKind::Binary(b) => {
401 var_refs_in_expr(b.left, var_name, out);
402 var_refs_in_expr(b.right, var_name, out);
403 }
404 ExprKind::UnaryPrefix(u) => var_refs_in_expr(u.operand, var_name, out),
405 ExprKind::UnaryPostfix(u) => var_refs_in_expr(u.operand, var_name, out),
406 ExprKind::Ternary(t) => {
407 var_refs_in_expr(t.condition, var_name, out);
408 if let Some(then_expr) = t.then_expr {
409 var_refs_in_expr(then_expr, var_name, out);
410 }
411 var_refs_in_expr(t.else_expr, var_name, out);
412 }
413 ExprKind::NullCoalesce(n) => {
414 var_refs_in_expr(n.left, var_name, out);
415 var_refs_in_expr(n.right, var_name, out);
416 }
417 ExprKind::Parenthesized(e) => var_refs_in_expr(e, var_name, out),
418 ExprKind::ErrorSuppress(e) => var_refs_in_expr(e, var_name, out),
419 ExprKind::Cast(_, e) => var_refs_in_expr(e, var_name, out),
420 ExprKind::Clone(e) => var_refs_in_expr(e, var_name, out),
421 ExprKind::ThrowExpr(e) => var_refs_in_expr(e, var_name, out),
422 ExprKind::Print(e) => var_refs_in_expr(e, var_name, out),
423 ExprKind::Empty(e) => var_refs_in_expr(e, var_name, out),
424 ExprKind::Eval(e) => var_refs_in_expr(e, var_name, out),
425 ExprKind::FunctionCall(f) => {
426 var_refs_in_expr(f.name, var_name, out);
427 for a in f.args.iter() {
428 var_refs_in_expr(&a.value, var_name, out);
429 }
430 }
431 ExprKind::MethodCall(m) => {
432 var_refs_in_expr(m.object, var_name, out);
433 for a in m.args.iter() {
434 var_refs_in_expr(&a.value, var_name, out);
435 }
436 }
437 ExprKind::NullsafeMethodCall(m) => {
438 var_refs_in_expr(m.object, var_name, out);
439 for a in m.args.iter() {
440 var_refs_in_expr(&a.value, var_name, out);
441 }
442 }
443 ExprKind::StaticMethodCall(s) => {
444 var_refs_in_expr(s.class, var_name, out);
445 for a in s.args.iter() {
446 var_refs_in_expr(&a.value, var_name, out);
447 }
448 }
449 ExprKind::New(n) => {
450 var_refs_in_expr(n.class, var_name, out);
451 for a in n.args.iter() {
452 var_refs_in_expr(&a.value, var_name, out);
453 }
454 }
455 ExprKind::ArrayAccess(a) => {
456 var_refs_in_expr(a.array, var_name, out);
457 if let Some(idx) = a.index {
458 var_refs_in_expr(idx, var_name, out);
459 }
460 }
461 ExprKind::PropertyAccess(p) => var_refs_in_expr(p.object, var_name, out),
462 ExprKind::NullsafePropertyAccess(p) => var_refs_in_expr(p.object, var_name, out),
463 ExprKind::StaticPropertyAccess(s) => var_refs_in_expr(s.class, var_name, out),
464 ExprKind::Yield(y) => {
465 if let Some(k) = y.key {
466 var_refs_in_expr(k, var_name, out);
467 }
468 if let Some(v) = y.value {
469 var_refs_in_expr(v, var_name, out);
470 }
471 }
472 ExprKind::Match(m) => {
473 var_refs_in_expr(m.subject, var_name, out);
474 for arm in m.arms.iter() {
475 if let Some(conds) = &arm.conditions {
476 for c in conds.iter() {
477 var_refs_in_expr(c, var_name, out);
478 }
479 }
480 var_refs_in_expr(&arm.body, var_name, out);
481 }
482 }
483 ExprKind::Array(elems) => {
484 for el in elems.iter() {
485 if let Some(k) = &el.key {
486 var_refs_in_expr(k, var_name, out);
487 }
488 var_refs_in_expr(&el.value, var_name, out);
489 }
490 }
491 ExprKind::Isset(exprs) => {
492 for e in exprs.iter() {
493 var_refs_in_expr(e, var_name, out);
494 }
495 }
496 ExprKind::Include(_, e) => var_refs_in_expr(e, var_name, out),
497 ExprKind::Exit(Some(e)) => var_refs_in_expr(e, var_name, out),
498 ExprKind::Closure(_) | ExprKind::ArrowFunction(_) => {}
500 _ => {}
501 }
502}
503
504pub fn collect_var_refs_in_scope(
509 stmts: &[Stmt<'_, '_>],
510 var_name: &str,
511 byte_off: usize,
512 out: &mut Vec<Span>,
513) {
514 for stmt in stmts {
515 if collect_in_fn_at(stmt, var_name, byte_off, out) {
516 return;
517 }
518 }
519 var_refs_in_stmts(stmts, var_name, out);
521}
522
523fn collect_in_fn_at(
526 stmt: &Stmt<'_, '_>,
527 var_name: &str,
528 byte_off: usize,
529 out: &mut Vec<Span>,
530) -> bool {
531 match &stmt.kind {
532 StmtKind::Function(f) => {
533 if byte_off < stmt.span.start as usize || byte_off >= stmt.span.end as usize {
534 return false;
535 }
536 for inner in f.body.iter() {
538 if collect_in_fn_at(inner, var_name, byte_off, out) {
539 return true;
540 }
541 }
542 for p in f.params.iter() {
544 if p.name == var_name {
545 out.push(p.span);
546 }
547 }
548 var_refs_in_stmts(&f.body, var_name, out);
549 true
550 }
551 StmtKind::Class(c) => {
552 for member in c.members.iter() {
553 if let ClassMemberKind::Method(m) = &member.kind {
554 if byte_off < member.span.start as usize || byte_off >= member.span.end as usize
555 {
556 continue;
557 }
558 if let Some(body) = &m.body {
559 for inner in body.iter() {
560 if collect_in_fn_at(inner, var_name, byte_off, out) {
561 return true;
562 }
563 }
564 for p in m.params.iter() {
565 if p.name == var_name {
566 out.push(p.span);
567 }
568 }
569 var_refs_in_stmts(body, var_name, out);
570 }
571 return true;
572 }
573 }
574 false
575 }
576 StmtKind::Trait(t) => {
577 for member in t.members.iter() {
578 if let ClassMemberKind::Method(m) = &member.kind {
579 if byte_off < member.span.start as usize || byte_off >= member.span.end as usize
580 {
581 continue;
582 }
583 if let Some(body) = &m.body {
584 for inner in body.iter() {
585 if collect_in_fn_at(inner, var_name, byte_off, out) {
586 return true;
587 }
588 }
589 for p in m.params.iter() {
590 if p.name == var_name {
591 out.push(p.span);
592 }
593 }
594 var_refs_in_stmts(body, var_name, out);
595 }
596 return true;
597 }
598 }
599 false
600 }
601 StmtKind::Namespace(ns) => {
602 if let NamespaceBody::Braced(inner) = &ns.body {
603 for s in inner.iter() {
604 if collect_in_fn_at(s, var_name, byte_off, out) {
605 return true;
606 }
607 }
608 }
609 false
610 }
611 _ => false,
612 }
613}
614
615pub fn property_refs_in_stmts(
622 source: &str,
623 stmts: &[Stmt<'_, '_>],
624 prop_name: &str,
625 out: &mut Vec<Span>,
626) {
627 for stmt in stmts {
628 property_refs_in_stmt(source, stmt, prop_name, out);
629 }
630}
631
632fn property_refs_in_stmt(source: &str, stmt: &Stmt<'_, '_>, prop_name: &str, out: &mut Vec<Span>) {
633 match &stmt.kind {
634 StmtKind::Expression(e) => property_refs_in_expr(source, e, prop_name, out),
635 StmtKind::Return(Some(e)) => property_refs_in_expr(source, e, prop_name, out),
636 StmtKind::Echo(exprs) => {
637 for e in exprs.iter() {
638 property_refs_in_expr(source, e, prop_name, out);
639 }
640 }
641 StmtKind::Function(f) => {
642 property_refs_in_stmts(source, &f.body, prop_name, out);
643 }
644 StmtKind::Class(c) => {
645 for member in c.members.iter() {
646 match &member.kind {
647 ClassMemberKind::Property(p) if p.name == prop_name => {
648 let offset = str_offset(source, p.name);
649 out.push(Span {
650 start: offset,
651 end: offset + p.name.len() as u32,
652 });
653 }
654 ClassMemberKind::Method(m) => {
655 if let Some(body) = &m.body {
656 property_refs_in_stmts(source, body, prop_name, out);
657 }
658 }
659 _ => {}
660 }
661 }
662 }
663 StmtKind::Trait(t) => {
664 for member in t.members.iter() {
665 match &member.kind {
666 ClassMemberKind::Property(p) if p.name == prop_name => {
667 let offset = str_offset(source, p.name);
668 out.push(Span {
669 start: offset,
670 end: offset + p.name.len() as u32,
671 });
672 }
673 ClassMemberKind::Method(m) => {
674 if let Some(body) = &m.body {
675 property_refs_in_stmts(source, body, prop_name, out);
676 }
677 }
678 _ => {}
679 }
680 }
681 }
682 StmtKind::Enum(e) => {
683 for member in e.members.iter() {
684 if let EnumMemberKind::Method(m) = &member.kind
685 && let Some(body) = &m.body
686 {
687 property_refs_in_stmts(source, body, prop_name, out);
688 }
689 }
690 }
691 StmtKind::Namespace(ns) => {
692 if let NamespaceBody::Braced(inner) = &ns.body {
693 property_refs_in_stmts(source, inner, prop_name, out);
694 }
695 }
696 StmtKind::If(i) => {
697 property_refs_in_expr(source, &i.condition, prop_name, out);
698 property_refs_in_stmt(source, i.then_branch, prop_name, out);
699 for ei in i.elseif_branches.iter() {
700 property_refs_in_expr(source, &ei.condition, prop_name, out);
701 property_refs_in_stmt(source, &ei.body, prop_name, out);
702 }
703 if let Some(e) = &i.else_branch {
704 property_refs_in_stmt(source, e, prop_name, out);
705 }
706 }
707 StmtKind::While(w) => {
708 property_refs_in_expr(source, &w.condition, prop_name, out);
709 property_refs_in_stmt(source, w.body, prop_name, out);
710 }
711 StmtKind::DoWhile(d) => {
712 property_refs_in_stmt(source, d.body, prop_name, out);
713 property_refs_in_expr(source, &d.condition, prop_name, out);
714 }
715 StmtKind::Foreach(f) => {
716 property_refs_in_expr(source, &f.expr, prop_name, out);
717 property_refs_in_stmt(source, f.body, prop_name, out);
718 }
719 StmtKind::For(f) => {
720 for e in f.init.iter() {
721 property_refs_in_expr(source, e, prop_name, out);
722 }
723 for cond in f.condition.iter() {
724 property_refs_in_expr(source, cond, prop_name, out);
725 }
726 for e in f.update.iter() {
727 property_refs_in_expr(source, e, prop_name, out);
728 }
729 property_refs_in_stmt(source, f.body, prop_name, out);
730 }
731 StmtKind::TryCatch(t) => {
732 property_refs_in_stmts(source, &t.body, prop_name, out);
733 for catch in t.catches.iter() {
734 property_refs_in_stmts(source, &catch.body, prop_name, out);
735 }
736 if let Some(finally) = &t.finally {
737 property_refs_in_stmts(source, finally, prop_name, out);
738 }
739 }
740 StmtKind::Block(inner) => property_refs_in_stmts(source, inner, prop_name, out),
741 _ => {}
742 }
743}
744
745fn property_refs_in_expr(source: &str, expr: &Expr<'_, '_>, prop_name: &str, out: &mut Vec<Span>) {
746 match &expr.kind {
747 ExprKind::PropertyAccess(p) => {
748 property_refs_in_expr(source, p.object, prop_name, out);
749 let span = p.property.span;
750 let name_in_src = source
751 .get(span.start as usize..span.end as usize)
752 .unwrap_or("");
753 if name_in_src == prop_name {
754 out.push(span);
755 }
756 }
757 ExprKind::NullsafePropertyAccess(p) => {
758 property_refs_in_expr(source, p.object, prop_name, out);
759 let span = p.property.span;
760 let name_in_src = source
761 .get(span.start as usize..span.end as usize)
762 .unwrap_or("");
763 if name_in_src == prop_name {
764 out.push(span);
765 }
766 }
767 ExprKind::Assign(a) => {
768 property_refs_in_expr(source, a.target, prop_name, out);
769 property_refs_in_expr(source, a.value, prop_name, out);
770 }
771 ExprKind::Binary(b) => {
772 property_refs_in_expr(source, b.left, prop_name, out);
773 property_refs_in_expr(source, b.right, prop_name, out);
774 }
775 ExprKind::UnaryPrefix(u) => property_refs_in_expr(source, u.operand, prop_name, out),
776 ExprKind::UnaryPostfix(u) => property_refs_in_expr(source, u.operand, prop_name, out),
777 ExprKind::Ternary(t) => {
778 property_refs_in_expr(source, t.condition, prop_name, out);
779 if let Some(then_expr) = t.then_expr {
780 property_refs_in_expr(source, then_expr, prop_name, out);
781 }
782 property_refs_in_expr(source, t.else_expr, prop_name, out);
783 }
784 ExprKind::NullCoalesce(n) => {
785 property_refs_in_expr(source, n.left, prop_name, out);
786 property_refs_in_expr(source, n.right, prop_name, out);
787 }
788 ExprKind::Parenthesized(e) => property_refs_in_expr(source, e, prop_name, out),
789 ExprKind::ErrorSuppress(e) => property_refs_in_expr(source, e, prop_name, out),
790 ExprKind::Cast(_, e) => property_refs_in_expr(source, e, prop_name, out),
791 ExprKind::Clone(e) => property_refs_in_expr(source, e, prop_name, out),
792 ExprKind::ThrowExpr(e) => property_refs_in_expr(source, e, prop_name, out),
793 ExprKind::Print(e) => property_refs_in_expr(source, e, prop_name, out),
794 ExprKind::Empty(e) => property_refs_in_expr(source, e, prop_name, out),
795 ExprKind::Eval(e) => property_refs_in_expr(source, e, prop_name, out),
796 ExprKind::FunctionCall(f) => {
797 property_refs_in_expr(source, f.name, prop_name, out);
798 for a in f.args.iter() {
799 property_refs_in_expr(source, &a.value, prop_name, out);
800 }
801 }
802 ExprKind::MethodCall(m) => {
803 property_refs_in_expr(source, m.object, prop_name, out);
804 for a in m.args.iter() {
805 property_refs_in_expr(source, &a.value, prop_name, out);
806 }
807 }
808 ExprKind::NullsafeMethodCall(m) => {
809 property_refs_in_expr(source, m.object, prop_name, out);
810 for a in m.args.iter() {
811 property_refs_in_expr(source, &a.value, prop_name, out);
812 }
813 }
814 ExprKind::StaticMethodCall(s) => {
815 property_refs_in_expr(source, s.class, prop_name, out);
816 for a in s.args.iter() {
817 property_refs_in_expr(source, &a.value, prop_name, out);
818 }
819 }
820 ExprKind::New(n) => {
821 property_refs_in_expr(source, n.class, prop_name, out);
822 for a in n.args.iter() {
823 property_refs_in_expr(source, &a.value, prop_name, out);
824 }
825 }
826 ExprKind::ArrayAccess(a) => {
827 property_refs_in_expr(source, a.array, prop_name, out);
828 if let Some(idx) = a.index {
829 property_refs_in_expr(source, idx, prop_name, out);
830 }
831 }
832 ExprKind::Yield(y) => {
833 if let Some(k) = y.key {
834 property_refs_in_expr(source, k, prop_name, out);
835 }
836 if let Some(v) = y.value {
837 property_refs_in_expr(source, v, prop_name, out);
838 }
839 }
840 ExprKind::Match(m) => {
841 property_refs_in_expr(source, m.subject, prop_name, out);
842 for arm in m.arms.iter() {
843 if let Some(conds) = &arm.conditions {
844 for c in conds.iter() {
845 property_refs_in_expr(source, c, prop_name, out);
846 }
847 }
848 property_refs_in_expr(source, &arm.body, prop_name, out);
849 }
850 }
851 ExprKind::Array(elems) => {
852 for el in elems.iter() {
853 if let Some(k) = &el.key {
854 property_refs_in_expr(source, k, prop_name, out);
855 }
856 property_refs_in_expr(source, &el.value, prop_name, out);
857 }
858 }
859 ExprKind::Isset(exprs) => {
860 for e in exprs.iter() {
861 property_refs_in_expr(source, e, prop_name, out);
862 }
863 }
864 ExprKind::Include(_, e) => property_refs_in_expr(source, e, prop_name, out),
865 ExprKind::Exit(Some(e)) => property_refs_in_expr(source, e, prop_name, out),
866 ExprKind::Closure(c) => property_refs_in_stmts(source, &c.body, prop_name, out),
867 ExprKind::ArrowFunction(a) => property_refs_in_expr(source, a.body, prop_name, out),
868 _ => {}
869 }
870}
871
872fn args(source: &str, arg_list: &[php_ast::Arg<'_, '_>], word: &str, out: &mut Vec<Span>) {
873 for a in arg_list.iter() {
874 refs_in_expr(source, &a.value, word, out);
875 }
876}
877
878pub fn refs_in_expr(source: &str, expr: &Expr<'_, '_>, word: &str, out: &mut Vec<Span>) {
879 match &expr.kind {
880 ExprKind::Identifier(name) => {
881 if name.as_str() == word {
882 out.push(expr.span);
883 }
884 }
885 ExprKind::FunctionCall(f) => {
886 refs_in_expr(source, f.name, word, out);
887 args(source, &f.args, word, out);
888 }
889 ExprKind::MethodCall(m) => {
890 refs_in_expr(source, m.object, word, out);
891 refs_in_expr(source, m.method, word, out);
892 args(source, &m.args, word, out);
893 }
894 ExprKind::NullsafeMethodCall(m) => {
895 refs_in_expr(source, m.object, word, out);
896 refs_in_expr(source, m.method, word, out);
897 args(source, &m.args, word, out);
898 }
899 ExprKind::StaticMethodCall(s) => {
900 refs_in_expr(source, s.class, word, out);
901 if s.method.as_ref() == word {
902 out.push(expr.span);
903 }
904 args(source, &s.args, word, out);
905 }
906 ExprKind::New(n) => {
907 refs_in_expr(source, n.class, word, out);
908 args(source, &n.args, word, out);
909 }
910 ExprKind::Assign(a) => {
911 refs_in_expr(source, a.target, word, out);
912 refs_in_expr(source, a.value, word, out);
913 }
914 ExprKind::Binary(b) => {
915 refs_in_expr(source, b.left, word, out);
916 refs_in_expr(source, b.right, word, out);
917 }
918 ExprKind::UnaryPrefix(u) => refs_in_expr(source, u.operand, word, out),
919 ExprKind::UnaryPostfix(u) => refs_in_expr(source, u.operand, word, out),
920 ExprKind::Ternary(t) => {
921 refs_in_expr(source, t.condition, word, out);
922 if let Some(then_expr) = t.then_expr {
923 refs_in_expr(source, then_expr, word, out);
924 }
925 refs_in_expr(source, t.else_expr, word, out);
926 }
927 ExprKind::NullCoalesce(n) => {
928 refs_in_expr(source, n.left, word, out);
929 refs_in_expr(source, n.right, word, out);
930 }
931 ExprKind::Parenthesized(e) => refs_in_expr(source, e, word, out),
932 ExprKind::ErrorSuppress(e) => refs_in_expr(source, e, word, out),
933 ExprKind::Cast(_, e) => refs_in_expr(source, e, word, out),
934 ExprKind::Clone(e) => refs_in_expr(source, e, word, out),
935 ExprKind::ThrowExpr(e) => refs_in_expr(source, e, word, out),
936 ExprKind::Print(e) => refs_in_expr(source, e, word, out),
937 ExprKind::Empty(e) => refs_in_expr(source, e, word, out),
938 ExprKind::Eval(e) => refs_in_expr(source, e, word, out),
939 ExprKind::Yield(y) => {
940 if let Some(k) = y.key {
941 refs_in_expr(source, k, word, out);
942 }
943 if let Some(v) = y.value {
944 refs_in_expr(source, v, word, out);
945 }
946 }
947 ExprKind::ArrayAccess(a) => {
948 refs_in_expr(source, a.array, word, out);
949 if let Some(idx) = a.index {
950 refs_in_expr(source, idx, word, out);
951 }
952 }
953 ExprKind::PropertyAccess(p) => refs_in_expr(source, p.object, word, out),
954 ExprKind::NullsafePropertyAccess(p) => refs_in_expr(source, p.object, word, out),
955 ExprKind::StaticPropertyAccess(s) => refs_in_expr(source, s.class, word, out),
956 ExprKind::ClassConstAccess(c) => {
957 refs_in_expr(source, c.class, word, out);
958 if c.member.as_ref() == word {
959 out.push(expr.span);
960 }
961 }
962 ExprKind::Closure(c) => refs_in_stmts(source, &c.body, word, out),
963 ExprKind::ArrowFunction(a) => refs_in_expr(source, a.body, word, out),
964 ExprKind::Match(m) => {
965 refs_in_expr(source, m.subject, word, out);
966 for arm in m.arms.iter() {
967 if let Some(conds) = &arm.conditions {
968 for cond in conds.iter() {
969 refs_in_expr(source, cond, word, out);
970 }
971 }
972 refs_in_expr(source, &arm.body, word, out);
973 }
974 }
975 ExprKind::Array(elements) => {
976 for elem in elements.iter() {
977 if let Some(key) = &elem.key {
978 refs_in_expr(source, key, word, out);
979 }
980 refs_in_expr(source, &elem.value, word, out);
981 }
982 }
983 ExprKind::Isset(exprs) => {
984 for e in exprs.iter() {
985 refs_in_expr(source, e, word, out);
986 }
987 }
988 ExprKind::Include(_, e) => refs_in_expr(source, e, word, out),
989 ExprKind::Exit(Some(e)) => refs_in_expr(source, e, word, out),
990 ExprKind::AnonymousClass(c) => {
991 for member in c.members.iter() {
992 if let ClassMemberKind::Method(m) = &member.kind
993 && let Some(body) = &m.body
994 {
995 refs_in_stmts(source, body, word, out);
996 }
997 }
998 }
999 _ => {}
1000 }
1001}
1002
1003pub fn function_refs_in_stmts(stmts: &[Stmt<'_, '_>], name: &str, out: &mut Vec<Span>) {
1009 let v = FunctionRefsVisitor { name };
1010 walk_refs(&v, stmts, out);
1011}
1012
1013struct FunctionRefsVisitor<'a> {
1014 name: &'a str,
1015}
1016
1017impl RefVisitor for FunctionRefsVisitor<'_> {
1018 fn visit_expr(&self, expr: &Expr<'_, '_>, out: &mut Vec<Span>) {
1019 function_refs_in_expr(expr, self.name, out);
1020 }
1021
1022 fn visit_stmt(&self, stmt: &Stmt<'_, '_>, out: &mut Vec<Span>) -> bool {
1023 match &stmt.kind {
1024 StmtKind::Function(f) => {
1025 walk_refs(self, &f.body, out);
1026 true
1027 }
1028 StmtKind::Class(c) => {
1029 for member in c.members.iter() {
1030 match &member.kind {
1031 ClassMemberKind::Method(m) => {
1032 if let Some(body) = &m.body {
1033 walk_refs(self, body, out);
1034 }
1035 }
1036 ClassMemberKind::Property(p) => {
1037 if let Some(default) = &p.default {
1038 function_refs_in_expr(default, self.name, out);
1039 }
1040 }
1041 _ => {}
1042 }
1043 }
1044 true
1045 }
1046 StmtKind::Trait(t) => {
1047 for member in t.members.iter() {
1048 if let ClassMemberKind::Method(m) = &member.kind
1049 && let Some(body) = &m.body
1050 {
1051 walk_refs(self, body, out);
1052 }
1053 }
1054 true
1055 }
1056 StmtKind::Enum(e) => {
1057 for member in e.members.iter() {
1058 if let EnumMemberKind::Method(m) = &member.kind
1059 && let Some(body) = &m.body
1060 {
1061 walk_refs(self, body, out);
1062 }
1063 }
1064 true
1065 }
1066 StmtKind::Namespace(ns) => {
1067 if let NamespaceBody::Braced(inner) = &ns.body {
1068 walk_refs(self, inner, out);
1069 }
1070 true
1071 }
1072 _ => false,
1073 }
1074 }
1075}
1076
1077fn function_refs_in_expr(expr: &Expr<'_, '_>, name: &str, out: &mut Vec<Span>) {
1078 match &expr.kind {
1079 ExprKind::FunctionCall(f) => {
1081 if let ExprKind::Identifier(id) = &f.name.kind
1082 && id.as_str() == name
1083 {
1084 out.push(f.name.span);
1085 }
1086 function_refs_in_expr(f.name, name, out);
1088 for a in f.args.iter() {
1089 function_refs_in_expr(&a.value, name, out);
1090 }
1091 }
1092 ExprKind::MethodCall(m) => {
1094 function_refs_in_expr(m.object, name, out);
1095 for a in m.args.iter() {
1097 function_refs_in_expr(&a.value, name, out);
1098 }
1099 }
1100 ExprKind::NullsafeMethodCall(m) => {
1101 function_refs_in_expr(m.object, name, out);
1102 for a in m.args.iter() {
1103 function_refs_in_expr(&a.value, name, out);
1104 }
1105 }
1106 ExprKind::StaticMethodCall(s) => {
1107 function_refs_in_expr(s.class, name, out);
1108 for a in s.args.iter() {
1109 function_refs_in_expr(&a.value, name, out);
1110 }
1111 }
1112 ExprKind::New(n) => {
1113 for a in n.args.iter() {
1114 function_refs_in_expr(&a.value, name, out);
1115 }
1116 }
1117 ExprKind::Assign(a) => {
1118 function_refs_in_expr(a.target, name, out);
1119 function_refs_in_expr(a.value, name, out);
1120 }
1121 ExprKind::Binary(b) => {
1122 function_refs_in_expr(b.left, name, out);
1123 function_refs_in_expr(b.right, name, out);
1124 }
1125 ExprKind::UnaryPrefix(u) => function_refs_in_expr(u.operand, name, out),
1126 ExprKind::UnaryPostfix(u) => function_refs_in_expr(u.operand, name, out),
1127 ExprKind::Ternary(t) => {
1128 function_refs_in_expr(t.condition, name, out);
1129 if let Some(e) = t.then_expr {
1130 function_refs_in_expr(e, name, out);
1131 }
1132 function_refs_in_expr(t.else_expr, name, out);
1133 }
1134 ExprKind::NullCoalesce(n) => {
1135 function_refs_in_expr(n.left, name, out);
1136 function_refs_in_expr(n.right, name, out);
1137 }
1138 ExprKind::Parenthesized(e) => function_refs_in_expr(e, name, out),
1139 ExprKind::ErrorSuppress(e) => function_refs_in_expr(e, name, out),
1140 ExprKind::Cast(_, e) => function_refs_in_expr(e, name, out),
1141 ExprKind::Clone(e) => function_refs_in_expr(e, name, out),
1142 ExprKind::ThrowExpr(e) => function_refs_in_expr(e, name, out),
1143 ExprKind::Print(e) => function_refs_in_expr(e, name, out),
1144 ExprKind::Empty(e) => function_refs_in_expr(e, name, out),
1145 ExprKind::Eval(e) => function_refs_in_expr(e, name, out),
1146 ExprKind::Yield(y) => {
1147 if let Some(k) = y.key {
1148 function_refs_in_expr(k, name, out);
1149 }
1150 if let Some(v) = y.value {
1151 function_refs_in_expr(v, name, out);
1152 }
1153 }
1154 ExprKind::ArrayAccess(a) => {
1155 function_refs_in_expr(a.array, name, out);
1156 if let Some(idx) = a.index {
1157 function_refs_in_expr(idx, name, out);
1158 }
1159 }
1160 ExprKind::PropertyAccess(p) => function_refs_in_expr(p.object, name, out),
1161 ExprKind::NullsafePropertyAccess(p) => function_refs_in_expr(p.object, name, out),
1162 ExprKind::StaticPropertyAccess(s) => function_refs_in_expr(s.class, name, out),
1163 ExprKind::Match(m) => {
1164 function_refs_in_expr(m.subject, name, out);
1165 for arm in m.arms.iter() {
1166 if let Some(conds) = &arm.conditions {
1167 for c in conds.iter() {
1168 function_refs_in_expr(c, name, out);
1169 }
1170 }
1171 function_refs_in_expr(&arm.body, name, out);
1172 }
1173 }
1174 ExprKind::Array(elements) => {
1175 for elem in elements.iter() {
1176 if let Some(key) = &elem.key {
1177 function_refs_in_expr(key, name, out);
1178 }
1179 function_refs_in_expr(&elem.value, name, out);
1180 }
1181 }
1182 ExprKind::Isset(exprs) => {
1183 for e in exprs.iter() {
1184 function_refs_in_expr(e, name, out);
1185 }
1186 }
1187 ExprKind::Include(_, e) => function_refs_in_expr(e, name, out),
1188 ExprKind::Exit(Some(e)) => function_refs_in_expr(e, name, out),
1189 ExprKind::Closure(c) => function_refs_in_stmts(&c.body, name, out),
1190 ExprKind::ArrowFunction(a) => function_refs_in_expr(a.body, name, out),
1191 ExprKind::AnonymousClass(c) => {
1192 for member in c.members.iter() {
1193 if let ClassMemberKind::Method(m) = &member.kind
1194 && let Some(body) = &m.body
1195 {
1196 function_refs_in_stmts(body, name, out);
1197 }
1198 }
1199 }
1200 _ => {}
1201 }
1202}
1203
1204pub fn method_refs_in_stmts(stmts: &[Stmt<'_, '_>], name: &str, out: &mut Vec<Span>) {
1207 let v = MethodRefsVisitor { name };
1208 walk_refs(&v, stmts, out);
1209}
1210
1211struct MethodRefsVisitor<'a> {
1212 name: &'a str,
1213}
1214
1215impl RefVisitor for MethodRefsVisitor<'_> {
1216 fn visit_expr(&self, expr: &Expr<'_, '_>, out: &mut Vec<Span>) {
1217 method_refs_in_expr(expr, self.name, out);
1218 }
1219
1220 fn visit_stmt(&self, stmt: &Stmt<'_, '_>, out: &mut Vec<Span>) -> bool {
1221 match &stmt.kind {
1222 StmtKind::Function(f) => {
1223 walk_refs(self, &f.body, out);
1224 true
1225 }
1226 StmtKind::Class(c) => {
1227 for member in c.members.iter() {
1228 if let ClassMemberKind::Method(m) = &member.kind
1229 && let Some(body) = &m.body
1230 {
1231 walk_refs(self, body, out);
1232 }
1233 }
1234 true
1235 }
1236 StmtKind::Trait(t) => {
1237 for member in t.members.iter() {
1238 if let ClassMemberKind::Method(m) = &member.kind
1239 && let Some(body) = &m.body
1240 {
1241 walk_refs(self, body, out);
1242 }
1243 }
1244 true
1245 }
1246 StmtKind::Enum(e) => {
1247 for member in e.members.iter() {
1248 if let EnumMemberKind::Method(m) = &member.kind
1249 && let Some(body) = &m.body
1250 {
1251 walk_refs(self, body, out);
1252 }
1253 }
1254 true
1255 }
1256 StmtKind::Namespace(ns) => {
1257 if let NamespaceBody::Braced(inner) = &ns.body {
1258 walk_refs(self, inner, out);
1259 }
1260 true
1261 }
1262 _ => false,
1263 }
1264 }
1265}
1266
1267fn method_refs_in_expr(expr: &Expr<'_, '_>, name: &str, out: &mut Vec<Span>) {
1268 match &expr.kind {
1269 ExprKind::MethodCall(m) => {
1270 method_refs_in_expr(m.object, name, out);
1271 if let ExprKind::Identifier(id) = &m.method.kind
1273 && id.as_str() == name
1274 {
1275 out.push(m.method.span);
1276 }
1277 for a in m.args.iter() {
1278 method_refs_in_expr(&a.value, name, out);
1279 }
1280 }
1281 ExprKind::NullsafeMethodCall(m) => {
1282 method_refs_in_expr(m.object, name, out);
1283 if let ExprKind::Identifier(id) = &m.method.kind
1284 && id.as_str() == name
1285 {
1286 out.push(m.method.span);
1287 }
1288 for a in m.args.iter() {
1289 method_refs_in_expr(&a.value, name, out);
1290 }
1291 }
1292 ExprKind::StaticMethodCall(s) => {
1293 method_refs_in_expr(s.class, name, out);
1294 if s.method.as_ref() == name {
1295 out.push(expr.span);
1299 }
1300 for a in s.args.iter() {
1301 method_refs_in_expr(&a.value, name, out);
1302 }
1303 }
1304 ExprKind::FunctionCall(f) => {
1305 method_refs_in_expr(f.name, name, out);
1306 for a in f.args.iter() {
1307 method_refs_in_expr(&a.value, name, out);
1308 }
1309 }
1310 ExprKind::New(n) => {
1311 for a in n.args.iter() {
1312 method_refs_in_expr(&a.value, name, out);
1313 }
1314 }
1315 ExprKind::Assign(a) => {
1316 method_refs_in_expr(a.target, name, out);
1317 method_refs_in_expr(a.value, name, out);
1318 }
1319 ExprKind::Binary(b) => {
1320 method_refs_in_expr(b.left, name, out);
1321 method_refs_in_expr(b.right, name, out);
1322 }
1323 ExprKind::UnaryPrefix(u) => method_refs_in_expr(u.operand, name, out),
1324 ExprKind::UnaryPostfix(u) => method_refs_in_expr(u.operand, name, out),
1325 ExprKind::Ternary(t) => {
1326 method_refs_in_expr(t.condition, name, out);
1327 if let Some(e) = t.then_expr {
1328 method_refs_in_expr(e, name, out);
1329 }
1330 method_refs_in_expr(t.else_expr, name, out);
1331 }
1332 ExprKind::NullCoalesce(n) => {
1333 method_refs_in_expr(n.left, name, out);
1334 method_refs_in_expr(n.right, name, out);
1335 }
1336 ExprKind::Parenthesized(e) => method_refs_in_expr(e, name, out),
1337 ExprKind::ErrorSuppress(e) => method_refs_in_expr(e, name, out),
1338 ExprKind::Cast(_, e) => method_refs_in_expr(e, name, out),
1339 ExprKind::Clone(e) => method_refs_in_expr(e, name, out),
1340 ExprKind::ThrowExpr(e) => method_refs_in_expr(e, name, out),
1341 ExprKind::Print(e) => method_refs_in_expr(e, name, out),
1342 ExprKind::Empty(e) => method_refs_in_expr(e, name, out),
1343 ExprKind::Eval(e) => method_refs_in_expr(e, name, out),
1344 ExprKind::Yield(y) => {
1345 if let Some(k) = y.key {
1346 method_refs_in_expr(k, name, out);
1347 }
1348 if let Some(v) = y.value {
1349 method_refs_in_expr(v, name, out);
1350 }
1351 }
1352 ExprKind::ArrayAccess(a) => {
1353 method_refs_in_expr(a.array, name, out);
1354 if let Some(idx) = a.index {
1355 method_refs_in_expr(idx, name, out);
1356 }
1357 }
1358 ExprKind::PropertyAccess(p) => method_refs_in_expr(p.object, name, out),
1359 ExprKind::NullsafePropertyAccess(p) => method_refs_in_expr(p.object, name, out),
1360 ExprKind::StaticPropertyAccess(s) => method_refs_in_expr(s.class, name, out),
1361 ExprKind::Match(m) => {
1362 method_refs_in_expr(m.subject, name, out);
1363 for arm in m.arms.iter() {
1364 if let Some(conds) = &arm.conditions {
1365 for c in conds.iter() {
1366 method_refs_in_expr(c, name, out);
1367 }
1368 }
1369 method_refs_in_expr(&arm.body, name, out);
1370 }
1371 }
1372 ExprKind::Array(elements) => {
1373 for elem in elements.iter() {
1374 if let Some(key) = &elem.key {
1375 method_refs_in_expr(key, name, out);
1376 }
1377 method_refs_in_expr(&elem.value, name, out);
1378 }
1379 }
1380 ExprKind::Isset(exprs) => {
1381 for e in exprs.iter() {
1382 method_refs_in_expr(e, name, out);
1383 }
1384 }
1385 ExprKind::Include(_, e) => method_refs_in_expr(e, name, out),
1386 ExprKind::Exit(Some(e)) => method_refs_in_expr(e, name, out),
1387 ExprKind::Closure(c) => method_refs_in_stmts(&c.body, name, out),
1388 ExprKind::ArrowFunction(a) => method_refs_in_expr(a.body, name, out),
1389 ExprKind::AnonymousClass(c) => {
1390 for member in c.members.iter() {
1391 if let ClassMemberKind::Method(m) = &member.kind
1392 && let Some(body) = &m.body
1393 {
1394 method_refs_in_stmts(body, name, out);
1395 }
1396 }
1397 }
1398 _ => {}
1399 }
1400}
1401
1402pub fn class_refs_in_stmts(stmts: &[Stmt<'_, '_>], class_name: &str, out: &mut Vec<Span>) {
1407 let v = ClassRefsVisitor { class_name };
1408 walk_refs(&v, stmts, out);
1409}
1410
1411struct ClassRefsVisitor<'a> {
1412 class_name: &'a str,
1413}
1414
1415impl RefVisitor for ClassRefsVisitor<'_> {
1416 fn visit_expr(&self, expr: &Expr<'_, '_>, out: &mut Vec<Span>) {
1417 class_refs_in_expr(expr, self.class_name, out);
1418 }
1419
1420 fn visit_stmt(&self, stmt: &Stmt<'_, '_>, out: &mut Vec<Span>) -> bool {
1421 match &stmt.kind {
1422 StmtKind::Function(f) => {
1423 for p in f.params.iter() {
1424 if let Some(th) = &p.type_hint {
1425 collect_class_in_type_hint(th, self.class_name, out);
1426 }
1427 }
1428 if let Some(rt) = &f.return_type {
1429 collect_class_in_type_hint(rt, self.class_name, out);
1430 }
1431 walk_refs(self, &f.body, out);
1432 true
1433 }
1434 StmtKind::Class(c) => {
1435 if let Some(ext) = &c.extends {
1437 let last = ext
1438 .to_string_repr()
1439 .rsplit('\\')
1440 .next()
1441 .unwrap_or("")
1442 .to_string();
1443 if last == self.class_name {
1444 let span = ext.span();
1445 let offset = (ext.to_string_repr().len() - last.len()) as u32;
1446 out.push(Span {
1447 start: span.start + offset,
1448 end: span.end,
1449 });
1450 }
1451 }
1452 for iface in c.implements.iter() {
1454 let last = iface
1455 .to_string_repr()
1456 .rsplit('\\')
1457 .next()
1458 .unwrap_or("")
1459 .to_string();
1460 if last == self.class_name {
1461 let span = iface.span();
1462 let offset = (iface.to_string_repr().len() - last.len()) as u32;
1463 out.push(Span {
1464 start: span.start + offset,
1465 end: span.end,
1466 });
1467 }
1468 }
1469 for member in c.members.iter() {
1470 match &member.kind {
1471 ClassMemberKind::Method(m) => {
1472 for p in m.params.iter() {
1473 if let Some(th) = &p.type_hint {
1474 collect_class_in_type_hint(th, self.class_name, out);
1475 }
1476 }
1477 if let Some(rt) = &m.return_type {
1478 collect_class_in_type_hint(rt, self.class_name, out);
1479 }
1480 if let Some(body) = &m.body {
1481 walk_refs(self, body, out);
1482 }
1483 }
1484 ClassMemberKind::Property(p) => {
1485 if let Some(th) = &p.type_hint {
1486 collect_class_in_type_hint(th, self.class_name, out);
1487 }
1488 if let Some(default) = &p.default {
1489 class_refs_in_expr(default, self.class_name, out);
1490 }
1491 }
1492 _ => {}
1493 }
1494 }
1495 true
1496 }
1497 StmtKind::Interface(i) => {
1498 for parent in i.extends.iter() {
1499 let last = parent
1500 .to_string_repr()
1501 .rsplit('\\')
1502 .next()
1503 .unwrap_or("")
1504 .to_string();
1505 if last == self.class_name {
1506 let span = parent.span();
1507 let offset = (parent.to_string_repr().len() - last.len()) as u32;
1508 out.push(Span {
1509 start: span.start + offset,
1510 end: span.end,
1511 });
1512 }
1513 }
1514 true
1515 }
1516 StmtKind::Trait(t) => {
1517 for member in t.members.iter() {
1518 if let ClassMemberKind::Method(m) = &member.kind {
1519 for p in m.params.iter() {
1520 if let Some(th) = &p.type_hint {
1521 collect_class_in_type_hint(th, self.class_name, out);
1522 }
1523 }
1524 if let Some(rt) = &m.return_type {
1525 collect_class_in_type_hint(rt, self.class_name, out);
1526 }
1527 if let Some(body) = &m.body {
1528 walk_refs(self, body, out);
1529 }
1530 }
1531 }
1532 true
1533 }
1534 StmtKind::Enum(e) => {
1535 for member in e.members.iter() {
1536 if let EnumMemberKind::Method(m) = &member.kind
1537 && let Some(body) = &m.body
1538 {
1539 walk_refs(self, body, out);
1540 }
1541 }
1542 true
1543 }
1544 StmtKind::Namespace(ns) => {
1545 if let NamespaceBody::Braced(inner) = &ns.body {
1546 walk_refs(self, inner, out);
1547 }
1548 true
1549 }
1550 StmtKind::TryCatch(t) => {
1551 walk_refs(self, &t.body, out);
1552 for catch in t.catches.iter() {
1553 for ty in catch.types.iter() {
1554 let last = ty
1555 .to_string_repr()
1556 .rsplit('\\')
1557 .next()
1558 .unwrap_or("")
1559 .to_string();
1560 if last == self.class_name {
1561 let span = ty.span();
1562 let offset = (ty.to_string_repr().len() - last.len()) as u32;
1563 out.push(Span {
1564 start: span.start + offset,
1565 end: span.end,
1566 });
1567 }
1568 }
1569 walk_refs(self, &catch.body, out);
1570 }
1571 if let Some(finally) = &t.finally {
1572 walk_refs(self, finally, out);
1573 }
1574 true
1575 }
1576 _ => false,
1577 }
1578 }
1579}
1580
1581fn class_refs_in_expr(expr: &Expr<'_, '_>, class_name: &str, out: &mut Vec<Span>) {
1582 match &expr.kind {
1583 ExprKind::New(n) => {
1585 if let ExprKind::Identifier(id) = &n.class.kind
1586 && id.rsplit('\\').next().unwrap_or(id) == class_name
1587 {
1588 out.push(n.class.span);
1589 } else {
1590 }
1592 for a in n.args.iter() {
1593 class_refs_in_expr(&a.value, class_name, out);
1594 }
1595 }
1596 ExprKind::Binary(b) => {
1598 class_refs_in_expr(b.left, class_name, out);
1599 if let ExprKind::Identifier(id) = &b.right.kind
1603 && id.rsplit('\\').next().unwrap_or(id) == class_name
1604 {
1605 out.push(b.right.span);
1606 } else {
1607 class_refs_in_expr(b.right, class_name, out);
1608 }
1609 }
1610 ExprKind::StaticMethodCall(s) => {
1612 if let ExprKind::Identifier(id) = &s.class.kind
1613 && id.rsplit('\\').next().unwrap_or(id) == class_name
1614 {
1615 out.push(s.class.span);
1616 }
1617 for a in s.args.iter() {
1618 class_refs_in_expr(&a.value, class_name, out);
1619 }
1620 }
1621 ExprKind::StaticPropertyAccess(s) => {
1622 if let ExprKind::Identifier(id) = &s.class.kind
1623 && id.rsplit('\\').next().unwrap_or(id) == class_name
1624 {
1625 out.push(s.class.span);
1626 }
1627 }
1628 ExprKind::ClassConstAccess(c) => {
1629 if let ExprKind::Identifier(id) = &c.class.kind
1630 && id.rsplit('\\').next().unwrap_or(id) == class_name
1631 {
1632 out.push(c.class.span);
1633 }
1634 }
1635 ExprKind::FunctionCall(f) => {
1637 for a in f.args.iter() {
1638 class_refs_in_expr(&a.value, class_name, out);
1639 }
1640 }
1641 ExprKind::MethodCall(m) => {
1642 class_refs_in_expr(m.object, class_name, out);
1643 for a in m.args.iter() {
1644 class_refs_in_expr(&a.value, class_name, out);
1645 }
1646 }
1647 ExprKind::NullsafeMethodCall(m) => {
1648 class_refs_in_expr(m.object, class_name, out);
1649 for a in m.args.iter() {
1650 class_refs_in_expr(&a.value, class_name, out);
1651 }
1652 }
1653 ExprKind::Assign(a) => {
1654 class_refs_in_expr(a.target, class_name, out);
1655 class_refs_in_expr(a.value, class_name, out);
1656 }
1657 ExprKind::UnaryPrefix(u) => class_refs_in_expr(u.operand, class_name, out),
1658 ExprKind::UnaryPostfix(u) => class_refs_in_expr(u.operand, class_name, out),
1659 ExprKind::Ternary(t) => {
1660 class_refs_in_expr(t.condition, class_name, out);
1661 if let Some(e) = t.then_expr {
1662 class_refs_in_expr(e, class_name, out);
1663 }
1664 class_refs_in_expr(t.else_expr, class_name, out);
1665 }
1666 ExprKind::NullCoalesce(n) => {
1667 class_refs_in_expr(n.left, class_name, out);
1668 class_refs_in_expr(n.right, class_name, out);
1669 }
1670 ExprKind::Parenthesized(e) => class_refs_in_expr(e, class_name, out),
1671 ExprKind::ErrorSuppress(e) => class_refs_in_expr(e, class_name, out),
1672 ExprKind::Cast(_, e) => class_refs_in_expr(e, class_name, out),
1673 ExprKind::Clone(e) => class_refs_in_expr(e, class_name, out),
1674 ExprKind::ThrowExpr(e) => class_refs_in_expr(e, class_name, out),
1675 ExprKind::Print(e) => class_refs_in_expr(e, class_name, out),
1676 ExprKind::Empty(e) => class_refs_in_expr(e, class_name, out),
1677 ExprKind::Eval(e) => class_refs_in_expr(e, class_name, out),
1678 ExprKind::Yield(y) => {
1679 if let Some(k) = y.key {
1680 class_refs_in_expr(k, class_name, out);
1681 }
1682 if let Some(v) = y.value {
1683 class_refs_in_expr(v, class_name, out);
1684 }
1685 }
1686 ExprKind::ArrayAccess(a) => {
1687 class_refs_in_expr(a.array, class_name, out);
1688 if let Some(idx) = a.index {
1689 class_refs_in_expr(idx, class_name, out);
1690 }
1691 }
1692 ExprKind::PropertyAccess(p) => class_refs_in_expr(p.object, class_name, out),
1693 ExprKind::NullsafePropertyAccess(p) => class_refs_in_expr(p.object, class_name, out),
1694 ExprKind::Match(m) => {
1695 class_refs_in_expr(m.subject, class_name, out);
1696 for arm in m.arms.iter() {
1697 if let Some(conds) = &arm.conditions {
1698 for c in conds.iter() {
1699 class_refs_in_expr(c, class_name, out);
1700 }
1701 }
1702 class_refs_in_expr(&arm.body, class_name, out);
1703 }
1704 }
1705 ExprKind::Array(elements) => {
1706 for elem in elements.iter() {
1707 if let Some(key) = &elem.key {
1708 class_refs_in_expr(key, class_name, out);
1709 }
1710 class_refs_in_expr(&elem.value, class_name, out);
1711 }
1712 }
1713 ExprKind::Isset(exprs) => {
1714 for e in exprs.iter() {
1715 class_refs_in_expr(e, class_name, out);
1716 }
1717 }
1718 ExprKind::Include(_, e) => class_refs_in_expr(e, class_name, out),
1719 ExprKind::Exit(Some(e)) => class_refs_in_expr(e, class_name, out),
1720 ExprKind::Closure(c) => class_refs_in_stmts(&c.body, class_name, out),
1721 ExprKind::ArrowFunction(a) => class_refs_in_expr(a.body, class_name, out),
1722 ExprKind::AnonymousClass(c) => {
1723 for member in c.members.iter() {
1724 if let ClassMemberKind::Method(m) = &member.kind
1725 && let Some(body) = &m.body
1726 {
1727 class_refs_in_stmts(body, class_name, out);
1728 }
1729 }
1730 }
1731 _ => {}
1732 }
1733}
1734
1735fn collect_class_in_type_hint(th: &TypeHint<'_, '_>, class_name: &str, out: &mut Vec<Span>) {
1737 match &th.kind {
1738 TypeHintKind::Named(name) => {
1739 let repr = name.to_string_repr();
1740 let last = repr.rsplit('\\').next().unwrap_or(repr.as_ref());
1741 if last == class_name {
1742 let span = name.span();
1743 let offset = (repr.len() - last.len()) as u32;
1744 out.push(Span {
1745 start: span.start + offset,
1746 end: span.end,
1747 });
1748 }
1749 }
1750 TypeHintKind::Nullable(inner) => collect_class_in_type_hint(inner, class_name, out),
1751 TypeHintKind::Union(types) | TypeHintKind::Intersection(types) => {
1752 for t in types.iter() {
1753 collect_class_in_type_hint(t, class_name, out);
1754 }
1755 }
1756 TypeHintKind::Keyword(_, _) => {}
1757 }
1758}
1759
1760#[cfg(test)]
1761mod tests {
1762 use super::*;
1763 use crate::ast::ParsedDoc;
1764
1765 fn spans_to_strs<'a>(source: &'a str, spans: &[Span]) -> Vec<&'a str> {
1767 spans
1768 .iter()
1769 .map(|s| &source[s.start as usize..s.end as usize])
1770 .collect()
1771 }
1772
1773 fn parse(src: &str) -> ParsedDoc {
1774 ParsedDoc::parse(src.to_string())
1775 }
1776
1777 #[test]
1780 fn refs_finds_function_declaration_and_call() {
1781 let src = "<?php\nfunction greet() {}\ngreet();";
1782 let doc = parse(src);
1783 let mut out = vec![];
1784 refs_in_stmts(src, &doc.program().stmts, "greet", &mut out);
1785 let texts = spans_to_strs(src, &out);
1786 assert!(texts.contains(&"greet"), "expected function decl name");
1787 assert_eq!(texts.iter().filter(|&&t| t == "greet").count(), 2);
1788 }
1789
1790 #[test]
1791 fn refs_finds_class_declaration_and_new() {
1792 let src = "<?php\nclass Foo {}\n$x = new Foo();";
1793 let doc = parse(src);
1794 let mut out = vec![];
1795 refs_in_stmts(src, &doc.program().stmts, "Foo", &mut out);
1796 let texts = spans_to_strs(src, &out);
1797 assert!(texts.iter().all(|&t| t == "Foo"));
1798 assert_eq!(texts.len(), 2);
1799 }
1800
1801 #[test]
1802 fn refs_finds_method_declaration_inside_class() {
1803 let src = "<?php\nclass Bar { function run() { $this->run(); } }";
1804 let doc = parse(src);
1805 let mut out = vec![];
1806 refs_in_stmts(src, &doc.program().stmts, "run", &mut out);
1807 let texts = spans_to_strs(src, &out);
1808 assert!(texts.iter().any(|&t| t == "run"));
1810 }
1811
1812 #[test]
1813 fn refs_returns_empty_for_unknown_name() {
1814 let src = "<?php\nfunction greet() {}";
1815 let doc = parse(src);
1816 let mut out = vec![];
1817 refs_in_stmts(src, &doc.program().stmts, "nope", &mut out);
1818 assert!(out.is_empty());
1819 }
1820
1821 #[test]
1824 fn refs_with_use_includes_use_import() {
1825 let src = "<?php\nuse Vendor\\Lib\\Foo;\n$x = new Foo();";
1826 let doc = parse(src);
1827 let mut out = vec![];
1828 refs_in_stmts_with_use(src, &doc.program().stmts, "Foo", &mut out);
1829 let texts = spans_to_strs(src, &out);
1830 assert!(
1832 texts.iter().filter(|&&t| t == "Foo").count() >= 2,
1833 "got: {texts:?}"
1834 );
1835 }
1836
1837 #[test]
1838 fn refs_without_use_misses_use_import() {
1839 let src = "<?php\nuse Vendor\\Lib\\Foo;\n$x = new Foo();";
1840 let doc = parse(src);
1841 let mut out = vec![];
1842 refs_in_stmts(src, &doc.program().stmts, "Foo", &mut out);
1843 let texts = spans_to_strs(src, &out);
1844 assert!(
1846 texts.iter().filter(|&&t| t == "Foo").count() < 2,
1847 "refs_in_stmts should not include use import; got: {texts:?}"
1848 );
1849 }
1850
1851 #[test]
1854 fn var_refs_finds_variable_in_assignment_and_echo() {
1855 let src = "<?php\n$x = 1;\necho $x;";
1856 let doc = parse(src);
1857 let mut out = vec![];
1858 var_refs_in_stmts(&doc.program().stmts, "x", &mut out);
1859 assert_eq!(out.len(), 2, "expected $x in assignment and echo");
1860 }
1861
1862 #[test]
1863 fn var_refs_respects_function_scope_boundary() {
1864 let src = "<?php\n$x = 1;\nfunction inner() { $x = 2; }";
1866 let doc = parse(src);
1867 let mut out = vec![];
1868 var_refs_in_stmts(&doc.program().stmts, "x", &mut out);
1869 assert_eq!(out.len(), 1, "inner $x must not cross scope boundary");
1871 }
1872
1873 #[test]
1874 fn var_refs_traverses_if_while_for_foreach() {
1875 let src = "<?php\n$x = 0;\nif ($x) { $x++; }\nwhile ($x > 0) { $x--; }\nfor ($x = 0; $x < 3; $x++) {}\nforeach ([$x] as $v) {}";
1876 let doc = parse(src);
1877 let mut out = vec![];
1878 var_refs_in_stmts(&doc.program().stmts, "x", &mut out);
1879 assert!(
1880 out.len() >= 5,
1881 "expected multiple $x refs, got {}",
1882 out.len()
1883 );
1884 }
1885
1886 #[test]
1887 fn var_refs_does_not_cross_closure_boundary() {
1888 let src = "<?php\n$x = 1;\n$f = function() { $x = 2; };";
1889 let doc = parse(src);
1890 let mut out = vec![];
1891 var_refs_in_stmts(&doc.program().stmts, "x", &mut out);
1892 assert_eq!(
1894 out.len(),
1895 1,
1896 "closure $x must not be collected by outer scope walk"
1897 );
1898 }
1899
1900 #[test]
1903 fn collect_scope_finds_var_inside_function() {
1904 let src = "<?php\nfunction foo($x) { return $x + 1; }";
1905 let doc = parse(src);
1906 let byte_off = src.find("return").unwrap();
1908 let mut out = vec![];
1909 collect_var_refs_in_scope(&doc.program().stmts, "x", byte_off, &mut out);
1910 assert!(
1912 out.len() >= 2,
1913 "expected param + body ref, got {}",
1914 out.len()
1915 );
1916 }
1917
1918 #[test]
1919 fn collect_scope_top_level_when_no_function() {
1920 let src = "<?php\n$x = 1;\necho $x;";
1921 let doc = parse(src);
1922 let byte_off = src.find("echo").unwrap();
1923 let mut out = vec![];
1924 collect_var_refs_in_scope(&doc.program().stmts, "x", byte_off, &mut out);
1925 assert_eq!(out.len(), 2);
1926 }
1927
1928 #[test]
1931 fn property_refs_finds_declaration_and_access() {
1932 let src = "<?php\nclass Baz { public int $val = 0; function get() { return $this->val; } }";
1933 let doc = parse(src);
1934 let mut out = vec![];
1935 property_refs_in_stmts(src, &doc.program().stmts, "val", &mut out);
1936 assert_eq!(out.len(), 2, "expected decl + access, got {}", out.len());
1938 }
1939
1940 #[test]
1941 fn property_refs_finds_nullsafe_access() {
1942 let src = "<?php\n$r = $obj?->name;";
1943 let doc = parse(src);
1944 let mut out = vec![];
1945 property_refs_in_stmts(src, &doc.program().stmts, "name", &mut out);
1946 assert_eq!(out.len(), 1);
1947 }
1948
1949 #[test]
1952 fn function_refs_only_matches_free_calls_not_methods() {
1953 let src = "<?php\nfunction run() {}\nrun();\n$obj->run();";
1954 let doc = parse(src);
1955 let mut out = vec![];
1956 function_refs_in_stmts(&doc.program().stmts, "run", &mut out);
1957 assert_eq!(out.len(), 1, "got: {out:?}");
1959 }
1960
1961 #[test]
1964 fn method_refs_only_matches_method_calls_not_free_functions() {
1965 let src = "<?php\nfunction run() {}\nrun();\n$obj->run();";
1966 let doc = parse(src);
1967 let mut out = vec![];
1968 method_refs_in_stmts(&doc.program().stmts, "run", &mut out);
1969 assert_eq!(out.len(), 1, "got: {out:?}");
1971 }
1972
1973 #[test]
1974 fn method_refs_finds_nullsafe_method_call() {
1975 let src = "<?php\n$obj?->process();";
1976 let doc = parse(src);
1977 let mut out = vec![];
1978 method_refs_in_stmts(&doc.program().stmts, "process", &mut out);
1979 assert_eq!(out.len(), 1);
1980 }
1981
1982 #[test]
1985 fn class_refs_finds_new_and_extends() {
1986 let src = "<?php\nclass Child extends Base {}\n$x = new Base();";
1987 let doc = parse(src);
1988 let mut out = vec![];
1989 class_refs_in_stmts(&doc.program().stmts, "Base", &mut out);
1990 assert!(out.len() >= 2, "expected extends + new, got {}", out.len());
1991 }
1992
1993 #[test]
1994 fn class_refs_does_not_match_free_function_with_same_name() {
1995 let src = "<?php\nfunction Foo() {}\nFoo();";
1996 let doc = parse(src);
1997 let mut out = vec![];
1998 class_refs_in_stmts(&doc.program().stmts, "Foo", &mut out);
1999 assert!(
2000 out.is_empty(),
2001 "free function call must not be a class ref; got: {out:?}"
2002 );
2003 }
2004
2005 #[test]
2006 fn class_refs_finds_type_hint_in_function_param() {
2007 let src = "<?php\nfunction take(MyClass $obj): MyClass { return $obj; }";
2008 let doc = parse(src);
2009 let mut out = vec![];
2010 class_refs_in_stmts(&doc.program().stmts, "MyClass", &mut out);
2011 assert_eq!(out.len(), 2, "got {out:?}");
2013 }
2014}