✨ feat(vscode): 代码阅读的链式调用卡片:语言插件读链、翻译模板排成起点和步骤

契约加 LanguageAdapter.chain、Translator.chain、RuleContext.chain、PropertyRow.chain、TextTranslation.rows;跨行写或实参里有闭包的链排成卡片,连着同名调用合成一步列出每次实参,注释挂在步骤上,闭包收起、子块挂到那一步(BlockView.step);Rust 插件读链、标准库常见方法写中文名;挤成一行时另起一行的 . 贴着前面
This commit is contained in:
cruldra committed 2026-10-15 20:54:59 +11:00
1 parent 80eda7c18e
commit ee595f3da9
16 files changed
+1041 -72

No files matched your search

+46 -2
View File
@@ -52,6 +52,49 @@ const template = createZhTemplateTranslator({
operatorParents: [],
emptyLiterals: {},
collections: { array_expression: '数组', tuple_expression: '元组' },
// 迭代器、Option、Result 上常用的方法;unwrap、expect 这类照写方法名
methods: {
iter: '遍历',
iter_mut: '可改地遍历',
into_iter: '逐个取出',
filter: '筛选',
map: '映射',
filter_map: '筛选并映射',
flat_map: '映射并展开',
flatten: '展开',
for_each: '逐个执行',
collect: '收集',
enumerate: '编号',
zip: '配对',
chain: '接上',
rev: '倒序',
take: '取前几个',
skip: '跳过前几个',
take_while: '一直取到',
skip_while: '一直跳过',
fold: '累积',
count: '计数',
sum: '求和',
min: '最小',
max: '最大',
any: '有一个满足',
all: '全都满足',
find: '查找',
position: '找位置',
last: '最后一个',
nth: '取第几个',
peekable: '可预看',
cloned: '复制每项',
copied: '复制每项',
inspect: '顺便查看',
map_err: '转换错误',
and_then: '接着',
ok_or: '没有时报错',
ok_or_else: '没有时报错',
unwrap_or: '取值或用默认',
unwrap_or_else: '取值或另算',
unwrap_or_default: '取值或用缺省',
},
stringNodes: ['string_literal'],
commentText,
docNodes,
@@ -85,12 +128,13 @@ export const rustZhTranslator: Translator = {
/** 写了 `return` 的返回块:`return x;` 整句,或 match 一支的值 `=> return x`。 */
const RETURN_WRITTEN = new Set(['expression_statement', 'return_expression'])
/** 一层最后那个不带分号的式子是返回值,源码里没有 return 这个词,译文照写了的那样在前面补「返回」。 */
/** 一层最后那个不带分号的式子是返回值,源码里没有 return 这个词,译文照写了的那样在前面补「返回」(整句是链时译文就只有它)。 */
function translate(block: TextBlock, document: SourceDocument): TextTranslation {
const translation = template.translate(block, document)
if (block.kind !== 'return' || RETURN_WRITTEN.has(block.node.type) || !Array.isArray(translation.text))
return translation
return { ...translation, text: [{ text: '返回', role: 'keyword' }, { text: ' ', role: 'plain' }, ...translation.text] }
const rest: Inline[] = translation.text.length > 0 ? [{ text: ' ', role: 'plain' }, ...translation.text] : []
return { ...translation, text: [{ text: '返回', role: 'keyword' }, ...rest] }
}
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
@@ -1442,7 +1442,7 @@ describe('跨几行写的调用', () => {
expect(propertyLines(rows.get(1) ?? []).slice(-4)).toEqual(['值: 新建 Server · 2 个参数', ' 第 1 个 | 新建 Options · 1 个参数', ' 第 1 个 | port', ' 第 2 个 | handler'])
})
it('链式调用跨了几行、实参列表各自写在一行里的不排成表;回调主体不在块头部里的调用也不排', async () => {
it('链式调用跨了几行、实参列表各自写在一行里的不排成表,挤成一行时另起一行的 . 贴着前面;回调主体不在块头部里的调用也不排', async () => {
const source = [
'const ids = items',
' .filter(a => a.ok)',
@@ -1452,7 +1452,7 @@ describe('跨几行写的调用', () => {
'})',
].join('\n')
const rows = await describeAll(ts, 'typescript', typescriptZhTranslator, source)
expect(propertyLines(rows.get(1) ?? []).at(-1)).toBe('值: items .filter(a => a.ok) .map(a => a.id)')
expect(propertyLines(rows.get(1) ?? []).at(-1)).toBe('值: items.filter(a => a.ok).map(a => a.id)')
const texts = await translateAll(ts, 'typescript', typescriptZhTranslator, source)
expect(texts.get(4)).toEqual(expect.not.objectContaining({ table: expect.anything() }))
})
+267 -24
View File
@@ -1,4 +1,4 @@
import type { Access, AttributeNames, BaseFacts, BlockKind, CallArgument, CallFacts, ClassFacts, CollectionFacts, CommandContext, CommandFacts, CommandForm, CommandReading, CommandRule, CommandWord, CommonAttribute, DeclaratorFacts, Defines, DirectiveFacts, Doc, ElementFacts, EmbeddedReading, EntryFacts, EnumFacts, ExceptionFacts, FactBlock, FunctionFacts, HealthFacts, ImportFacts, ImportItem, Inline, InstructionFlag, InterpreterFacts, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Markdown, Node, OptionsFacts, ParameterFacts, PositionalParameter, PreviewTable, PropertyItem, PropertyRow, Redirect, Segment, SegmentGroup, SourceDocument, SourceRange, SourceSpan, StageFacts, StartFacts, TextBlock, TextTranslation, TokenRole, Translator, VariableBlock } from '../contract'
import type { Access, AttributeNames, BaseFacts, BlockKind, CallArgument, CallFacts, ChainArgument, ChainFacts, ChainGroup, ChainStep, ChainStepFacts, ChainView, ClassFacts, CollectionFacts, CommandContext, CommandFacts, CommandForm, CommandReading, CommandRule, CommandWord, CommonAttribute, DeclaratorFacts, DeferredFacts, Defines, DirectiveFacts, Doc, ElementFacts, EmbeddedReading, EntryFacts, EnumFacts, ExceptionFacts, FactBlock, FunctionFacts, HealthFacts, ImportFacts, ImportItem, Inline, InstructionFlag, InterpreterFacts, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Markdown, Node, OptionsFacts, ParameterFacts, PositionalParameter, PreviewTable, PropertyItem, PropertyRow, Redirect, Segment, SegmentGroup, SourceDocument, SourceRange, SourceSpan, StageFacts, StartFacts, TextBlock, TextTranslation, TokenRole, Translator, VariableBlock } from '../contract'
import type { ParamDocs } from '../rules/docRows'
import { readDoc } from '../docTags'
import { commonIndent, splitLines } from '../editText'
@@ -45,6 +45,8 @@ export interface ZhTable {
emptyLiterals: Record<string, string>
/** 写出各项的字面量(语言插件给了各项,见 LanguageAdapter.collection)的叫法,按节点类型查:属性表里拆开的值写成「字典 3 项」「列表 2 项」。 */
collections: Record<string, string>
/** 链式调用卡片里常见方法的中文名,按方法名查(`iter` → 遍历):方法那一列写它,悬停看原名;没有的写 `.方法名`。 */
methods?: Record<string, string>
/** 整段原样照抄、按字符串着色的节点。 */
stringNodes: string[]
/** 注释(或 docstring)节点去掉注释符号后的原文,可以多行;text 是节点的源码去掉代码阅读的记号(SourceDocument.markers)以后剩下的。 */
@@ -322,14 +324,10 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
return parts.every((part): part is string | Node => part != null) ? parts : undefined
}
/**
* 源码里上一个词到 index 之间有空白,下一个词前面就空一格;跨行写的一对圆括号挤成一行时贴着里面的内容
* (`f(\n x\n)` 写成 `f(x)`),方括号、花括号照旧空一格。
*/
/** 源码里上一个词到 index 之间有空白,下一个词前面就空一格;跨行写的挤成一行时换行处该贴着写的贴着写(见 [hugs])。 */
function advance(out: Collector, index: number): void {
const between = out.document.source.slice(out.cursor, index)
const hugging = between.includes('\n') && (out.tokens.at(-1)?.raw === '(' || out.document.source[index] === ')')
out.gap ||= !hugging && /\s/.test(between)
out.gap ||= !hugs(between, out.tokens.at(-1)?.raw, out.document.source.slice(index, index + 1)) && /\s/.test(between)
out.cursor = index
}
@@ -719,28 +717,43 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
/**
* `node` 的源码原样照抄:叶子按源码着色(给了 `role` 就都按它),名字带源码下标,叶子之间的空白、标点照抄;
* `flat` 为真时压成一行,连着的空白(含换行)收成一个空格。
* `flat` 为真时压成一行:连着的空白(含换行)收成一个空格,换行处该贴着写的贴着写(见 [hugs]),注释不抄,右括号前的尾逗号去掉。
*/
function verbatim(node: Node, { source, language }: SourceDocument, role?: TokenRole, flat = false): Segment[] {
const copy = (text: string): string => flat ? text.replace(/\s+/g, ' ') : text
const segments: Segment[] = []
let cursor = node.startIndex
const visit = (current: Node): void => {
const leaves: Node[] = []
const gather = (current: Node): void => {
if (current.startIndex === current.endIndex)
return
if (current.childCount > 0) {
for (const child of current.children) {
if (child)
visit(child)
}
if (current.childCount === 0) {
leaves.push(current)
return
}
pushSegment(segments, copy(source.slice(cursor, current.startIndex)), 'plain')
pushSegment(segments, copy(current.text), role ?? language.tokenRole(current), offsetOf(current, language))
cursor = current.endIndex
for (const child of current.children) {
if (child)
gather(child)
}
}
visit(node)
pushSegment(segments, copy(source.slice(cursor, node.endIndex)), 'plain')
gather(node)
const segments: Segment[] = []
let cursor = node.startIndex
// 压成一行时不抄的注释、尾逗号连同它前面的空白,记到下一个词前面
let pending = ''
let previous: string | undefined
leaves.forEach((leaf, i) => {
const between = pending + source.slice(cursor, leaf.startIndex)
cursor = leaf.endIndex
const own = language.tokenRole(leaf)
if (flat && (own === 'comment' || (leaf.type === ',' && CLOSING.has(leaves[i + 1]?.type ?? '')))) {
pending = own === 'comment' ? `${between} ` : between
return
}
pending = ''
pushSegment(segments, !flat ? between : hugs(between, previous, leaf.text) ? '' : between.replace(/\s+/g, ' '), 'plain')
pushSegment(segments, flat ? leaf.text.replace(/\s+/g, ' ') : leaf.text, role ?? own, offsetOf(leaf, language))
previous = leaf.text
})
const tail = source.slice(cursor, node.endIndex)
pushSegment(segments, flat ? tail.replace(/\s+/g, ' ') : tail, 'plain')
return segments
}
@@ -757,12 +770,206 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
return { markdown: docText(nodes.map(node => commentOf(node, document))) }
}
// ─── 链式调用 ─────────────────────────────────────────────────────────────
/** 一条链值不值得排成卡片:跨行写(某一步的 `.` 另起一行)或实参里有闭包时是它的结构;不值得、语言插件读不出链时没有。 */
function cardChain(node: Node, document: SourceDocument): ChainFacts | undefined {
const facts = document.language.chain?.(node)
if (!facts)
return undefined
const broken = facts.steps.some((step, i) => step.dot.startPosition.row > (i === 0 ? facts.start : stepEnd(facts.steps[i - 1])).endPosition.row)
return broken || facts.steps.some(step => step.arguments.some(arg => arg.closure)) ? facts : undefined
}
/**
* 链排成卡片的样子:起点照抄一行,各步照源码顺序。规则给的读法(groups)先合;别的连着调用同一个方法的合成一步(见 [repeatedStep]),
* 剩下的一步一项(见 [singleStep])。
*/
function chainView(facts: ChainFacts, document: SourceDocument, groups: readonly ChainGroup[] = []): ChainView {
const { steps } = facts
const out: ChainStep[] = []
for (let i = 0; i < steps.length;) {
const from = i
const group = groups.find(item => item.from === from && item.to >= from && item.to < steps.length)
if (group) {
out.push(groupStep(steps.slice(from, group.to + 1), group, document))
i = group.to + 1
continue
}
let end = from + 1
while (end < steps.length && steps[end].method.text === steps[from].method.text && !groups.some(item => item.from === end))
end++
out.push(end - from > 1 ? repeatedStep(steps.slice(from, end), document) : singleStep(steps[from], document))
i = end
}
return { start: verbatim(facts.start, document, undefined, true), steps: out }
}
/** 一步一项:实参本身是值得排成卡片的链时排成小链,写在这一步上面的注释是「注释」一行。 */
function singleStep(step: ChainStepFacts, document: SourceDocument): ChainStep {
const lines = miniLines(step, document)
const rows = noteRows([step], document)
return {
name: stepName([step], document),
value: stepValue(step, document, lines !== undefined),
...(lines && { lines }),
...(rows && { rows }),
range: stepRange([step]),
}
}
/** 连着几次调用同一个方法合成一步:值是「N 次」,下面一张表一行一次,列是各个实参(每次最多一个时只有一列「实参」)。 */
function repeatedStep(steps: ChainStepFacts[], document: SourceDocument): ChainStep {
const most = Math.max(...steps.map(step => step.arguments.length))
const columns = most <= 1 ? ['实参'] : Array.from({ length: most }, (_, i) => `第 ${i + 1} 个`)
const rows = noteRows(steps, document)
return {
name: stepName(steps, document),
value: [hint(`${steps.length} 次`)],
...(most > 0 && { table: { columns, items: steps.map(step => ({ cells: step.arguments.map(arg => argumentText(arg, document)) })) } }),
...(rows && { rows }),
range: stepRange(steps),
}
}
/** 规则合成的一步(见 [ChainGroup]):叫法是规则的;规则没换值、没给表时一步的照常,几步的每步一行排成小链。 */
function groupStep(steps: ChainStepFacts[], group: ChainGroup, document: SourceDocument): ChainStep {
const [first] = steps
const single = steps.length === 1 ? first : undefined
const replaced = group.value !== undefined || group.table !== undefined
const mini = single && !replaced ? miniLines(single, document) : undefined
const lines = replaced ? undefined : single ? mini : steps.map(step => callLine(step, document))
const rows = noteRows(steps, document)
return {
name: stepName(steps, document, group.label),
value: group.value ?? (single ? stepValue(single, document, mini !== undefined) : []),
...(lines && { lines }),
...(rows && { rows }),
...(group.table && { table: group.table }),
range: stepRange(steps),
}
}
/**
* 方法那一列:起了叫法(规则给的 label,或词表里这个方法的中文名,见 [ZhTable] 的 methods)写叫法,几步都是同一个方法时悬停看原名、
* ⌘ / Ctrl + 点击跳到第一步的方法名;几步不同的方法合成的没有原名可讲。没有叫法时写 `.名字`。
*/
function stepName(steps: ChainStepFacts[], document: SourceDocument, label?: string): Inline[] {
const [first] = steps
const same = steps.every(step => step.method.text === first.method.text)
const word = label ?? (same ? table.methods?.[first.method.text] : undefined)
if (word === undefined)
return methodText(first, document)
if (!same)
return [{ text: word, role: 'function' }]
return [{ text: word, role: 'function', refers: { startIndex: first.method.startIndex, endIndex: first.method.endIndex }, tooltip: `.${first.method.text}` }]
}
/** `.名字`,名字按函数着色、带源码下标;写了泛型实参的跟着 `::<…>`。 */
function methodText(step: ChainStepFacts, document: SourceDocument): Inline[] {
return [
{ text: '.', role: 'plain' },
segment(step.method.text, 'function', step.method.startIndex),
...(step.typeArguments ? [{ text: '::', role: 'plain' as const }, ...verbatim(step.typeArguments, document, undefined, true)] : []),
]
}
/** 值那一列:实参(见 [argumentText])用逗号隔开,`.await`、`?` 跟在后面(见 [afterText]);实参排成了小链时只剩后面这截。 */
function stepValue(step: ChainStepFacts, document: SourceDocument, mini: boolean): Inline[] {
const args = mini ? [] : joined(step.arguments.map(arg => argumentText(arg, document)), ', ')
const after = afterText(step, document)
return args.length > 0 && after.length > 0 ? [...args, { text: ' ', role: 'plain' }, ...after] : [...args, ...after]
}
/** 一个实参:收起来的闭包、async 块是一个 fold 框(见 [foldGroup]),别的照抄成一行。 */
function argumentText(arg: ChainArgument, document: SourceDocument): Inline[] {
return arg.deferred ? [foldGroup(arg.deferred, document)] : verbatim(arg.node, document, undefined, true)
}
/** 收起来的闭包、async 块:「闭包 |参数| · N 句」,主体只有一个 match 时是「闭包 |参数| · match 值 · N 支」;参数、match 照抄。 */
function foldGroup(deferred: DeferredFacts, document: SourceDocument): SegmentGroup {
const say = (node: Node): Inline[] => verbatim(node, document, undefined, true)
const parts: Inline[][] = [
...(deferred.parameters ? [say(deferred.parameters)] : []),
...(deferred.match
? [[...say(firstLeaf(deferred.match.node)), { text: ' ', role: 'plain' as const }, ...say(deferred.match.value)], [hint(`${deferred.match.arms} 支`)]]
: [[hint(`${deferred.statements} 句`)]]),
]
return { style: 'fold', label: deferred.kind === 'closure' ? '闭包' : '异步块', segments: parts.flatMap((part, i) => i > 0 ? [hint(' · '), ...part] : part) }
}
/** 跟在调用后面的 `.await`、`?` 照常翻(「等待」),连接的 `.` 不写。 */
function afterText(step: ChainStepFacts, document: SourceDocument): Inline[] {
const out: Inline[] = []
let from = step.call.endIndex
for (const layer of step.after) {
const mark = layer.children.find(child => child !== null && child.startIndex >= from && child.type !== '.')
if (mark)
appendInline(out, joinTokens(tokensIn(layer, { ...nodeSpan(layer), startIndex: mark.startIndex }, document)))
from = layer.endIndex
}
return out
}
/** 只有一个实参、它本身是值得排成卡片的链时排成小链:第一行是它的起点,后面一步一行(见 [callLine]);别的没有。 */
function miniLines(step: ChainStepFacts, document: SourceDocument): Inline[][] | undefined {
const [only] = step.arguments
const inner = step.arguments.length === 1 && only && !only.deferred ? cardChain(only.node, document) : undefined
return inner && [verbatim(inner.start, document, undefined, true), ...inner.steps.map(item => callLine(item, document))]
}
/** 小链的一节:`.名字(实参)`,`.await`、`?` 跟在后面。 */
function callLine(step: ChainStepFacts, document: SourceDocument): Inline[] {
const after = afterText(step, document)
return [
...methodText(step, document),
{ text: '(', role: 'plain' },
...joined(step.arguments.map(arg => argumentText(arg, document)), ', '),
{ text: ')', role: 'plain' },
...(after.length > 0 ? [{ text: ' ', role: 'plain' as const }, ...after] : []),
]
}
/** 写在这几步上面的注释读成「注释」一行(Markdown 原文),每一步的一段,空行隔开;都没有时没有。 */
function noteRows(steps: ChainStepFacts[], document: SourceDocument): PropertyRow[] | undefined {
const parts = steps.filter(step => step.comments.length > 0).map(step => docText(step.comments.map(comment => commentOf(comment, document))))
return parts.length > 0 ? [{ label: KIND_ZH.comment, value: { markdown: parts.join('\n\n') } }] : undefined
}
/** 块整句就是一条值得排成卡片的链(外面只套着 return 这类关键字和分号):从块的节点往下,沿着只有一个具名子节点的那几层找。 */
function wholeChain(root: Node, span: SourceSpan, document: SourceDocument): { node: Node, facts: ChainFacts } | undefined {
for (let node: Node | undefined = root; node && inside(node, span);) {
const facts = cardChain(node, document)
if (facts)
return { node, facts }
const kids: Node[] = node.namedChildren.filter((child): child is Node => child !== null && !child.isExtra)
node = kids.length === 1 ? kids[0] : undefined
}
return undefined
}
/** 整句是链的块:译文只剩链外面的词(`return` 的「返回」,分号不写),下面是「起点」「步骤」两行,链的各步在「步骤」那一行上。 */
function chainTranslation(block: TextBlock, chain: Node, facts: ChainFacts, document: SourceDocument): TextTranslation {
const { header } = block
const tokens = [
...tokensIn(block.node, { ...header, endIndex: chain.startIndex }, document),
...tokensIn(block.node, { ...header, startIndex: chain.endIndex }, document),
]
while (tokens.length > 0 && SEPARATORS.has(tokens[tokens.length - 1].raw))
tokens.pop()
const view = chainView(facts, document)
return { text: joinTokens(tokens), rows: [{ label: '起点', value: view.start }, { label: '步骤', value: count(view.steps.length), chain: view.steps }] }
}
function translate(block: TextBlock, document: SourceDocument): TextTranslation {
if (block.kind === 'comment')
return { text: translateComment(block, document) }
const group = block.group && table.groups?.[block.node.type]
if (block.group && group)
return group(block.group, readerContext(collector(block.node, block.header, document), block.header))
const chained = wholeChain(block.node, block.header, document)
if (chained)
return chainTranslation(block, chained.node, chained.facts, document)
const slot: Slot = {}
const tokens = tokensIn(block.node, block.header, document, slot)
const compound = block.children.length > 0 || block.header.endIndex < block.node.endIndex
@@ -788,6 +995,10 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
const phrases: Phrases = {
translated: node => joinTokens(tokensIn(node, clipped(nodeSpan(node)), document)),
value: node => cellValue(node, clipped(nodeSpan(node)), document),
chain: (node) => {
const facts = inside(node, header) ? cardChain(node, document) : undefined
return facts && chainView(facts, document)
},
body: node => expression(node, document),
returned: (node) => {
const [value] = language.command?.(node)?.arguments ?? []
@@ -849,10 +1060,36 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
expression,
verbatim: (node, document, role) => verbatim(node, document, role, true),
doc,
chain: (node, document, groups) => {
const facts = document.language.chain?.(node)
return facts && chainView(facts, document, groups)
},
attributeNames: ZH_ATTRIBUTE_NAMES,
}
}
/**
* 跨行写的挤成一行时,两个词中间的换行处贴着写:圆括号贴着里面的内容(`f(\n x\n)` 写成 `f(x)`),另起一行的 `.` 贴着前面
* (链式调用 `a\n .b()` 写成 `a.b()`);方括号、花括号照旧空一格。
* @param between 两个词中间的源码
* @param previous 前一个词
* @param next 后一个词(或从它开头的源码)
*/
/** 链式调用一步的结尾:调用,套着 `.await`、`?` 时是最外面那层。 */
function stepEnd(step: ChainStepFacts): Node {
return step.after[step.after.length - 1] ?? step.call
}
/** 几步合起来在源码里的范围:第一步的 `.` 到最后一步的结尾(见 ChainStep.range)。 */
function stepRange(steps: readonly ChainStepFacts[]): SourceRange {
const last = steps[steps.length - 1]
return { startIndex: steps[0].dot.startIndex, endIndex: stepEnd(last).endIndex }
}
function hugs(between: string, previous: string | undefined, next: string): boolean {
return between.includes('\n') && (previous === '(' || next.startsWith(')') || next.startsWith('.'))
}
function nodeSpan(node: Node): SourceSpan {
return { startIndex: node.startIndex, endIndex: node.endIndex, startRow: node.startPosition.row, endRow: node.endPosition.row }
}
@@ -928,6 +1165,8 @@ interface Phrases {
translated: (node: Node) => Inline[]
/** 一格里的值,只翻落在块头部里的那一段:字面量的各项拆成下一层子行,跨几行写的调用排成「新建 / 调用 …」带实参表(见 cellValue)。 */
value: (node: Node) => CellValue
/** 值是一条值得排成卡片的链式调用、整个落在块头部里时它排成的样子(见 ChainView);别的值没有。 */
chain: (node: Node) => ChainView | undefined
/** 函数主体里的一句(不在块头部里)整句照常翻:函数打印的那几句。 */
body: (node: Node) => Inline[]
/** 一句 return 返回的值(语言插件读出的命令的第一个词);只写了 return 时是空的。 */
@@ -1389,9 +1628,13 @@ function variableRows(block: VariableBlock, say: Phrases): PropertyRow[] {
}
/**
* 声明的值(见 Phrases.value):拆开的字面量的各项、跨几行写的调用的实参表(参数 | 值,带列名)都展开在这一行下面。
* 声明的值(见 Phrases.value):拆开的字面量的各项、跨几行写的调用的实参表(参数 | 值,带列名)都展开在这一行下面;
* 值是一条值得排成卡片的链式调用时,这一行写起点,链的各步接在下面(见 Phrases.chain)。
*/
function declaredValue(value: Node, say: Phrases): Pick<PropertyRow, 'value' | 'items' | 'columns'> {
function declaredValue(value: Node, say: Phrases): Pick<PropertyRow, 'value' | 'items' | 'columns' | 'chain'> {
const chain = say.chain(value)
if (chain)
return { value: chain.start, chain: chain.steps }
const { text, items, table } = say.value(value)
if (table)
return { value: text, ...table }