diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7595a41..c76d726 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,8 @@ jobs: uses: stefanzweifel/git-auto-commit-action@v4 with: branch: ${{ github.head_ref || github.ref_name }}.changes + # remove once https://github.com/stefanzweifel/git-auto-commit-action/issues/295 is fixed + create_branch: true push_options: '--force' commit_message: Unit Changes commit_user_name: Bot diff --git a/sphinxcontrib/phpdomain.py b/sphinxcontrib/phpdomain.py index af78ba9..f0ba36e 100644 --- a/sphinxcontrib/phpdomain.py +++ b/sphinxcontrib/phpdomain.py @@ -9,6 +9,7 @@ :license: BSD, see LICENSE for details. """ import re +import inspect from docutils import nodes from docutils.parsers.rst import directives, Directive @@ -18,17 +19,46 @@ from sphinx.locale import _ from sphinx.domains import Domain, ObjType, Index from sphinx.directives import ObjectDescription +from sphinx.util import logging from sphinx.util.nodes import make_refnode from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx import __version__ as sphinx_version +# log informative messages +def log_info( + fromdocnode, + message: str +): + logger = logging.getLogger(__name__) + logger.info(f"[phpdomain] {message}", location=fromdocnode) + +# log messages that should fail CI +def log_warning( + fromdocnode, + message: str +): + logger = logging.getLogger(__name__) + logger.warning(f"[phpdomain] {message}", location=fromdocnode) + +# log assertions that should fail CI +def log_assert( + fromdocnode, + value: bool +): + if not value: + caller = inspect.getframeinfo(inspect.stack()[1][0]) + logger = logging.getLogger(__name__) + logger.warning(f"[phpdomain-assert] line {caller.lineno}", location=fromdocnode) + php_sig_re = re.compile( r'''^ (public\ |protected\ |private\ )? # visibility (final\ |abstract\ |static\ )? # modifiers - ([\w.]*\:\:)? # class name(s) - (\$?\w+) \s* # thing name - (?: \((.*)\) # optional: arguments - (?:\s* -> \s* (.*))?)? # return annotation + ([\w\\]+\:\:)? # class name(s) + (\$?[\w\\]+) \s* # thing name + (?: + \((.*)\) # optional: arguments + (?:\s* -> \s* (.*))? # return annotation + )? (?:\s* : \s* (.*))? # backed enum type / case value $ # and nothing more ''', re.VERBOSE) @@ -170,13 +200,13 @@ def handle_signature(self, sig, signode): # determine module and class name (if applicable), as well as full name modname = self.options.get( 'namespace', self.env.temp_data.get('php:namespace')) - - classname = self.env.temp_data.get('php:class') separator = separators[self.objtype] - # Method declared as Class::methodName - if not classname and '::' in name_prefix: + if '::' in name_prefix: classname = name_prefix.rstrip('::') + else: + classname = self.env.temp_data.get('php:class') + if self.objtype == 'global' or self.objtype == 'function': add_module = False modname = None @@ -189,10 +219,10 @@ def handle_signature(self, sig, signode): if name_prefix and self.objtype != 'staticmethod': if name_prefix.startswith(classname): name_prefix = name_prefix[len(classname):].rstrip('::') - classname = classname.rstrip('::') + classname = classname.rstrip('::') # TODO seems like wrongly coded, there should be no '::' in the classname fullname = name_prefix + classname + separator + name elif name_prefix: - classname = classname.rstrip('::') + classname = classname.rstrip('::') # TODO seems like wrongly coded, there should be no '::' in the classname fullname = name_prefix + name # Currently in a class, but not creating another class, @@ -544,12 +574,15 @@ def process_link(self, env, refnode, has_explicit_title, title, target): title = title[2:] target = target.lstrip('~') # only has a meaning for the title - # If the first char is ~ don't display the leading namespace & class. + # If the first char is '~' don't display the leading namespace & class. if title.startswith('~'): m = re.search(r"(?:.+[:]{2}|(?:.*?\\{1,2})+)?(.*)\Z", title) if m: title = m.group(1) + if title.startswith('\\'): + title = title[1:] + refnode['php:namespace'] = env.temp_data.get('php:namespace') refnode['php:class'] = env.temp_data.get('php:class') @@ -735,7 +768,7 @@ def resolve_xref(self, env, fromdocname, builder, modname = node.get('php:namespace') clsname = node.get('php:class') searchorder = node.hasattr('refspecific') and 1 or 0 - name, obj = self.find_obj(env, modname, clsname, + name, obj = self.find_obj(env, node, modname, clsname, target, typ, searchorder) if not obj: return None @@ -743,23 +776,28 @@ def resolve_xref(self, env, fromdocname, builder, return make_refnode(builder, fromdocname, obj[0], name, contnode, name) - def find_obj(self, env, modname, classname, name, type, searchorder=0): + def find_obj(self, env, fromdocnode, modname, classname, name, type, searchorder=0): """ - Find a PHP object for "name", perhaps using the given namespace and/or - classname. + Find a PHP object for "name", using the given namespace and classname. """ # skip parens if name[-2:] == '()': name = name[:-2] - if not name: - return None, None - objects = self.data['objects'] + if name.startswith('\\'): + absname = name[1:] + else: + absname = (modname + NS if modname else "") \ + + (classname + NS if classname and '::' not in name else "") \ + + name + newname = None if searchorder == 1: - if modname and classname and \ + if absname in objects: + newname = absname + elif modname and classname and \ modname + NS + classname + '::' + name in objects: newname = modname + NS + classname + '::' + name elif modname and modname + NS + name in objects: @@ -773,7 +811,9 @@ def find_obj(self, env, modname, classname, name, type, searchorder=0): elif name in objects: newname = name else: - if name in objects: + if absname in objects: + newname = absname + elif name in objects: newname = name elif classname and classname + '::' + name in objects: newname = classname + '::' + name @@ -792,6 +832,7 @@ def find_obj(self, env, modname, classname, name, type, searchorder=0): 'object::' + name in objects: newname = 'object::' + name if newname is None: + log_info(fromdocnode, f"Target not found '{absname}'") return None, None return newname, objects[newname] diff --git a/test/unit/ns.html b/test/unit/ns.html index bf0c670..d9655f3 100644 --- a/test/unit/ns.html +++ b/test/unit/ns.html @@ -47,5 +47,216 @@
+
+ Foo\Bar::simplify
+
+
\
implies absolute class name¶These cross references must not have a link as the target methods are not defined.
+
+
+ A2::simplify
+
+
+
+ A::simplify
+
+