✨ feat(vscode): 代码阅读的注释和函数说明按 Markdown 渲染
This commit is contained in:
1 parent
aae9012f14
commit
a53c2a6aac
18 files changed
+394
-66
No files matched your search
@@ -11,16 +11,21 @@ import { createZhTemplateTranslator } from './zhTemplate'
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
const py = createPythonAdapter(WASM_DIR)
|
||||
|
||||
interface Translated { kind: string, segments: Segment[] }
|
||||
/** 一块的译文:一行译文在 segments 里;注释块是 Markdown,原文在 markdown 里,segments 为空。 */
|
||||
interface Translated { kind: string, segments: Segment[], markdown?: string }
|
||||
|
||||
/** 一行译文块的译文,按 header 起始行(1 起)索引;同一行有多块时取外层那块。属性表块见 describeAll。 */
|
||||
/** 一行译文块、注释块的译文,按 header 起始行(1 起)索引;同一行有多块时取外层那块。属性表块见 describeAll。 */
|
||||
function translateAll(adapter: LanguageAdapter, languageId: string, translator: Translator, source: string): Promise<Map<number, Translated>> {
|
||||
return withBlocks(adapter, languageId, source, (blocks) => {
|
||||
const byLine = new Map<number, Translated>()
|
||||
walk(blocks, (block) => {
|
||||
const line = block.header.startRow + 1
|
||||
if (!byLine.has(line) && !('facts' in block))
|
||||
byLine.set(line, { kind: block.kind, segments: translator.translate(block, source) })
|
||||
if (byLine.has(line) || 'facts' in block)
|
||||
return
|
||||
const translation = translator.translate(block, source)
|
||||
byLine.set(line, 'markdown' in translation
|
||||
? { kind: block.kind, segments: [], markdown: translation.markdown }
|
||||
: { kind: block.kind, segments: translation })
|
||||
})
|
||||
return byLine
|
||||
})
|
||||
@@ -44,7 +49,10 @@ function rowTexts(map: Map<number, PropertyRow[]>): Record<number, string[]> {
|
||||
}
|
||||
|
||||
function valueOf(rows: PropertyRow[] | undefined, label: string): Segment[] {
|
||||
return rows?.find(row => row.label === label)?.value ?? []
|
||||
const value = rows?.find(row => row.label === label)?.value ?? []
|
||||
if ('markdown' in value)
|
||||
throw new Error(`「${label}」是 Markdown`)
|
||||
return value
|
||||
}
|
||||
|
||||
function text(segments: Segment[] | undefined): string {
|
||||
@@ -52,7 +60,7 @@ function text(segments: Segment[] | undefined): string {
|
||||
}
|
||||
|
||||
function texts(map: Map<number, Translated>): Record<number, string> {
|
||||
return Object.fromEntries([...map].map(([line, { segments }]) => [line, text(segments)]))
|
||||
return Object.fromEntries([...map].map(([line, { segments, markdown }]) => [line, markdown ?? text(segments)]))
|
||||
}
|
||||
|
||||
/** 只看文字和着色:去掉各段的源码下标、属性行讲的定义(这两样另有用例)。 */
|
||||
@@ -93,14 +101,23 @@ describe('typeScript 中文直译', () => {
|
||||
expect(text(issue.get(20)?.segments)).toBe('返回 { locked: 真, prerequisiteNumber: prereq.number, prerequisiteColumn: prereq.column }')
|
||||
})
|
||||
|
||||
it('其余块:结构照抄,复合语句去掉结尾的 {,注释去掉注释符号', async () => {
|
||||
it('其余块:结构照抄,复合语句去掉结尾的 {,注释去掉注释符号、保留换行', async () => {
|
||||
const env = texts(await translateAll(ts, 'typescript', typescriptZhTranslator, readFixture('envLock.ts.txt')))
|
||||
const issue = texts(await translateAll(ts, 'typescript', typescriptZhTranslator, readFixture('issueLock.ts.txt')))
|
||||
expect(env[32]).toBe('公共 接口 EnvLockResult')
|
||||
expect(env[42]).toBe('尝试')
|
||||
expect(env[46]).toBe('捕获 (err)')
|
||||
expect(env[47]).toBe('result.failed.push({ path: f, reason: 如果 err 属于 Error 则 err.message 否则 String(err) })')
|
||||
expect(env[1]).toBe('.env file lock / unlock helpers. Recursively scans the main workspace folder for `.env*` files (skipping `node_modules`, `.git`, `.claude`) and chmods them to 0o444 (locked) or 0o644 (unlocked) in bulk. Backs the toolbar Lock toggle in `PanelHeader`. No persistent watcher — re-scan happens on every toggle, so newly created `.env` files are only picked up the next time the user clicks the button.')
|
||||
expect(env[1]).toBe([
|
||||
'.env file lock / unlock helpers.',
|
||||
'',
|
||||
'Recursively scans the main workspace folder for `.env*` files (skipping',
|
||||
'`node_modules`, `.git`, `.claude`) and chmods them to 0o444 (locked) or',
|
||||
'0o644 (unlocked) in bulk. Backs the toolbar Lock toggle in `PanelHeader`.',
|
||||
'',
|
||||
'No persistent watcher — re-scan happens on every toggle, so newly created',
|
||||
'`.env` files are only picked up the next time the user clicks the button.',
|
||||
].join('\n'))
|
||||
expect(issue[5]).toBe('prerequisiteNumber?: 数值')
|
||||
expect(issue[15]).toBe('如果 (非 issue 或者 issue.prerequisite 全等 未定义)')
|
||||
})
|
||||
@@ -382,7 +399,7 @@ describe('属性表', () => {
|
||||
const rows = colors(env.get(18) ?? [])
|
||||
expect(rows.map(row => row.label)).toEqual(['名称', '说明', '访问级别', '异步', '输入参数', '输出参数'])
|
||||
expect(rows[0].value).toEqual([{ text: 'findEnvFiles', role: 'function' }])
|
||||
expect(rows[1].value).toEqual([{ text: valueOf(env.get(18), '说明')[0].text, role: 'comment' }])
|
||||
expect(rows[1].value).toEqual({ markdown: expect.stringMatching(/^Scan the workspace folder/) })
|
||||
expect(rows[2].value).toEqual([{ text: '公共', role: 'keyword' }])
|
||||
expect(rows[3].value).toEqual([{ text: '是', role: 'storage' }])
|
||||
expect(rows[4].value).toEqual([{ text: '1 个', role: 'hint' }])
|
||||
@@ -447,6 +464,57 @@ describe('属性表', () => {
|
||||
expect(pyRows[8].slice(0, 2)).toEqual(['名称: g', '访问级别: 公共'])
|
||||
})
|
||||
|
||||
it('注释块和函数的说明是 Markdown 原文:只去注释符号、引号和共同缩进,reST 角色、双反引号、JSDoc 标签、HTML 照抄', async () => {
|
||||
const pySource = [
|
||||
'"""手机号规范化 / 脱敏工具.',
|
||||
'',
|
||||
'凡是入库、比对的手机号都先过 :func:`normalize_phone`,日志里打印 ``user`` 时用 <b>mask</b>。',
|
||||
'"""',
|
||||
'# 用法:',
|
||||
'#',
|
||||
'# mask_phone("13800138000")',
|
||||
'def mask_phone(phone: str) -> str:',
|
||||
' """把中间四位换成 ``*``.',
|
||||
'',
|
||||
' - 不足 7 位原样返回',
|
||||
' - 见 https://example.com/phone',
|
||||
' """',
|
||||
].join('\n')
|
||||
const pyText = await translateAll(py, 'python', pythonZhTranslator, pySource)
|
||||
expect(pyText.get(1)).toEqual({
|
||||
kind: 'comment',
|
||||
segments: [],
|
||||
markdown: '手机号规范化 / 脱敏工具.\n\n凡是入库、比对的手机号都先过 :func:`normalize_phone`,日志里打印 ``user`` 时用 <b>mask</b>。',
|
||||
})
|
||||
expect(pyText.get(5)?.markdown).toBe('用法:\n\n mask_phone("13800138000")')
|
||||
const pyRows = await describeAll(py, 'python', pythonZhTranslator, pySource)
|
||||
expect(pyRows.get(8)?.find(row => row.label === '说明')?.value).toEqual({
|
||||
markdown: '把中间四位换成 ``*``.\n\n- 不足 7 位原样返回\n- 见 https://example.com/phone',
|
||||
})
|
||||
|
||||
const tsSource = [
|
||||
'// 第一行',
|
||||
'// - 缩进的列表',
|
||||
'/* 单行块注释 */',
|
||||
'/**',
|
||||
' * 读配置,见 {@link load}。',
|
||||
' *',
|
||||
' * ```ts',
|
||||
' * load(\'a\')',
|
||||
' * ```',
|
||||
' * @param path 文件路径',
|
||||
' */',
|
||||
'function load(path: string) {}',
|
||||
].join('\n')
|
||||
const tsText = texts(await translateAll(ts, 'typescript', typescriptZhTranslator, tsSource))
|
||||
expect(tsText[1]).toBe('第一行\n - 缩进的列表')
|
||||
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 文件路径',
|
||||
})
|
||||
})
|
||||
|
||||
it('导入:默认导出、全部、整个模块、副作用导入;Python 没有「仅类型」', async () => {
|
||||
const tsSource = [
|
||||
'import d, { b as c } from \'y\'',
|
||||
@@ -517,7 +585,7 @@ describe('源码下标(跳到定义用)', () => {
|
||||
}
|
||||
|
||||
function propertySegments(rows: PropertyRow[]): Segment[] {
|
||||
return rows.flatMap(row => [...row.value, ...(row.items ?? []).flatMap(item => item.cells.flat())])
|
||||
return rows.flatMap(row => [...('markdown' in row.value ? [] : row.value), ...(row.items ?? []).flatMap(item => item.cells.flat())])
|
||||
}
|
||||
|
||||
it('python:点分模块名每一节、导入项、别名各自带下标,点号不带;前面有中文注释也对得上', async () => {
|
||||
|
||||
Reference in new issue
Block a user