================
@@ -0,0 +1,404 @@
+#!/usr/bin/env python3
+#
+#
===-----------------------------------------------------------------------===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#
===-----------------------------------------------------------------------===#
+
+"""
+
+Clang-Tidy Alphabetical Order Checker
+=====================================
+
+Normalize Clang-Tidy documentation with deterministic sorting for
linting/tests.
+
+Behavior:
+- Sort entries in docs/clang-tidy/checks/list.rst csv-table.
+- Sort key sections in docs/ReleaseNotes.rst.
+- Detect duplicated entries in 'Changes in existing checks'.
+
+Flags:
+ -o/--output Write normalized content to this path instead of updating docs.
+"""
+
+import argparse
+import io
+import os
+import re
+import sys
+from typing import Dict, List, Optional, Sequence, Tuple, NamedTuple
+from operator import itemgetter
+
+# Matches a :doc:`label <path>` or :doc:`label` reference anywhere in text and
+# captures the label. Used to sort bullet items alphabetically in ReleaseNotes
+# items by their label.
+DOC_LABEL_RN_RE = re.compile(r":doc:`(?P<label>[^`<]+)\s*(?:<[^>]+>)?`")
+
+# Matches a single csv-table row line in list.rst that begins with a :doc:
+# reference, capturing the label. Used to extract the sort key per row.
+DOC_LINE_RE = re.compile(r"^\s*:doc:`(?P<label>[^`<]+?)\s*<[^>]+>`.*$")
+
+
+EXTRA_DIR = os.path.join(os.path.dirname(__file__), "../..")
+DOCS_DIR = os.path.join(EXTRA_DIR, "docs")
+CLANG_TIDY_DOCS_DIR = os.path.join(DOCS_DIR, "clang-tidy")
+CHECKS_DOCS_DIR = os.path.join(CLANG_TIDY_DOCS_DIR, "checks")
+LIST_DOC = os.path.join(CHECKS_DOCS_DIR, "list.rst")
+RELEASE_NOTES_DOC = os.path.join(DOCS_DIR, "ReleaseNotes.rst")
+
+
+# Label extracted from :doc:`...`.
+CheckLabel = str
+Lines = List[str]
+BulletBlock = List[str]
+
+# Pair of the extracted label and its block
+BulletItem = Tuple[CheckLabel, BulletBlock]
+
+# Index of the first line of a bullet block within the full lines list.
+BulletStart = int
+
+# All occurrences for a given label.
+DuplicateOccurrences = List[Tuple[BulletStart, BulletBlock]]
+
+
+class BulletBlocks(NamedTuple):
+ """Structured result of parsing a bullet-list section.
+
+ - prefix: lines before the first bullet within the section range.
+ - blocks: list of (label, block-lines) pairs for each bullet block.
+ - suffix: lines after the last bullet within the section range.
+ """
+
+ prefix: Lines
+ blocks: List[BulletItem]
+ suffix: Lines
+
+
+class ScannedBlocks(NamedTuple):
+ """Result of scanning bullet blocks within a section range.
+
+ - blocks_with_pos: list of (start_index, block_lines) for each bullet
block.
+ - next_index: index where scanning stopped; start of the suffix region.
+ """
+
+ blocks_with_pos: List[Tuple[BulletStart, BulletBlock]]
+ next_index: int
+
+
+def _scan_bullet_blocks(lines: Sequence[str], start: int, end: int) ->
ScannedBlocks:
+ """Scan consecutive bullet blocks and return (blocks_with_pos, next_index).
+
+ Each entry in blocks_with_pos is a tuple of (start_index, block_lines).
+ next_index is the index where scanning stopped (start of suffix).
+ """
+ i = start
+ n = end
+ blocks_with_pos: List[Tuple[BulletStart, BulletBlock]] = []
+ while i < n:
+ if not _is_bullet_start(lines[i]):
+ break
+ bstart = i
+ i += 1
+ while i < n and not _is_bullet_start(lines[i]):
+ if (
+ i + 1 < n
+ and set(lines[i + 1].rstrip("\n")) == {"^"}
+ and lines[i].strip()
+ ):
+ break
+ i += 1
+ block: BulletBlock = list(lines[bstart:i])
+ blocks_with_pos.append((bstart, block))
+ return ScannedBlocks(blocks_with_pos, i)
+
+
+def read_text(path: str) -> List[str]:
+ with io.open(path, "r", encoding="utf-8") as f:
+ return f.read().splitlines(True)
+
+
+def write_text(path: str, content: str) -> None:
+ with io.open(path, "w", encoding="utf-8", newline="") as f:
+ f.write(content)
+
+
+def _normalize_list_rst_lines(lines: Sequence[str]) -> List[str]:
+ """Return normalized content of checks list.rst as a list of lines."""
+ out: List[str] = []
+ i = 0
+ n = len(lines)
+
+ def check_name(line: str):
+ m = DOC_LINE_RE.match(line)
+ if not m:
+ return (1, "")
+ return (0, m.group("label"))
----------------
EugeneZelenko wrote:
```suggestion
if m := DOC_LINE_RE.match(line):
return (0, m.group("label"))
return (1, "")
```
Walrus operator was introduced in 3.8.
https://github.com/llvm/llvm-project/pull/166072
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits