Files
Deal/scripts/fix_private_docs.py
T
Rustam Khalimov 713d554dc2 Нормализовать переводы строк в LF
Решение по TD-STYLE-ANALYZERS: LF — инструменты проекта (Python/Node) пишут LF,
CRLF-.sh не работают на Linux CI (sh scripts/ci.sh), большинство файлов уже были
LF. Добавлен .gitattributes (* text=auto eol=lf, бинарные исключения),
.editorconfig переведён на lf, 1029 файлов конвертированы, git add --renormalize.
Из индекса убраны закравшиеся archive/**/__pycache__/*.pyc.
2026-09-11 19:01:42 +03:00

156 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Понижение XML-док комментариев с private/internal членов до обычных `//` (код-стайл «Дейл» §5).
Правило: XML-doc — только на public/protected. Приватные детали реализации — при необходимости короткий
обычный комментарий. Скрипт сохраняет текст (теги <param>/<returns> и т.п. разворачиваются в читаемый вид).
Режимы:
python scripts/fix_private_docs.py --preview [N] # показать N примеров (по умолчанию 10)
python scripts/fix_private_docs.py --apply # применить
python scripts/fix_private_docs.py --check # сколько блоков будет изменено
"""
import os
import re
import sys
ROOT = r"C:\telbase\src"
EXC = ("bin", "obj", "node_modules", ".git")
DOC_RE = re.compile(r"^(\s*)///[ \t]?(.*)$")
ACC_RE = re.compile(r"\b(public|protected|private|internal)\b")
def demote(block_content):
"""block_content: список строк после '///'. Возвращает список строк текста обычного комментария."""
t = "\n".join(block_content)
t = re.sub(r"<summary>\s*(.*?)\s*</summary>", r"\1", t, flags=re.S)
t = re.sub(r"<remarks>\s*(.*?)\s*</remarks>", r"\1", t, flags=re.S)
t = re.sub(r"<typeparamref\s+name=\"([^\"]+)\"\s*/?>", r"\1", t)
t = re.sub(r"<paramref\s+name=\"([^\"]+)\"\s*/?>", r"\1", t)
t = re.sub(r"<typeparam\s+name=\"([^\"]+)\"\s*>\s*(.*?)\s*</typeparam>", r"\1: \2", t, flags=re.S)
t = re.sub(r"<param\s+name=\"([^\"]+)\"\s*>\s*(.*?)\s*</param>", r"\1: \2", t, flags=re.S)
t = re.sub(r"<returns>\s*(.*?)\s*</returns>", r"Возвращает: \1", t, flags=re.S)
t = re.sub(
r"<exception\s+cref=\"([^\"]+)\"\s*>\s*(.*?)\s*</exception>",
r"Исключение \1: \2",
t,
flags=re.S,
)
t = re.sub(r"<see\s+cref=\"([^\"]+)\"\s*/?>", r"\1", t)
t = re.sub(r"<seealso\s+cref=\"[^\"]+\"\s*/?>", "", t)
t = re.sub(r"<inheritdoc\s*/?>", "", t)
t = re.sub(r"</?c>", "", t)
t = re.sub(r"<[^>]+>", "", t)
out = []
for line in t.split("\n"):
s = line.strip()
if s:
out.append(s)
return out
def iter_cs():
for dp, dn, fn in os.walk(ROOT):
dn[:] = [d for d in dn if d not in EXC]
for f in fn:
if f.endswith(".cs"):
yield os.path.join(dp, f)
def find_blocks(lines):
"""Возвращает список (start, end, block_content, decl_index, decl)."""
res = []
i = 0
while i < len(lines):
m = DOC_RE.match(lines[i].rstrip("\r"))
if not m:
i += 1
continue
start = i
block = []
while i < len(lines):
m2 = DOC_RE.match(lines[i].rstrip("\r"))
if not m2:
break
block.append(m2.group(2))
i += 1
end = i # exclusive
j = i
decl = ""
while j < len(lines):
s = lines[j].strip()
if s == "" or s.startswith("["):
j += 1
continue
decl = s
break
if not decl:
continue
if not any("<summary>" in b or "</summary>" in b for b in block):
continue
am = ACC_RE.search(decl)
if am and am.group(1) in ("private", "internal"):
res.append((start, end, block, decl))
return res
def process_file(path, apply):
raw = open(path, "rb").read().decode("utf-8")
lines = raw.splitlines(keepends=True)
plain = [l.rstrip("\r\n") for l in lines]
endings = [l[len(l.rstrip("\r\n")):] for l in lines]
blocks = find_blocks(plain)
if not blocks:
return 0, []
changes = []
for start, end, block, decl in blocks:
indent = DOC_RE.match(plain[start]).group(1)
comments = demote(block)
new_lines = [indent + "//" + (" " + c if c else "") for c in comments]
changes.append((start, end, new_lines, decl))
# применяем с конца
for start, end, new_lines, decl in reversed(changes):
ending = endings[start] if endings[start] else (endings[end - 1] if end > start else "\n")
replacement = [nl + ending for nl in new_lines]
lines[start:end] = replacement
if apply:
open(path, "w", encoding="utf-8", newline="").write("".join(lines))
return len(changes), [(b[0], b[3], demote(b[2])) for b in [(s, e, bl, d) for s, e, bl, d in blocks]]
def main():
if "--preview" in sys.argv:
n = 10
idx = sys.argv.index("--preview")
if idx + 1 < len(sys.argv) and sys.argv[idx + 1].isdigit():
n = int(sys.argv[idx + 1])
shown = 0
for path in iter_cs():
raw = open(path, "rb").read().decode("utf-8")
plain = [l.rstrip("\r\n") for l in raw.split("\n")]
for start, end, block, decl in find_blocks(plain):
print("FILE:", os.path.relpath(path, r"C:\telbase"), f"line {start + 1}")
print(" DECL:", decl[:100])
print(" BEFORE:")
for b in plain[start:end]:
print(" ", b)
print(" AFTER:")
for c in demote(block):
print(" // " + c)
print()
shown += 1
if shown >= n:
return
return
apply = "--apply" in sys.argv
total = 0
files = 0
for path in iter_cs():
cnt, _ = process_file(path, apply)
if cnt:
files += 1
total += cnt
print(("APPLIED" if apply else "DRY-RUN"), "blocks:", total, "files:", files)
if __name__ == "__main__":
main()