Files
playbook/tools/tsl-codegen
csh aa8a3e73a8 🐛 fix(tsl-codegen): make TSF conversion best effort
Recover recognizable function, procedure, class, and unit structure from
nonstandard TSF input while preserving strict parsing for compliant files.

Keep STANDARD.md limited to published format requirements and cover
recovery paths with regression tests.
2026-07-30 09:20:18 +08:00
..

TSL Codegen Toolkit

本工具把一个或多个 tsf function、独立 class、完整 unit 转换为统一 declarations json/yaml 录入文件,再生成 TSL API skill 使用的 markdown 文档, 并根据 markdown 重建统一 API 索引 function_index.tsv

目录结构

tools/tsl-codegen/
  ├─ README.md                         使用说明
  ├─ STANDARD.md                       API 文档与录入格式标准
  ├─ examples/
  │   ├─ example.yaml                  yaml 录入例子
  │   └─ example.json                  json 录入例子
  ├─ scripts/
  │   ├─ convert_tsf.py                 tsf → json/yaml
  │   ├─ generate.py                    yaml/json → markdown
  │   ├─ lint.py                        markdown 格式校验
  │   ├─ api_markdown.py                lint/index 共用标题模型
  │   └─ build_index.py                 重建 13 列 function_index.tsv
  └─ tests/                             工具测试

使用顺序

1. 进入仓库根目录

先切换到包含 tools/skills/ 的仓库根目录。后续命令都从该目录运行:

cd /path/to/playbook

json 使用 Python 标准库,不需要额外安装解析包。yaml 需要安装 pyyaml

python -m pip install pyyaml

markdown 生成器会强制使用仓库锁定版本的 Prettier。首次使用前安装 Node.js,并在 仓库根目录安装依赖:

npm install

2. 阅读标准

先阅读 STANDARD.md。其中定义:

  • function、class、unit 同级 H2 顶级声明和混合页面的固定层级
  • tsf function、独立 class 和完整 unit 文档块格式
  • yaml/json 统一 declarations 判别联合与成员字段
  • 13 列统一 API 索引和完全限定名称
  • 参数表、返回类型和示例代码规则

录入文件和生成的 markdown 都必须符合该标准

3. 准备自己的 yaml 或 json

从 tsf 转换

转换器接受任意混合输入:function、独立 class 和完整 unit TSF 可以在同一条命令中 出现。每个文件贡献一个对外 declaration,生成的 declarations 严格保持命令行 输入顺序。页面级 modulepath 通过参数统一提供,--format 明确选择 json 或 yaml

python tools/tsl-codegen/scripts/convert_tsf.py \
  src/OpenXmlAttribute.tsf \
  src/ParseOpenXml.tsf \
  src/OpenXmlRuntime.tsf \
  --format json \
  --module "OfficeXml / OpenXml" \
  --path officexml/openxml/elements \
  --output tmp/openxml-elements.json

生成 yaml 时,把 --format json 改为 --format yaml,并把输出文件后缀改为 .yaml。混合输入规则不变。

查看中文帮助:

python tools/tsl-codegen/scripts/convert_tsf.py --help

