Skip to content

Commit 590c1ae

Browse files
committed
fixed deep links
1 parent 42e7dcd commit 590c1ae

File tree

1 file changed

+63
-30
lines changed

1 file changed

+63
-30
lines changed

src/autodoc2/render/fern_.py

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -641,33 +641,35 @@ def _are_on_same_page(self, item1_name: str, item2_name: str) -> bool:
641641
return item1_page == item2_page
642642

643643
def _get_page_for_item(self, full_name: str) -> str:
644-
"""Get the page where this item is rendered."""
644+
"""Get the page where this item is rendered.
645+
646+
Based on CLI logic: only packages and modules get their own files.
647+
All other items (classes, functions, methods, etc.) are rendered
648+
on their parent module/package page.
649+
"""
645650
item = self.get_item(full_name)
646651
if not item:
647652
return full_name
648653

649654
item_type = item['type']
650655
parts = full_name.split('.')
651656

652-
# Only packages, modules, and classes get their own dedicated pages
653-
if item_type in ('package', 'module', 'class'):
657+
# Only packages and modules get their own dedicated pages/files
658+
if item_type in ('package', 'module'):
654659
return full_name
655660

656-
# Functions, methods, properties, attributes, data are rendered on their parent's page
657-
elif item_type in ('function', 'method', 'property', 'attribute', 'data'):
658-
# Find the parent (class, module, or package)
661+
# All other items (classes, functions, methods, properties, attributes, data)
662+
# are rendered on their parent module/package page
663+
else:
664+
# Find the parent module or package
659665
for i in range(len(parts) - 1, 0, -1):
660666
parent_name = '.'.join(parts[:i])
661667
parent_item = self.get_item(parent_name)
662-
if parent_item and parent_item['type'] in ('package', 'module', 'class'):
668+
if parent_item and parent_item['type'] in ('package', 'module'):
663669
return parent_name
664670

665-
# Fallback - shouldn't happen
666-
return full_name
667-
668-
else:
669-
# Unknown type, assume it gets its own page
670-
return full_name
671+
# Fallback - shouldn't happen, but return the root module
672+
return parts[0] if parts else full_name
671673

672674
def _get_cross_reference_link(self, target_name: str, display_name: str = None, current_page: str = None) -> str:
673675
"""Generate cross-reference link to another documented object."""
@@ -706,23 +708,9 @@ def _format_code_block_with_links(self, code: str, language: str = "python") ->
706708
import re
707709
word_pattern = r'\b' + re.escape(short_name) + r'\b'
708710
if re.search(word_pattern, code) and item['type'] in ('class', 'function', 'method'):
709-
# Determine the correct page and anchor
710-
if item['type'] == 'method':
711-
# Methods appear on their class page, but we need the module page slug for the current context
712-
class_parts = full_name.split('.')
713-
if len(class_parts) >= 3: # module.Class.method
714-
module_name = '.'.join(class_parts[:-2]) # Just the module
715-
page_slug = self._generate_slug(module_name)
716-
else:
717-
page_slug = self._generate_slug('.'.join(class_parts[:-1]))
718-
else:
719-
# Classes and functions link to their own pages
720-
if item['type'] in ('class', 'function'):
721-
page_slug = self._generate_slug(full_name)
722-
else:
723-
parent_name = '.'.join(full_name.split('.')[:-1]) if '.' in full_name else full_name
724-
page_slug = self._generate_slug(parent_name)
725-
711+
# Use the corrected page mapping logic
712+
page_name = self._get_page_for_item(full_name)
713+
page_slug = self._generate_slug(page_name)
726714
anchor_id = self._generate_anchor_id(full_name)
727715
links[short_name] = f"{page_slug}#{anchor_id}"
728716

@@ -757,6 +745,51 @@ def replace_ref(match):
757745

758746
return re.sub(pattern, replace_ref, text)
759747

748+
def validate_all_links(self, output_dir: str = None) -> dict[str, list[str]]:
749+
"""Validate all generated links and return any issues found.
750+
751+
Fast lightweight validation focusing on core link integrity.
752+
753+
Returns:
754+
Dict with 'errors' and 'warnings' keys containing lists of issues.
755+
"""
756+
issues = {"errors": [], "warnings": []}
757+
758+
# Sample a few items to validate the core logic works
759+
sample_items = []
760+
for item_type in ("package", "module", "class", "function"):
761+
type_items = list(self._db.get_by_type(item_type))
762+
if type_items:
763+
sample_items.append(type_items[0]) # Just take first item of each type
764+
765+
for item in sample_items:
766+
full_name = item["full_name"]
767+
768+
# Validate that we can determine the correct page for this item
769+
try:
770+
page_name = self._get_page_for_item(full_name)
771+
anchor_id = self._generate_anchor_id(full_name)
772+
773+
if not anchor_id:
774+
issues["errors"].append(f"Empty anchor ID generated for {full_name}")
775+
776+
# Test cross-reference link generation
777+
test_link = self._get_cross_reference_link(full_name, None, "test.module")
778+
if not test_link or test_link == full_name:
779+
issues["warnings"].append(f"Link generation may have issues for {full_name}")
780+
781+
except Exception as e:
782+
issues["errors"].append(f"Error processing {full_name}: {e}")
783+
784+
# Quick check: verify some common patterns
785+
packages = list(self._db.get_by_type("package"))
786+
modules = list(self._db.get_by_type("module"))
787+
788+
if not packages and not modules:
789+
issues["errors"].append("No packages or modules found - this seems wrong")
790+
791+
return issues
792+
760793
def generate_navigation_yaml(self) -> str:
761794
"""Generate navigation YAML for Fern docs.yml following sphinx autodoc2 toctree logic."""
762795
import yaml

0 commit comments

Comments
 (0)