✨ feat(vscode): 代码阅读把说明里的 @param / @return / @throws 拆到参数表、输出、异常表
This commit is contained in:
1 parent
c2982537d7
commit
0958506681
43 files changed
+1300
-323
No files matched your search
@@ -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 () => {
|
||||
|
||||
Reference in new issue
Block a user