HTTPResource.add_target_and_index() does two things and treats them differently on purpose:
def add_target_and_index(self, name_cls, sig, signode):
signode['ids'].append(http_resource_anchor(*name_cls[1:])) # always
if 'noindex' not in self.options: # gated
self.env.domaindata['http'][self.method][sig] = (...)
i.e. the per-page anchor is unconditional and only the global route registration is gated behind :noindex:. However, Sphinx's ObjectDescription.run() skips the call to add_target_and_index() entirely when the option is set (this outer gate exists at least as far back as Sphinx 2.4.5), so the inner distinction never runs: a :noindex:'d directive loses its #<method>--<path> anchor and the HTML permalink along with the registration, and external deep links into that page land at the top of the page.
Anchors are per-document HTML ids and cannot collide across pages, so keeping them under :noindex: is always safe; only the global registration can legitimately be suppressed.
Motivating case: a project documenting the same routes on more than one page (in our case saltstack/salt, where three netapi modules document overlapping routes). Duplicate registrations are only detected in HTTPDomain.merge_domaindata(), i.e. during parallel reads, so sphinx-build -W -j auto fails nondeterministically depending on where the worker chunk boundary lands between the colliding documents (saltstack/salt#69724). :noindex: on the non-canonical copies is the natural remedy, but today it costs the anchors and permalinks on those pages.
Proposed fix: handle the option in HTTPResource.run() - pop it so the base class still calls add_target_and_index(), keep the anchor, and gate only the registration. PR to follow.
HTTPResource.add_target_and_index()does two things and treats them differently on purpose:i.e. the per-page anchor is unconditional and only the global route registration is gated behind
:noindex:. However, Sphinx'sObjectDescription.run()skips the call toadd_target_and_index()entirely when the option is set (this outer gate exists at least as far back as Sphinx 2.4.5), so the inner distinction never runs: a:noindex:'d directive loses its#<method>--<path>anchor and the HTML permalink along with the registration, and external deep links into that page land at the top of the page.Anchors are per-document HTML ids and cannot collide across pages, so keeping them under
:noindex:is always safe; only the global registration can legitimately be suppressed.Motivating case: a project documenting the same routes on more than one page (in our case saltstack/salt, where three netapi modules document overlapping routes). Duplicate registrations are only detected in
HTTPDomain.merge_domaindata(), i.e. during parallel reads, sosphinx-build -W -j autofails nondeterministically depending on where the worker chunk boundary lands between the colliding documents (saltstack/salt#69724).:noindex:on the non-canonical copies is the natural remedy, but today it costs the anchors and permalinks on those pages.Proposed fix: handle the option in
HTTPResource.run()- pop it so the base class still callsadd_target_and_index(), keep the anchor, and gate only the registration. PR to follow.