📦 deps(tsl): sync tsl-playbook from 13be5ea0

Source-Commit: 13be5ea0aa
This commit is contained in:
ci[bot]
2026-07-29 07:58:05 +00:00
parent 9d2b696f58
commit 80357f4bc5
280 changed files with 48658 additions and 13929 deletions
+359
View File
@@ -0,0 +1,359 @@
"""Shared recognition of TSL codegen API headings and declaration lines."""
from bisect import bisect_right
from dataclasses import dataclass
import re
TOP_LEVEL_RE = re.compile(r"^##(?!#)\s+`(.+?)`\s*$")
PLAIN_H2_RE = re.compile(r"^##(?!#)\s+")
BARE_API_RE = re.compile(r"^(#{3,5})(?!#)\s+`(.+?)`\s*$")
LEGACY_TYPED_RE = re.compile(
r"^(#{3,5})(?!#)\s+"
r"(class function|static function|static field|static const|"
r"function|property|field|const|var|class)\s+`(.+?)`\s*$"
)
DECLARATION_LINE_RE = re.compile(r"^声明:(.*?)\s*$")
DECLARATION_PREFIX_RE = re.compile(r"^声明[:]", re.IGNORECASE)
LOOSE_DECLARATION_RE = re.compile(r"^声明[:]\s*(.*?)\s*$", re.IGNORECASE)
TOP_LEVEL_TYPES = {"function", "class", "unit"}
CLASS_DECLARATIONS = {
"function": ("method", "instance"),
"class function": ("method", "class"),
"property": ("property", "instance"),
"field": ("field", "instance"),
"static field": ("field", "static"),
"const": ("constant", "instance"),
"static const": ("constant", "static"),
}
UNIT_DECLARATIONS = {
"function": ("function", "unit"),
"var": ("variable", "unit"),
"const": ("constant", "unit"),
"class": ("class", "unit"),
}
HEADING_RE = re.compile(r"^(#{1,6})(?!#)\s+")
FENCE_RE = re.compile(r"^\s*(```|~~~)")
@dataclass(frozen=True)
class ApiHeading:
level: int
kind: str
binding: str
name: str
signature: str
visible_title: str
root_kind: str
valid: bool = True
error: str = ""
@dataclass(frozen=True)
class ApiEntry:
heading: ApiHeading
start: int
end: int
def simple_name(signature):
return signature.split("(", 1)[0].strip()
def fence_flags(lines):
flags = []
in_fence = False
marker = ""
for line in lines:
stripped = line.lstrip()
if not in_fence and FENCE_RE.match(line):
marker = stripped[:3]
flags.append(True)
in_fence = True
continue
flags.append(in_fence)
if in_fence and stripped.startswith(marker):
in_fence = False
marker = ""
return flags
def heading_body_end(lines, start, flags=None):
if flags is None:
flags = fence_flags(lines)
for index in range(start + 1, len(lines)):
if flags[index]:
continue
if HEADING_RE.match(lines[index]):
return index
return len(lines)
def top_level_header_end(lines, start, flags=None):
return heading_body_end(lines, start, flags)
def _declaration(lines, start, end, flags):
content = []
declaration_lines = []
for index in range(start + 1, end):
if flags[index]:
continue
stripped = lines[index].strip()
if not stripped:
continue
content.append((index, stripped))
if DECLARATION_PREFIX_RE.match(stripped):
declaration_lines.append((index, stripped))
if not content:
return "", "API 标题后的第一条非空正文必须是 `声明:...`"
first_index, first = content[0]
match = DECLARATION_LINE_RE.fullmatch(first)
if not match:
loose = LOOSE_DECLARATION_RE.fullmatch(first)
if loose and loose.group(1).strip():
expected = f"声明:{loose.group(1).strip().casefold()}"
return "", f"声明行必须精确写为 `{expected}`"
return (
"",
"API 标题后的第一条非空正文必须是规范的 `声明:...` 行",
)
value = match.group(1).strip().casefold()
expected = f"声明:{value}"
if first != expected:
return "", f"声明行必须精确写为 `{expected}`"
if len(declaration_lines) != 1 or declaration_lines[0][0] != first_index:
return "", "每个 API 必须且只能包含一行声明"
return value, ""
def top_level_type(lines, start, end):
return _declaration(lines, start, end, fence_flags(lines))
def invalid_heading(level, name, signature, visible_title, root_kind, message):
return ApiHeading(
level,
"invalid",
"",
name,
signature,
visible_title,
root_kind,
False,
message,
)
def _valid_heading(level, kind, binding, signature, root_kind):
return ApiHeading(
level,
kind,
binding,
simple_name(signature),
signature,
signature,
root_kind,
)
def top_level_heading(signature, declaration, error):
if error:
return invalid_heading(
2,
simple_name(signature),
signature,
signature,
"",
error,
)
if declaration not in TOP_LEVEL_TYPES:
return invalid_heading(
2,
simple_name(signature),
signature,
signature,
"",
f"顶级 API 声明必须是 function、class 或 unit,实为 {declaration}",
)
return _valid_heading(2, declaration, "", signature, declaration)
def member_heading(
level,
declaration,
signature,
root_kind,
unit_class_open,
error,
):
name = simple_name(signature)
if error:
return invalid_heading(
level, name, signature, signature, root_kind, error
)
if declaration == "static function":
return invalid_heading(
level,
name,
signature,
signature,
root_kind,
"不存在 static function,请使用 class function",
)
if root_kind == "class":
if level != 3 or declaration not in CLASS_DECLARATIONS:
return invalid_heading(
level,
name,
signature,
signature,
root_kind,
"class API 成员必须使用约定的 H3 声明",
)
kind, binding = CLASS_DECLARATIONS[declaration]
return _valid_heading(level, kind, binding, signature, root_kind)
if root_kind == "unit":
if level == 3:
if declaration not in UNIT_DECLARATIONS:
return invalid_heading(
level,
name,
signature,
signature,
root_kind,
"unit interface 成员必须使用约定的 H3 声明",
)
kind, binding = UNIT_DECLARATIONS[declaration]
return _valid_heading(level, kind, binding, signature, root_kind)
if (
level == 4
and unit_class_open
and declaration in CLASS_DECLARATIONS
):
kind, binding = CLASS_DECLARATIONS[declaration]
return _valid_heading(level, kind, binding, signature, root_kind)
return invalid_heading(
level,
name,
signature,
signature,
root_kind,
"unit class 成员必须位于所属 class 的 H4",
)
return invalid_heading(
level,
name,
signature,
signature,
root_kind,
"function 顶级 API 不能包含 API 成员",
)
def collect_headings_and_boundaries(lines):
flags = fence_flags(lines)
headings = []
h2_boundaries = []
root_kind = ""
unit_class_open = False
for index, line in enumerate(lines):
if flags[index]:
continue
if PLAIN_H2_RE.match(line):
h2_boundaries.append(index)
root_kind = ""
unit_class_open = False
top_level = TOP_LEVEL_RE.match(line)
if not top_level:
continue
signature = top_level.group(1)
end = heading_body_end(lines, index, flags)
declaration, error = _declaration(
lines, index, end, flags
)
heading = top_level_heading(signature, declaration, error)
if heading.valid:
root_kind = heading.kind
headings.append((index, heading))
continue
if not root_kind:
continue
heading_match = HEADING_RE.match(line)
if root_kind == "unit" and heading_match:
if len(heading_match.group(1)) == 3:
unit_class_open = False
legacy = LEGACY_TYPED_RE.match(line)
if legacy:
level = len(legacy.group(1))
signature = legacy.group(3)
headings.append(
(
index,
invalid_heading(
level,
simple_name(signature),
signature,
line.lstrip("# "),
root_kind,
"API 标题只写名称或调用签名,声明种类写在声明行",
),
)
)
continue
bare = BARE_API_RE.match(line)
if not bare:
continue
level = len(bare.group(1))
signature = bare.group(2)
end = heading_body_end(lines, index, flags)
declaration, error = _declaration(lines, index, end, flags)
heading = member_heading(
level,
declaration,
signature,
root_kind,
unit_class_open,
error,
)
if root_kind == "unit" and level == 3:
unit_class_open = heading.valid and heading.kind == "class"
headings.append((index, heading))
return headings, h2_boundaries
def collect_headings(lines):
headings, _ = collect_headings_and_boundaries(lines)
return headings
def iter_api_entries(lines):
headings, h2_boundaries = collect_headings_and_boundaries(lines)
for index, (start, heading) in enumerate(headings):
next_heading = (
headings[index + 1][0]
if index + 1 < len(headings)
else len(lines)
)
boundary_index = bisect_right(h2_boundaries, start)
next_h2 = (
h2_boundaries[boundary_index]
if boundary_index < len(h2_boundaries)
else len(lines)
)
yield ApiEntry(heading, start, min(next_heading, next_h2))
def slug(text):
return re.sub(r"[^a-z0-9_]", "", text.casefold())