✨ 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

+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('无')] }) },