|
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