from pathlib import Path from typing import List, Text, Tuple import pytest DOCS_BASE_DIR = Path("docs/") MDX_DOCS_FILES = list((DOCS_BASE_DIR / "docs").glob("**/*.mdx")) CODE_BLOCK_OPEN = ["```", "
"] def get_heading_level(heading: Text) -> int: return len(heading.split(" ", 1)[0]) def get_heading(heading: Text) -> Text: return heading.split(" ", 1)[1] def line_opens_code_block(line: Text) -> bool: for open_tag in CODE_BLOCK_OPEN: if line.startswith(open_tag): return True return False def line_closes_code_block(line: Text) -> bool: for close_tag in CODE_BLOCK_CLOSE: if line.endswith(close_tag): return True return False @pytest.mark.parametrize("mdx_file_path", MDX_DOCS_FILES) def test_docs_header_hierarchy(mdx_file_path: Path): with mdx_file_path.open("r") as handle: mdx_content = handle.read() headings: List[Tuple[int, Text, Text]] = [] inside_code_block = False for idx, line in enumerate(mdx_content.split("\n")): line = line.strip() if not inside_code_block and line_opens_code_block(line): inside_code_block = True elif inside_code_block and line_closes_code_block(line): inside_code_block = False if not inside_code_block and line.startswith("#"): headings.append((idx + 1, get_heading(line), get_heading_level(line))) errors: List[Text] = [] prev_level = 1 for line_number, title, level in headings: if level == 1: errors.append( f"\n - Title '# {title}' at line {line_number} " f"cannot be level 1. Use '## {title}' instead." ) if level > prev_level + 1: errors.append( f"\n - Title '{'#' * level} {title}' at line {line_number} " f"is skipping a level. Use '{'#' * (prev_level + 1)} {title}' instead." ) # that way we continue to have errors prev_level = prev_level + 1 else: prev_level = level if errors: raise AssertionError(f"({mdx_file_path}):{''.join(errors)}")