✨ feat(vscode): 代码阅读预览 Rust 函数体按语句拆成子块

let / let-else、if / else、match 各支、循环、隐式返回、闭包、unsafe / async 块照 Python / TypeScript 的约定拆块;特质同实现,方法卡挂在方法表那一行;函数卡去掉分情况表;Rust 语言插件给出调用结构
This commit is contained in:
cruldra committed 2026-10-15 17:07:07 +11:00
1 parent d5c627c54a
commit 86422e4671
7 files changed
+682 -135

No files matched your search

+353 -7
View File
@@ -20,6 +20,10 @@ function views(source: string, fileName?: string): Promise<BlockView[]> {
}))
}
function tree(source: string): Promise<Outline[]> {
return withBlocks(adapter, 'rust', source, outline)
}
function card(list: BlockView[], kind: string): BlockView {
const found = list.find(block => block.kind === kind)
if (!found)
@@ -85,15 +89,31 @@ describe('rust 语言卡片', () => {
' 输入参数: 1 个',
' addr | SocketAddr',
' 返回: Result<(), StartError>',
' 分情况: 2 支',
' Ok(listener) | 记下监听地址',
' Err(err) | 返回 StartError',
'trait 41',
' if 29: 匹配 result',
' if 30: Ok(listener)',
' statement 31: self.listener = Some(listener);',
' 注释: 记下监听地址',
' if 34: Err(err)',
' return 35: 返回 Err(StartError);',
' 注释: 返回 StartError',
'rust.trait 41',
' 名称: Store',
' 说明: 按 id 读写代理。',
' 方法: 2 个',
' get | &self, id: Uuid | Result<AgentRow, StoreError>',
' save | &mut self, row: AgentRow | Result<(), StoreError>',
' function 43',
' 名称: get',
' 参数: 2 个',
' self | &self',
' id | Uuid',
' 返回: Result<AgentRow, StoreError>',
' function 44',
' 名称: save',
' 参数: 2 个',
' self | &mut self',
' row | AgentRow',
' 返回: Result<(), StoreError>',
])
})
@@ -171,6 +191,7 @@ describe('rust 语言卡片', () => {
' 名称: open',
' 可见性: 公开',
' 返回: u32',
' return 8: 返回 LIMIT',
' function 10',
' 名称: bar',
' 说明: 外部。',
@@ -189,6 +210,333 @@ describe('rust 语言卡片', () => {
})
})
describe('rust 函数体拆成子块', () => {
// 执行器的 device_resume(tauri 命令):let、let … else、if、隐式返回
const RESUME = [
'#[tauri::command]',
'async fn device_resume(user_id: u64, app: AppHandle, state: State<\'_, AppState>) -> Result<ResumeReply, String> {',
' let origin = web::api_origin();',
' let Some(credential) = device::load(origin.as_str(), user_id) else {',
' return Ok(ResumeReply::Register(device::registration()));',
' };',
' if state.linked.lock().unwrap().as_ref() != Some(&link) {',
' start_connection(link, app, &state)?;',
' }',
' Ok(ResumeReply::Linked { device_id })',
'}',
].join('\n')
it('let → variable,let … else 的 else 是它的子块;if → if;带分号的调用、? → statement;末尾不带分号的式子是 return', async () => {
expect(await tree(RESUME)).toEqual([
{ at: [1, 2], kind: 'function', kids: [
{ at: 3, kind: 'variable' },
{ at: 4, kind: 'variable', kids: [{ at: 4, kind: 'else', kids: [{ at: 5, kind: 'return' }] }] },
{ at: 7, kind: 'if', kids: [{ at: 8, kind: 'statement' }] },
{ at: 10, kind: 'return' },
] },
])
})
it('子块照常翻译:变量是属性表,else、条件、语句、返回各一行译文;隐式返回值前面补「返回」', async () => {
expect(viewLines(await views(RESUME))).toEqual([
'function 1',
' 名称: device_resume',
' 异步: 是',
' 可见性: 本模块',
' 输入参数: 3 个',
' user_id | u64',
' app | AppHandle',
' state | State<\'_, AppState>',
' 返回: Result<ResumeReply, String>',
' variable 3',
' 名称: origin',
' 初始值: web::api_origin()',
' variable 4',
' 名称: Some(credential)',
' 初始值: device::load(origin.as_str(), user_id)',
' else 4: 否则',
' return 5: 返回 Ok(ResumeReply::Register(device::registration()));',
' if 7: 如果 state.linked.lock().unwrap().as_ref() != Some(&link)',
' statement 8: start_connection(link, app, &state)?;',
' return 10: 返回 Ok(ResumeReply::Linked { device_id })',
])
})
it('if / else if let / else 展平成兄弟块;match 一块、每一支一块,都是条件;排在最后的 if、match 各支里的值是 return', async () => {
const source = [
'fn pick(a: Option<u8>, b: bool) -> u8 {',
' if b {',
' log();',
' } else if let Some(x) = a {',
' x += 1;',
' } else {',
' reset();',
' }',
' match a {',
' Some(v) if v > 0 => v,',
' Some(_) => {',
' warn();',
' 0',
' }',
' None => return 1,',
' }',
'}',
'fn sign(x: i32) -> i32 {',
' if x > 0 { 1 } else { -1 }',
'}',
].join('\n')
expect(await tree(source)).toEqual([
{ at: 1, kind: 'function', kids: [
{ at: 2, kind: 'if', kids: [{ at: 3, kind: 'statement' }] },
{ at: 4, kind: 'else', kids: [{ at: 5, kind: 'statement' }] },
{ at: 6, kind: 'else', kids: [{ at: 7, kind: 'statement' }] },
{ at: 9, kind: 'if', kids: [
{ at: 10, kind: 'if', kids: [{ at: 10, kind: 'return' }] },
{ at: 11, kind: 'if', kids: [{ at: 12, kind: 'statement' }, { at: 13, kind: 'return' }] },
{ at: 15, kind: 'if', kids: [{ at: 15, kind: 'return' }] },
] },
] },
{ at: 18, kind: 'function', kids: [
{ at: 19, kind: 'if', kids: [{ at: 19, kind: 'return' }] },
{ at: 19, kind: 'else', kids: [{ at: 19, kind: 'return' }] },
] },
])
expect(viewLines(await views(source)).filter(line => /^ {2,4}if|^ {2}else/.test(line))).toEqual([
' if 2: 如果 b',
' else 4: 否则 如果 令 Some(x) = a',
' else 6: 否则',
' if 9: 匹配 a',
' if 10: Some(v) 如果 v > 0',
' if 11: Some(_)',
' if 15: None',
' if 19: 如果 x > 0',
' else 19: 否则',
])
})
it('for / while let / loop → loop,循环体里最后那个式子不是返回值', async () => {
const source = [
'fn walk(items: Vec<u8>) {',
' for item in items {',
' handle(item)',
' }',
' while let Some(x) = next() {',
' continue;',
' }',
' \'outer: loop {',
' break \'outer;',
' }',
'}',
].join('\n')
expect(await tree(source)).toEqual([
{ at: 1, kind: 'function', kids: [
{ at: 2, kind: 'loop', kids: [{ at: 3, kind: 'statement' }] },
{ at: 5, kind: 'loop', kids: [{ at: 6, kind: 'statement' }] },
{ at: 8, kind: 'loop', kids: [{ at: 9, kind: 'statement' }] },
] },
])
expect(viewLines(await views(source)).filter(line => /^ {2}loop/.test(line))).toEqual([
' loop 2: 遍历 item 在 items',
' loop 5: 当 令 Some(x) = next()',
' loop 8: \'outer: 循环',
])
})
it('闭包、嵌套 fn → function;unsafe、async、带标签的块是带子块的 statement,什么都没写的 {} 展平;实参里的闭包、async 块是语句的子块', async () => {
const source = [
'fn run() -> u8 {',
' let add = |a: u8, b| -> u8 { a + b };',
' let double = |x| x * 2;',
' fn inner() -> u8 { 1 }',
' unsafe {',
' do_it();',
' }',
' {',
' scoped();',
' }',
' \'done: {',
' break \'done;',
' }',
' tokio::spawn(async move {',
' work().await;',
' });',
' let task = async { 1 };',
' items.iter().for_each(|x| {',
' println!("{x}");',
' });',
' add(1, 2)',
'}',
].join('\n')
expect(await tree(source)).toEqual([
{ at: 1, kind: 'function', kids: [
{ at: 2, kind: 'function', kids: [{ at: 2, kind: 'return' }] },
{ at: 3, kind: 'variable' },
{ at: 4, kind: 'function', kids: [{ at: 4, kind: 'return' }] },
{ at: 5, kind: 'statement', kids: [{ at: 6, kind: 'statement' }] },
{ at: 9, kind: 'statement' },
{ at: 11, kind: 'statement', kids: [{ at: 12, kind: 'statement' }] },
{ at: 14, kind: 'statement', kids: [{ at: 15, kind: 'statement' }] },
{ at: 17, kind: 'variable', kids: [{ at: 17, kind: 'return' }] },
{ at: 18, kind: 'statement', kids: [{ at: 19, kind: 'statement' }] },
{ at: 21, kind: 'return' },
] },
])
expect(viewLines(await views(source))).toEqual([
'function 1',
' 名称: run',
' 可见性: 本模块',
' 返回: u8',
' function 2',
' 名称: add',
' 输入参数: 2 个',
' a | u8',
' b | —',
' 返回: u8',
' return 2: 返回 a + b',
' variable 3',
' 名称: double',
' 初始值: |x| x * 2',
' function 4',
' 名称: inner',
' 可见性: 本模块',
' 返回: u8',
' return 4: 返回 1',
' statement 5: 不安全',
' statement 6: do_it();',
' statement 9: scoped();',
' statement 11: \'done',
' statement 12: 跳出 \'done;',
' statement 14: tokio::spawn(异步 移动',
' statement 15: work().等待;',
' variable 17',
' 名称: task',
' 初始值: 异步',
' return 17: 返回 1',
' statement 18: items.iter().for_each(|x|',
' statement 19: println!("{x}");',
' return 21: 返回 add(1, 2)',
])
})
it('函数体里的注释同文件顶层:挂到后面那句上,行尾的不单独成块,最后的挂到前面那句', async () => {
const source = [
'fn f() {',
' // 先取',
' let a = 1;',
' let b = 2; // 行尾',
' // 最后的注释',
'}',
].join('\n')
expect(await tree(source)).toEqual([
{ at: 1, kind: 'function', kids: [
{ at: 3, kind: 'variable', notes: [2] },
{ at: 4, kind: 'variable', notes: [5] },
] },
])
})
it('特质里带默认实现的方法、实现里的方法都拆函数体;特质同实现,方法卡挂在方法表那一行;特质里、impl Trait for 里的方法跟着特质,没有可见性', async () => {
const source = [
'pub trait Greet {',
' fn name(&self) -> String;',
' fn hello(&self) -> String {',
' format!("hi {}", self.name())',
' }',
'}',
'impl Greet for Bot {',
' fn name(&self) -> String {',
' let n = self.id;',
' n.to_string()',
' }',
'}',
].join('\n')
expect(await tree(source)).toEqual([
{ at: 1, kind: 'trait', kids: [
{ at: 2, kind: 'function' },
{ at: 3, kind: 'function', kids: [{ at: 4, kind: 'return' }] },
] },
{ at: 7, kind: 'impl', kids: [
{ at: 8, kind: 'function', kids: [{ at: 9, kind: 'variable' }, { at: 10, kind: 'return' }] },
] },
])
const list = await views(source)
expect(viewLines(list)).toEqual([
'rust.trait 1',
' 名称: Greet',
' 方法: 2 个',
' name | &self | String',
' hello | &self | String',
' function 2',
' 名称: name',
' 参数: 1 个',
' self | &self',
' 返回: String',
' function 3',
' 名称: hello',
' 参数: 1 个',
' self | &self',
' 返回: String',
' return 4: 返回 format!("hi {}", self.name())',
'rust.impl 7',
' 名称: Bot 的方法',
' 方法: 1 个',
' name | &self | String',
' function 8',
' 名称: name',
' 参数: 1 个',
' self | &self',
' 返回: String',
' variable 9',
' 名称: n',
' 初始值: self.id',
' return 10: 返回 n.to_string()',
])
expect(card(list, 'rust.trait').children.map(child => child.slot)).toEqual([{ row: 1, item: 0 }, { row: 1, item: 1 }])
})
})
describe('rust 调用', () => {
const calls = (source: string): Promise<unknown[]> => nodeFacts(adapter, 'rust', source, node => adapter.call?.(node))
it('实参照源码顺序都是按位置传的,注释不算;被调用的取最后那个名字:路径、方法、泛型函数;被调用的不是名字时没有 name,宏调用不算', async () => {
const source = [
'fn f() {',
' device::load(',
' origin.as_str(), // 后端地址',
' user_id,',
' );',
' parse::<u8>(s);',
' handlers[k]();',
' println!("{}", x);',
'}',
].join('\n')
expect(await calls(source)).toEqual([
{
callee: 'device::load',
name: 'load',
argumentList: '(\n origin.as_str(), // 后端地址\n user_id,\n )',
arguments: [{ kind: 'positional', value: 'origin.as_str()' }, { kind: 'positional', value: 'user_id' }],
},
{ callee: 'origin.as_str', name: 'as_str', argumentList: '()', arguments: [] },
{ callee: 'parse::<u8>', name: 'parse', argumentList: '(s)', arguments: [{ kind: 'positional', value: 's' }] },
{ callee: 'handlers[k]', argumentList: '()', arguments: [] },
])
})
it('跨几行写的调用排成一行「调用 … · N 个参数」,实参摊成表', async () => {
const source = 'fn f() {\n device::load(\n origin,\n user_id,\n );\n}\n'
expect(viewLines(await views(source))).toEqual([
'function 1',
' 名称: f',
' 可见性: 本模块',
' 返回: ()',
' statement 2: 调用 device::load · 2 个参数;',
' 第 1 个 | origin',
' 第 2 个 | user_id',
])
})
})
describe('rust 字面量的各项', () => {
it('数组、元组的各项照源码顺序,注释不算一项;[值; 个数]、别的值不是', async () => {
const source = 'const A: [&str; 2] = [\n "a", // 第一个\n "b",\n];\nconst Z: [u8; 4] = [0; 4];\nconst T: (u8, u8) = (1, 2);\nfn f() { g(a, b); }'
@@ -401,8 +749,6 @@ describe('rust 编译属性', () => {
})
describe('rust 注释挂到它讲的项上', () => {
const tree = (source: string): Promise<Outline[]> => withBlocks(adapter, 'rust', source, outline)
it('同一层后面最近的那项(隔着空行也算),和 /// 说明并存;只有注释的 mod 挂到 mod 上;impl 最后的挂到前面那项;文件最后的挂到最后一项;//! 是文件的说明', async () => {
const source = [
'//! 凭证存储。',
@@ -429,7 +775,7 @@ describe('rust 注释挂到它讲的项上', () => {
expect(await tree(source)).toEqual([
{ at: 1, kind: 'comment' },
{ at: 3, kind: 'import' },
{ at: [7, 8], kind: 'function', notes: [5] },
{ at: [7, 8], kind: 'function', notes: [5], kids: [{ at: 8, kind: 'return' }] },
{ at: 10, kind: 'module', notes: [11] },
{ at: 14, kind: 'impl', notes: [20], kids: [{ at: 16, kind: 'function', notes: [15, 17] }] },
])
+254 -20
View File
@@ -1,5 +1,5 @@
import type { Access, AttributeFacts, AttributeItem, CfgPredicate, ClassFacts, CodeBlock, CollectionFacts, CommentBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, SourceSpan, TokenRole, TraitBlock } from '../contract'
import type { CommentRules } from './shared'
import type { Access, AttributeFacts, AttributeItem, BlockShape, CallFacts, CfgPredicate, ClassFacts, CodeBlock, CollectionFacts, CommentBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, SourceSpan, TextKind, TokenRole, TraitBlock } from '../contract'
import type { CommentRules, ListBlocks } from './shared'
import * as path from 'node:path'
import { leadingAttributes, visibilityFacts } from '../rules/rustTree'
import { attachComments, children, compoundShape, extendHeader, headerSpan, insideOf, isCommentNode, leafBlock, leafShape, listBlocks, named, syntaxChildren } from './shared'
@@ -15,10 +15,33 @@ export function createRustAdapter(wasmDir: string): LanguageAdapter {
tokenRole: rustTokenRole,
lineComment: '//',
freshDoc,
call: rustCall,
collection: rustCollection,
}
}
/**
* 调用 `f(a)`、`obj.method(a)`、`Type::new(a)`、`parse::<u8>(s)` 的结构,实参都是按位置传的;
* 元组结构体、变体(`Some(x)`)写法和调用一样,不给 construct,翻译插件问名字的定义是哪种块。
* 宏调用(`println!(…)`)括号里不是语法树,不算。
*/
function rustCall(node: Node): CallFacts | undefined {
const callee = node.type === 'call_expression' ? node.childForFieldName('function') : null
const list = node.childForFieldName('arguments')
if (!callee || list?.type !== 'arguments')
return undefined
const target = callee.type === 'generic_function' ? callee.childForFieldName('function') : callee
const name = target?.type === 'identifier'
? target
: target?.type === 'field_expression' ? target.childForFieldName('field') : target?.type === 'scoped_identifier' ? target.childForFieldName('name') : null
return {
callee,
...(name && { name }),
argumentList: list,
arguments: syntaxChildren(list).filter(arg => arg.type !== 'attribute_item').map(value => ({ kind: 'positional', value })),
}
}
/** 数组 `[a, b]`、元组 `(a, b)` 的各项;`[值; 个数]` 不是一项项写出来的,别的节点也没有。项前面的 `#[…]` 不算一项。 */
function rustCollection(node: Node): CollectionFacts | undefined {
const listed = node.type === 'tuple_expression' || (node.type === 'array_expression' && !node.childForFieldName('length'))
@@ -62,7 +85,7 @@ function docMarker(comment: Node): string {
return named(comment).find(child => child.type === 'outer_doc_comment_marker' || child.type === 'inner_doc_comment_marker')?.text ?? ''
}
/** 一层的各项(文件顶层、模块身体、impl 身体)→ 块;afterRow 同 listBlocks。`#[…]` 和多出来的空 `;` 不出块。 */
/** 一层的各项(文件顶层,模块、特质、实现的身体)→ 块;afterRow 同 listBlocks。`#[…]` 和多出来的空 `;` 不出块。 */
function topBlocks(items: Node[], afterRow: number): CodeBlock[] {
return listBlocks(items.flatMap(unwrapExtern), afterRow, (item) => {
if (item.type === 'attribute_item' || item.type === 'empty_statement')
@@ -107,7 +130,7 @@ function moduleBlock(node: Node): CodeBlock {
const { access, attributes } = visibilityFacts(node)
return {
kind: 'module',
...compoundShape(node, body.startIndex, named(body), topBlocks),
...itemsShape(node),
...(attributes && { attributes }),
facts: classFacts(node, access),
}
@@ -323,29 +346,30 @@ function enumBlock(node: Node): EnumBlock {
function traitBlock(node: Node): TraitBlock {
const { access, attributes } = visibilityFacts(node)
return { kind: 'trait', ...shape(node), ...(attributes && { attributes }), facts: classFacts(node, access) }
return { kind: 'trait', ...itemsShape(node), ...(attributes && { attributes }), facts: classFacts(node, access) }
}
function implBlock(node: Node): ImplBlock {
const { access, attributes } = visibilityFacts(node)
const body = node.childForFieldName('body')
return {
kind: 'impl',
...compoundShape(node, body?.startIndex ?? node.endIndex, named(body), topBlocks),
...itemsShape(node),
...(attributes && { attributes }),
facts: classFacts(node, access, implType(node)),
}
}
/** 身体是一层项的块(模块、特质、实现):子块是身体里的各项,跟文件顶层同一条路。 */
function itemsShape(node: Node): BlockShape {
const body = node.childForFieldName('body')
return compoundShape(node, body?.startIndex ?? node.endIndex, named(body), topBlocks)
}
function functionBlock(node: Node): FunctionBlock {
const { access, attributes } = visibilityFacts(node)
const body = node.childForFieldName('body')
const headerEnd = body?.startIndex ?? node.endIndex
return {
kind: 'function',
node,
header: headerSpan(node, headerEnd),
children: [],
...bodyShape(node, node.childForFieldName('body'), true),
...(attributes && { attributes }),
facts: {
name: node.childForFieldName('name') ?? undefined,
@@ -358,6 +382,213 @@ function functionBlock(node: Node): FunctionBlock {
}
}
/**
* 块里写的声明(项、`use`、`macro_rules!`……),跟文件顶层的项走同一条路([itemBlock])。
* 块里别的具名子节点是 `let`、表达式语句,和排在最后、不带分号的那个式子(这一层的值)。
*/
const DECLARATIONS = new Set([
'const_item',
'static_item',
'use_declaration',
'extern_crate_declaration',
'macro_definition',
'inner_attribute_item',
'mod_item',
'foreign_mod_item',
'struct_item',
'union_item',
'enum_item',
'type_item',
'function_item',
'function_signature_item',
'impl_item',
'trait_item',
'associated_type',
])
/**
* 带主体的一块:主体是 `{}` 时子块来自里面的各句,否则主体本身是唯一的子块(match 的一支 `Ok(v) => v`)。
* 头部到 `{` 之前,带标签的 `'a: {}` 标签算在头部里。tail 见 [statements]。
*/
function bodyShape(outer: Node, body: Node | null, tail: boolean): BlockShape {
if (!body)
return leafShape(outer)
const list: ListBlocks = (items, afterRow) => statements(items, afterRow, tail)
if (body.type !== 'block')
return compoundShape(outer, body.startIndex, [body], list)
const open = children(body).find(child => child.type === '{')
return compoundShape(outer, open?.startIndex ?? body.startIndex, blockItems(body), list)
}
function bodyBlock(kind: TextKind, outer: Node, body: Node | null, tail: boolean): CodeBlock {
return { kind, ...bodyShape(outer, body, tail) }
}
/** `{}` 里的各句和注释;`'a: {}` 的标签不算。 */
function blockItems(block: Node): Node[] {
return named(block).filter(child => child.type !== 'label')
}
/**
* 函数体里的一层(函数体、条件的一支、match 的一支、循环体……)→ 块,注释同文件顶层。
* tail 是这一层算出的值就是所在函数的返回值:函数体、闭包体、async 块是;排在最后、不带分号的条件、match、`{}` 把它传给自己的各支;
* 循环体、`let … else` 的 else 不是。这时最后那个不带分号的式子是 return。
*/
function statements(items: Node[], afterRow: number, tail: boolean): CodeBlock[] {
const list = items.flatMap(unwrapExtern)
const last = list.filter(item => !isCommentNode(item)).at(-1)
return listBlocks(list, afterRow, item =>
statementBlocks(item, tail && item === last).map((block, i) => i === 0 ? withAttributes(block) : block), COMMENTS)
}
/** 一句 → 块;tail 是这句排在 tail 的那一层最后。`#[…]` 算进后面那句的头部,自己和多出来的空 `;` 不出块。 */
function statementBlocks(item: Node, tail: boolean): CodeBlock[] {
switch (item.type) {
case 'attribute_item':
case 'empty_statement':
return []
case 'let_declaration':
return [letBlock(item)]
case 'expression_statement': {
const [expression] = syntaxChildren(item)
// 带分号的式子算出的值扔掉了,不带分号的(条件、match、`{}`)排在最后才是这一层的值
return expression ? expressionBlocks(item, expression, tail && item.lastChild?.type !== ';') : [leafBlock('statement', item)]
}
default:
return DECLARATIONS.has(item.type) ? [itemBlock(item)] : expressionBlocks(item, item, tail)
}
}
/**
* 当一句写的式子 → 块:outer 是整句(表达式语句,或一层最后那个不带分号的式子本身)。
* 条件、match、循环、`{}` 照控制流拆;是这一层的值(tail)时是 return;别的是 statement,带着闭包、async 块时子块来自它(见 [callbackBody])。
*/
function expressionBlocks(outer: Node, expression: Node, tail: boolean): CodeBlock[] {
switch (expression.type) {
case 'if_expression':
return ifBlocks(expression, outer, 'if', tail)
case 'match_expression':
return [matchBlock(outer, expression, tail)]
case 'for_expression':
case 'while_expression':
case 'loop_expression':
return [bodyBlock('loop', outer, expression.childForFieldName('body'), false)]
case 'block':
// 前面什么都没写的 `{}` 同 TypeScript 展平成各句;带标签的标签是头部
return named(expression).some(child => child.type === 'label')
? [bodyBlock('statement', outer, expression, tail)]
: statements(blockItems(expression), expression.startPosition.row, tail)
case 'unsafe_block':
return [bodyBlock('statement', outer, named(expression).find(child => child.type === 'block') ?? null, tail)]
case 'async_block':
return [bodyBlock('statement', outer, deferredBody(expression), true)]
case 'return_expression':
return [leafBlock('return', outer)]
default:
return [tail ? leafBlock('return', outer) : bodyBlock('statement', outer, callbackBody(expression), true)]
}
}
/** if 的 else / else if 展平成紧跟其后的兄弟块,同 TypeScript;`if let` 的头部连着模式。 */
function ifBlocks(ifNode: Node, outer: Node, kind: 'if' | 'else', tail: boolean): CodeBlock[] {
const blocks = [bodyBlock(kind, outer, ifNode.childForFieldName('consequence'), tail)]
const alternative = ifNode.childForFieldName('alternative')
if (alternative) {
const [inner = null] = syntaxChildren(alternative)
blocks.push(...(inner?.type === 'if_expression' ? ifBlocks(inner, alternative, 'else', tail) : [bodyBlock('else', alternative, inner, tail)]))
}
return blocks
}
/** match 是一块条件,每一支是它的子块,也是条件,同 Python 的 match / case、TypeScript 的 switch / case。 */
function matchBlock(outer: Node, match: Node, tail: boolean): CodeBlock {
const body = match.childForFieldName('body')
if (!body)
return leafBlock('if', outer)
const arms: ListBlocks = (list, afterRow) => listBlocks(list, afterRow, arm => arm.type === 'match_arm' ? [armBlock(arm, tail)] : [], COMMENTS)
return { kind: 'if', ...compoundShape(outer, body.startIndex, named(body), arms) }
}
/** match 的一支:头部是模式和守卫(到 `=>` 之前),子块来自 `=>` 后面的值。 */
function armBlock(arm: Node, tail: boolean): CodeBlock {
const shape = bodyShape(arm, arm.childForFieldName('value'), tail)
const arrow = children(arm).find(child => child.type === '=>')
return { kind: 'if', ...shape, header: arrow ? headerSpan(arm, arrow.startIndex) : shape.header }
}
/**
* `let` → variable,模式是名字;值是主体写成 `{}` 的闭包时是 function,同 TypeScript 把箭头函数赋给变量。
* `let … else` 的 else 是它的子块;值带着闭包、async 块时子块来自它(见 [callbackBody])。
*/
function letBlock(node: Node): CodeBlock {
const pattern = node.childForFieldName('pattern')
if (!pattern)
return leafBlock('statement', node)
const value = node.childForFieldName('value')
if (value?.type === 'closure_expression' && deferredBody(value))
return closureBlock(node, value, pattern)
const alternative = node.childForFieldName('alternative')
return {
kind: 'variable',
...(alternative ? letElseShape(node, alternative) : bodyShape(node, value && callbackBody(value), true)),
facts: { declarators: [{ name: pattern, type: node.childForFieldName('type') ?? undefined, value: value ?? undefined }] },
}
}
/**
* `let 模式 = 值 else { … }`:头部到 else 之前,else 照 if / else 的写法成一块,是唯一的子块。
* else 的节点仍是整条 `let`(else 这个词不在别的节点里);那一支必须跳出去(return、break……),里面最后那个式子不算返回值。
*/
function letElseShape(node: Node, alternative: Node): BlockShape {
const keyword = children(node).find(child => child.type === 'else')
if (!keyword)
return leafShape(node)
const branch = bodyShape(node, alternative, false)
const otherwise: CodeBlock = { kind: 'else', ...branch, header: { ...branch.header, startIndex: keyword.startIndex, startRow: keyword.startPosition.row } }
return { node, header: headerSpan(node, keyword.startIndex), children: [otherwise] }
}
/** 赋给变量、主体写成 `{}` 的闭包 → function:outer 是整条 `let`,名字是它的模式;闭包没有可见性、外部属性。 */
function closureBlock(outer: Node, closure: Node, name: Node): FunctionBlock {
return {
kind: 'function',
...bodyShape(outer, deferredBody(closure), true),
facts: {
name,
...(isAsync(closure) && { async: true }),
decorators: [],
parameters: parametersOf(closure.childForFieldName('parameters')),
returnType: closure.childForFieldName('return_type') ?? undefined,
},
}
}
/** 闭包、async 块的 `{}`:它们的值是里面最后那个式子,里面的 return 也只回到它们自己。闭包主体不是 `{}` 时没有。 */
function deferredBody(node: Node): Node | null {
const body = node.type === 'closure_expression'
? node.childForFieldName('body')
: node.type === 'async_block' ? named(node).find(child => child.type === 'block') : undefined
return body?.type === 'block' ? body : null
}
/**
* 式子带着的闭包、async 块的 `{}`,同 TypeScript 的回调:值本身是它(`async { … }`),或调用的实参里第一个
* (`tokio::spawn(async move { … })`、`.for_each(|x| { … })`);`.await`、`?` 看里面那个式子。
*/
function callbackBody(expression: Node): Node | null {
let inner: Node | null = expression
while (inner?.type === 'await_expression' || inner?.type === 'try_expression')
inner = inner.firstNamedChild
if (inner?.type !== 'call_expression')
return inner && deferredBody(inner)
for (const arg of named(inner.childForFieldName('arguments'))) {
const body = deferredBody(arg)
if (body)
return body
}
return null
}
function classFacts(node: Node, access: Access | undefined, name = node.childForFieldName('name') ?? undefined): ClassFacts {
return { name, access, decorators: leadingAttributes(node), typeParameters: [], bases: [], implements: [] }
}
@@ -375,22 +606,25 @@ function shape(node: Node): { node: Node, header: { startIndex: number, endIndex
function parametersOf(list: Node | null): ParameterFacts[] {
if (!list)
return []
return named(list).flatMap((child) => {
return syntaxChildren(list).flatMap((child) => {
if (child.type === 'self_parameter') {
const name = [...child.children].reverse().find(token => token?.type === 'self')
return name ? [{ name, optional: false, type: child }] : []
}
if (child.type !== 'parameter')
return []
const name = child.childForFieldName('pattern')
const type = child.childForFieldName('type') ?? undefined
return name ? [{ name, optional: false, type }] : []
if (child.type === 'parameter') {
const name = child.childForFieldName('pattern')
const type = child.childForFieldName('type') ?? undefined
return name ? [{ name, optional: false, type }] : []
}
// 闭包不写类型的参数(`|x|`)只是一个模式
return list.type === 'closure_parameters' ? [{ name: child, optional: false }] : []
})
}
/** 写了 `async`:`fn` 的写在 function_modifiers 里,闭包的直接写在前面。 */
function isAsync(node: Node): boolean {
const modifiers = named(node).find(child => child.type === 'function_modifiers')
return modifiers?.children.some(child => child?.type === 'async') ?? false
const modifiers = named(node).find(child => child.type === 'function_modifiers') ?? node
return modifiers.children.some(child => child?.type === 'async')
}
const insideComment = insideOf({ line_comment: true, block_comment: true })
+20 -12
View File
@@ -1,33 +1,41 @@
import type { ImplBlock, KindBadge, LibraryRule, RuleCard, RuleContext, ThemeColor } from '../contract'
import { implRows, plainSay } from './rustRows'
import type { ImplBlock, KindBadge, LibraryRule, PropertyRow, RuleCard, TraitBlock } from '../contract'
import { implRows, plainSay, traitRows } from './rustRows'
/** 和函数卡同一个黄,实现块看起来仍是函数那一挂。 */
const COLOR: ThemeColor = { dark: '#dcdcaa', light: '#795e26' }
const BADGE: KindBadge = { label: '实现', color: COLOR, icon: 'square-function' }
const IMPL: KindBadge = { label: '实现', color: { dark: '#dcdcaa', light: '#795e26' }, icon: 'square-function' }
/** 和声明关键字同一个蓝,同特质这个种类在预览里的颜色。 */
const TRAIT: KindBadge = { label: '特质', color: { dark: '#569cd6', light: '#0000ff' }, icon: 'network' }
/**
* impl 的方法表:点方法名展开那张函数卡。
* impl、trait 的方法表:点方法名展开那张函数卡,函数体各句在卡里。
* 方法挂在方法表的子行上(RuleSlot)。不看导入,Rust 文件都启用。
*/
export const rustImplRule: LibraryRule = {
id: 'rust',
label: 'Rust 实现',
label: 'Rust 实现与特质',
languageIds: ['rust'],
libraries: [],
claim: (block, context) => block.kind === 'impl' ? implCard(block, context) : undefined,
claim: (block, context) => {
const say = plainSay(context.doc)
if (block.kind === 'impl')
return methodsCard(block, 'rust.impl', IMPL, implRows(block, say))
if (block.kind === 'trait')
return methodsCard(block, 'rust.trait', TRAIT, traitRows(block, say))
return undefined
},
attributeNames: () => ({
attributes: {},
values: { kind: { 'rust.impl': '实现' } },
values: { kind: { 'rust.impl': '实现', 'rust.trait': '特质' } },
}),
}
function implCard(block: ImplBlock, context: RuleContext): RuleCard {
const rows = implRows(block, plainSay(context.doc))
/** 带方法表的卡片:子块里的每个函数挂在「方法」那一行讲它的子行上,跟 rows 里的顺序一一对上。 */
function methodsCard(block: ImplBlock | TraitBlock, kind: string, badge: KindBadge, rows: PropertyRow[]): RuleCard {
const row = rows.findIndex(item => item.label === '方法')
const methods = block.children.filter(child => child.kind === 'function')
return {
kind: 'rust.impl',
badge: BADGE,
kind,
badge,
rows,
...(block.facts.name && { defines: { startIndex: block.facts.name.startIndex, endIndex: block.facts.name.endIndex } }),
absorbs: [],
@@ -65,6 +65,7 @@ describe('serde 与 thiserror', () => {
' 路径 | /agents/{id}',
' 路径参数 | id: Uuid',
' 返回: Result<Json<AgentRow>, StatusCode>',
' return 32: 返回 todo!()',
'axum.route 35',
' 名称: health',
' 请求: GET /health',
@@ -92,6 +93,7 @@ describe('serde 与 thiserror', () => {
' state | State<App> | 窗口状态',
' name | String | 调用方传入',
' 返回: Result<String, String>',
' return 47: 返回 todo!()',
])
})
+30 -35
View File
@@ -1,7 +1,7 @@
import type { AttributeBlock, AttributeFacts, AttributeItem, CfgPredicate, ClassBlock, CodeBlock, Doc, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, ModuleBlock, Node, PropertyItem, PropertyRow, SourceSpan, TokenRole, TraitBlock } from '../contract'
import type { RustVisibility } from './rustTree'
import { paramDocCell, paramDocs, returnNote, throwsRows, unmatchedParamRows } from './docRows'
import { bodyFunctions, docSummary, isMethod, matchArms, oneLine, returnText, signatureParams, structFields, variantPayload } from './rustTree'
import { docSummary, isFnItem, isMethod, oneLine, returnText, signatureParams, structFields, variantPayload } from './rustTree'
/** 把类型、名字画成原文,读说明。翻译插件传入时,名字带源码下标。 */
export interface RustSay {
@@ -182,10 +182,14 @@ export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] {
]
}
/**
* 函数卡片的行;函数体各句是子块,不在这里讲。有可见性才有那一行(特质里、`impl Trait for` 里的方法和闭包没有);
* 没写返回类型的 `fn` 项返回 `()`,闭包没写时不知道,不出「返回」这一行。
*/
export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[] {
const arms = matchArms(block.node)
const visible = block.facts.access ?? block.attributes?.access
const doc = block.facts.doc && say.doc(block.facts.doc)
const { parameters } = block.facts
const { parameters, returnType } = block.facts
const docs = paramDocs(doc, parameters.map(param => param.name.text))
const described = parameters.some(param => docs.of(param.name.text))
return [
@@ -193,7 +197,7 @@ export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[]
...docRow(doc),
...unmatchedParamRows(docs.unmatched),
...(block.facts.async ? [{ label: '异步', value: [keyword('是')] }] : []),
visibilityRow(block),
...(visible ? [visibilityRow(block)] : []),
...tableRow(isMethod(block.node) ? '参数' : '输入参数', ['名称', '类型', ...(described ? ['说明'] : [])], parameters.map(param => ({
cells: [
say.name(param.name, 'variable'),
@@ -202,55 +206,46 @@ export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[]
],
defines: range(param.name),
}))),
{ label: '返回', value: [plain(returnText(block.node))], ...returnNote(doc) },
...(returnType || isFnItem(block.node) ? [{ label: '返回', value: [plain(returnType ? oneLine(returnType.text) : '()')], ...returnNote(doc) }] : []),
...throwsRows(doc),
...tableRow('分情况', ['分支', '做什么'], arms.map(arm => ({
cells: [[plain(arm.pattern)], [hint(arm.does)]],
})), '支'),
]
}
export function traitRows(block: TraitBlock, say: RustSay): PropertyRow[] {
const methods = bodyFunctions(block.node)
return [
nameRow(block.facts.name, say, 'type'),
...docRow(block.facts.doc && say.doc(block.facts.doc)),
...tableRow('方法', ['名称', '参数', '返回'], methods.map((method) => {
const name = method.childForFieldName('name')
return {
cells: [
name ? say.name(name, 'function') : [hint('匿名')],
[plain(signatureParams(method))],
[plain(returnText(method))],
],
...(name && { defines: range(name) }),
}
})),
...methodRows(block, say),
]
}
export function implRows(block: ImplBlock, say: RustSay): PropertyRow[] {
const methods = block.children.filter((child): child is FunctionBlock => child.kind === 'function')
const title: Inline[] = block.facts.name
? [...say.name(block.facts.name, 'type'), plain(' 的方法')]
: [hint('实现')]
return [
{ label: '名称', value: title, ...(block.facts.name && { defines: range(block.facts.name) }) },
...docRow(block.facts.doc && say.doc(block.facts.doc)),
...tableRow('方法', ['名称', '参数', '返回'], methods.map((method) => {
const name = method.facts.name
return {
cells: [
name ? say.name(name, 'function') : [hint('匿名')],
[plain(signatureParams(method.node))],
[plain(returnText(method.node))],
],
...(name && { defines: range(name) }),
}
})),
...methodRows(block, say),
]
}
/** 特质、实现的方法表「名称 | 参数 | 返回」:子块里的函数每个一行,照源码顺序;[Rust 实现与特质规则](vscode/src/codeReader/rules/rustImpl.ts#rustImplRule) 把那张函数卡挂在这一行上。 */
function methodRows(block: TraitBlock | ImplBlock, say: RustSay): PropertyRow[] {
const methods = block.children.filter((child): child is FunctionBlock => child.kind === 'function')
return tableRow('方法', ['名称', '参数', '返回'], methods.map((method) => {
const name = method.facts.name
return {
cells: [
name ? say.name(name, 'function') : [hint('匿名')],
[plain(signatureParams(method.node))],
[plain(returnText(method.node))],
],
...(name && { defines: range(name) }),
}
}))
}
function nameRow(name: Node | undefined, say: RustSay, role: TokenRole): PropertyRow {
return {
label: '名称',
@@ -268,11 +263,11 @@ function visibilityRow(block: CodeBlock): PropertyRow {
return { label: '可见性', value: [keyword(visibilityWord(block))] }
}
/** 没有行就不占一行。countUnit 默认「个」,分情况用「支」。 */
function tableRow(label: string, columns: string[], items: PropertyItem[], countUnit = '个'): PropertyRow[] {
/** 没有行就不占一行。 */
function tableRow(label: string, columns: string[], items: PropertyItem[]): PropertyRow[] {
if (items.length === 0)
return []
return [{ label, value: [hint(`${items.length} ${countUnit}`)], columns, items }]
return [{ label, value: [hint(`${items.length} 个`)], columns, items }]
}
function hint(text: string): Inline {
+9 -59
View File
@@ -61,6 +61,10 @@ export function rustVisibility(node: Node): RustVisibility {
/** 公开写在 facts.access 上;其余写在块的 access 属性上,盖过 facts。 */
export function visibilityFacts(node: Node): { access?: Access, attributes?: BlockAttributes } {
// 特质里的项、给类型实现特质(`impl Trait for Type`)里的项不能写可见性,跟着特质
const owner = node.parent?.parent
if (owner?.type === 'trait_item' || (owner?.type === 'impl_item' && owner.childForFieldName('trait')))
return {}
const visibility = rustVisibility(node)
return visibility === 'public' ? { access: 'public' } : { attributes: { access: visibility } }
}
@@ -79,26 +83,6 @@ export function returnText(fn: Node): string {
return returned ? oneLine(returned.text) : '()'
}
/** 函数体里直接写的 match,每一支的模式和「做什么」。 */
export function matchArms(fn: Node): { pattern: string, does: string }[] {
const body = fn.childForFieldName('body')
if (!body)
return []
const arms: { pattern: string, does: string }[] = []
for (const statement of named(body)) {
const expr = statement.type === 'expression_statement' ? named(statement)[0] : statement
if (expr?.type !== 'match_expression')
continue
const block = expr.childForFieldName('body')
for (const arm of named(block).filter(child => child.type === 'match_arm')) {
const pattern = arm.childForFieldName('pattern')
if (pattern)
arms.push({ pattern: oneLine(pattern.text), does: armDoes(arm) })
}
}
return arms
}
/** 变体携带的数据:没带是空的,元组变体是类型,结构变体是字段原文。body 是变体的 body。 */
export function variantPayload(body: Node | undefined): string | undefined {
if (!body)
@@ -180,6 +164,11 @@ export function isMethod(node: Node): boolean {
return parent?.type === 'declaration_list' && (parent.parent?.type === 'impl_item' || parent.parent?.type === 'trait_item')
}
/** 是 `fn` 项(含 extern 块里只写签名的),不是赋给变量的闭包:项没写返回类型就是返回 `()`。 */
export function isFnItem(node: Node): boolean {
return node.type === 'function_item' || node.type === 'function_signature_item'
}
/** 结构体的字段:名字、类型、可见性。元组结构体没有名字,不进这张表。 */
export function structFields(node: Node): { name: Node, type: Node, visibility: RustVisibility }[] {
const body = node.childForFieldName('body')
@@ -193,42 +182,3 @@ export function structFields(node: Node): { name: Node, type: Node, visibility:
return name && type ? [{ name, type, visibility: rustVisibility(field) }] : []
})
}
/** trait 或 impl 主体里的函数(含只写签名的)。 */
export function bodyFunctions(node: Node): Node[] {
const body = node.childForFieldName('body')
return named(body).filter(child => child.type === 'function_item' || child.type === 'function_signature_item')
}
function armDoes(arm: Node): string {
const comment = firstComment(arm)
if (comment)
return firstSentence(commentPlain(comment)) || '—'
const value = arm.childForFieldName('value')
if (!value)
return '—'
if (value.type === 'return_expression') {
const returned = named(value)[0]
return returned ? `返回 ${oneLine(returned.text)}` : '返回'
}
if (value.type === 'block') {
const inner = firstComment(value)
if (inner)
return firstSentence(commentPlain(inner)) || '—'
const statement = named(value)[0]
return statement ? oneLine(statement.text) : '—'
}
return oneLine(value.text)
}
function firstComment(node: Node): Node | undefined {
if (node.type === 'line_comment' || node.type === 'block_comment')
return node
for (const child of node.children) {
if (!child)
continue
const found = firstComment(child)
if (found)
return found
}
return undefined
}
+14 -2
View File
@@ -1,4 +1,4 @@
import type { FactBlock, Inline, Node, PropertyRow, SourceDocument, SourceSpan, Translator } from '../contract'
import type { FactBlock, Inline, Node, PropertyRow, SourceDocument, SourceSpan, TextBlock, TextTranslation, Translator } from '../contract'
import { attributeRows, enumRows, functionRows, implRows, importRows, moduleRows, plainSay, structRows, traitRows } from '../rules/rustRows'
import { isCrateRoot } from '../rules/rustTree'
import { createZhTemplateTranslator, KIND_ZH, ZH_ATTRIBUTE_NAMES } from './zhTemplate'
@@ -58,7 +58,7 @@ const template = createZhTemplateTranslator({
})
/**
* Rust 的中文直译。属性表的用词按代码阅读的 Rust 原型:结构体、可见性、变体、分情况。
* Rust 的中文直译。属性表的用词按代码阅读的 Rust 原型:结构体、可见性、变体。
* 关键字和注释仍走中文直译那一套。常量和变量的行不另写一套,走模板里现成的那套。
*/
export const rustZhTranslator: Translator = {
@@ -77,9 +77,21 @@ export const rustZhTranslator: Translator = {
},
},
},
translate,
describe,
}
/** 写了 `return` 的返回块:`return x;` 整句,或 match 一支的值 `=> return x`。 */
const RETURN_WRITTEN = new Set(['expression_statement', 'return_expression'])
/** 一层最后那个不带分号的式子是返回值,源码里没有 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] }
}
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
const say = plainSay(node => template.doc(node, document))
switch (block.kind) {