11"""
2- Sphinx PHP domain.
3-
4- The PHP domain. Based off of the rubydomain by SHIBUKAWA Yoshiki
2+ Sphinx PHP domain.
53
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.
88"""
9+
910import re
1011import inspect
1112
@@ -70,9 +71,9 @@ def throw_if_false(fromdocnode, value, message: str):
7071
7172separators = {
7273 "global" : None ,
73- "namespace" : None ,
74- "function" : None ,
75- "interface" : None ,
74+ "namespace" : NS ,
75+ "function" : NS ,
76+ "interface" : NS ,
7677 "class" : None ,
7778 "trait" : None ,
7879 "enum" : None ,
@@ -229,17 +230,20 @@ def handle_signature(self, sig, signode):
229230 )
230231 separator = separators [self .objtype ]
231232
232- if "::" in name_prefix :
233+ classname = self .env .temp_data .get ("php:class" )
234+ # Method declared as Class::methodName
235+ if not classname and "::" in name_prefix :
233236 classname = name_prefix .rstrip ("::" )
234- else :
235- classname = self .env .temp_data .get ("php:class" )
236237
237- if self .objtype == "global" :
238+ if self .objtype == "global" or self .objtype == "function" :
239+ add_module = False
238240 namespace = None
239241 classname = None
240242 fullname = name
241243 else :
244+ add_module = True
242245 if name_prefix :
246+ classname = classname .rstrip ("::" )
243247 fullname = name_prefix + name
244248
245249 # Currently in a class, but not creating another class,
@@ -259,6 +263,14 @@ def handle_signature(self, sig, signode):
259263 classname = ""
260264 fullname = name
261265
266+ # A leading \ means the name is fully qualified
267+ # and should not inherit the current namespace.
268+ if fullname .startswith (NS ) and namespace :
269+ add_module = False
270+ name = name [1 :]
271+ fullname = fullname [1 :]
272+ namespace = None
273+
262274 signode ["namespace" ] = namespace
263275 signode ["class" ] = self .class_name = classname
264276 signode ["fullname" ] = fullname
@@ -279,15 +291,21 @@ def handle_signature(self, sig, signode):
279291 name_prefix = namespace + NS + name_prefix
280292 signode += addnodes .desc_addname (name_prefix , name_prefix )
281293
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 )
294+ elif add_module and self .env .config .add_module_names :
295+ if self .objtype == "global" :
296+ nodetext = ""
297+ signode += addnodes .desc_addname (nodetext , nodetext )
298+ else :
299+ namespace = self .options .get (
300+ "namespace" , self .env .temp_data .get ("php:namespace" )
301+ )
302+
303+ if namespace and not self .env .temp_data .get ("php:in_class" , False ):
304+ nodetext = namespace + NS
305+ signode += addnodes .desc_addname (nodetext , nodetext )
289306
290307 signode += addnodes .desc_name (name , name )
308+
291309 if not arglist :
292310 if self .needs_arglist ():
293311 # for callables, add an empty parameter list
@@ -603,26 +621,18 @@ class PhpXRefRole(XRefRole):
603621
604622 def process_link (self , env , refnode , has_explicit_title , title , target ):
605623 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 :]
624+ if title .startswith ("::" ):
625+ title = title [2 :]
626+ target = target .lstrip ("~" ) # only has a meaning for the title
615627
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" )
628+ # If the first char is ~ don't display the leading namespace & class.
629+ if title .startswith ("~" ):
630+ m = re .search (r"(?:.+[:]{2}|(?:.*?\\{1,2})+)?(.*)\Z" , title )
631+ if m :
632+ title = m .group (1 )
623633
624- refnode ["php:namespace" ] = namespace
625- refnode ["php:class" ] = classname
634+ refnode ["php:namespace" ] = env . temp_data . get ( "php: namespace" )
635+ refnode ["php:class" ] = env . temp_data . get ( "php:class" )
626636
627637 return title , target
628638
@@ -814,76 +824,79 @@ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
814824 else :
815825 namespace = node .get ("php:namespace" )
816826 clsname = node .get ("php:class" )
817- name , obj = self .find_obj (env , node , namespace , clsname , target , typ )
827+ searchorder = node .hasattr ("refspecific" ) and 1 or 0
828+ name , obj = self .find_obj (
829+ env , node , namespace , clsname , target , typ , searchorder
830+ )
818831 if not obj :
819832 return None
820833 else :
821834 return make_refnode (builder , fromdocname , obj [0 ], name , contnode , name )
822835
823- def find_obj (self , env , fromdocnode , namespace , classname , name , type ):
836+ def find_obj (
837+ self , env , fromdocnode , namespace , classname , name , type , searchorder = 0
838+ ):
824839 """
825840 Find a PHP object for "name", using the given namespace and classname.
826841 """
827842 # strip parenthesis
828843 if name [- 2 :] == "()" :
829844 name = name [:- 2 ]
830845
846+ if not name :
847+ return None , None
848+
831849 objects = self .data ["objects" ]
832850
833- if name .startswith (NS ):
834- absname = name [1 :]
851+ newname = None
852+ if searchorder == 1 :
853+ if (
854+ namespace
855+ and classname
856+ and namespace + NS + classname + "::" + name in objects
857+ ):
858+ newname = namespace + NS + classname + "::" + name
859+ elif namespace and namespace + NS + name in objects :
860+ newname = namespace + NS + name
861+ elif namespace and namespace + NS + name in objects :
862+ newname = namespace + NS + name
863+ elif classname and classname + "::" + name in objects :
864+ newname = classname + "." + name
865+ elif classname and classname + "::$" + name in objects :
866+ newname = classname + "::$" + name
867+ elif name in objects :
868+ newname = name
835869 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
870+ if name in objects :
871+ newname = name
872+ elif classname and classname + "::" + name in objects :
873+ newname = classname + "::" + name
874+ elif classname and classname + "::$" + name in objects :
875+ newname = classname + "::$" + name
876+ elif namespace and namespace + NS + name in objects :
877+ newname = namespace + NS + name
878+ elif (
879+ namespace
880+ and classname
881+ and namespace + NS + classname + "::" + name in objects
882+ ):
883+ newname = namespace + NS + classname + "::" + name
884+ elif (
885+ namespace
886+ and classname
887+ and namespace + NS + classname + "::$" + name in objects
888+ ):
889+ newname = namespace + NS + classname + "::$" + name
890+ # special case: object methods
891+ elif (
892+ type in ("func" , "meth" )
893+ and "::" not in name
894+ and "object::" + name in objects
895+ ):
896+ newname = "object::" + name
897+ if newname is None :
898+ return None , None
899+ return newname , objects [newname ]
887900
888901 def get_objects (self ):
889902 for ns , info in self .data ["namespaces" ].items ():
0 commit comments