✨ feat(vscode): 代码阅读预览里跨几行写的列表按项拆开,列表里整项是调用的带着自己的实参表,一层层能往下套

This commit is contained in:
cruldra committed 2026-10-13 13:32:45 +11:00
1 parent 7d6e7592c9
commit 2c490cac2d
22 files changed
+848 -314

No files matched your search

@@ -1336,7 +1336,8 @@ describe('跨几行写的调用', () => {
expect(value(1)).toBe('初始值: (调用 make · 1 个参数)[0]')
expect(value(4)).toBe('初始值: (调用 make · 1 个参数) 或者 fallback')
expect(value(7)).toBe('初始值: fallback 或者 调用 make · 1 个参数')
expect(value(10)).toBe('初始值: [ 调用 make · 1 个参数, b ]')
// 跨几行写的列表按项拆开(见「跨几行写的列表」),整项是调用的不括
expect(propertyLines(values.get(10) ?? []).slice(2)).toEqual(['初始值: 列表 2 项', ' 第 1 项 | 调用 make · 1 个参数', ' 第 1 个 | a', ' 第 2 项 | b'])
const cell = (await translateAll(py, 'python', pythonZhTranslator, source)).get(16)
expect(tableLines(cell?.table)).toEqual(['key | (调用 Model · 1 个参数).model_dump()', ' a | 1'])
})
@@ -1398,6 +1399,183 @@ describe('跨几行写的调用', () => {
})
})
describe('跨几行写的列表', () => {
/** Filter 的定义是类,别的名字还没查到。 */
const calleeKind = (call: CallFacts): BlockKind | undefined => call.callee.text === 'Filter' ? 'class' : undefined
const FILTERS = [
'async def list_sessions(archived: bool, current_user: User):',
' filters = [',
' Filter(field="user_id", operator=FilterOperator.EQ, value=current_user.id),',
' Filter(',
' field="archived_at",',
' operator=FilterOperator.NE if archived else FilterOperator.EQ,',
' value=None,',
' ),',
' ]',
].join('\n')
it('初始值写「列表 N 项」,每项一个子行「第 N 项 | 值」;整项是调用的写成一行还是几行都排成「新建 / 调用 …」,实参表跟着这一项', async () => {
const rows = (await describeAll(py, 'python', pythonZhTranslator, FILTERS, calleeKind)).get(2) ?? []
expect(propertyLines(rows)).toEqual([
'名称: filters',
'初始值: 列表 2 项',
' 第 1 项 | 新建 Filter · 3 个参数',
' field | "user_id"',
' operator | FilterOperator.EQ',
' value | current_user.id',
' 第 2 项 | 新建 Filter · 3 个参数',
' field | "archived_at"',
' operator | 如果 archived 则 FilterOperator.NE 否则 FilterOperator.EQ',
' value | 空',
])
const value = rows.find(row => row.label === '初始值')
expect(value?.columns).toBeUndefined()
expect(colors(value?.value)).toEqual([{ text: '列表 2 项', role: 'hint' }])
const [first, second] = value?.items ?? []
expect(first?.cells[0]).toEqual([{ text: '第 1 项', role: 'hint' }])
expect(first).not.toHaveProperty('items')
expect(first?.table?.columns).toEqual(['参数', '值'])
expect(second?.table?.columns).toEqual(['参数', '值'])
// 拆开以后名字照样带源码下标(跳到定义、悬停、中文名、出现处高亮都靠它):被调用的名字、按名字传的名字、值里的名字
const multiline = FILTERS.indexOf('Filter(\n')
expect(inlineOf(second?.cells[1] ?? []).find(piece => 'role' in piece && piece.text === 'Filter')).toEqual({ text: 'Filter', role: 'type', offset: multiline })
expect(second?.table?.items[0].cells[0]).toEqual([{ text: 'field', role: 'variable', offset: FILTERS.indexOf('field="archived_at"') }])
const user = FILTERS.indexOf('current_user.id')
expect(linked(first?.table?.items[2].cells[1])).toEqual([['current_user', user], ['id', user + 13]])
})
it('写在一行里的列表照旧一行译文,里面的调用也照旧;空列表写「空列表」', async () => {
const source = 'ids = [a, Filter(field="x")]\nempty = []'
const rows = await describeAll(py, 'python', pythonZhTranslator, source, calleeKind)
expect(propertyLines(rows.get(1) ?? [])).toEqual(['名称: ids', '访问级别: 公共', '初始值: [a, Filter(field="x")]'])
expect(propertyLines(rows.get(2) ?? [])).toEqual(['名称: empty', '访问级别: 公共', '初始值: 空列表'])
})
it('元组、集合各用自己的叫法,*x 写「展开」、「第 N 项」只数不是展开的;项是字典照字典拆,是跨几行的列表接着拆,一层层往下套', async () => {
const source = [
'PAIR = (',
' first,',
' *rest,',
' second,',
')',
'TAGS = {',
' "a",',
' "b",',
'}',
'CONFIG = [',
' {"name": "a", "tags": [',
' "x",',
' "y",',
' ]},',
' [1, 2],',
' [',
' 3,',
' 4,',
' ],',
']',
].join('\n')
const rows = await describeAll(py, 'python', pythonZhTranslator, source)
expect(propertyLines(rows.get(1) ?? []).slice(2)).toEqual(['值: 元组 3 项', ' 第 1 项 | first', ' 展开 | rest', ' 第 2 项 | second'])
expect(propertyLines(rows.get(6) ?? []).slice(2)).toEqual(['值: 集合 2 项', ' 第 1 项 | "a"', ' 第 2 项 | "b"'])
expect(propertyLines(rows.get(10) ?? []).slice(2)).toEqual([
'值: 列表 3 项',
' 第 1 项 | 字典 2 项',
' "name" | "a"',
' "tags" | 列表 2 项',
' 第 1 项 | "x"',
' 第 2 项 | "y"',
' 第 2 项 | [1, 2]',
' 第 3 项 | 列表 2 项',
' 第 1 项 | 3',
' 第 2 项 | 4',
])
// 字典的项照旧标出它讲的定义(键),列表的项不讲哪个定义
const config = rows.get(10)?.find(row => row.label === '值')
expect(config?.items?.[0]).not.toHaveProperty('defines')
const tags = source.indexOf('"tags"')
expect(config?.items?.[0].items?.[1].defines).toEqual({ startIndex: tags, endIndex: tags + 6 })
})
it('字典一项的值是跨几行写的列表时也按项拆', async () => {
const source = 'ROUTES = {"admin": [\n "users",\n "roles",\n]}'
expect(propertyLines((await describeAll(py, 'python', pythonZhTranslator, source)).get(1) ?? []).slice(2)).toEqual([
'值: 字典 1 项',
' "admin" | 列表 2 项',
' 第 1 项 | "users"',
' 第 2 项 | "roles"',
])
})
it('实参的值是跨几行写的列表时也按项拆,嵌在那一格下面;写在一行里的列表、实参里的字典照旧一行', async () => {
const source = [
'session.query(',
' filters=[',
' Filter(field="a"),',
' other,',
' ],',
' ids=[1, 2],',
' meta={"a": 1},',
')',
].join('\n')
const line = (await translateAll(py, 'python', pythonZhTranslator, source, calleeKind)).get(1)
expect(text(line?.segments)).toBe('调用 session.query · 3 个参数')
expect(tableLines(line?.table)).toEqual([
'filters | 列表 2 项',
' 第 1 项 | 新建 Filter · 1 个参数',
' field | "a"',
' 第 2 项 | other',
'ids | [1, 2]',
'meta | {"a": 1}',
])
expect(line?.table?.items[0].items?.[0].table?.columns).toEqual(['参数', '值'])
})
it('typeScript:数组写「数组 N 项」,new 是新建、别的调用是调用,整项是调用的都排开;...x 写「展开」,对象照对象拆;实参的值是跨几行的数组也拆', async () => {
const source = [
'const filters = [',
' new Filter({ field: \'user_id\' }),',
' makeFilter(',
' \'archived_at\',',
' archived,',
' ),',
' ...rest,',
' [1, 2],',
' {',
' a: 1,',
' },',
']',
'const ids = [a, f(b)]',
'register(',
' \'x\',',
' [',
' a,',
' b,',
' ],',
')',
].join('\n')
const rows = await describeAll(ts, 'typescript', typescriptZhTranslator, source)
expect(propertyLines(rows.get(1) ?? [])).toEqual([
'名称: filters',
'访问级别: 私有',
'值: 数组 5 项',
' 第 1 项 | 新建 Filter · 1 个参数',
' 第 1 个 | { field: \'user_id\' }',
' 第 2 项 | 调用 makeFilter · 2 个参数',
' 第 1 个 | \'archived_at\'',
' 第 2 个 | archived',
' 展开 | rest',
' 第 3 项 | [1, 2]',
' 第 4 项 | 对象 1 项',
' a | 1',
])
expect(propertyLines(rows.get(13) ?? []).at(-1)).toBe('值: [a, f(b)]')
const line = (await translateAll(ts, 'typescript', typescriptZhTranslator, source)).get(14)
expect(text(line?.segments)).toBe('调用 register · 2 个参数')
expect(tableLines(line?.table)).toEqual(['第 1 个 | \'x\'', '第 2 个 | 数组 2 项', ' 第 1 项 | a', ' 第 2 项 | b'])
})
})
describe('中文模板引擎', () => {
function testTranslator(table: Partial<ZhTable>): Translator {
return createZhTemplateTranslator({
@@ -1411,7 +1589,7 @@ describe('中文模板引擎', () => {
operators: {},
operatorParents: [],
emptyLiterals: {},
mapLiterals: {},
collections: {},
stringNodes: [],
commentText: node => node.text,
nodeTemplates: {},