✨ feat(vscode): 代码阅读预览 Rust 函数体按语句拆成子块
let / let-else、if / else、match 各支、循环、隐式返回、闭包、unsafe / async 块照 Python / TypeScript 的约定拆块;特质同实现,方法卡挂在方法表那一行;函数卡去掉分情况表;Rust 语言插件给出调用结构
This commit is contained in:
1 parent
d5c627c54a
commit
86422e4671
7 files changed
+682
-135
No files matched your search
@@ -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] }] },
|
||||
])
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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!()',
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user