If ``call_node`` is a builtin ``getattr(obj, "name"[, default])`` whose name argument is a PLAIN string literal, return ``(name, string_node)``: the string names an attribute looked up by that exact name, so it resolves to a callable def of the same label. A dynamic name — a
(call_node)
| 5008 | yield ch |
| 5009 | |
| 5010 | def _getattr_ref_name(call_node): |
| 5011 | """If ``call_node`` is a builtin ``getattr(obj, "name"[, default])`` whose name |
| 5012 | argument is a PLAIN string literal, return ``(name, string_node)``: the string |
| 5013 | names an attribute looked up by that exact name, so it resolves to a callable |
| 5014 | def of the same label. A dynamic name — a variable, an f-string, a concatenation, |
| 5015 | any expression — is not statically resolvable and yields ``None`` (no edge is |
| 5016 | manufactured), as do the 1-arg form and ``obj.getattr(...)`` (a method, not the |
| 5017 | builtin). Unlike an identifier, a string is an attribute name and is never |
| 5018 | shadowed by a param/local, so callers resolve it without the shadow guard. |
| 5019 | """ |
| 5020 | fn = call_node.child_by_field_name("function") |
| 5021 | if fn is None or fn.type != "identifier" or _read_text(fn, source) != "getattr": |
| 5022 | return None |
| 5023 | args = call_node.child_by_field_name("arguments") |
| 5024 | if args is None: |
| 5025 | return None |
| 5026 | positional = [c for c in args.children |
| 5027 | if c.is_named and c.type not in ("keyword_argument", "comment")] |
| 5028 | if len(positional) < 2: |
| 5029 | return None |
| 5030 | name_node = positional[1] |
| 5031 | if name_node.type != "string" or any( |
| 5032 | ch.type == "interpolation" for ch in name_node.children |
| 5033 | ): |
| 5034 | return None # variable, f-string, concatenation, or expression — dynamic |
| 5035 | content = next( |
| 5036 | (ch for ch in name_node.children if ch.type == "string_content"), None) |
| 5037 | if content is None: |
| 5038 | return None # empty string "" — no attribute name |
| 5039 | return _read_text(content, source), name_node |
| 5040 | |
| 5041 | def _php_class_const_scope(n) -> str | None: |
| 5042 | scope = n.child_by_field_name("scope") |
no test coverage detected