✨ 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

+49 -19
View File
@@ -1,5 +1,7 @@
import type { FunctionBlock, ImportFacts, IndexEntry, Inline, KindIconName, LibraryRule, Node, ParameterFacts, PropertyItem, PropertyRow, RuleCard, RuleContext, Segment, ThemeColor, VariableBlock, WorkspaceIndex } from '../contract'
import type { FunctionBlock, ImportFacts, IndexEntry, Inline, KindIconName, LibraryRule, Node, ParameterFacts, PreviewText, PropertyItem, PropertyRow, RuleCard, RuleContext, Segment, ThemeColor, VariableBlock, WorkspaceIndex } from '../contract'
import type { ParamDocs } from './docRows'
import type { Call, LibraryNames } from './pythonTree'
import { docBodyRows, paramDocs, returnNote, throwsRows, unmatchedParamRows } from './docRows'
import { callOf, genericOf, lastName, libraryName, stringContent, syntaxChildren, unwrapType } from './pythonTree'
import { hint, joined, name, range, word } from './segments'
@@ -74,6 +76,8 @@ interface Words {
no: string
count: (n: number) => string
requestHeads: string[]
/** 请求表里有参数写了说明(`@param`)时多出的那一列 */
requestDoc: string
responseHeads: string[]
/** 路由器卡片的接口表:方法 | 路径 | 函数 | 说明 */
routeHeads: string[]
@@ -113,6 +117,7 @@ const ZH: Words = {
no: '否',
count: n => `${n} 个`,
requestHeads: ['项', '值'],
requestDoc: '说明',
responseHeads: ['状态码', '内容', '说明'],
routeHeads: ['方法', '路径', '函数', '说明'],
items: { url: 'URL', method: '方法', path: '路径参数', query: '查询参数', header: '请求头', cookie: 'Cookie', body: '请求体' },
@@ -325,11 +330,11 @@ function parametersOf(definition: Node): Map<string, Node> {
return new Map(names.flatMap(param => param?.type === 'identifier' ? [[param.text, param] as const] : []))
}
/** 函数 docstring 的第一行(翻译插件取的说明原文),没有 docstring 时没有。 */
/** 函数 docstring 正文的第一行(翻译插件读出的说明,块标签已经拿掉),没有 docstring 时没有。 */
function docFirstLine(definition: Node, env: Env): string | undefined {
const [first] = syntaxChildren(definition.childForFieldName('body'))
const [doc] = first?.type === 'expression_statement' ? syntaxChildren(first) : []
const markdown = doc?.type === 'string' ? env.context.doc(doc)?.markdown : undefined
const markdown = doc?.type === 'string' ? env.context.doc(doc)?.body?.markdown : undefined
return markdown?.split('\n').map(line => line.trim()).find(line => line !== '')
}
@@ -624,9 +629,37 @@ function methodText(method: string): Inline[] {
return [{ text: method, role: 'keyword', ...(METHOD_COLORS[method] && { color: METHOD_COLORS[method] }) }]
}
/** 请求表的一行:项 | 值。 */
function requestItem(label: string, value: Inline[]): PropertyItem {
return { cells: [[hint(label)], value] }
/** 请求表的一行:项 | 值,表里有说明一列时再加说明(note,没有说明的是空的)。 */
function requestItem(label: string, value: Inline[], note?: PreviewText): PropertyItem {
return { cells: [[hint(label)], value, ...(note ? [note] : [])] }
}
/**
* 请求表的各项:URL、方法,然后路径参数、查询参数、请求头、Cookie、请求体,有才列。说明里有请求参数对上了 `@param`(docs)时
* described:表多一列说明,这几种参数一个参数一行、各带它的说明;没有时同一种参数逗号隔开写在一行。
*/
function requestItems(url: Inline[], method: string, placed: readonly Placed[], docs: ParamDocs, env: Env): { items: PropertyItem[], described: boolean } {
const { words, context } = env
const described = placed.some(item => item.place !== 'dependency' && docs.of(item.param.text))
const item = (label: string, value: Inline[], note?: PreviewText): PropertyItem => requestItem(label, value, described ? note ?? [] : undefined)
const at = (place: Place): Placed[] => placed.filter(one => one.place === place)
const listed = (place: Exclude<Place, 'dependency'>): PropertyItem[] => {
const found = at(place)
if (described)
return found.map(one => item(words.items[place], paramsText([one], env), docs.of(one.param.text)))
return found.length > 0 ? [item(words.items[place], paramsText(found, env))] : []
}
const body = at('body')
const [only] = body
return {
items: [
item(words.items.url, url),
item(words.items.method, methodText(method)),
...(['path', 'query', 'header', 'cookie'] as const).flatMap(listed),
...(body.length === 1 && only?.type ? [item(words.items.body, context.translate(only.type), docs.of(only.param.text))] : listed('body')),
],
described,
}
}
function routeCard(block: FunctionBlock, route: Route, env: Env): RuleCard {
@@ -639,16 +672,12 @@ function routeCard(block: FunctionBlock, route: Route, env: Env): RuleCard {
const inPath = pathNames([router.prefix, route.path])
const placed = facts.parameters.flatMap(parameter => placeOf(parameter, inPath, env) ?? [])
const at = (place: Place): Placed[] => placed.filter(item => item.place === place)
const body = at('body')
const params = new Map(facts.parameters.flatMap(({ name: param }) => param.type === 'identifier' ? [[param.text, param] as const] : []))
const url = [...pathSegments(router.prefix, params, env), ...pathSegments(route.path, params, env)]
const request: PropertyItem[] = [
requestItem(words.items.url, url),
requestItem(words.items.method, methodText(route.method)),
...(['path', 'query', 'header', 'cookie'] as const).flatMap(place => at(place).length > 0 ? [requestItem(words.items[place], paramsText(at(place), env))] : []),
...(body.length > 0 ? [requestItem(words.items.body, body.length === 1 && body[0].type ? context.translate(body[0].type) : paramsText(body, env))] : []),
]
const doc = facts.doc && context.doc(facts.doc)
const docs = paramDocs(doc, facts.parameters.map(parameter => parameter.name.text))
const request = requestItems(url, route.method, placed, docs, env)
const statuses = new Statuses()
const success = call.keywords.get('status_code')
@@ -668,7 +697,6 @@ function routeCard(block: FunctionBlock, route: Route, env: Env): RuleCard {
if (placed.some(item => item.place !== 'dependency'))
statuses.add(numericStatus('422'), [hint(words.validation)], [hint(words.automatic)])
const doc = facts.doc && context.doc(facts.doc)
const tags = [...router.tags, ...listItems(call.keywords.get('tags'))].map(tag => tagText(tag, env))
const decorators = facts.decorators.filter(other => other !== route.decorator)
const dependencies = [
@@ -680,7 +708,8 @@ function routeCard(block: FunctionBlock, route: Route, env: Env): RuleCard {
]
const rows: PropertyRow[] = [
...(facts.name ? [{ label: labels.name, value: [name(facts.name, 'function')] }] : []),
...(doc ? [{ label: labels.doc, value: doc }] : []),
...docBodyRows(doc, labels.doc),
...unmatchedParamRows(docs.unmatched),
{ label: labels.router, value: [name(router.target, 'variable')] },
...(tags.length > 0 ? [{ label: labels.tags, value: joined(tags, separator) }] : []),
{ label: labels.async, value: [word(facts.async ? words.yes : words.no)] },
@@ -689,10 +718,11 @@ function routeCard(block: FunctionBlock, route: Route, env: Env): RuleCard {
{
label: labels.request,
value: [hint(`${route.method} ${textOf(url)}`)],
columns: words.requestHeads,
items: request,
columns: request.described ? [...words.requestHeads, words.requestDoc] : words.requestHeads,
items: request.items,
},
responsesRow(labels.responses, statuses, env),
{ ...responsesRow(labels.responses, statuses, env), ...returnNote(doc) },
...throwsRows(doc),
]
return {
kind: KIND,
@@ -712,7 +742,7 @@ function routerCard(block: VariableBlock, router: Router, routes: readonly Route
const { context, words } = env
const { labels } = words
const separator = hint(words.listSeparator)
const doc = block.facts.doc && context.doc(block.facts.doc)
const doc = block.facts.doc && context.doc(block.facts.doc)?.body
const tags = router.tags.map(tag => tagText(tag, env))
const dependencies = listedDependencies(router.dependencies, env)
const statuses = new Statuses()