✨ feat(vscode): 代码阅读函数块默认折叠,函数说明进属性表「说明」行
This commit is contained in:
1 parent
62f48fe67c
commit
9b32d4b5a8
17 files changed
+489
-100
No files matched your search
@@ -90,7 +90,6 @@ describe('typeScript 中文直译', () => {
|
||||
expect(text(env.get(26)?.segments)).toBe('返回 空数组')
|
||||
expect(text(env.get(41)?.segments)).toBe('遍历 (常量 f 取自 files)')
|
||||
expect(text(env.get(35)?.segments)).toBe('failed: { path: 字符串, reason: 字符串 }[]')
|
||||
expect(text(env.get(53)?.segments)).toBe('Set every `.env*` file in the workspace to 0o444 (read-only).')
|
||||
expect(text(issue.get(20)?.segments)).toBe('返回 { locked: 真, prerequisiteNumber: prereq.number, prerequisiteColumn: prereq.column }')
|
||||
})
|
||||
|
||||
@@ -103,7 +102,6 @@ describe('typeScript 中文直译', () => {
|
||||
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(issue[5]).toBe('prerequisiteNumber?: 数值')
|
||||
expect(issue[9]).toBe('纯判定:工单被未完成的前置挡住才算锁定。快照里找不到工单或前置一律放行, 与 webview 的 `isIssueLocked` 保持同一语义。')
|
||||
expect(issue[15]).toBe('如果 (非 issue 或者 issue.prerequisite 全等 未定义)')
|
||||
})
|
||||
|
||||
@@ -212,7 +210,6 @@ describe('python 中文直译', () => {
|
||||
const map = await translateAll(py, 'python', pythonZhTranslator, readFixture('sample.py'))
|
||||
expect(texts(map)).toEqual({
|
||||
1: 'Scan a workspace for .env files.',
|
||||
11: 'Return every .env file under root.',
|
||||
12: '跳过备份文件',
|
||||
14: '如果 非 found',
|
||||
15: '返回 空列表',
|
||||
@@ -327,8 +324,9 @@ describe('属性表', () => {
|
||||
const env = rowTexts(await describeAll(ts, 'typescript', typescriptZhTranslator, readFixture('envLock.ts.txt')))
|
||||
const issue = rowTexts(await describeAll(ts, 'typescript', typescriptZhTranslator, readFixture('issueLock.ts.txt')))
|
||||
const routes = rowTexts(await describeAll(py, 'python', pythonZhTranslator, readFixture('routes.py')))
|
||||
expect(env[23]).toEqual([
|
||||
expect(env[18]).toEqual([
|
||||
'名称: findEnvFiles',
|
||||
'说明: Scan the workspace folder rooted at `workspaceRoot` for all `.env*`\nfiles, skipping `node_modules`, `.git`, and `.claude`. Returns absolute\nfs paths. Empty array if the workspace folder isn\'t currently open.',
|
||||
'访问级别: 公共',
|
||||
'异步: 是',
|
||||
'输入参数: 1 个',
|
||||
@@ -360,6 +358,7 @@ describe('属性表', () => {
|
||||
])
|
||||
expect(routes[29]).toEqual([
|
||||
'名称: get_user',
|
||||
'说明: 获取用户详情。\n\n管理员可查看任意用户,普通用户只能查看自己。',
|
||||
'访问级别: 公共',
|
||||
'异步: 是',
|
||||
'装饰器: @router.get("/{user_id}", response_model=UserDetailResponse)',
|
||||
@@ -373,21 +372,22 @@ describe('属性表', () => {
|
||||
expect(routes[118]).toEqual(['名称: payload', '初始值: 等待 file.read()'])
|
||||
|
||||
const sample = rowTexts(await describeAll(py, 'python', pythonZhTranslator, readFixture('sample.py')))
|
||||
expect(sample[10]).toEqual(['名称: find_env_files', '访问级别: 公共', '异步: 否', '输入参数: 1 个', ' root | 字符串 | ', '输出参数: 列表[字符串]'])
|
||||
expect(sample[10]).toEqual(['名称: find_env_files', '说明: Return every .env file under root.', '访问级别: 公共', '异步: 否', '输入参数: 1 个', ' root | 字符串 | ', '输出参数: 列表[字符串]'])
|
||||
const port = rowTexts(await describeAll(py, 'python', pythonZhTranslator, '_default_port = 5680 if _role == "beat" else 5679'))
|
||||
expect(port[1]).toEqual(['名称: _default_port', '访问级别: 私有', '初始值: 如果 _role 等于 "beat" 则 5680 否则 5679'])
|
||||
})
|
||||
|
||||
it('属性名是纯文字;值按词性着色,说明词(未标注、N 个、默认)用 hint', async () => {
|
||||
const env = await describeAll(ts, 'typescript', typescriptZhTranslator, readFixture('envLock.ts.txt'))
|
||||
const rows = colors(env.get(23) ?? [])
|
||||
expect(rows.map(row => row.label)).toEqual(['名称', '访问级别', '异步', '输入参数', '输出参数'])
|
||||
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: '公共', role: 'keyword' }])
|
||||
expect(rows[2].value).toEqual([{ text: '是', role: 'storage' }])
|
||||
expect(rows[3].value).toEqual([{ text: '1 个', role: 'hint' }])
|
||||
expect(rows[3].items).toEqual([{ cells: [[{ text: 'workspaceRoot', role: 'variable' }], [{ text: '字符串', role: 'type' }], []] }])
|
||||
expect(rows[4].value).toEqual([
|
||||
expect(rows[1].value).toEqual([{ text: valueOf(env.get(18), '说明')[0].text, role: 'comment' }])
|
||||
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' }])
|
||||
expect(rows[4].items).toEqual([{ cells: [[{ text: 'workspaceRoot', role: 'variable' }], [{ text: '字符串', role: 'type' }], []] }])
|
||||
expect(rows[5].value).toEqual([
|
||||
{ text: 'Promise', role: 'type' },
|
||||
{ text: '<', role: 'plain' },
|
||||
{ text: '字符串', role: 'type' },
|
||||
@@ -410,6 +410,43 @@ describe('属性表', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('说明:紧跟名称,去掉注释符号、引号、共同缩进和首尾空行,保留换行和 @ 标签行;没有文字不显示', async () => {
|
||||
const tsSource = [
|
||||
'class K {',
|
||||
' /**',
|
||||
' * 读配置。',
|
||||
' *',
|
||||
' * 例子:',
|
||||
' * load(\'a\')',
|
||||
' * @param path 文件路径',
|
||||
' * @returns 配置',
|
||||
' */',
|
||||
' load(path: string) {}',
|
||||
' /** */',
|
||||
' empty() {}',
|
||||
'}',
|
||||
].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[11].slice(0, 2)).toEqual(['名称: empty', '访问级别: 公共'])
|
||||
|
||||
const pySource = [
|
||||
'def f():',
|
||||
' \'\'\'',
|
||||
' 第一段',
|
||||
' 缩进的一行',
|
||||
'',
|
||||
' 第二段',
|
||||
' \'\'\'',
|
||||
'def g():',
|
||||
' """"""',
|
||||
' return 1',
|
||||
].join('\n')
|
||||
const pyRows = rowTexts(await describeAll(py, 'python', pythonZhTranslator, pySource))
|
||||
expect(pyRows[1].slice(0, 3)).toEqual(['名称: f', '说明: 第一段\n 缩进的一行\n\n第二段', '访问级别: 公共'])
|
||||
expect(pyRows[8].slice(0, 2)).toEqual(['名称: g', '访问级别: 公共'])
|
||||
})
|
||||
|
||||
it('导入:默认导出、全部、整个模块、副作用导入;Python 没有「仅类型」', async () => {
|
||||
const tsSource = [
|
||||
'import d, { b as c } from \'y\'',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Access, FactBlock, FunctionFacts, ImportFacts, ImportItem, Node, PropertyRow, Segment, SourceSpan, TextBlock, TokenRole, Translator, VariableBlock } from '../contract'
|
||||
import { commonIndent, splitLines } from '../editText'
|
||||
|
||||
/** 按所在语法节点区分译法的关键字,例如 Python 的 else:if 后「否则」,for / while 后「未中断时」。 */
|
||||
export interface ContextualWord {
|
||||
@@ -100,6 +101,16 @@ function oneLine(text: string): string {
|
||||
return text.replace(/\s*\n\s*/g, ' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* 说明文字排版:去掉行尾空白、首尾空行和共同缩进,保留换行(段落之间的空行也留着)。
|
||||
* 第一行紧跟在引号 / 注释符号后面,本来就没有缩进,不参与算共同缩进(同 PEP 257 的做法)。
|
||||
*/
|
||||
function docText(raw: string): string {
|
||||
const [first = '', ...rest] = splitLines(raw).map(line => line.trimEnd())
|
||||
const indent = commonIndent(rest).length
|
||||
return [first.trim(), ...rest.map(line => line.slice(indent))].join('\n').replace(/^\n+|\n+$/g, '')
|
||||
}
|
||||
|
||||
/** 模板槽位引用的子节点;注释是语法之外的节点,不占具名子节点序号。 */
|
||||
function slotChild(node: Node, slot: Exclude<TemplatePart, string>): Node | null | undefined {
|
||||
if ('field' in slot)
|
||||
@@ -287,6 +298,10 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
// 点分模块名逐节照抄,每一节各自能跳到定义,和 VS Code 里一样
|
||||
module: node => stringNodes.has(node.type) ? [segment(node.text, 'string', offsetOf(node))] : verbatim(node, source, 'type'),
|
||||
name: (node, role) => [segment(node.text, role, offsetOf(node))],
|
||||
doc: (node) => {
|
||||
const text = docText(table.commentText(node))
|
||||
return text ? [{ text, role: 'comment' }] : []
|
||||
},
|
||||
}
|
||||
switch (block.kind) {
|
||||
case 'function':
|
||||
@@ -347,6 +362,8 @@ interface Phrases {
|
||||
module: (node: Node) => Segment[]
|
||||
/** 源码里的一个名字原样照抄、按 role 着色(函数名称)。 */
|
||||
name: (node: Node, role: TokenRole) => Segment[]
|
||||
/** 函数的说明(JSDoc、docstring):去掉注释符号、引号和共同缩进,保留换行,按注释着色;没有文字时是空的。 */
|
||||
doc: (node: Node) => Segment[]
|
||||
}
|
||||
|
||||
const ACCESS_ZH: Record<Access, string> = { public: '公共', private: '私有', protected: '受保护' }
|
||||
@@ -380,8 +397,10 @@ function aliased(item: Segment[], alias: Node | undefined, say: Phrases): Segmen
|
||||
/** 函数:名称 / 访问级别 / 异步 / 装饰器 / 输入参数(每个参数一行:名称、类型、默认值)/ 输出参数。 */
|
||||
function functionRows(facts: FunctionFacts, say: Phrases): PropertyRow[] {
|
||||
const decorators = facts.decorators.map(say.verbatim)
|
||||
const doc = facts.doc ? say.doc(facts.doc) : []
|
||||
return [
|
||||
{ label: '名称', value: facts.name ? say.name(facts.name, 'function') : [hint('匿名')] },
|
||||
...(doc.length > 0 ? [{ label: '说明', value: doc }] : []),
|
||||
...accessRows(facts.access),
|
||||
{ label: '异步', value: yesNo(facts.async) },
|
||||
...(decorators.length > 0 ? [{ label: '装饰器', value: joined(decorators, '\n') }] : []),
|
||||
|
||||
Reference in new issue
Block a user