转换规则:

  • function TSF 只转换第一个独立顶层 function
  • 独立 class 只公开第一个与文件名一致的 class;后续内部 class 和 private 成员忽略
  • class 开头默认 publicpublic/protected 进入草稿;类方法只接受 class function
  • 类方法没有 /// 描述时,使用最终声明或修饰符分号后的同一行 // 注释作为描述; 正式 /// 描述优先,类外实现行和函数体内注释不读取
  • property 类型可选;源码未声明类型时草稿中的 type 为空字符串,访问方式仍然必填
  • 完整 unit 只收录 interface 的 function/var/const/classimplementation 全部忽略
  • function、class、unit 可以任意混合;重复 function signature、重复 class/unit 名称会报错,不同 signature 的 function 重载和跨 kind 同名允许
  • 公开 API 中的 procedure 当前不支持
  • 输入支持 utf-8(含 BOM)和 gb18030;输出统一为 utf-8
  • 参数类型和返回类型都是可选的 TSL 注解;转换器从函数签名和 /// 文档块中尽量 解析,无法取得时保留空字段,生成待手工完善的录入稿
  • @param: name {类型} 参数说明 可以为无类型签名补充参数类型;花括号内的全部内容 都属于类型;@returns: 可以补充返回类型;签名和 /// 同时提供类型时必须一致
  • 描述、参数类型或说明等编辑性缺口不会阻止转换;类型冲突、文档结构或声明冲突会按 “文件:行号”报错;全部输入成功后才写输出文件
  • json/yaml 输出补齐各 kind 的固定维护字段:function/method 固定包含 descparamsreturnsproperty 固定包含 desctypeparamsaccess field/variable 固定包含 desctypeconstant 固定包含 desctypevalue 参数固定包含 nametypedescclass/unit 固定包含 descmembers;缺失值 使用空字符串或空数组,条件字段只在源码确实存在时输出
  • 生成器把 method returns、property/constant type 的空字符串按可选字段未填写处理; 顶级/unit function 返回类型和 field/variable 类型等必填内容仍须补全
  • 输出文件不得与任一输入 tsf 相同

手工准备

从以下例子选择一种格式:

两份完整例子深度等价,同时包含顶级 function、完整 class、class function、 typed/untyped property、字段、常量、完整 unit 和 interface class 成员

例子中的 API 是格式示例,不是真实 TSL API。复制例子到自己的工作目录,再修改 modulepath 和非空有序 declarations。录入根只允许这三个字段;旧 functionsclassunit 根不兼容,生成器会直接拒绝。混合页面的最小 完整外形:

{
  "module": "OfficeXml / OpenXml",
  "path": "officexml/openxml/elements",
  "declarations": [
    {
      "kind": "class",
      "name": "OpenXmlAttribute",
      "desc": "表示 OpenXml 属性",
      "members": []
    },
    {
      "kind": "function",
      "name": "ParseOpenXml",
      "signature": "ParseOpenXml(xml)",
      "desc": "解析 OpenXml 文本",
      "params": [
        {"name": "xml", "type": "string", "desc": "OpenXml 文本"}
      ],
      "returns": "OpenXmlElement"
    }
  ]
}

生成的 markdown 使用纯 API 标题和独立声明行。class function 的参数类型进入参数表, 返回类型写在返回行:

# OfficeXml / OpenXml

## `OpenXmlAttribute`

声明:class

表示 OpenXml 属性

### `CreateVirtual(position, row_index, _story)`

声明:class function

创建虚段落

可见性:`public`

| 参数        | 类型      | 说明     |
| ----------- | --------- | -------- |
| `position`  | integer   | 段落位置 |
| `row_index` | integer   | 行索引   |
| `_story`    | StoryNode | 故事节点 |

返回:ParagraphSegment

录入文件可放在自己的临时目录,例如:

tmp/my-api.yaml
tmp/my-api.json

一个录入文件对应一个 markdown 叶子页。录入文件不放入 skill;是否长期保留由 维护者自行决定

4. 选择 Skill 中的目标位置

TSL API skill 的相关目录如下:

skills/tsl-api-reference/
  ├─ SKILL.md
  ├─ data/
  │   └─ function_index.tsv
  ├─ references/
  │   └─ codegen/
  │       ├─ builtin/                    playbook 维护
  │       ├─ dotnet/                     playbook 维护
  │       └─ project/                    用户项目文档的默认 scope
  │           └─ <module-dir>/<page>.md
  └─ scripts/
      └─ lookup.py

markdown 目标路径固定为:

skills/tsl-api-reference/references/codegen/<scope>/<module-dir>/<page>.md
  • <scope>:用户文档默认使用 project,也可以自定义单级目录名。builtindotnet 由 playbook 项目维护,不应用于存放用户自己的 API 文档
  • <module-dir>:功能分类目录,例如 baseruntimedocument
  • <page>.md:相关 API 的叶子文档,例如 array.mdelements.md

录入文件的 module 是 markdown 一级标题,不是目录名。例如:

module: 我的项目 / 数组
path: base/array
目标文件: project/base/array.md

