|
1 | 1 | """ |
2 | | - Sphinx PHP domain. |
3 | | - |
4 | | - The PHP domain. Based off of the rubydomain by SHIBUKAWA Yoshiki |
| 2 | +Sphinx PHP domain. |
5 | 3 |
|
6 | | - :copyright: Copyright 2016 by Mark Story |
7 | | - :license: BSD, see LICENSE for details. |
| 4 | +The PHP domain. Based off of the rubydomain by SHIBUKAWA Yoshiki |
| 5 | +
|
| 6 | +:copyright: Copyright 2016 by Mark Story |
| 7 | +:license: BSD, see LICENSE for details. |
8 | 8 | """ |
9 | 9 | import re |
10 | 10 | import inspect |
@@ -70,9 +70,9 @@ def throw_if_false(fromdocnode, value, message: str): |
70 | 70 |
|
71 | 71 | separators = { |
72 | 72 | "global": None, |
73 | | - "namespace": None, |
74 | | - "function": None, |
75 | | - "interface": None, |
| 73 | + "namespace": NS, |
| 74 | + "function": NS, |
| 75 | + "interface": NS, |
76 | 76 | "class": None, |
77 | 77 | "trait": None, |
78 | 78 | "enum": None, |
@@ -229,17 +229,27 @@ def handle_signature(self, sig, signode): |
229 | 229 | ) |
230 | 230 | separator = separators[self.objtype] |
231 | 231 |
|
232 | | - if "::" in name_prefix: |
| 232 | + classname = self.env.temp_data.get("php:class") |
| 233 | + # Method declared as Class::methodName |
| 234 | + if not classname and "::" in name_prefix: |
233 | 235 | classname = name_prefix.rstrip("::") |
234 | | - else: |
235 | | - classname = self.env.temp_data.get("php:class") |
236 | 236 |
|
237 | | - if self.objtype == "global": |
| 237 | + if self.objtype == "global" or self.objtype == "function": |
| 238 | + add_module = False |
238 | 239 | namespace = None |
239 | 240 | classname = None |
240 | 241 | fullname = name |
241 | 242 | else: |
242 | | - if name_prefix: |
| 243 | + add_module = True |
| 244 | + # name_prefix and a non-static method, means the classname was |
| 245 | + # repeated. Trim off the <class>:: |
| 246 | + if name_prefix and self.objtype != 'staticmethod': |
| 247 | + if name_prefix.startswith(classname): |
| 248 | + name_prefix = name_prefix[len(classname):].rstrip('::') |
| 249 | + classname = classname.rstrip('::') |
| 250 | + fullname = name_prefix + classname + separator + name |
| 251 | + elif name_prefix: |
| 252 | + classname = classname.rstrip('::') |
243 | 253 | fullname = name_prefix + name |
244 | 254 |
|
245 | 255 | # Currently in a class, but not creating another class, |
@@ -279,13 +289,17 @@ def handle_signature(self, sig, signode): |
279 | 289 | name_prefix = namespace + NS + name_prefix |
280 | 290 | signode += addnodes.desc_addname(name_prefix, name_prefix) |
281 | 291 |
|
282 | | - elif ( |
283 | | - namespace |
284 | | - and not self.env.temp_data.get("php:in_class", False) |
285 | | - and self.env.config.add_module_names |
286 | | - ): |
287 | | - nodetext = namespace + NS |
288 | | - signode += addnodes.desc_addname(nodetext, nodetext) |
| 292 | + elif add_module and self.env.config.add_module_names: |
| 293 | + if self.objtype == 'global': |
| 294 | + nodetext = '' |
| 295 | + signode += addnodes.desc_addname(nodetext, nodetext) |
| 296 | + else: |
| 297 | + namespace = self.options.get( |
| 298 | + 'namespace', self.env.temp_data.get('php:namespace')) |
| 299 | + |
| 300 | + if namespace and not self.env.temp_data.get('php:in_class', False): |
| 301 | + nodetext = namespace + NS |
| 302 | + signode += addnodes.desc_addname(nodetext, nodetext) |
289 | 303 |
|
290 | 304 | signode += addnodes.desc_name(name, name) |
291 | 305 | if not arglist: |
@@ -603,26 +617,18 @@ class PhpXRefRole(XRefRole): |
603 | 617 |
|
604 | 618 | def process_link(self, env, refnode, has_explicit_title, title, target): |
605 | 619 | if not has_explicit_title: |
606 | | - # If the first char is '~' don't display the leading namespace & class. |
607 | | - if target.startswith("~"): # only has a meaning for the title |
608 | | - target = title[1:] |
609 | | - if title.startswith("~"): |
610 | | - title = title[1:] |
611 | | - title = re.sub(r"^[\w\\]+::", "", title) |
612 | | - |
613 | | - if title.startswith(NS): |
614 | | - title = title[1:] |
615 | | - |
616 | | - reftype = refnode.attributes["reftype"] |
617 | | - if reftype == "global": |
618 | | - namespace = None |
619 | | - classname = None |
620 | | - else: |
621 | | - namespace = env.temp_data.get("php:namespace") |
622 | | - classname = env.temp_data.get("php:class") |
| 620 | + if title.startswith("::"): |
| 621 | + title = title[2:] |
| 622 | + target = target.lstrip('~') # only has a meaning for the title |
623 | 623 |
|
624 | | - refnode["php:namespace"] = namespace |
625 | | - refnode["php:class"] = classname |
| 624 | + # If the first char is ~ don't display the leading namespace & class. |
| 625 | + if title.startswith('~'): |
| 626 | + m = re.search(r"(?:.+[:]{2}|(?:.*?\\{1,2})+)?(.*)\Z", title) |
| 627 | + if m: |
| 628 | + title = m.group(1) |
| 629 | + |
| 630 | + refnode["php:namespace"] = env.temp_data.get("php:namespace") |
| 631 | + refnode["php:class"] = env.temp_data.get("php:class") |
626 | 632 |
|
627 | 633 | return title, target |
628 | 634 |
|
@@ -814,76 +820,63 @@ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): |
814 | 820 | else: |
815 | 821 | namespace = node.get("php:namespace") |
816 | 822 | clsname = node.get("php:class") |
817 | | - name, obj = self.find_obj(env, node, namespace, clsname, target, typ) |
| 823 | + searchorder = node.hasattr("refspecific") and 1 or 0 |
| 824 | + name, obj = self.find_obj(env, node, namespace, clsname, target, typ, searchorder) |
818 | 825 | if not obj: |
819 | 826 | return None |
820 | 827 | else: |
821 | 828 | return make_refnode(builder, fromdocname, obj[0], name, contnode, name) |
822 | 829 |
|
823 | | - def find_obj(self, env, fromdocnode, namespace, classname, name, type): |
| 830 | + def find_obj(self, env, fromdocnode, namespace, classname, name, type, searchorder=0): |
824 | 831 | """ |
825 | 832 | Find a PHP object for "name", using the given namespace and classname. |
826 | 833 | """ |
827 | 834 | # strip parenthesis |
828 | 835 | if name[-2:] == "()": |
829 | 836 | name = name[:-2] |
830 | 837 |
|
831 | | - objects = self.data["objects"] |
832 | | - |
833 | | - if name.startswith(NS): |
834 | | - absname = name[1:] |
| 838 | + if not name: |
| 839 | + return None, None |
| 840 | + |
| 841 | + objects = self.data['objects'] |
| 842 | + |
| 843 | + newname = None |
| 844 | + if searchorder == 1: |
| 845 | + if namespace and classname and \ |
| 846 | + namespace + NS + classname + '::' + name in objects: |
| 847 | + newname = namespace + NS + classname + '::' + name |
| 848 | + elif namespace and namespace + NS + name in objects: |
| 849 | + newname = namespace + NS + name |
| 850 | + elif namespace and namespace + NS + name in objects: |
| 851 | + newname = namespace + NS + name |
| 852 | + elif classname and classname + '::' + name in objects: |
| 853 | + newname = classname + '.' + name |
| 854 | + elif classname and classname + '::$' + name in objects: |
| 855 | + newname = classname + '::$' + name |
| 856 | + elif name in objects: |
| 857 | + newname = name |
835 | 858 | else: |
836 | | - absname = (namespace + NS if namespace else "") + name |
837 | | - |
838 | | - if absname not in objects and name in objects: |
839 | | - # constants/functions can be namespaced, but allow fallback to global namespace the same way as PHP does |
840 | | - name_type = objects[name][1] |
841 | | - if ( |
842 | | - (name_type == "function" or name_type == "const") |
843 | | - and NS not in name |
844 | | - and "::" not in name |
845 | | - ): |
846 | | - absname = name |
847 | | - else: |
848 | | - if namespace and name.startswith(namespace + NS): |
849 | | - log_info( |
850 | | - fromdocnode, |
851 | | - f"Target {absname} not found - did you mean to write {name[len(namespace + NS):]}?", |
852 | | - ) |
853 | | - else: |
854 | | - log_info( |
855 | | - fromdocnode, |
856 | | - f"Target {absname} not found - did you mean to write {NS + name}?", |
857 | | - ) |
858 | | - absname = name # fallback for BC, might be removed in the next major release |
859 | | - |
860 | | - if absname in objects: |
861 | | - return absname, objects[absname] |
862 | | - |
863 | | - # PHP reserved keywords are never resolved using NS and ignore them when not defined |
864 | | - if name not in [ |
865 | | - "array", |
866 | | - "bool", |
867 | | - "callable", |
868 | | - "false", |
869 | | - "float", |
870 | | - "int", |
871 | | - "iterable", |
872 | | - "mixed", |
873 | | - "never", |
874 | | - "null", |
875 | | - "object", |
876 | | - "parent", |
877 | | - "resource", |
878 | | - "self", |
879 | | - "static", |
880 | | - "string", |
881 | | - "true", |
882 | | - "void", |
883 | | - ]: |
884 | | - log_info(fromdocnode, f"Target {absname} not found") |
885 | | - |
886 | | - return None, None |
| 859 | + if name in objects: |
| 860 | + newname = name |
| 861 | + elif classname and classname + '::' + name in objects: |
| 862 | + newname = classname + '::' + name |
| 863 | + elif classname and classname + '::$' + name in objects: |
| 864 | + newname = classname + '::$' + name |
| 865 | + elif namespace and namespace + NS + name in objects: |
| 866 | + newname = namespace + NS + name |
| 867 | + elif namespace and classname and \ |
| 868 | + namespace + NS + classname + '::' + name in objects: |
| 869 | + newname = namespace + NS + classname + '::' + name |
| 870 | + elif namespace and classname and \ |
| 871 | + namespace + NS + classname + '::$' + name in objects: |
| 872 | + newname = namespace + NS + classname + '::$' + name |
| 873 | + # special case: object methods |
| 874 | + elif type in ('func', 'meth') and '::' not in name and \ |
| 875 | + 'object::' + name in objects: |
| 876 | + newname = 'object::' + name |
| 877 | + if newname is None: |
| 878 | + return None, None |
| 879 | + return newname, objects[newname] |
887 | 880 |
|
888 | 881 | def get_objects(self): |
889 | 882 | for ns, info in self.data["namespaces"].items(): |
|
0 commit comments