Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions properdocs/structure/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,10 @@ def validate_anchor_links(self, *, files: Files, log_level: int) -> None:
continue
context = ""
if to_file == self.file:
problem = "there is no such anchor on this page"
if original_link.endswith('#' + anchor):
problem = "there is no such anchor on this page"
else:
problem = f"there is no anchor '#{anchor}' on this page"
if anchor.startswith('fnref:'):
context = " This seems to be a footnote that is never referenced."
else:
Expand Down Expand Up @@ -416,6 +419,16 @@ def _possible_target_uris(
yield guess
tried.add(guess)

def register_anchor(self, *, file: File, anchor: str, url: str) -> None:
if not anchor:
return
# Detect https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment/Text_fragments#syntax
if (index := anchor.find(':~:')) > -1:
if index == 0:
return # This is entirely just a directive, no anchor.
anchor = anchor[:index]
self.links_to_anchors.setdefault(file, {}).setdefault(anchor, url)

def path_to_url(self, url: str) -> str:
scheme, netloc, path, query, anchor = urlsplit(url)

Expand All @@ -433,9 +446,8 @@ def path_to_url(self, url: str) -> str:
elif AMP_SUBSTITUTE in url: # AMP_SUBSTITUTE is used internally by Markdown only for email.
return url
elif not path: # Self-link containing only query or anchor.
if anchor:
# Register that the page links to itself with an anchor.
self.links_to_anchors.setdefault(self.file, {}).setdefault(anchor, url)
# Register that the page links to itself with an anchor.
self.register_anchor(file=self.file, anchor=anchor, url=url)
return url

path = urlunquote(path)
Expand Down Expand Up @@ -498,9 +510,8 @@ def path_to_url(self, url: str) -> str:
assert target_uri is not None
assert target_file is not None

if anchor:
# Register that this page links to the target file with an anchor.
self.links_to_anchors.setdefault(target_file, {}).setdefault(anchor, url)
# Register that this page links to the target file with an anchor.
self.register_anchor(file=target_file, anchor=anchor, url=url)

if target_file.inclusion.is_excluded():
if self.file.inclusion.is_excluded():
Expand Down
19 changes: 18 additions & 1 deletion properdocs/tests/build_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ def test_anchor_no_warning_with_html(self, site_dir, docs_dir):
}
)
@tempdir()
def test_anchor_warning_and_query(self, site_dir, docs_dir):
def test_anchor_and_query_warning(self, site_dir, docs_dir):
cfg = load_config(docs_dir=docs_dir, site_dir=site_dir, validation={'anchors': 'info'})

expected_logs = '''
Expand Down Expand Up @@ -806,6 +806,23 @@ def test_anchor_warning_for_footnote(self, site_dir, docs_dir):
with self._assert_build_logs(expected_logs):
build.build(cfg)

@tempdir(
files={
'test/foo.md': '# page1 heading\n\n[bar](bar.md#page1-heading:~:text=a)\n\n[just text](#:~:text=text)',
'test/bar.md': '# page2 heading\n\n[aaa](#a:~:text=a)\n\n[bbb](#page2-heading:~:text=a)',
}
)
@tempdir()
def test_anchor_with_directive_warnings(self, site_dir, docs_dir):
cfg = load_config(docs_dir=docs_dir, site_dir=site_dir, validation={'anchors': 'warn'})

expected_logs = '''
WARNING:Doc file 'test/bar.md' contains a link '#a:~:text=a', but there is no anchor '#a' on this page.
WARNING:Doc file 'test/foo.md' contains a link 'bar.md#page1-heading:~:text=a', but the doc 'test/bar.md' does not contain an anchor '#page1-heading'.
'''
with self._assert_build_logs(expected_logs):
build.build(cfg)

@tempdir(
files={
'foo.md': 'page1 content',
Expand Down
Loading