✨ feat(vscode): 代码阅读把说明里的 @param / @return / @throws 拆到参数表、输出、异常表

This commit is contained in:
cruldra committed 2026-10-08 07:17:26 +08:00
1 parent c2982537d7
commit 0958506681
43 files changed
+1300 -323

No files matched your search

+13 -2
View File
@@ -3,8 +3,6 @@ import { attributeRows, enumRows, functionRows, implRows, importRows, moduleRows
import { isCrateRoot } from '../rules/rustTree'
import { createZhTemplateTranslator, KIND_ZH, ZH_ATTRIBUTE_NAMES } from './zhTemplate'
const say = plainSay()
const template = createZhTemplateTranslator({
id: 'rust.zh-template',
label: '中文直译',
@@ -55,6 +53,7 @@ const template = createZhTemplateTranslator({
mapLiterals: {},
stringNodes: ['string_literal'],
commentText,
docNodes,
nodeTemplates: {},
})
@@ -82,6 +81,7 @@ export const rustZhTranslator: Translator = {
}
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
const say = plainSay(node => template.doc(node, document))
switch (block.kind) {
case 'import':
return importRows(block, say)
@@ -130,6 +130,17 @@ function original(node: Node, span: SourceSpan, { source, language }: SourceDocu
return out
}
/**
* 一项的文档注释:`///` 一行一个节点,从第一条起往下连着写的(下一行紧跟着另一条 `///`)都算这一段;块注释本来就是一个节点。
*/
function docNodes(first: Node): Node[] {
const outer = (node: Node): boolean => node.type === 'line_comment' && node.namedChildren.some(child => child?.type === 'outer_doc_comment_marker')
const nodes = [first]
for (let next = first.nextSibling; outer(first) && next && outer(next) && next.startPosition.row === nodes.at(-1)!.startPosition.row + 1; next = next.nextSibling)
nodes.push(next)
return nodes
}
/** 行注释去掉 `//`(文档注释连 `/`、`!`)和后面一个空格,缩进留给 Markdown;块注释去掉开头结尾的注释符号。 */
function commentText(node: Node, text: string): string {
if (node.type === 'line_comment')
@@ -1,9 +1,9 @@
import type { BlockKind, CallFacts, Inline, LanguageAdapter, PreviewTable, PropertyItem, PropertyRow, Segment, SourceDocument, TokenRole, Translator } from '../contract'
import type { BlockKind, CallFacts, Inline, LanguageAdapter, PreviewTable, PreviewText, PropertyItem, PropertyRow, Segment, SourceDocument, TokenRole, Translator } from '../contract'
import type { ZhTable } from './zhTemplate'
import { describe, expect, it } from 'vitest'
import { createPythonAdapter } from '../languages/python'
import { createTypeScriptAdapter } from '../languages/typescript'
import { flatSegments, propertyLines, readFixture, segmentText, tableLines, walk, WASM_DIR, withBlocks } from '../testSupport'
import { flatSegments, inlineOf, previewText, propertyLines, readFixture, tableLines, walk, WASM_DIR, withBlocks } from '../testSupport'
import { pythonZhTranslator } from './pythonZh'
import { typescriptZhTranslator } from './typescriptZh'
import { createZhTemplateTranslator } from './zhTemplate'
@@ -58,14 +58,14 @@ function valueOf(rows: PropertyRow[] | undefined, label: string): Inline[] {
return value
}
/** 译文写成文字,框写成 `⟦框头|框里的文字⟧`。 */
function text(segments: Inline[] | undefined): string {
return segmentText(segments ?? [])
/** 译文写成文字,框写成 `⟦框头|框里的文字⟧`;Markdown 取原文。 */
function text(segments: PreviewText | undefined): string {
return previewText(segments ?? [])
}
/** 带下标的段(框里的也算),按出现顺序写成 [文字, 下标]。 */
function linked(segments: Inline[] | undefined): [string, number][] {
return flatSegments(segments ?? []).flatMap(segment => segment.offset === undefined ? [] : [[segment.text, segment.offset] as [string, number]])
function linked(segments: PreviewText | undefined): [string, number][] {
return flatSegments(inlineOf(segments ?? [])).flatMap(segment => segment.offset === undefined ? [] : [[segment.text, segment.offset] as [string, number]])
}
function texts(map: Map<number, Translated>): Record<number, string> {
@@ -578,7 +578,7 @@ describe('属性表', () => {
])
})
it('说明:紧跟名称,去掉注释符号、引号、共同缩进和首尾空行,保留换行和 @ 标签行;没有文字不显示', async () => {
it('说明:紧跟名称,去掉注释符号、引号、共同缩进和首尾空行,保留换行;块标签拆到参数表、输出参数后面;没有文字不显示', async () => {
const tsSource = [
'class K {',
' /**',
@@ -595,7 +595,8 @@ describe('属性表', () => {
'}',
].join('\n')
const tsRows = rowTexts(await describeAll(ts, 'typescript', typescriptZhTranslator, tsSource))
expect(tsRows[2].slice(0, 3)).toEqual(['名称: load', '说明: 读配置。\n\n例子:\n load(\'a\')\n@param path 文件路径\n@returns 配置', '访问级别: 公共'])
expect(tsRows[2].slice(0, 3)).toEqual(['名称: load', '说明: 读配置。\n\n例子:\n load(\'a\')', '访问级别: 公共'])
expect(tsRows[2].slice(-3)).toEqual(['输入参数: 1 个', ' path | 字符串 | | 文件路径', '输出参数: 未标注 — 配置'])
expect(tsRows[11].slice(0, 2)).toEqual(['名称: empty', '访问级别: 公共'])
const pySource = [
@@ -615,7 +616,7 @@ describe('属性表', () => {
expect(pyRows[8].slice(0, 2)).toEqual(['名称: g', '访问级别: 公共'])
})
it('注释块和函数的说明是 Markdown 原文:只去注释符号、引号和共同缩进,reST 角色、双反引号、JSDoc 标签、HTML 照抄', async () => {
it('注释块和函数的说明是 Markdown 原文:只去注释符号、引号和共同缩进,reST 角色、双反引号、{@link}、HTML 照抄,块标签不进正文', async () => {
const pySource = [
'"""手机号规范化 / 脱敏工具.',
'',
@@ -662,7 +663,7 @@ describe('属性表', () => {
expect(tsText[3]).toBe('单行块注释')
const tsRows = await describeAll(ts, 'typescript', typescriptZhTranslator, tsSource)
expect(tsRows.get(4)?.find(row => row.label === '说明')?.value).toEqual({
markdown: '读配置,见 {@link load}。\n\n```ts\nload(\'a\')\n```\n@param path 文件路径',
markdown: '读配置,见 {@link load}。\n\n```ts\nload(\'a\')\n```',
})
})
@@ -854,12 +855,12 @@ describe('属性表', () => {
})
describe('源码下标(跳到定义用)', () => {
function itemCells(rows: PropertyRow[] | undefined, label: string): Inline[][] {
function itemCells(rows: PropertyRow[] | undefined, label: string): PreviewText[] {
return rows?.find(row => row.label === label)?.items?.flatMap(item => item.cells) ?? []
}
function propertySegments(rows: PropertyRow[]): Segment[] {
const itemSegments = (items: PropertyItem[] | undefined): Segment[] => (items ?? []).flatMap(item => [...flatSegments(item.cells.flat()), ...itemSegments(item.items)])
const itemSegments = (items: PropertyItem[] | undefined): Segment[] => (items ?? []).flatMap(item => [...flatSegments(item.cells.flatMap(inlineOf)), ...itemSegments(item.items)])
return rows.flatMap(row => [...('markdown' in row.value ? [] : flatSegments(row.value)), ...itemSegments(row.items)])
}
@@ -1033,6 +1034,82 @@ describe('源码下标(跳到定义用)', () => {
})
})
describe('说明里的块标签', () => {
it('render_conversation_brief:@param 进输入参数表的说明列,@return 跟在输出参数后面,说明里只剩正文', async () => {
const rows = await describeAll(py, 'python', pythonZhTranslator, readFixture('conversationBrief.py'))
const lines = rowTexts(rows)[25]
expect(lines.find(line => line.startsWith('说明'))).toBe('说明: 把对话转写成「前情摘要 + 近期消息」一段文字; 什么都没有时返回空串.')
expect(lines.join('\n')).not.toMatch(/@param|@return/)
const params = rows.get(25)?.find(row => row.label === '输入参数')
expect(params?.columns).toEqual(['名称', '类型', '默认值', '说明'])
expect(params?.items?.map(item => [text(item.cells[0]), text(item.cells[3])])).toEqual([
['messages', '对话历史 (`messages` 状态).'],
['summary', '更早对话的压缩摘要, 没有就传 `None`.'],
['speakers', '转写谁说的话: [USER] / [ASSISTANT]; 只要用户原话时只传 [USER].'],
['max_lines', '近期消息最多留几行, 多了从头丢弃并注明.'],
])
expect(params?.items?.[3].cells[3]).toEqual({ markdown: '近期消息最多留几行, 多了从头丢弃并注明.' })
const output = rows.get(25)?.find(row => row.label === '输出参数')
expect(output?.value).toEqual([{ text: '字符串', role: 'type' }])
expect(output?.note).toEqual({ markdown: '以 `[会话进展]` 开头的转写.' })
expect(lines.some(line => line.startsWith('对不上的参数'))).toBe(false)
})
it('gate.py:@throws 成「异常」表,第一行带完整链接,第二行是不带路径的引用', async () => {
const rows = await describeAll(py, 'python', pythonZhTranslator, readFixture('localRuntimeGate.py'))
const lines = rowTexts(rows)[18]
expect(lines.join('\n')).not.toMatch(/@throws/)
expect(lines.slice(-3)).toEqual([
'异常: 2 个',
' [DesktopOnly](backend/src/luffy_agent/sandbox/local_runtime/visibility.py#DesktopOnly) | 请求不是桌面端发来的',
' [LocalRuntimeDisabled] | 总开关关着',
])
const throws = rows.get(18)?.find(row => row.label === '异常')
expect(throws?.columns).toEqual(['异常', '触发条件'])
expect(throws?.items?.[1].cells).toEqual([{ markdown: '[LocalRuntimeDisabled]' }, { markdown: '总开关关着' }])
})
it('名字对不上的 @param 排在说明下面,提示色,悬停「没有这个参数」;没有对上的参数时参数表不加说明列', async () => {
const source = [
'/**',
' * 读文件。',
' * @param pth 路径',
' * @throws Error 读不到',
' */',
'export function read(path: string): string { return \'\' }',
].join('\n')
const rows = await describeAll(ts, 'typescript', typescriptZhTranslator, source)
const lines = rowTexts(rows)[1]
expect(lines.slice(0, 4)).toEqual(['名称: read', '说明: 读文件。', '对不上的参数: 1 个', ' pth | 路径'])
const unmatched = rows.get(1)?.find(row => row.label === '对不上的参数')
expect(unmatched?.items?.[0].cells[0]).toEqual([{ text: 'pth', role: 'hint', tooltip: '没有这个参数' }])
expect(rows.get(1)?.find(row => row.label === '输入参数')?.columns).toEqual(['名称', '类型', '默认值'])
expect(lines.slice(-2)).toEqual(['异常: 1 个', ' [Error] | 读不到'])
})
it('类:没有构造参数表可对,@param 照写成「构造参数」表;异常类的 @param 对到构造参数表,*args 去掉星号对名字', async () => {
const source = [
'class K:',
' """类。',
'',
' @param a 甲',
' """',
'',
'class E(Exception):',
' """出错了。',
'',
' @param code 错误码',
' @param args 其余参数',
' """',
' def __init__(self, code: int, *args):',
' pass',
].join('\n')
const lines = rowTexts(await describeAll(py, 'python', pythonZhTranslator, source))
expect(lines[1]).toEqual(['名称: K', '说明: 类。', '访问级别: 公共', '构造参数: 1 个', ' a | 甲'])
expect(lines[7].slice(-3)).toEqual(['构造参数: 2 个', ' code | 整数 | | 错误码', ' *args | 未标注 | | 其余参数'])
})
})
describe('跨几行写的调用', () => {
const routes = readFixture('routes.py')
const calls = readFixture('calls.ts.txt')
@@ -1189,10 +1266,10 @@ describe('跨几行写的调用', () => {
expect(asked).toEqual(expect.arrayContaining(['PaymentCapabilityDiagnostic', 'CredentialPresence']))
const nested = line?.table?.items[6]
expect(nested?.table?.columns).toEqual(['参数', '值'])
expect(nested?.cells[1].find(piece => 'role' in piece && piece.text === 'CredentialPresence')).toEqual({ text: 'CredentialPresence', role: 'type', offset: payment.indexOf('CredentialPresence(', payment.indexOf('credentials_present=')) })
expect(inlineOf(nested?.cells[1] ?? []).find(piece => 'role' in piece && piece.text === 'CredentialPresence')).toEqual({ text: 'CredentialPresence', role: 'type', offset: payment.indexOf('CredentialPresence(', payment.indexOf('credentials_present=')) })
expect(nested?.table?.items[0].cells[0]).toEqual([{ text: 'mp_app_secret', role: 'variable', offset: payment.indexOf('mp_app_secret=') }])
const unresolved = (await translateAll(py, 'python', pythonZhTranslator, payment)).get(64)
expect(segmentText(unresolved?.table?.items[6].cells[1] ?? [])).toBe('调用 CredentialPresence · 4 个参数')
expect(text(unresolved?.table?.items[6].cells[1])).toBe('调用 CredentialPresence · 4 个参数')
})
it('嵌套一层层往下套;await 的调用写「等待」', async () => {
+64 -34
View File
@@ -1,5 +1,8 @@
import type { Access, AttributeNames, BaseFacts, BlockKind, CallArgument, CallFacts, ClassFacts, CommandContext, CommandFacts, CommandForm, CommandReading, CommandRule, CommandWord, CommonAttribute, DeclaratorFacts, Defines, DirectiveFacts, EmbeddedReading, EntryFacts, EnumFacts, ExceptionFacts, FactBlock, FunctionFacts, HealthFacts, ImportFacts, ImportItem, Inline, InstructionFlag, InterpreterFacts, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Markdown, Node, OptionsFacts, ParameterFacts, PositionalParameter, PreviewTable, PropertyItem, PropertyRow, Redirect, Segment, SegmentGroup, SourceDocument, SourceRange, SourceSpan, StageFacts, StartFacts, TextBlock, TextTranslation, TokenRole, Translator, VariableBlock } from '../contract'
import type { Access, AttributeNames, BaseFacts, BlockKind, CallArgument, CallFacts, ClassFacts, CommandContext, CommandFacts, CommandForm, CommandReading, CommandRule, CommandWord, CommonAttribute, DeclaratorFacts, Defines, DirectiveFacts, Doc, EmbeddedReading, EntryFacts, EnumFacts, ExceptionFacts, FactBlock, FunctionFacts, HealthFacts, ImportFacts, ImportItem, Inline, InstructionFlag, InterpreterFacts, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Markdown, Node, OptionsFacts, ParameterFacts, PositionalParameter, PreviewTable, PropertyItem, PropertyRow, Redirect, Segment, SegmentGroup, SourceDocument, SourceRange, SourceSpan, StageFacts, StartFacts, TextBlock, TextTranslation, TokenRole, Translator, VariableBlock } from '../contract'
import type { ParamDocs } from '../rules/docRows'
import { readDoc } from '../docTags'
import { commonIndent, splitLines } from '../editText'
import { docBodyRows, paramDocCell, paramDocs, returnNote, throwsRows, unmatchedParamRows, writtenParamRows } from '../rules/docRows'
/** 按所在语法节点区分译法的关键字,例如 Python 的 else:if 后「否则」,for / while 后「未中断时」。 */
export interface ContextualWord {
@@ -46,6 +49,11 @@ export interface ZhTable {
stringNodes: string[]
/** 注释(或 docstring)节点去掉注释符号后的原文,可以多行;text 是节点的源码去掉代码阅读的记号(SourceDocument.markers)以后剩下的。 */
commentText: (node: Node, text: string) => string
/**
* 说明是连着几条行注释写的(Rust 的 `///`)时,从第一条(facts.doc)起这一段的各条注释节点,照源码顺序;
* 不填就是那一个节点(JSDoc、docstring 本来就是一个节点)。
*/
docNodes?: (first: Node) => Node[]
/**
* 节点模板:节点类型 → 按中文语序重排的译文。命中的节点只输出模板里的译词和槽位,节点自己的关键字、标点不再输出;
* 槽位里的子节点照常递归翻译。模板引用的子节点缺了,或节点没整个落在要翻的这一段里,就不重排、照常翻。
@@ -661,9 +669,9 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
return joinTokens(tokensIn(node, nodeSpan(node), document))
}
function doc(node: Node, document: SourceDocument): Markdown | undefined {
const markdown = docText([commentOf(node, document)])
return markdown ? { markdown } : undefined
function doc(node: Node, document: SourceDocument): Doc | undefined {
const markdown = docText((table.docNodes?.(node) ?? [node]).map(part => commentOf(part, document)))
return markdown ? readDoc(markdown) : undefined
}
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
@@ -817,8 +825,8 @@ interface Phrases {
module: (node: Node) => Segment[]
/** 源码里的一个名字原样照抄、按 role 着色(函数、类的名称)。 */
name: (node: Node, role: TokenRole) => Segment[]
/** 函数、类的说明(JSDoc、docstring):去掉注释符号、引号和共同缩进的 Markdown 原文;没有文字时没有。 */
doc: (node: Node) => Markdown | undefined
/** 函数、类的说明(JSDoc、docstring):去掉注释符号、引号和共同缩进的 Markdown 原文拆成正文和块标签;没有文字时没有。 */
doc: (node: Node) => Doc | undefined
/** 一条要执行的命令:数组写法交给命令规则读,shell 写法是宿主读好的嵌着的代码(SourceDocument.embedded),可以带一张选项表。 */
command: (form: CommandForm) => { text: Inline[], table?: PreviewTable }
}
@@ -907,38 +915,50 @@ function aliased(item: Inline[], alias: Node | undefined, say: Phrases): Inline[
}
/**
* 函数:名称 / 说明 / 访问级别 / 异步(只有分这个的语言才有)/ 装饰器 / 输入参数 / 输出参数。
* 输入参数是一张表,列名是名称 / 类型 / 默认值,每个参数一行;没有默认值的格子留空。
* 函数:名称 / 说明 / 对不上的参数 / 访问级别 / 异步(只有分这个的语言才有)/ 装饰器 / 输入参数 / 输出参数 / 异常。
* 输入参数是一张表,列名是名称 / 类型 / 默认值,每个参数一行;没有默认值的格子留空。说明里的块标签拆开放:
* `@param` 进输入参数表多出的「说明」列(有参数对上才有这一列),名字对不上的排在说明下面;`@return` 跟在输出参数的类型后面;
* `@throws` 是「异常」一行。
*/
function functionRows(facts: FunctionFacts, say: Phrases): PropertyRow[] {
const doc = facts.doc && say.doc(facts.doc)
const names = facts.positional ? facts.positional.flatMap(param => param.name ? [param.name.text] : []) : facts.parameters.map(param => param.name.text)
const docs = paramDocs(doc, names)
return [
{ label: '名称', value: facts.name ? say.name(facts.name, 'function') : [hint('匿名')] },
...docRows(facts.doc, say),
...docBodyRows(doc),
...unmatchedParamRows(docs.unmatched),
...accessRows(facts.access),
...flagRows('async', facts.async),
...decoratorRows(facts.decorators, say),
...(facts.positional
? positionalRows(facts, facts.positional, say)
? positionalRows(facts, facts.positional, say, docs, doc)
: [
parameterRow('输入参数', facts.parameters, say),
{ label: '输出参数', value: facts.returnType ? say.translated(facts.returnType) : [hint('未标注')] },
parameterRow('输入参数', facts.parameters, say, docs),
{ label: '输出参数', value: facts.returnType ? say.translated(facts.returnType) : [hint('未标注')], ...returnNote(doc) },
]),
...throwsRows(doc),
]
}
/**
* 按位置取参数的函数(shell):输入参数写「N 个,按位置」,下面一张表「位置 | 函数里叫」(没起名的写 $N);
* 按位置取参数的函数(shell):输入参数写「N 个,按位置」,下面一张表「位置 | 函数里叫」(没起名的写 $N;`@param` 按起的名字对上时多一列说明);
* 输出是它打印的那几句(调用处用 $(函数 …) 拿去用时注明);返回是写了的 return 值,没写 return、也不打印时是最后一句成不成功。
*/
function positionalRows(facts: FunctionFacts, positional: PositionalParameter[], say: Phrases): PropertyRow[] {
function positionalRows(facts: FunctionFacts, positional: PositionalParameter[], say: Phrases, docs: ParamDocs, doc: Doc | undefined): PropertyRow[] {
const described = positional.some(param => param.name && docs.of(param.name.text))
const inputs: PropertyRow = positional.length === 0
? { label: '输入参数', value: [hint('无')] }
: {
label: '输入参数',
value: [hint(`${positional.length} 个,按位置`)],
columns: ['位置', '函数里叫'],
columns: described ? ['位置', '函数里叫', '说明'] : ['位置', '函数里叫'],
items: positional.map(param => ({
cells: [[hint(`第 ${param.position} 个`)], param.name ? say.name(param.name, 'variable') : [segment(`$${param.position}`, 'variable', undefined)]],
cells: [
[hint(`第 ${param.position} 个`)],
param.name ? say.name(param.name, 'variable') : [segment(`$${param.position}`, 'variable', undefined)],
...(described ? [param.name ? paramDocCell(docs, param.name.text) : []] : []),
],
...defining(param.name),
})),
}
@@ -950,7 +970,9 @@ function positionalRows(facts: FunctionFacts, positional: PositionalParameter[],
const result: PropertyRow[] = values.length > 0
? [{ label: '返回', value: joined(values, '、') }]
: prints.length === 0 ? [{ label: '返回', value: [hint('最后一句成不成功')] }] : []
return [inputs, ...output, ...result]
// `@return` 跟在返回那一行后面;只有打印、没写 return 时跟在输出那一行后面
const [noted] = [...result, ...output]
return [inputs, ...[...output, ...result].map(row => row === noted ? { ...row, ...returnNote(doc) } : row)]
}
/** 解释器:叫什么,怎么找到它(按 PATH 找,或写死的路径)。 */
@@ -991,7 +1013,7 @@ function flagValue(flag: InstructionFlag): Inline[] {
* 接着前面某个阶段建的写「阶段 X」)/ 平台(写了 --platform 才有)。
*/
function stageRows(facts: StageFacts, say: Phrases): PropertyRow[] {
const doc = (facts.doc ?? []).map(node => say.doc(node)?.markdown).filter(Boolean).join('\n')
const doc = (facts.doc ?? []).map(node => say.doc(node)?.body?.markdown).filter(Boolean).join('\n')
const platform = facts.flags.find(flag => flag.name === 'platform')
const image: Inline[] = facts.stage
? [segment('阶段', 'keyword', undefined), segment(' ', 'plain', undefined), ...say.name(facts.image, 'variable')]
@@ -1078,17 +1100,22 @@ function scopedVariableRows(declarators: DeclaratorFacts[], scope: 'build' | 'en
}]
}
/** 参数表:列名是名称 / 类型 / 默认值,每个参数一行,讲的定义是参数名;没有默认值的格子留空。 */
function parameterRow(label: string, parameters: ParameterFacts[], say: Phrases): PropertyRow {
/**
* 参数表:列名是名称 / 类型 / 默认值,每个参数一行,讲的定义是参数名;没有默认值的格子留空。
* 说明里有参数对上了(docs)时多一列「说明」,没对上的格子留空。
*/
function parameterRow(label: string, parameters: ParameterFacts[], say: Phrases, docs: ParamDocs): PropertyRow {
const described = parameters.some(param => docs.of(param.name.text))
return {
label,
value: count(parameters.length),
columns: ['名称', '类型', '默认值'],
columns: described ? ['名称', '类型', '默认值', '说明'] : ['名称', '类型', '默认值'],
items: parameters.map(param => ({
cells: [
param.optional ? [...say.translated(param.name), { text: '?', role: 'plain' }] : say.translated(param.name),
param.type ? say.translated(param.type) : [hint('未标注')],
param.default ? say.translated(param.default) : [],
...(described ? [paramDocCell(docs, param.name.text)] : []),
],
...defining(param.name),
})),
@@ -1098,19 +1125,23 @@ function parameterRow(label: string, parameters: ParameterFacts[], say: Phrases)
/**
* 类:名称 / 说明 / 访问级别 / 抽象(只有分这个的语言才有)/ 装饰器 / 类型参数 / 父类 / 实现的接口,
* 说明及以后各行有才显示;类型参数、父类、实现的接口各是一行,多项用逗号隔开。类型参数那一行讲的是这几个泛型参数的定义。
* 类没有构造参数表可对,说明里的 `@param` 照写成「构造参数」一张「名称 | 说明」表;`@throws` 是「异常」一行。
*/
function classRows(facts: ClassFacts, say: Phrases): PropertyRow[] {
const listRow = (label: string, parts: Inline[][], defines: Defines = {}): PropertyRow[] =>
parts.length > 0 ? [{ label, value: joined(parts, ', '), ...defines }] : []
const doc = facts.doc && say.doc(facts.doc)
return [
{ label: '名称', value: facts.name ? say.name(facts.name, 'type') : [hint('匿名')] },
...docRows(facts.doc, say),
...docBodyRows(doc),
...accessRows(facts.access),
...flagRows('abstract', facts.abstract),
...decoratorRows(facts.decorators, say),
...listRow('类型参数', facts.typeParameters.map(say.translated), defining(...facts.typeParameters)),
...listRow('父类', facts.bases.map(base => baseText(base, say))),
...listRow('实现的接口', facts.implements.map(say.translated)),
...writtenParamRows(doc?.params ?? [], '构造参数'),
...throwsRows(doc),
]
}
@@ -1129,7 +1160,7 @@ function enumRows(facts: EnumFacts, say: Phrases): PropertyRow[] {
const described = docs.some(Boolean)
return [
{ label: '名称', value: say.name(facts.name, 'type') },
...docRows(facts.doc, say),
...docBodyRows(facts.doc && say.doc(facts.doc)),
...accessRows(facts.access),
...decoratorRows(facts.decorators, say),
...baseRows(facts.bases, say),
@@ -1153,6 +1184,7 @@ function enumRows(facts: EnumFacts, say: Phrases): PropertyRow[] {
* 异常(Python、TypeScript 同一张表):名称 / 说明 / 访问级别 / 装饰器 / 继承(照写的父类),说明及以后有才显示;
* 类属性收成一张表:属性 | 类型 | 值 | 说明,一个属性都没写类型时没有「类型」列、都没有说明时没有「说明」列,没写的格子留空;
* 构造参数同函数的输入参数表。没有类属性、没写构造函数时没有这两行。
* 说明里的 `@param` 对到构造参数表(同函数的输入参数,对不上的排在说明下面;没写构造函数时同类,照写成一张表),`@throws` 是「异常」一行。
*/
function exceptionRows(facts: ExceptionFacts, say: Phrases): PropertyRow[] {
const docs = facts.fields.map(field => docsText(field.docs, say))
@@ -1172,14 +1204,18 @@ function exceptionRows(facts: ExceptionFacts, say: Phrases): PropertyRow[] {
...defining(field.name),
})),
}
const doc = facts.doc && say.doc(facts.doc)
const params = facts.parameters && paramDocs(doc, facts.parameters.map(param => param.name.text))
return [
{ label: '名称', value: facts.name ? say.name(facts.name, 'type') : [hint('匿名')] },
...docRows(facts.doc, say),
...docBodyRows(doc),
...unmatchedParamRows(params?.unmatched ?? []),
...accessRows(facts.access),
...decoratorRows(facts.decorators, say),
...baseRows(facts.bases, say),
...(facts.fields.length > 0 ? [fields] : []),
...(facts.parameters ? [parameterRow('构造参数', facts.parameters, say)] : []),
...(facts.parameters && params ? [parameterRow('构造参数', facts.parameters, say, params)] : writtenParamRows(doc?.params ?? [], '构造参数')),
...throwsRows(doc),
]
}
@@ -1188,21 +1224,15 @@ function baseRows(bases: BaseFacts[], say: Phrases): PropertyRow[] {
return bases.length > 0 ? [{ label: '继承', value: joined(bases.map(base => baseText(base, say)), ', ') }] : []
}
/** 表格里一格说明:几段说明文字的原文用空格连起来。 */
/** 表格里一格说明:几段说明文字的正文用空格连起来。 */
function docsText(nodes: Node[], say: Phrases): string {
return nodes.map(node => say.doc(node)?.markdown).filter(Boolean).join(' ')
return nodes.map(node => say.doc(node)?.body?.markdown).filter(Boolean).join(' ')
}
function docCell(text: string): Inline[] {
return text ? [segment(text, 'comment', undefined)] : []
}
/** 函数、类、常量、变量的说明:有文字才有这一行。 */
function docRows(doc: Node | undefined, say: Phrases): PropertyRow[] {
const markdown = doc && say.doc(doc)
return markdown ? [{ label: '说明', value: markdown }] : []
}
/** 装饰器:源码原样照抄,多个换行。 */
function decoratorRows(decorators: Node[], say: Phrases): PropertyRow[] {
return decorators.length > 0 ? [{ label: '装饰器', value: joined(decorators.map(say.verbatim), '\n') }] : []
@@ -1238,7 +1268,7 @@ function variableRows(block: VariableBlock, say: Phrases): PropertyRow[] {
const valueLabel = block.kind === 'const' ? '值' : '初始值'
return block.facts.declarators.flatMap((declarator, i) => [
{ label: '名称', value: say.translated(declarator.name), ...defining(declarator.name) },
...(i === 0 ? docRows(block.facts.doc, say) : []),
...(i === 0 ? docBodyRows(block.facts.doc && say.doc(block.facts.doc)) : []),
...accessRows(block.facts.access),
...(declarator.type ? [{ label: '类型', value: say.translated(declarator.type) }] : []),
{ label: valueLabel, ...(declarator.value ? declaredValue(declarator.value, declarator.entries, say) : { value: [hint('无')] }) },