Skip to content

Commit

Permalink
Fix resolve for absolute reference names
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Jul 31, 2023
1 parent 70fca3a commit 3731f66
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 21 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ jobs:
cd test/unit
find . -name '*.html' -exec rm {} \;
make html SPHINXOPTS='-W'
(cd _build/html && rm genindex.html index.html search.html php-modindex.html)
(cd _build/html && find . -name '*.html' -exec sh -c 'xmllint {} --xpath '"'"'//div[@role="main"]'"'"' | xmllint --format - > ../../{}' \;)
rm genindex.html index.html search.html php-modindex.html
- name: Diff Unit Outputs
run: |
Expand All @@ -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
Expand Down
81 changes: 61 additions & 20 deletions sphinxcontrib/phpdomain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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')

Expand Down Expand Up @@ -735,31 +768,36 @@ 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
else:
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:
Expand All @@ -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
Expand All @@ -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]

Expand Down
120 changes: 120 additions & 0 deletions test/unit/ns.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,125 @@ <h2>Cross linking<a class="headerlink" href="#cross-linking" title="Permalink to
</ul>
</section>
</section>
<section id="namespace-Foo\Bar">
<span id="ns-can-be-changed"/>
<h1>NS can be changed<a class="headerlink" href="#namespace-Foo\Bar" title="Permalink to this heading">&#xB6;</a></h1>
<dl class="php class">
<dt class="sig sig-object php" id="Foo\Bar\A">
<em class="property">
<span class="pre">class</span>
</em>
<span class="sig-prename descclassname">
<span class="pre">Foo\Bar\</span>
</span>
<span class="sig-name descname">
<span class="pre">A</span>
</span>
<a class="headerlink" href="#Foo\Bar\A" title="Permalink to this definition">&#xB6;</a>
</dt>
<dd/>
</dl>
<dl class="php method">
<dt class="sig sig-object php" id="Foo\Bar\A::simplify">
<span class="sig-prename descclassname">
<span class="pre">Foo\Bar\A::</span>
</span>
<span class="sig-name descname">
<span class="pre">simplify</span>
</span>
<span class="sig-paren">(</span>
<span class="sig-paren">)</span>
<a class="headerlink" href="#Foo\Bar\A::simplify" title="Permalink to this definition">&#xB6;</a>
</dt>
<dd/>
</dl>
<span class="target" id="namespace-Bar"/>
<dl class="php class">
<dt class="sig sig-object php" id="Bar\A">
<em class="property">
<span class="pre">class</span>
</em>
<span class="sig-prename descclassname">
<span class="pre">Bar\</span>
</span>
<span class="sig-name descname">
<span class="pre">A</span>
</span>
<a class="headerlink" href="#Bar\A" title="Permalink to this definition">&#xB6;</a>
</dt>
<dd/>
</dl>
<dl class="php method">
<dt class="sig sig-object php" id="Bar\A::simplify">
<span class="sig-prename descclassname">
<span class="pre">Bar\A::</span>
</span>
<span class="sig-name descname">
<span class="pre">simplify</span>
</span>
<span class="sig-paren">(</span>
<span class="sig-paren">)</span>
<a class="headerlink" href="#Bar\A::simplify" title="Permalink to this definition">&#xB6;</a>
</dt>
<dd/>
</dl>
<section id="id1">
<h2>Cross linking<a class="headerlink" href="#id1" title="Permalink to this heading">&#xB6;</a></h2>
<ul class="simple">
<li>
<p>
<a class="reference internal" href="#Bar\A::simplify" title="Bar\A::simplify">
<code class="xref php php-meth docutils literal notranslate">
<span class="pre">A::simplify</span>
</code>
</a>
</p>
</li>
<li>
<p>
<a class="reference internal" href="#Foo\Bar\A::simplify" title="Foo\Bar\A::simplify">
<code class="xref php php-meth docutils literal notranslate">
<span class="pre">Foo\Bar\A::simplify</span>
</code>
</a>
</p>
</li>
<li>
<p>
<a class="reference internal" href="#Bar\A::simplify" title="Bar\A::simplify">
<code class="xref php php-meth docutils literal notranslate">
<span class="pre">Bar\A::simplify</span>
</code>
</a>
</p>
</li>
</ul>
</section>
</section>
<section id="ns-must-not-be-guessed">
<h1>NS must not be guessed<a class="headerlink" href="#ns-must-not-be-guessed" title="Permalink to this heading">&#xB6;</a></h1>
<div class="note docutils">
<p>These cross references must not have a link as the target methods are not defined.</p>
</div>
<ul class="simple">
<li>
<p>
<code class="xref php php-meth docutils literal notranslate">
<span class="pre">A2::simplify</span>
</code>
</p>
</li>
</ul>
<span class="target" id="namespace-Bar2"/>
<ul class="simple">
<li>
<p>
<code class="xref php php-meth docutils literal notranslate">
<span class="pre">A::simplify</span>
</code>
</p>
</li>
</ul>
</section>
<div class="clearer"/>
</div>
39 changes: 39 additions & 0 deletions test/unit/ns.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,42 @@
## Cross linking

- {php:meth}`A::simplify`

# NS can be changed

:::{php:namespace} Foo\Bar
:::

:::{php:class} A
:::

:::{php:method} simplify()
:::

:::{php:namespace} Bar
:::

:::{php:class} A
:::

:::{php:method} simplify()
:::

## Cross linking

- {php:meth}`A::simplify`
- {php:meth}`\Foo\Bar\A::simplify`
- {php:meth}`\Bar\A::simplify`

# NS must not be guessed

:::note
These cross references must not have a link as the target methods are not defined.
:::

- {php:meth}`\A2::simplify`

:::{php:namespace} Bar2
:::

- {php:meth}`A::simplify`

0 comments on commit 3731f66

Please sign in to comment.