查找项目中的现有页面:

rg --files skills/tsl-api-reference/references/codegen/project

生成后可以直接打开目标 markdown 手动阅读。例如:

skills/tsl-api-reference/references/codegen/project/base/array.md

5. 生成 markdown

新建叶子页

目标文件不存在时,可以直接生成到 skill。例如:

python tools/tsl-codegen/scripts/generate.py tmp/my-api.yaml

json 使用相同命令:

python tools/tsl-codegen/scripts/generate.py tmp/my-api.json

生成器读取录入文件中的 path,默认写入 skills/tsl-api-reference/references/codegen/project/<path>.md。不指定 --scope 时,scope 就是 project

写入前会自动使用仓库的 .prettierrc.json 格式化 markdown,使新页面与现有 builtin 页面保持一致。未安装 Prettier 或格式化失败时,生成器会停止且不写目标文件

需要使用自定义 scope 时,通过 --scope 指定单级目录名:

python tools/tsl-codegen/scripts/generate.py tmp/my-api.json --scope my-project

修改现有叶子页

生成器会整体覆盖 path 对应的页面。只有录入文件包含该页面的全部 API 时才运行 生成器。只修改现有页面中的少量条目时,应按照 STANDARD.md 直接编辑 markdown

6. 手动检查并校验 markdown

以下命令以新建页面 skills/tsl-api-reference/references/codegen/project/base/my_api.md 为例

先打开文件,检查页面标题、API 名称、成员层级、参数、返回类型和示例

6.1 格式说明

通过 generate.py 生成的页面已经完成 Prettier 格式化,不需要再次处理。直接手工 编辑 markdown 后,可以单独格式化目标文件:

npx --no-install prettier --write skills/tsl-api-reference/references/codegen/project/base/my_api.md

6.2 校验

校验目标文件:

python tools/tsl-codegen/scripts/lint.py --file skills/tsl-api-reference/references/codegen/project/base/my_api.md

校验整个项目目录:

python tools/tsl-codegen/scripts/lint.py --dir skills/tsl-api-reference/references/codegen/project

使用 --strict 时,警告也会导致校验失败

校验器将以下问题视为错误:

  • 缺少描述
  • 顶层/unit function 缺少返回类型
  • 有参数但没有参数表
  • 无参数但存在参数表
  • 参数表不是固定三列,或参数名称/顺序与签名不一致
  • 参数表数据行缺少参数名、类型或说明
  • class member 缺少 visibility
  • property 缺少访问方式,field/variable 缺少类型,constant 缺少值
  • class/unit 成员标题层级、声明形式或子标题错误,或出现 private API

以下问题默认作为警告:

  • 可选参数说明未以 可选。 开头
  • tags 行为空

7. 更新并验证统一 API 索引

markdown 确认无误后,重建 TSV

索引固定为 13 列:保留原 8 列并追加 kind、binding、visibility、owner、 qualified_name。关键词检索会同时匹配原有字段和这些扩展字段。

python tools/tsl-codegen/scripts/build_index.py --skill-dir skills/tsl-api-reference

检查 TSV 是否与 markdown 一致:

python tools/tsl-codegen/scripts/build_index.py --skill-dir skills/tsl-api-reference --check

默认从 <skill-dir>/references/codegen 读取 markdown,并写入 <skill-dir>/data/function_index.tsv

最后使用简单名称或完全限定名称验证 skill 可以检索到新条目:

python skills/tsl-api-reference/scripts/lookup.py --name myFunction
python skills/tsl-api-reference/scripts/lookup.py --name DemoUnit.Document.Save

查询简单成员名会返回所有 owner 下的同名 API;Unit.Class.Member 等完全限定名称 用于缩小范围。重载共享完全限定名称,查询会返回全部重载。关键词候选显示 class functionstatic field 等实际声明术语。

最终提交:

  • 新增或修改的 markdown 叶子页
  • skills/tsl-api-reference/data/function_index.tsv

TSL API skill 只需要 markdown 和 TSV。录入用的 yaml/json 可以由维护者在自己的 版本库中管理